Ejemplo n.º 1
0
 def _activate(self, application):
     if self.interface is not None:
         self.interface.roster.window.present()
         return
     from gajim.gui_interface import Interface
     self.interface = Interface()
     self.interface.run(self)
     self.add_actions()
     from gajim import gui_menu_builder
     gui_menu_builder.build_accounts_menu()
Ejemplo n.º 2
0
    def _activate(self, application):
        if self.interface is not None:
            self.interface.roster.window.present()
            return
        from gajim.gui_interface import Interface
        self.interface = Interface()
        self.interface.run(self)
        self.add_actions()
        from gajim import gui_menu_builder
        gui_menu_builder.build_accounts_menu()

        app.ged.register_event_handler('feature-discovered',
                                       ged.CORE,
                                       self._on_feature_discovered)
Ejemplo n.º 3
0
    def test_links_regexp_entire(self):
        sut = Interface()

        def assert_matches_all(str_):
            m = sut.basic_pattern_re.match(str_)

            # the match should equal the string
            str_span = (0, len(str_))
            self.assertEqual(m.span(), str_span)

        # these entire strings should be parsed as links
        assert_matches_all('http://google.com/')
        assert_matches_all('http://google.com')
        assert_matches_all('http://www.google.ca/search?q=xmpp')

        assert_matches_all(
            'http://tools.ietf.org/html/draft-saintandre-rfc3920bis-05#section-12.3'
        )

        assert_matches_all('http://en.wikipedia.org/wiki/Protocol_(computing)')
        assert_matches_all(
            'http://en.wikipedia.org/wiki/Protocol_%28computing%29')

        assert_matches_all('mailto:[email protected]')

        assert_matches_all('xmpp:[email protected]')
        assert_matches_all('xmpp:[email protected]/some-resource')
        assert_matches_all('xmpp:[email protected]?message')
        assert_matches_all(
            'xmpp://[email protected]/[email protected]?message')
