From dd7496d7a7efc68648c91b3aaca23625138c9330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sat, 27 Apr 2019 12:17:16 +0200 Subject: [PATCH] Add dedicated method for parsing and opening URIs --- gajim/app_actions.py | 10 ++- gajim/application.py | 2 +- gajim/chat_control.py | 3 +- gajim/chat_control_base.py | 3 - gajim/common/const.py | 16 +++++ gajim/common/helpers.py | 121 +++++++++++++++++++++++++-------- gajim/common/structs.py | 18 +++++ gajim/conversation_textview.py | 2 +- gajim/groupchat_control.py | 4 +- gajim/gtk/about.py | 4 +- gajim/gtk/dataform.py | 6 +- gajim/gtk/htmltextview.py | 48 +++---------- gajim/gui_interface.py | 9 --- gajim/gui_menu_builder.py | 31 +++++---- gajim/plugins/gui.py | 4 +- 15 files changed, 169 insertions(+), 112 deletions(-) create mode 100644 gajim/common/structs.py diff --git a/gajim/app_actions.py b/gajim/app_actions.py index 27de3fb2a..1fc68f77e 100644 --- a/gajim/app_actions.py +++ b/gajim/app_actions.py @@ -285,13 +285,11 @@ def on_delete_motd(action, param): def on_contents(action, param): - helpers.launch_browser_mailer( - 'url', 'https://dev.gajim.org/gajim/gajim/wikis') + helpers.open_uri('https://dev.gajim.org/gajim/gajim/wikis') def on_faq(action, param): - helpers.launch_browser_mailer( - 'url', 'https://dev.gajim.org/gajim/gajim/wikis/help/gajimfaq') + helpers.open_uri('https://dev.gajim.org/gajim/gajim/wikis/help/gajimfaq') def on_keyboard_shortcuts(action, param): @@ -351,8 +349,8 @@ def show_next_pending_event(action, param): def open_link(_action, param): - kind, link = param.get_strv() - helpers.launch_browser_mailer(kind, link) + uri = param.get_string() + helpers.open_uri(uri) def copy_text(_action, param): diff --git a/gajim/application.py b/gajim/application.py index 2235e4074..fa521c1c0 100644 --- a/gajim/application.py +++ b/gajim/application.py @@ -420,7 +420,7 @@ class GajimApplication(Gtk.Application): act.connect("activate", app_actions.copy_text) self.add_action(act) - act = Gio.SimpleAction.new('open-link', GLib.VariantType.new('as')) + act = Gio.SimpleAction.new('open-link', GLib.VariantType.new('s')) act.connect("activate", app_actions.open_link) self.add_action(act) diff --git a/gajim/chat_control.py b/gajim/chat_control.py index 17eeb298b..9721f8d73 100644 --- a/gajim/chat_control.py +++ b/gajim/chat_control.py @@ -43,6 +43,7 @@ from gajim.common import ged from gajim.common import i18n from gajim.common.i18n import _ from gajim.common.helpers import AdditionalDataDict +from gajim.common.helpers import open_uri from gajim.common.contacts import GC_Contact from gajim.common.const import AvatarSize from gajim.common.const import KindConstant @@ -651,7 +652,7 @@ class ChatControl(ChatControlBase): uri = 'https://www.openstreetmap.org/?' + \ 'mlat=%(lat)s&mlon=%(lon)s&zoom=16' % {'lat': location['lat'], 'lon': location['lon']} - helpers.launch_browser_mailer('url', uri) + open_uri(uri) def on_location_eventbox_leave_notify_event(self, widget, event): """ diff --git a/gajim/chat_control_base.py b/gajim/chat_control_base.py index d66c639c5..5ec2c90bb 100644 --- a/gajim/chat_control_base.py +++ b/gajim/chat_control_base.py @@ -167,9 +167,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): if self.parent_win: self.parent_win.redraw_tab(self) - def status_url_clicked(self, widget, url): - helpers.launch_browser_mailer('url', url) - def setup_seclabel(self): self.seclabel_combo.hide() self.seclabel_combo.set_no_show_all(True) diff --git a/gajim/common/const.py b/gajim/common/const.py index de43a49a0..e50e0f5a3 100644 --- a/gajim/common/const.py +++ b/gajim/common/const.py @@ -184,6 +184,22 @@ class Display(Enum): QUARTZ = 'GdkQuartzDisplay' +class URIType(Enum): + UNKNOWN = 'unknown' + XMPP = 'xmpp' + MAIL = 'mail' + GEO = 'geo' + WEB = 'web' + FILE = 'file' + AT = 'at' + + +class URIAction(Enum): + MESSAGE = 'message' + JOIN = 'join' + SUBSCRIBE = 'subscribe' + + EME_MESSAGES = { 'urn:xmpp:otr:0': _('This message was encrypted with OTR ' diff --git a/gajim/common/helpers.py b/gajim/common/helpers.py index 8fa9c4cfb..9c848203f 100644 --- a/gajim/common/helpers.py +++ b/gajim/common/helpers.py @@ -65,6 +65,9 @@ from gajim.common.i18n import _ from gajim.common.i18n import ngettext from gajim.common.const import ShowConstant from gajim.common.const import Display +from gajim.common.const import URIType +from gajim.common.const import URIAction +from gajim.common.structs import URI if app.is_installed('PYCURL'): import pycurl @@ -629,35 +632,6 @@ def get_contact_dict_for_account(account): contacts_dict[name] = contact return contacts_dict -def launch_browser_mailer(kind, uri): - # kind = 'url' or 'mail' - if kind == 'url' and uri.startswith('file://'): - launch_file_manager(uri) - return - if kind in ('mail', 'sth_at_sth') and not uri.startswith('mailto:'): - uri = 'mailto:' + uri - - if kind == 'url' and uri.startswith('www.'): - uri = 'http://' + uri - - if not app.config.get('autodetect_browser_mailer'): - if kind == 'url': - command = app.config.get('custombrowser') - elif kind in ('mail', 'sth_at_sth'): - command = app.config.get('custommailapp') - if command == '': # if no app is configured - return - - command = build_command(command, uri) - try: - exec_command(command) - except Exception: - pass - - else: - webbrowser.open(uri) - - def launch_file_manager(path_to_open): if os.name == 'nt': try: @@ -1502,3 +1476,92 @@ def delay_execution(milliseconds): milliseconds, timeout_wrapper) return func_wrapper return delay_execution_decorator + + +def parse_uri(uri): + if uri.startswith('xmpp:'): + uri = uri[5:] + if '?' in uri: + jid, action = uri.split('?') + try: + return URI(type=URIType.XMPP, + action=URIAction(action), + data=jid) + except ValueError: + # Unknown action + pass + + return URI(type=URIType.XMPP, action=URIAction.MESSAGE, data=uri) + + if uri.startswith('mailto:'): + uri = uri[7:] + return URI(type=URIType.MAIL, data=uri) + + if app.interface.sth_at_sth_dot_sth_re.match(uri): + return URI(type=URIType.AT, data=uri) + + if uri.startswith('geo:'): + location = uri[4:] + lat, _, lon = location.partition(',') + if not lon: + return URI(type=URIType.UNKNOWN, data=uri) + + uri = ('https://www.openstreetmap.org/?' + 'mlat=%s&mlon=%s&zoom=16') % (lat, lon) + return URI(type=URIType.GEO, data=uri) + + if uri.startswith('file://'): + return URI(type=URIType.FILE, data=uri) + + return URI(type=URIType.WEB, data=uri) + + +def open_uri(uri, account=None): + if not isinstance(uri, URI): + uri = parse_uri(uri) + + if uri.type == URIType.FILE: + launch_file_manager(uri.data) + + elif uri.type == URIType.MAIL: + uri = 'mailto:%s' % uri.data + if not app.config.get('autodetect_browser_mailer'): + open_uri_with_custom('custommailapp', 'mailto:%s' % uri) + else: + webbrowser.open(uri) + + elif uri.type in (URIType.WEB, URIType.GEO): + if not app.config.get('autodetect_browser_mailer'): + open_uri_with_custom('custombrowser', uri.data) + else: + webbrowser.open(uri.data) + + elif uri.type == URIType.AT: + app.interface.new_chat_from_jid(account, uri.data) + + elif uri.type == URIType.XMPP: + if account is None: + log.warning('Account must be specified to open XMPP uri') + return + + if uri.action == URIAction.JOIN: + app.interface.join_gc_minimal(account, uri.data) + elif uri.action == URIAction.MESSAGE: + app.interface.new_chat_from_jid(account, uri.data) + else: + log.warning('Cant open URI: %s', uri) + + else: + log.warning('Cant open URI: %s', uri) + + +def open_uri_with_custom(config_app, uri): + command = app.config.get(config_app) + if not command: + log.warning('No custom application set') + return + command = build_command(command, uri) + try: + exec_command(command) + except Exception: + pass diff --git a/gajim/common/structs.py b/gajim/common/structs.py new file mode 100644 index 000000000..9d1dd421f --- /dev/null +++ b/gajim/common/structs.py @@ -0,0 +1,18 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see . + +from collections import namedtuple + +URI = namedtuple('URI', 'type action data') +URI.__new__.__defaults__ = (None, None) # type: ignore diff --git a/gajim/conversation_textview.py b/gajim/conversation_textview.py index ba0f5721a..d1cd712ca 100644 --- a/gajim/conversation_textview.py +++ b/gajim/conversation_textview.py @@ -518,7 +518,7 @@ class ConversationTextview(GObject.GObject): """ Basically it filters out the widget instance """ - helpers.launch_browser_mailer('url', link) + helpers.open_uri(link) def on_textview_populate_popup(self, textview, menu): """ diff --git a/gajim/groupchat_control.py b/gajim/groupchat_control.py index 74097fbd7..4bc00ce29 100644 --- a/gajim/groupchat_control.py +++ b/gajim/groupchat_control.py @@ -55,7 +55,7 @@ from gajim.common.caps_cache import muc_caps_cache from gajim.common import events from gajim.common import app from gajim.common import helpers -from gajim.common.helpers import launch_browser_mailer +from gajim.common.helpers import open_uri from gajim.common.helpers import AdditionalDataDict from gajim.common import ged from gajim.common.i18n import _ @@ -3106,5 +3106,5 @@ class SubjectPopover(Gtk.Popover): def _on_activate_link(_label, uri): # We have to use this, because the default GTK handler # is not cross-platform compatible - launch_browser_mailer(None, uri) + open_uri(uri) return Gdk.EVENT_STOP diff --git a/gajim/gtk/about.py b/gajim/gtk/about.py index 0bc5c8105..c95ab8304 100644 --- a/gajim/gtk/about.py +++ b/gajim/gtk/about.py @@ -19,7 +19,7 @@ from gi.repository import Gtk from gi.repository import GObject from gajim.common import app -from gajim.common.helpers import launch_browser_mailer +from gajim.common.helpers import open_uri from gajim.common.i18n import _ from gajim.common.const import DEVS_CURRENT from gajim.common.const import DEVS_PAST @@ -75,7 +75,7 @@ class AboutDialog(Gtk.AboutDialog): def _on_activate_link(_label, uri): # We have to use this, because the default GTK handler # is not cross-platform compatible - launch_browser_mailer(None, uri) + open_uri(uri) return Gdk.EVENT_STOP def _connect_link_handler(self, parent): diff --git a/gajim/gtk/dataform.py b/gajim/gtk/dataform.py index 85a25f51c..467c9f278 100644 --- a/gajim/gtk/dataform.py +++ b/gajim/gtk/dataform.py @@ -21,7 +21,7 @@ from gajim.gtkgui_helpers import scale_pixbuf_from_data from gajim.common import app from gajim.common.i18n import _ -from gajim.common.helpers import launch_browser_mailer +from gajim.common.helpers import open_uri from gajim.common.modules.dataforms import extend_form from gajim.gtk.util import MultiLineLabel @@ -588,9 +588,7 @@ class FakeDataFormWidget(Gtk.ScrolledWindow): button = Gtk.Button(label='Register') button.set_halign(Gtk.Align.CENTER) button.get_style_context().add_class('suggested-action') - button.connect('clicked', - lambda *args: launch_browser_mailer('url', - redirect_url)) + button.connect('clicked', lambda *args: open_uri(redirect_url)) self._grid.attach(button, 0, self._row_count, 2, 1) else: self._add_fields() diff --git a/gajim/gtk/htmltextview.py b/gajim/gtk/htmltextview.py index 1575c2816..2fb439486 100644 --- a/gajim/gtk/htmltextview.py +++ b/gajim/gtk/htmltextview.py @@ -49,6 +49,8 @@ from gajim.common import app from gajim.common import helpers from gajim.common.i18n import _ from gajim.common.const import StyleAttr +from gajim.common.helpers import open_uri +from gajim.common.helpers import parse_uri from gajim.gtk.util import load_icon from gajim.gtk.util import get_cursor @@ -877,8 +879,8 @@ class HtmlTextView(Gtk.TextView): self._cursor_changed = False return False - def show_context_menu(self, _event, kind, text): - menu = get_conv_context_menu(self.account, kind, text) + def show_context_menu(self, uri): + menu = get_conv_context_menu(self.account, uri) if menu is None: return @@ -904,53 +906,21 @@ class HtmlTextView(Gtk.TextView): # Detect XHTML-IM link word = getattr(texttag, 'href', None) - if word: - if word.startswith('xmpp'): - kind = 'xmpp' - elif word.startswith('mailto:'): - kind = 'mail' - elif app.interface.sth_at_sth_dot_sth_re.match(word): - # it's a JID or mail - kind = 'sth_at_sth' - else: + if not word: word = self.get_buffer().get_text(begin_iter, end_iter, True) + uri = parse_uri(word) if event.button.button == 3: # right click - self.show_context_menu(event, kind, word) + self.show_context_menu(uri) return True self.plugin_modified = False app.plugin_manager.extension_point( - 'hyperlink_handler', word, kind, self, - self.get_toplevel()) + 'hyperlink_handler', uri, self, self.get_toplevel()) if self.plugin_modified: return - # we launch the correct application - if kind == 'xmpp': - word = word[5:] - if '?' in word: - (jid, action) = word.split('?') - if action == 'join': - app.interface.join_gc_minimal(self.account, jid) - else: - app.interface.new_chat_from_jid(self.account, jid) - else: - app.interface.new_chat_from_jid(self.account, word) - - # handle geo:-URIs - elif word[:4] == 'geo:': - location = word[4:] - lat, _, lon = location.partition(',') - if lon == '': - return - uri = 'https://www.openstreetmap.org/?' \ - 'mlat=%(lat)s&mlon=%(lon)s&zoom=16' % \ - {'lat': lat, 'lon': lon} - helpers.launch_browser_mailer(kind, uri) - else: - # other URIs - helpers.launch_browser_mailer(kind, word) + open_uri(uri, account=self.account) def display_html(self, html, textview, conv_textview, iter_=None): buffer_ = self.get_buffer() diff --git a/gajim/gui_interface.py b/gajim/gui_interface.py index 208d50a70..455c21b0a 100644 --- a/gajim/gui_interface.py +++ b/gajim/gui_interface.py @@ -1957,10 +1957,6 @@ class Interface: self.systray_enabled = False self.systray.hide_icon() - @staticmethod - def on_launch_browser_mailer(widget, url, kind): - helpers.launch_browser_mailer(kind, url) - def process_connections(self): """ Called each foo (200) milliseconds. Check for idlequeue timeouts @@ -2381,11 +2377,6 @@ class Interface: app.config.get_per('accounts', account, 'active'): app.connections[account] = Connection(account) - # gtk hooks -# Gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail') -# Gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url') -# Gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url') - self.instances = {} for a in app.connections: diff --git a/gajim/gui_menu_builder.py b/gajim/gui_menu_builder.py index eb1cb6f34..a3793ff7a 100644 --- a/gajim/gui_menu_builder.py +++ b/gajim/gui_menu_builder.py @@ -25,6 +25,8 @@ from gajim.common import app from gajim.common import helpers from gajim.common.i18n import ngettext from gajim.common.i18n import _ +from gajim.common.const import URIType +from gajim.common.const import URIAction from gajim.gtk.util import get_builder @@ -918,9 +920,9 @@ def get_encryption_menu(control_id, type_id, zeroconf=False): return menu -def get_conv_context_menu(account, kind, text): - if kind == 'xmpp': - if '?join' in text: +def get_conv_context_menu(account, uri): + if uri.type == URIType.XMPP: + if uri.action == URIAction.JOIN: context_menu = [ ('copy-text', _('Copy JID')), ('-join-groupchat', _('Join Groupchat')), @@ -932,19 +934,25 @@ def get_conv_context_menu(account, kind, text): ('-add-contact', _('Add to Roster…')), ] - elif kind == 'url': + elif uri.type == URIType.WEB: context_menu = [ ('copy-text', _('Copy Link Location')), ('open-link', _('Open Link in Browser')), ] - elif kind == 'mail': + elif uri.type == URIType.MAIL: context_menu = [ ('copy-text', _('Copy Email Address')), ('open-link', _('Open Email Composer')), ] - elif kind == 'sth_at_sth': + elif uri.type == URIType.GEO: + context_menu = [ + ('copy-text', _('Copy Location')), + ('open-link', _('Show Location')), + ] + + elif uri.type == URIType.AT: context_menu = [ ('copy-text', _('Copy JID/Email')), ('open-link', _('Open Email Composer')), @@ -953,6 +961,7 @@ def get_conv_context_menu(account, kind, text): ('-add-contact', _('Add to Roster…')), ] else: + log.warning('No handler for URI type: %s', uri) return menu = Gtk.Menu() @@ -962,19 +971,15 @@ def get_conv_context_menu(account, kind, text): menuitem.set_label(label) if action.startswith('-'): - text = text.replace('xmpp:', '') - text = text.split('?')[0] action = 'app.%s%s' % (account, action) else: action = 'app.%s' % action menuitem.set_action_name(action) - if action == 'app.open-link': - value = GLib.Variant.new_strv([kind, text]) - elif action == 'app.copy-text': - value = GLib.Variant.new_string(text) + if action in ('app.open-link', 'app.copy-text'): + value = GLib.Variant.new_string(uri.data) else: - value = GLib.Variant.new_strv([account, text]) + value = GLib.Variant.new_strv([account, uri.data]) menuitem.set_action_target_value(value) menuitem.show() menu.append(menuitem) diff --git a/gajim/plugins/gui.py b/gajim/plugins/gui.py index 2b7efe5be..6b447cb50 100644 --- a/gajim/plugins/gui.py +++ b/gajim/plugins/gui.py @@ -32,7 +32,7 @@ from gi.repository import Gdk from gajim.common import app from gajim.common import configpaths from gajim.common.exceptions import PluginsystemError -from gajim.common.helpers import launch_browser_mailer +from gajim.common.helpers import open_uri from gajim.plugins.helpers import log_calls from gajim.plugins.helpers import GajimPluginActivateException @@ -250,7 +250,7 @@ class PluginsWindow: @log_calls('PluginsWindow') def on_install_plugin_button_clicked(self, widget): if app.is_flatpak(): - launch_browser_mailer('url', 'https://dev.gajim.org/gajim/gajim/wikis/help/flathub') + open_uri('https://dev.gajim.org/gajim/gajim/wikis/help/flathub') return def show_warn_dialog():