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 _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)
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')
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'
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)
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)
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):
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 /> ''', '''