Ejemplo n.º 4
0
class GajimApplication(Gtk.Application):
    '''Main class handling activation and command line.'''
    def __init__(self):
        flags = (Gio.ApplicationFlags.HANDLES_COMMAND_LINE
                 | Gio.ApplicationFlags.HANDLES_OPEN)
        Gtk.Application.__init__(self,
                                 application_id='org.gajim.Gajim',
                                 flags=flags)

        # required to track screensaver state
        self.props.register_session = True

        self.add_main_option('version', ord('V'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE,
                             _('Show the application\'s version'))

        self.add_main_option('quiet', ord('q'),
                             GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
                             _('Show only critical errors'))

        self.add_main_option(
            'separate', ord('s'), GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
            _('Separate profile files completely '
              '(even history database and plugins)'))

        self.add_main_option(
            'verbose', ord('v'), GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
            _('Print XML stanzas and other debug information'))

        self.add_main_option(
            'profile', ord('p'), GLib.OptionFlags.NONE, GLib.OptionArg.STRING,
            _('Use defined profile in configuration directory'), 'NAME')

        self.add_main_option('config-path', ord('c'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.STRING,
                             _('Set configuration directory'), 'PATH')

        self.add_main_option('loglevel', ord('l'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.STRING,
                             _('Configure logging system'), 'LEVEL')

        self.add_main_option('warnings', ord('w'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, _('Show all warnings'))

        self.add_main_option('ipython', ord('i'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, _('Open IPython shell'))

        self.add_main_option('show-next-pending-event', 0,
                             GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
                             _('Pops up a window with the next pending event'))

        self.add_main_option('start-chat', 0, GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, _('Start a new chat'))

        self.add_main_option('simulate-network-lost', 0, GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE,
                             _('Simulate loss of connectivity'))

        self.add_main_option('simulate-network-connected', 0,
                             GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
                             _('Simulate regaining connectivity'))

        self.add_main_option_entries(self._get_remaining_entry())

        self.connect('handle-local-options', self._handle_local_options)
        self.connect('command-line', self._handle_remote_options)
        self.connect('startup', self._startup)
        self.connect('activate', self._activate)
        if sys.platform not in ('win32', 'darwin'):
            self.connect("notify::screensaver-active",
                         self._screensaver_active)

        self.interface = None

        GLib.set_prgname('gajim')
        if GLib.get_application_name() != 'Gajim':
            GLib.set_application_name('Gajim')

    @staticmethod
    def _get_remaining_entry():
        option = GLib.OptionEntry()
        option.arg = GLib.OptionArg.STRING_ARRAY
        option.arg_data = None
        option.arg_description = ('[URI …]')
        option.flags = GLib.OptionFlags.NONE
        option.long_name = GLib.OPTION_REMAINING
        option.short_name = 0
        return [option]

    def _startup(self, _application):

        # Create and initialize Application Paths & Databases
        app.detect_dependencies()
        configpaths.create_paths()
        try:
            app.logger = logger.Logger()
            caps_cache.initialize(app.logger)
        except exceptions.DatabaseMalformed as error:
            dlg = Gtk.MessageDialog(transient_for=None,
                                    destroy_with_parent=True,
                                    modal=True,
                                    message_type=Gtk.MessageType.ERROR,
                                    buttons=Gtk.ButtonsType.OK,
                                    text=_('Database Error'))
            dlg.format_secondary_text(str(error))
            dlg.run()
            dlg.destroy()
            sys.exit()

        from gajim.gtk.util import load_user_iconsets
        load_user_iconsets()

        # Set Application Menu
        app.app = self
        from gajim.gtk.util import get_builder
        builder = get_builder('application_menu.ui')
        menubar = builder.get_object("menubar")
        appmenu = builder.get_object("appmenu")
        if app.prefers_app_menu():
            self.set_app_menu(appmenu)
        else:
            # Add it to the menubar instead
            menubar.prepend_submenu('Gajim', appmenu)
        self.set_menubar(menubar)

    def _activate(self, _application):
        if self.interface is not None:
            self.interface.roster.window.present()
            return
        from gajim.gui_interface import Interface
        self.interface = Interface()
        self.interface.run(self)
        self.add_actions()
        self._set_shortcuts()
        from gajim import gui_menu_builder
        gui_menu_builder.build_accounts_menu()
        self.update_app_actions_state()

        app.ged.register_event_handler('feature-discovered', ged.CORE,
                                       self._on_feature_discovered)

    def _open_uris(self, uris):
        accounts = list(app.connections.keys())
        if not accounts:
            return

        for uri in uris:
            app.log('uri_handler').info('open %s', uri)
            if not uri.startswith('xmpp:'):
                continue
            # remove xmpp:
            uri = uri[5:]
            try:
                jid, cmd = uri.split('?')
            except ValueError:
                # No query argument
                jid, cmd = uri, 'message'

            try:
                jid = JID(jid)
            except InvalidJid as error:
                app.log('uri_handler').warning('Invalid JID %s: %s', uri,
                                               error)
                continue

            if cmd == 'join' and jid.getResource():
                app.log('uri_handler').warning('Invalid MUC JID %s', uri)
                continue

            jid = str(jid)

            if cmd == 'join':
                if len(accounts) == 1:
                    self.activate_action(
                        'groupchat-join', GLib.Variant('as',
                                                       [accounts[0], jid]))
                else:
                    self.activate_action('start-chat', GLib.Variant('s', jid))

            elif cmd == 'roster':
                self.activate_action('add-contact', GLib.Variant('s', jid))

            elif cmd.startswith('message'):
                attributes = cmd.split(';')
                message = None
                for key in attributes:
                    if not key.startswith('body'):
                        continue
                    try:
                        message = unquote(key.split('=')[1])
                    except Exception:
                        app.log('uri_handler').error('Invalid URI: %s', cmd)

                if len(accounts) == 1:
                    app.interface.new_chat_from_jid(accounts[0], jid, message)
                else:
                    self.activate_action('start-chat', GLib.Variant('s', jid))

    def do_shutdown(self, *args):
        Gtk.Application.do_shutdown(self)
        # Shutdown GUI and save config
        if hasattr(self.interface, 'roster') and self.interface.roster:
            self.interface.roster.prepare_quit()

        # Commit any outstanding SQL transactions
        app.logger.commit()

    def _handle_remote_options(self, _application, command_line):
        # Parse all options that should be executed on a remote instance
        options = command_line.get_options_dict()

        remote_commands = [
            'ipython',
            'show-next-pending-event',
            'start-chat',
        ]

        remaining = options.lookup_value(GLib.OPTION_REMAINING,
                                         GLib.VariantType.new('as'))

        for cmd in remote_commands:
            if options.contains(cmd):
                self.activate_action(cmd)
                return 0

        if options.contains('simulate-network-lost'):
            app.interface.network_status_changed(None, False)
            return 0

        if options.contains('simulate-network-connected'):
            app.interface.network_status_changed(None, True)
            return 0

        if remaining is not None:
            self._open_uris(remaining.unpack())
            return 0

        self.activate()
        return 0

    def _handle_local_options(self, _application: Gtk.Application,
                              options: GLib.VariantDict) -> int:
        # Parse all options that have to be executed before ::startup
        if options.contains('version'):
            print(gajim.__version__)
            return 0
        if options.contains('profile'):
            # Incorporate profile name into application id
            # to have a single app instance for each profile.
            profile = options.lookup_value('profile').get_string()
            app_id = '%s.%s' % (self.get_application_id(), profile)
            self.set_application_id(app_id)
            configpaths.set_profile(profile)
        if options.contains('separate'):
            configpaths.set_separation(True)
        if options.contains('config-path'):
            path = options.lookup_value('config-path').get_string()
            configpaths.set_config_root(path)

        configpaths.init()

        if app.get_debug_mode():
            # Redirect has to happen before logging init
            self._cleanup_debug_logs()
            self._redirect_output()
            logging_helpers.init()
            logging_helpers.set_verbose()
        else:
            logging_helpers.init()

        if options.contains('quiet'):
            logging_helpers.set_quiet()
        if options.contains('verbose'):
            logging_helpers.set_verbose()
        if options.contains('loglevel'):
            loglevel = options.lookup_value('loglevel').get_string()
            logging_helpers.set_loglevels(loglevel)
        if options.contains('warnings'):
            self.show_warnings()

        return -1

    @staticmethod
    def show_warnings():
        import traceback
        import warnings

        def warn_with_traceback(message,
                                category,
                                filename,
                                lineno,
                                _file=None,
                                line=None):
            traceback.print_stack(file=sys.stderr)
            sys.stderr.write(
                warnings.formatwarning(message, category, filename, lineno,
                                       line))

        warnings.showwarning = warn_with_traceback
        warnings.filterwarnings(action="always")

    @staticmethod
    def _redirect_output():
        debug_folder = Path(configpaths.get('DEBUG'))
        date = datetime.today().strftime('%d%m%Y-%H%M%S')
        filename = '%s-debug.log' % date
        fd = open(debug_folder / filename, 'a')
        sys.stderr = sys.stdout = fd

    @staticmethod
    def _cleanup_debug_logs():
        debug_folder = Path(configpaths.get('DEBUG'))
        debug_files = list(debug_folder.glob('*-debug.log*'))
        now = time.time()
        for file in debug_files:
            # Delete everything older than 3 days
            if file.stat().st_ctime < now - 259200:
                file.unlink()

    def add_actions(self):
        ''' Build Application Actions '''
        from gajim import app_actions

        # General Stateful Actions

        act = Gio.SimpleAction.new_stateful(
            'merge', None,
            GLib.Variant.new_boolean(app.config.get('mergeaccounts')))
        act.connect('change-state', app_actions.on_merge_accounts)
        self.add_action(act)

        actions = [
            ('quit', app_actions.on_quit),
            ('add-account', app_actions.on_add_account),
            ('manage-proxies', app_actions.on_manage_proxies),
            ('history-manager', app_actions.on_history_manager),
            ('preferences', app_actions.on_preferences),
            ('plugins', app_actions.on_plugins),
            ('xml-console', app_actions.on_xml_console),
            ('file-transfer', app_actions.on_file_transfers),
            ('history', app_actions.on_history),
            ('shortcuts', app_actions.on_keyboard_shortcuts),
            ('features', app_actions.on_features),
            ('content', app_actions.on_contents),
            ('about', app_actions.on_about),
            ('faq', app_actions.on_faq),
            ('ipython', app_actions.toggle_ipython),
            ('show-next-pending-event', app_actions.show_next_pending_event),
            ('start-chat', 's', app_actions.on_new_chat),
            ('accounts', 's', app_actions.on_accounts),
            ('add-contact', 's', app_actions.on_add_contact_jid),
            ('copy-text', 's', app_actions.copy_text),
            ('open-link', 'as', app_actions.open_link),
            ('open-mail', 's', app_actions.open_mail),
            ('create-groupchat', 's', app_actions.on_create_gc),
            ('browse-history', 'a{sv}', app_actions.on_browse_history),
            ('groupchat-join', 'as', app_actions.on_groupchat_join),
        ]

        for action in actions:
            if len(action) == 2:
                action_name, func = action
                variant = None
            else:
                action_name, variant, func = action
                variant = GLib.VariantType.new(variant)

            act = Gio.SimpleAction.new(action_name, variant)
            act.connect('activate', func)
            self.add_action(act)

        accounts_list = sorted(app.config.get_per('accounts'))
        if not accounts_list:
            return
        if len(accounts_list) > 1:
            for acc in accounts_list:
                self.add_account_actions(acc)
        else:
            self.add_account_actions(accounts_list[0])

    @staticmethod
    def _get_account_actions(account):
        from gajim import app_actions as a

        if account == 'Local':
            return []

        return [
            ('-bookmarks', a.on_bookmarks, 'online', 's'),
            ('-start-single-chat', a.on_single_message, 'online', 's'),
            ('-start-chat', a.start_chat, 'online', 'as'),
            ('-add-contact', a.on_add_contact, 'online', 'as'),
            ('-services', a.on_service_disco, 'online', 's'),
            ('-profile', a.on_profile, 'feature', 's'),
            ('-server-info', a.on_server_info, 'online', 's'),
            ('-archive', a.on_mam_preferences, 'feature', 's'),
            ('-pep-config', a.on_pep_config, 'online', 's'),
            ('-sync-history', a.on_history_sync, 'online', 's'),
            ('-privacylists', a.on_privacy_lists, 'feature', 's'),
            ('-blocking', a.on_blocking_list, 'feature', 's'),
            ('-send-server-message', a.on_send_server_message, 'online', 's'),
            ('-set-motd', a.on_set_motd, 'online', 's'),
            ('-update-motd', a.on_update_motd, 'online', 's'),
            ('-delete-motd', a.on_delete_motd, 'online', 's'),
            ('-open-event', a.on_open_event, 'always', 'a{sv}'),
            ('-import-contacts', a.on_import_contacts, 'online', 's'),
        ]

    def add_account_actions(self, account):
        for action in self._get_account_actions(account):
            action_name, func, state, type_ = action
            action_name = account + action_name
            if self.lookup_action(action_name):
                # We already added this action
                continue
            act = Gio.SimpleAction.new(action_name,
                                       GLib.VariantType.new(type_))
            act.connect("activate", func)
            if state != 'always':
                act.set_enabled(False)
            self.add_action(act)

    def remove_account_actions(self, account):
        for action in self._get_account_actions(account):
            action_name = account + action[0]
            self.remove_action(action_name)

    def set_account_actions_state(self, account, new_state=False):
        for action in self._get_account_actions(account):
            action_name, _, state, _ = action
            if not new_state and state in ('online', 'feature'):
                # We go offline
                self.lookup_action(account + action_name).set_enabled(False)
            elif new_state and state == 'online':
                # We go online
                self.lookup_action(account + action_name).set_enabled(True)

    def update_app_actions_state(self):
        active_accounts = bool(app.get_connected_accounts(exclude_local=True))
        self.lookup_action('create-groupchat').set_enabled(active_accounts)

        enabled_accounts = app.contacts.get_accounts()
        self.lookup_action('start-chat').set_enabled(enabled_accounts)

    def _set_shortcuts(self):
        shortcuts = {
            'app.quit': ['<Primary>Q'],
            'app.preferences': ['<Primary>P'],
            'app.plugins': ['<Primary>E'],
            'app.xml-console': ['<Primary><Shift>X'],
            'app.file-transfer': ['<Primary>T'],
            'app.ipython': ['<Primary><Alt>I'],
            'app.start-chat::': ['<Primary>N'],
            'app.accounts::': ['<Alt>A'],
            'app.create-groupchat::': ['<Primary>G'],
            'win.show-roster': ['<Primary>R'],
            'win.show-offline': ['<Primary>O'],
            'win.show-active': ['<Primary>Y'],
            'win.change-nickname': ['<Control><Shift>n'],
            'win.change-subject': ['<Alt>t'],
            'win.escape': ['Escape'],
            'win.browse-history': ['<Control>h'],
            'win.send-file': ['<Control>f'],
            'win.show-contact-info': ['<Control>i'],
            'win.show-emoji-chooser': ['<Alt>m'],
            'win.clear-chat': ['<Control>l'],
            'win.delete-line': ['<Control>u'],
            'win.close-tab': ['<Control>w'],
            'win.move-tab-up': ['<Control><Shift>Page_Up'],
            'win.move-tab-down': ['<Control><Shift>Page_Down'],
            'win.switch-next-tab': ['<Alt>Right'],
            'win.switch-prev-tab': ['<Alt>Left'],
            'win.switch-next-unread-tab-right':
            ['<Control>Tab', '<Control>Page_Down'],
            'win.switch-next-unread-tab-left':
            ['<Control>ISO_Left_Tab', '<Control>Page_Up'],
            'win.switch-tab-1': ['<Alt>1', '<Alt>KP_1'],
            'win.switch-tab-2': ['<Alt>2', '<Alt>KP_2'],
            'win.switch-tab-3': ['<Alt>3', '<Alt>KP_3'],
            'win.switch-tab-4': ['<Alt>4', '<Alt>KP_4'],
            'win.switch-tab-5': ['<Alt>5', '<Alt>KP_5'],
            'win.switch-tab-6': ['<Alt>6', '<Alt>KP_6'],
            'win.switch-tab-7': ['<Alt>7', '<Alt>KP_7'],
            'win.switch-tab-8': ['<Alt>8', '<Alt>KP_8'],
            'win.switch-tab-9': ['<Alt>9', '<Alt>KP_9'],
            'win.copy-text': ['<Control><Shift>c'],
        }

        for action, accels in shortcuts.items():
            self.set_accels_for_action(action, accels)

    def _on_feature_discovered(self, event):
        if event.feature == nbxmpp.NS_VCARD:
            action = '%s-profile' % event.account
            self.lookup_action(action).set_enabled(True)
        elif event.feature in (nbxmpp.NS_MAM_1, nbxmpp.NS_MAM_2):
            action = '%s-archive' % event.account
            self.lookup_action(action).set_enabled(True)
        elif event.feature == nbxmpp.NS_PRIVACY:
            action = '%s-privacylists' % event.account
            self.lookup_action(action).set_enabled(True)
        elif event.feature == nbxmpp.NS_BLOCKING:
            action = '%s-blocking' % event.account
            self.lookup_action(action).set_enabled(True)

    def _screensaver_active(self, _application, _param):
        '''Signal handler for screensaver active change'''

        active = self.get_property('screensaver-active')

        roster = app.interface.roster

        if not active:
            for account in app.connections:
                if app.account_is_connected(account) and \
                        app.sleeper_state[account] == 'autoaway-forced':
                    # We came back online after screensaver autoaway
                    roster.send_status(account, 'online',
                                       app.status_before_autoaway[account])
                    app.status_before_autoaway[account] = ''
                    app.sleeper_state[account] = 'online'
            return
        if not app.config.get('autoaway'):
            # Don't go auto away if user disabled the option
            return
        for account in app.connections:
            if (account not in app.sleeper_state
                    or not app.sleeper_state[account]):
                continue
            if app.sleeper_state[account] == 'online':
                if not app.account_is_connected(account):
                    continue
                # we save our online status
                app.status_before_autoaway[account] = \
                    app.connections[account].status
                # we go away (no auto status) [we pass True to auto param]
                auto_message = app.config.get('autoaway_message')
                if not auto_message:
                    auto_message = app.connections[account].status
                else:
                    auto_message = auto_message.replace('$S', '%(status)s')
                    auto_message = auto_message.replace('$T', '%(time)s')
                    auto_message = auto_message % {
                        'status': app.status_before_autoaway[account],
                        'time': app.config.get('autoxatime')
                    }
                roster.send_status(account, 'away', auto_message, auto=True)
                app.sleeper_state[account] = 'autoaway-forced'
Ejemplo n.º 5
0
class GajimApplication(Gtk.Application):
    '''Main class handling activation and command line.'''
    def __init__(self):
        Gtk.Application.__init__(
            self,
            application_id='org.gajim.Gajim',
            flags=(Gio.ApplicationFlags.HANDLES_COMMAND_LINE
                   | Gio.ApplicationFlags.HANDLES_OPEN))

        self.add_main_option('version', ord('V'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE,
                             _('Show the application\'s version'))
        self.add_main_option('quiet', ord('q'),
                             GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
                             _('Show only critical errors'))
        self.add_main_option(
            'separate', ord('s'), GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
            _('Separate profile files completely (even '
              'history database and plugins)'))
        self.add_main_option(
            'verbose', ord('v'), GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
            _('Print XML stanzas and other debug '
              'information'))
        self.add_main_option(
            'profile', ord('p'), GLib.OptionFlags.NONE, GLib.OptionArg.STRING,
            _('Use defined profile in configuration '
              'directory'), 'NAME')
        self.add_main_option('config-path', ord('c'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.STRING,
                             _('Set configuration directory'), 'PATH')
        self.add_main_option('loglevel', ord('l'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.STRING,
                             _('Configure logging system'), 'LEVEL')
        self.add_main_option('warnings', ord('w'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, _('Show all warnings'))
        self.add_main_option('ipython', ord('i'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, _('Open IPython shell'))
        self.add_main_option('show-next-pending-event', 0,
                             GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
                             _('Pops up a window with the next pending event'))
        self.add_main_option('start-chat', 0, GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, _('Start a new chat'))

        self.add_main_option_entries(self._get_remaining_entry())

        self.connect('handle-local-options', self._handle_local_options)
        self.connect('command-line', self._handle_remote_options)
        self.connect('startup', self._startup)
        self.connect('activate', self._activate)

        self.interface = None

        GLib.set_prgname('gajim')
        if GLib.get_application_name() != 'Gajim':
            GLib.set_application_name('Gajim')

    @staticmethod
    def _get_remaining_entry():
        option = GLib.OptionEntry()
        option.arg = GLib.OptionArg.STRING_ARRAY
        option.arg_data = None
        option.arg_description = ('[URI …]')
        option.flags = GLib.OptionFlags.NONE
        option.long_name = GLib.OPTION_REMAINING
        option.short_name = 0
        return [option]

    def _startup(self, application):

        # Create and initialize Application Paths & Databases
        app.detect_dependencies()
        configpaths.create_paths()
        try:
            app.logger = logger.Logger()
            caps_cache.initialize(app.logger)
        except exceptions.DatabaseMalformed as error:
            dlg = Gtk.MessageDialog(
                None,
                Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL,
                Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, _('Database Error'))
            dlg.format_secondary_text(str(error))
            dlg.run()
            dlg.destroy()
            sys.exit()

        # Set Application Menu
        app.app = self
        from gajim import gtkgui_helpers
        builder = gtkgui_helpers.get_gtk_builder('application_menu.ui')
        menubar = builder.get_object("menubar")
        appmenu = builder.get_object("appmenu")
        if app.prefers_app_menu():
            self.set_app_menu(appmenu)
        else:
            # Add it to the menubar instead
            menubar.prepend_submenu('Gajim', appmenu)
        self.set_menubar(menubar)

    def _activate(self, application):
        if self.interface is not None:
            self.interface.roster.window.present()
            return
        from gajim.gui_interface import Interface
        self.interface = Interface()
        self.interface.run(self)
        self.add_actions()
        from gajim import gui_menu_builder
        gui_menu_builder.build_accounts_menu()

    def _open_uris(self, uris):
        for uri in uris:
            app.log('uri_handler').info('open %s', uri)
            if not uri.startswith('xmpp:'):
                continue
            # remove xmpp:
            uri = uri[5:]
            try:
                jid, cmd = uri.split('?')
            except ValueError:
                # No query argument
                jid, cmd = uri, 'message'
            if cmd == 'join':
                self.interface.join_gc_minimal(None, jid)
            elif cmd == 'roster':
                self.activate_action('add-contact', GLib.Variant('s', jid))
            elif cmd.startswith('message'):
                attributes = cmd.split(';')
                message = None
                for key in attributes:
                    if key.startswith('body'):
                        try:
                            message = unquote(key.split('=')[1])
                        except Exception:
                            app.log('uri_handler').error(
                                'Invalid URI: %s', cmd)
                accounts = list(app.connections.keys())
                if not accounts:
                    continue
                if len(accounts) == 1:
                    app.interface.new_chat_from_jid(accounts[0], jid, message)
                else:
                    self.activate_action('start-chat')
                    start_chat_window = app.interface.instances['start_chat']
                    start_chat_window.search_entry.set_text(jid)

    def do_shutdown(self, *args):
        Gtk.Application.do_shutdown(self)
        # Shutdown GUI and save config
        if hasattr(self.interface, 'roster') and self.interface.roster:
            self.interface.roster.prepare_quit()

        # Commit any outstanding SQL transactions
        app.logger.commit()

    def _handle_remote_options(self, application, command_line):
        # Parse all options that should be executed on a remote instance
        options = command_line.get_options_dict()

        remote_commands = [
            'ipython',
            'show-next-pending-event',
            'start-chat',
        ]

        remaining = options.lookup_value(GLib.OPTION_REMAINING,
                                         GLib.VariantType.new('as'))

        for cmd in remote_commands:
            if options.contains(cmd):
                self.activate_action(cmd)
                return 0

        if remaining is not None:
            self._open_uris(remaining.unpack())
            return 0

        self.activate()
        return 0

    def _handle_local_options(self, application,
                              options: GLib.VariantDict) -> int:
        # Parse all options that have to be executed before ::startup
        if options.contains('profile'):
            # Incorporate profile name into application id
            # to have a single app instance for each profile.
            profile = options.lookup_value('profile').get_string()
            app_id = '%s.%s' % (self.get_application_id(), profile)
            self.set_application_id(app_id)
            configpaths.set_profile(profile)
        if options.contains('separate'):
            configpaths.set_separation(True)
        if options.contains('config-path'):
            path = options.lookup_value('config-path').get_string()
            configpaths.set_config_root(path)

        configpaths.init()
        logging_helpers.init()

        if options.contains('quiet'):
            logging_helpers.set_quiet()
        if options.contains('verbose'):
            logging_helpers.set_verbose()
        if options.contains('loglevel'):
            loglevel = options.lookup_value('loglevel').get_string()
            logging_helpers.set_loglevels(loglevel)
        if options.contains('warnings'):
            self.show_warnings()

        return -1

    def show_warnings(self):
        import traceback
        import warnings

        def warn_with_traceback(message,
                                category,
                                filename,
                                lineno,
                                file=None,
                                line=None):
            traceback.print_stack(file=sys.stderr)
            sys.stderr.write(
                warnings.formatwarning(message, category, filename, lineno,
                                       line))

        warnings.showwarning = warn_with_traceback
        warnings.filterwarnings(action="always")

    def add_actions(self):
        ''' Build Application Actions '''
        from gajim import app_actions

        # General Stateful Actions

        act = Gio.SimpleAction.new_stateful(
            'merge', None,
            GLib.Variant.new_boolean(app.config.get('mergeaccounts')))
        act.connect('change-state', app_actions.on_merge_accounts)
        self.add_action(act)

        act = Gio.SimpleAction.new_stateful(
            'agent', None,
            GLib.Variant.new_boolean(app.config.get('use_gpg_agent')))
        self.add_action(act)

        # General Actions

        general_actions = [
            ('quit', app_actions.on_quit),
            ('accounts', app_actions.on_accounts),
            ('add-account', app_actions.on_add_account),
            ('join-groupchat', app_actions.on_join_gc),
            ('manage-proxies', app_actions.on_manage_proxies),
            ('start-chat', app_actions.on_new_chat),
            ('bookmarks', app_actions.on_manage_bookmarks),
            ('history-manager', app_actions.on_history_manager),
            ('preferences', app_actions.on_preferences),
            ('plugins', app_actions.on_plugins),
            ('file-transfer', app_actions.on_file_transfers),
            ('history', app_actions.on_history),
            ('shortcuts', app_actions.on_keyboard_shortcuts),
            ('features', app_actions.on_features),
            ('content', app_actions.on_contents),
            ('about', app_actions.on_about),
            ('faq', app_actions.on_faq),
            ('ipython', app_actions.toggle_ipython),
            ('show-next-pending-event', app_actions.show_next_pending_event),
        ]

        act = Gio.SimpleAction.new('add-contact', GLib.VariantType.new('s'))
        act.connect("activate", app_actions.on_add_contact_jid)
        self.add_action(act)

        for action in general_actions:
            action_name, func = action
            act = Gio.SimpleAction.new(action_name, None)
            act.connect("activate", func)
            self.add_action(act)

        accounts_list = sorted(app.config.get_per('accounts'))
        if not accounts_list:
            return
        if len(accounts_list) > 1:
            for acc in accounts_list:
                self.add_account_actions(acc)
        else:
            self.add_account_actions(accounts_list[0])

    def _get_account_actions(self, account):
        from gajim import app_actions

        if account == 'Local':
            return [('-xml-console', app_actions.on_xml_console, 'always', 's')
                    ]

        return [
            ('-start-single-chat', app_actions.on_single_message, 'online',
             's'),
            ('-join-groupchat', app_actions.on_join_gc, 'online', 's'),
            ('-add-contact', app_actions.on_add_contact, 'online', 's'),
            ('-services', app_actions.on_service_disco, 'online', 's'),
            ('-profile', app_actions.on_profile, 'feature', 's'),
            ('-xml-console', app_actions.on_xml_console, 'always', 's'),
            ('-server-info', app_actions.on_server_info, 'online', 's'),
            ('-archive', app_actions.on_mam_preferences, 'feature', 's'),
            ('-sync-history', app_actions.on_history_sync, 'online', 's'),
            ('-privacylists', app_actions.on_privacy_lists, 'feature', 's'),
            ('-send-server-message', app_actions.on_send_server_message,
             'online', 's'),
            ('-set-motd', app_actions.on_set_motd, 'online', 's'),
            ('-update-motd', app_actions.on_update_motd, 'online', 's'),
            ('-delete-motd', app_actions.on_delete_motd, 'online', 's'),
            ('-activate-bookmark', app_actions.on_activate_bookmark, 'online',
             'a{sv}'),
            ('-open-event', app_actions.on_open_event, 'always', 'a{sv}'),
            ('-import-contacts', app_actions.on_import_contacts, 'online',
             's'),
        ]

    def add_account_actions(self, account):
        for action in self._get_account_actions(account):
            action_name, func, state, type_ = action
            action_name = account + action_name
            if self.lookup_action(action_name):
                # We already added this action
                continue
            act = Gio.SimpleAction.new(action_name,
                                       GLib.VariantType.new(type_))
            act.connect("activate", func)
            if state != 'always':
                act.set_enabled(False)
            self.add_action(act)

    def remove_account_actions(self, account):
        for action in self._get_account_actions(account):
            action_name = account + action[0]
            self.remove_action(action_name)

    def set_account_actions_state(self, account, new_state=False):
        for action in self._get_account_actions(account):
            action_name, _, state, _ = action
            if not new_state and state in ('online', 'feature'):
                # We go offline
                self.lookup_action(account + action_name).set_enabled(False)
            elif new_state and state == 'online':
                # We go online
                self.lookup_action(account + action_name).set_enabled(True)
Ejemplo n.º 6
0
class GajimApplication(Gtk.Application):
    '''Main class handling activation and command line.'''
    def __init__(self):
        Gtk.Application.__init__(self, application_id='org.gajim.Gajim')

        self.add_main_option('version', ord('V'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE,
                             _('Show the application\'s version'))
        self.add_main_option('quiet', ord('q'),
                             GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
                             _('Show only critical errors'))
        self.add_main_option(
            'separate', ord('s'), GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
            _('Separate profile files completely (even '
              'history db and plugins)'))
        self.add_main_option(
            'verbose', ord('v'), GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
            _('Print XML stanzas and other debug '
              'information'))
        self.add_main_option(
            'profile', ord('p'), GLib.OptionFlags.NONE, GLib.OptionArg.STRING,
            _('Use defined profile in configuration '
              'directory'), 'NAME')
        self.add_main_option('config-path', ord('c'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.STRING,
                             _('Set configuration directory'), 'PATH')
        self.add_main_option('loglevel', ord('l'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.STRING,
                             _('Configure logging system'), 'LEVEL')
        self.add_main_option('warnings', ord('w'), GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, _('Show all warnings'))
        self.add_main_option(GLib.OPTION_REMAINING, 0, GLib.OptionFlags.HIDDEN,
                             GLib.OptionArg.STRING_ARRAY, "")

        self.connect('handle-local-options', self._handle_local_options)
        self.connect('startup', self._startup)
        self.connect('activate', self._activate)

        self.profile = ''
        self.config_path = None
        self.profile_separation = False
        self.interface = None

        GLib.set_prgname('gajim')
        if GLib.get_application_name() != 'Gajim':
            GLib.set_application_name('Gajim')

    def _startup(self, application):
        from gajim import gtkexcepthook
        gtkexcepthook.init()

        try:
            import nbxmpp
        except ImportError:
            print('Gajim needs python-nbxmpp to run. Quitting…')
            sys.exit(1)

        from distutils.version import LooseVersion as V
        if V(nbxmpp.__version__) < V(MIN_NBXMPP_VER):
            print('Gajim needs python-nbxmpp >= %s to run. '
                  'Quitting...' % MIN_NBXMPP_VER)
            sys.exit(1)

        # Create and initialize Application Paths & Databases
        from gajim.common import configpaths
        configpaths.gajimpaths.init(self.config_path, self.profile,
                                    self.profile_separation)

        from gajim.common import app
        from gajim.common import check_paths
        from gajim.common import exceptions
        from gajim.common import logger
        from gajim.common import caps_cache
        try:
            app.logger = logger.Logger()
            caps_cache.initialize(app.logger)
            check_paths.check_and_possibly_create_paths()
        except exceptions.DatabaseMalformed as error:
            dlg = Gtk.MessageDialog(
                None,
                Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL,
                Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, _('Database Error'))
            dlg.format_secondary_text(str(error))
            dlg.run()
            dlg.destroy()
            sys.exit()

        if os.name == 'nt':
            import gettext
            # needed for docutils
            sys.path.append('.')
            APP = 'gajim'
            DIR = '../po'
            lang = locale.getdefaultlocale()[0]
            os.environ['LANG'] = lang
            gettext.bindtextdomain(APP, DIR)
            gettext.textdomain(APP)
            gettext.install(APP, DIR)

        # This is for Windows translation which is currently not
        # working on GTK 3.18.9
        #    locale.setlocale(locale.LC_ALL, '')
        #    import ctypes
        #    import ctypes.util
        #    libintl_path = ctypes.util.find_library('intl')
        #    if libintl_path == None:
        #        local_intl = os.path.join('gtk', 'bin', 'intl.dll')
        #        if os.path.exists(local_intl):
        #            libintl_path = local_intl
        #    if libintl_path == None:
        #        raise ImportError('intl.dll library not found')
        #    libintl = ctypes.cdll.LoadLibrary(libintl_path)
        #    libintl.bindtextdomain(APP, DIR)
        #    libintl.bind_textdomain_codeset(APP, 'UTF-8')
        #    plugins_locale_dir = os.path.join(common.configpaths.gajimpaths[
        #       'PLUGINS_USER'], 'locale').encode(locale.getpreferredencoding())
        #    libintl.bindtextdomain('gajim_plugins', plugins_locale_dir)
        #    libintl.bind_textdomain_codeset('gajim_plugins', 'UTF-8')

        if Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL:
            i18n.direction_mark = '\u200F'

        from ctypes import CDLL
        from ctypes.util import find_library
        import platform

        sysname = platform.system()
        if sysname in ('Linux', 'FreeBSD', 'OpenBSD', 'NetBSD'):
            libc = CDLL(find_library('c'))

            # The constant defined in <linux/prctl.h> which is used to set the name
            # of the process.
            PR_SET_NAME = 15

            if sysname == 'Linux':
                libc.prctl(PR_SET_NAME, 'gajim')
            elif sysname in ('FreeBSD', 'OpenBSD', 'NetBSD'):
                libc.setproctitle('gajim')

        def sigint_cb(num, stack):
            print('SIGINT/SIGTERM received')
            self.quit()

        # ^C exits the application normally
        signal.signal(signal.SIGINT, sigint_cb)
        signal.signal(signal.SIGTERM, sigint_cb)

        # Set Application Menu
        app.app = self
        path = os.path.join(configpaths.get('GUI'), 'application_menu.ui')
        builder = Gtk.Builder()
        builder.set_translation_domain(i18n.APP)
        builder.add_from_file(path)
        menubar = builder.get_object("menubar")
        appmenu = builder.get_object("appmenu")
        if app.prefers_app_menu():
            self.set_app_menu(appmenu)
        else:
            # Add it to the menubar instead
            menubar.prepend_submenu('Gajim', appmenu)
        self.set_menubar(menubar)

    def _activate(self, application):
        if self.interface is not None:
            self.interface.roster.window.present()
            return
        from gajim.gui_interface import Interface
        from gajim import gtkgui_helpers
        self.interface = Interface()
        gtkgui_helpers.load_css()
        self.interface.run(self)
        self.add_actions()
        from gajim import gui_menu_builder
        gui_menu_builder.build_accounts_menu()

    def do_shutdown(self, *args):
        Gtk.Application.do_shutdown(self)
        # Shutdown GUI and save config
        if hasattr(self.interface, 'roster') and self.interface.roster:
            self.interface.roster.prepare_quit()

        # Commit any outstanding SQL transactions
        from gajim.common import app
        app.logger.commit()

    def _handle_local_options(self, application,
                              options: GLib.VariantDict) -> int:

        logging_helpers.init()

        if options.contains('profile'):
            # Incorporate profile name into application id
            # to have a single app instance for each profile.
            profile = options.lookup_value('profile').get_string()
            app_id = '%s.%s' % (self.get_application_id(), profile)
            self.set_application_id(app_id)
            self.profile = profile
        if options.contains('separate'):
            self.profile_separation = True
        if options.contains('config-path'):
            self.config_path = options.lookup_value('config-path').get_string()
        if options.contains('version'):
            from gajim import __version__
            print(__version__)
            return 0
        if options.contains('quiet'):
            logging_helpers.set_quiet()
        if options.contains('verbose'):
            logging_helpers.set_verbose()
        if options.contains('loglevel'):
            loglevel = options.lookup_value('loglevel').get_string()
            logging_helpers.set_loglevels(loglevel)
        if options.contains('warnings'):
            self.show_warnings()
        if options.contains(GLib.OPTION_REMAINING):
            unhandled = options.lookup_value(GLib.OPTION_REMAINING).get_strv()
            print('Error: Unhandled arguments: %s' % unhandled)
            return 0
        return -1

    def show_warnings(self):
        import traceback
        import warnings

        def warn_with_traceback(message,
                                category,
                                filename,
                                lineno,
                                file=None,
                                line=None):
            traceback.print_stack(file=sys.stderr)
            sys.stderr.write(
                warnings.formatwarning(message, category, filename, lineno,
                                       line))

        warnings.showwarning = warn_with_traceback
        warnings.filterwarnings(action="always")

    def add_actions(self):
        ''' Build Application Actions '''
        from gajim.app_actions import AppActions
        from gajim.common import app
        action = AppActions(self)

        self.account_actions = [
            ('-start-single-chat', action.on_single_message, 'online', 's'),
            ('-start-chat', action.on_new_chat, 'online', 's'),
            ('-join-groupchat', action.on_join_gc, 'online', 's'),
            ('-add-contact', action.on_add_contact, 'online', 's'),
            ('-services', action.on_service_disco, 'online', 's'),
            ('-profile', action.on_profile, 'feature', 's'),
            ('-xml-console', action.on_xml_console, 'always', 's'),
            ('-server-info', action.on_server_info, 'online', 's'),
            ('-archive', action.on_archiving_preferences, 'feature', 's'),
            ('-sync-history', action.on_history_sync, 'online', 's'),
            ('-privacylists', action.on_privacy_lists, 'feature', 's'),
            ('-send-server-message', action.on_send_server_message, 'online',
             's'),
            ('-set-motd', action.on_set_motd, 'online', 's'),
            ('-update-motd', action.on_update_motd, 'online', 's'),
            ('-delete-motd', action.on_delete_motd, 'online', 's'),
            ('-activate-bookmark', action.on_activate_bookmark, 'online',
             'a{sv}'),
            ('-open-event', action.on_open_event, 'always', 'a{sv}'),
            ('-import-contacts', action.on_import_contacts, 'online', 's'),
        ]

        # General Stateful Actions

        act = Gio.SimpleAction.new_stateful(
            'merge', None,
            GLib.Variant.new_boolean(app.config.get('mergeaccounts')))
        act.connect('change-state', action.on_merge_accounts)
        self.add_action(act)

        act = Gio.SimpleAction.new_stateful(
            'agent', None,
            GLib.Variant.new_boolean(app.config.get('use_gpg_agent')))
        self.add_action(act)

        # General Actions

        self.general_actions = [
            ('quit', action.on_quit),
            ('accounts', action.on_accounts),
            ('add-account', action.on_add_account),
            ('manage-proxies', action.on_manage_proxies),
            ('bookmarks', action.on_manage_bookmarks),
            ('history-manager', action.on_history_manager),
            ('preferences', action.on_preferences),
            ('plugins', action.on_plugins),
            ('file-transfer', action.on_file_transfers),
            ('history', action.on_history),
            ('shortcuts', action.on_keyboard_shortcuts),
            ('features', action.on_features),
            ('content', action.on_contents),
            ('about', action.on_about),
            ('faq', action.on_faq),
        ]

        for action in self.general_actions:
            action_name, func = action
            act = Gio.SimpleAction.new(action_name, None)
            act.connect("activate", func)
            self.add_action(act)

        accounts_list = sorted(app.config.get_per('accounts'))
        if not accounts_list:
            return
        if len(accounts_list) > 1:
            for acc in accounts_list:
                self.add_account_actions(acc)
        else:
            self.add_account_actions(accounts_list[0])

    def add_account_actions(self, account):
        for action in self.account_actions:
            action_name, func, state, type_ = action
            action_name = account + action_name
            if self.lookup_action(action_name):
                # We already added this action
                continue
            act = Gio.SimpleAction.new(action_name,
                                       GLib.VariantType.new(type_))
            act.connect("activate", func)
            if state != 'always':
                act.set_enabled(False)
            self.add_action(act)

    def remove_account_actions(self, account):
        for action in self.account_actions:
            action_name = account + action[0]
            self.remove_action(action_name)

    def set_account_actions_state(self, account, new_state=False):
        for action in self.account_actions:
            action_name, _, state, _ = action
            if not new_state and state in ('online', 'feature'):
                # We go offline
                self.lookup_action(account + action_name).set_enabled(False)
            elif new_state and state == 'online':
                # We go online
                self.lookup_action(account + action_name).set_enabled(True)
Ejemplo n.º 7
0
        self.add_child_at_anchor(emoji, anchor)
        buffer_.delete_mark(start_mark)
        buffer_.delete_mark(end_mark)

change_cursor = None

if __name__ == '__main__':
    from gajim.conversation_textview import ConversationTextview
    from gajim.gui_interface import Interface
    from gajim.common import app, logger, caps_cache
    # TODO: don't call Logger() it will create the DB
    # maybe mock this object for tests
    # app.logger = logger.Logger()
    # caps_cache.initialize(app.logger)

    Interface()

    # create fake app.plugin_manager.gui_extension_point method for tests
    def extension_point(*args):
        pass

    def gui_extension_point(*args):
        pass

    app.plugin_manager = Interface()
    app.plugin_manager.extension_point = extension_point
    app.plugin_manager.gui_extension_point = gui_extension_point

    htmlview = ConversationTextview(None)

    def on_textview_motion_notify_event(widget, event):
Ejemplo n.º 8
0
from gajim import gui

gui.init('gtk')

from gajim.common.helpers import AdditionalDataDict

from gajim.conversation_textview import ConversationTextview
from gajim.gui_interface import Interface

app.settings = MagicMock()
app.plugin_manager = MagicMock()
app.logger = MagicMock()
app.cert_store = MagicMock()
app.storage = MagicMock()
app.interface = Interface()

XHTML = [
    '''
    <div>
        <span style="color: red; text-decoration:underline">Hello</span>
        <br/>\n
        <img src="http://images.slashdot.org/topics/topicsoftware.gif"/>
        <br/>\n
        <span style="font-size: 500%; font-family: serif">World</span>\n
    </div>
    ''',
    '''
    <hr />
    ''',
    '''