Add dedicated method for parsing and opening URIs

This commit is contained in:
Philipp Hörist 2019-04-27 12:17:16 +02:00
parent 6c6c4bdcfe
commit dd7496d7a7
15 changed files with 169 additions and 112 deletions

View file

@ -285,13 +285,11 @@ def on_delete_motd(action, param):
def on_contents(action, param): def on_contents(action, param):
helpers.launch_browser_mailer( helpers.open_uri('https://dev.gajim.org/gajim/gajim/wikis')
'url', 'https://dev.gajim.org/gajim/gajim/wikis')
def on_faq(action, param): def on_faq(action, param):
helpers.launch_browser_mailer( helpers.open_uri('https://dev.gajim.org/gajim/gajim/wikis/help/gajimfaq')
'url', 'https://dev.gajim.org/gajim/gajim/wikis/help/gajimfaq')
def on_keyboard_shortcuts(action, param): def on_keyboard_shortcuts(action, param):
@ -351,8 +349,8 @@ def show_next_pending_event(action, param):
def open_link(_action, param): def open_link(_action, param):
kind, link = param.get_strv() uri = param.get_string()
helpers.launch_browser_mailer(kind, link) helpers.open_uri(uri)
def copy_text(_action, param): def copy_text(_action, param):

View file

@ -420,7 +420,7 @@ class GajimApplication(Gtk.Application):
act.connect("activate", app_actions.copy_text) act.connect("activate", app_actions.copy_text)
self.add_action(act) 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) act.connect("activate", app_actions.open_link)
self.add_action(act) self.add_action(act)

View file

@ -43,6 +43,7 @@ from gajim.common import ged
from gajim.common import i18n from gajim.common import i18n
from gajim.common.i18n import _ from gajim.common.i18n import _
from gajim.common.helpers import AdditionalDataDict from gajim.common.helpers import AdditionalDataDict
from gajim.common.helpers import open_uri
from gajim.common.contacts import GC_Contact from gajim.common.contacts import GC_Contact
from gajim.common.const import AvatarSize from gajim.common.const import AvatarSize
from gajim.common.const import KindConstant from gajim.common.const import KindConstant
@ -651,7 +652,7 @@ class ChatControl(ChatControlBase):
uri = 'https://www.openstreetmap.org/?' + \ uri = 'https://www.openstreetmap.org/?' + \
'mlat=%(lat)s&mlon=%(lon)s&zoom=16' % {'lat': location['lat'], 'mlat=%(lat)s&mlon=%(lon)s&zoom=16' % {'lat': location['lat'],
'lon': location['lon']} 'lon': location['lon']}
helpers.launch_browser_mailer('url', uri) open_uri(uri)
def on_location_eventbox_leave_notify_event(self, widget, event): def on_location_eventbox_leave_notify_event(self, widget, event):
""" """

View file

@ -167,9 +167,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
if self.parent_win: if self.parent_win:
self.parent_win.redraw_tab(self) self.parent_win.redraw_tab(self)
def status_url_clicked(self, widget, url):
helpers.launch_browser_mailer('url', url)
def setup_seclabel(self): def setup_seclabel(self):
self.seclabel_combo.hide() self.seclabel_combo.hide()
self.seclabel_combo.set_no_show_all(True) self.seclabel_combo.set_no_show_all(True)

View file

@ -184,6 +184,22 @@ class Display(Enum):
QUARTZ = 'GdkQuartzDisplay' 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 = { EME_MESSAGES = {
'urn:xmpp:otr:0': 'urn:xmpp:otr:0':
_('This message was encrypted with OTR ' _('This message was encrypted with OTR '

View file

@ -65,6 +65,9 @@ from gajim.common.i18n import _
from gajim.common.i18n import ngettext from gajim.common.i18n import ngettext
from gajim.common.const import ShowConstant from gajim.common.const import ShowConstant
from gajim.common.const import Display 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'): if app.is_installed('PYCURL'):
import pycurl import pycurl
@ -629,35 +632,6 @@ def get_contact_dict_for_account(account):
contacts_dict[name] = contact contacts_dict[name] = contact
return contacts_dict 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): def launch_file_manager(path_to_open):
if os.name == 'nt': if os.name == 'nt':
try: try:
@ -1502,3 +1476,92 @@ def delay_execution(milliseconds):
milliseconds, timeout_wrapper) milliseconds, timeout_wrapper)
return func_wrapper return func_wrapper
return delay_execution_decorator 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

18
gajim/common/structs.py Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
from collections import namedtuple
URI = namedtuple('URI', 'type action data')
URI.__new__.__defaults__ = (None, None) # type: ignore

View file

@ -518,7 +518,7 @@ class ConversationTextview(GObject.GObject):
""" """
Basically it filters out the widget instance 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): def on_textview_populate_popup(self, textview, menu):
""" """

View file

@ -55,7 +55,7 @@ from gajim.common.caps_cache import muc_caps_cache
from gajim.common import events from gajim.common import events
from gajim.common import app from gajim.common import app
from gajim.common import helpers 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.helpers import AdditionalDataDict
from gajim.common import ged from gajim.common import ged
from gajim.common.i18n import _ from gajim.common.i18n import _
@ -3106,5 +3106,5 @@ class SubjectPopover(Gtk.Popover):
def _on_activate_link(_label, uri): def _on_activate_link(_label, uri):
# We have to use this, because the default GTK handler # We have to use this, because the default GTK handler
# is not cross-platform compatible # is not cross-platform compatible
launch_browser_mailer(None, uri) open_uri(uri)
return Gdk.EVENT_STOP return Gdk.EVENT_STOP

View file

@ -19,7 +19,7 @@ from gi.repository import Gtk
from gi.repository import GObject from gi.repository import GObject
from gajim.common import app 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.i18n import _
from gajim.common.const import DEVS_CURRENT from gajim.common.const import DEVS_CURRENT
from gajim.common.const import DEVS_PAST from gajim.common.const import DEVS_PAST
@ -75,7 +75,7 @@ class AboutDialog(Gtk.AboutDialog):
def _on_activate_link(_label, uri): def _on_activate_link(_label, uri):
# We have to use this, because the default GTK handler # We have to use this, because the default GTK handler
# is not cross-platform compatible # is not cross-platform compatible
launch_browser_mailer(None, uri) open_uri(uri)
return Gdk.EVENT_STOP return Gdk.EVENT_STOP
def _connect_link_handler(self, parent): def _connect_link_handler(self, parent):

View file

@ -21,7 +21,7 @@ from gajim.gtkgui_helpers import scale_pixbuf_from_data
from gajim.common import app from gajim.common import app
from gajim.common.i18n import _ 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.common.modules.dataforms import extend_form
from gajim.gtk.util import MultiLineLabel from gajim.gtk.util import MultiLineLabel
@ -588,9 +588,7 @@ class FakeDataFormWidget(Gtk.ScrolledWindow):
button = Gtk.Button(label='Register') button = Gtk.Button(label='Register')
button.set_halign(Gtk.Align.CENTER) button.set_halign(Gtk.Align.CENTER)
button.get_style_context().add_class('suggested-action') button.get_style_context().add_class('suggested-action')
button.connect('clicked', button.connect('clicked', lambda *args: open_uri(redirect_url))
lambda *args: launch_browser_mailer('url',
redirect_url))
self._grid.attach(button, 0, self._row_count, 2, 1) self._grid.attach(button, 0, self._row_count, 2, 1)
else: else:
self._add_fields() self._add_fields()

View file

@ -49,6 +49,8 @@ from gajim.common import app
from gajim.common import helpers from gajim.common import helpers
from gajim.common.i18n import _ from gajim.common.i18n import _
from gajim.common.const import StyleAttr 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 load_icon
from gajim.gtk.util import get_cursor from gajim.gtk.util import get_cursor
@ -877,8 +879,8 @@ class HtmlTextView(Gtk.TextView):
self._cursor_changed = False self._cursor_changed = False
return False return False
def show_context_menu(self, _event, kind, text): def show_context_menu(self, uri):
menu = get_conv_context_menu(self.account, kind, text) menu = get_conv_context_menu(self.account, uri)
if menu is None: if menu is None:
return return
@ -904,53 +906,21 @@ class HtmlTextView(Gtk.TextView):
# Detect XHTML-IM link # Detect XHTML-IM link
word = getattr(texttag, 'href', None) word = getattr(texttag, 'href', None)
if word: if not 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:
word = self.get_buffer().get_text(begin_iter, end_iter, True) word = self.get_buffer().get_text(begin_iter, end_iter, True)
uri = parse_uri(word)
if event.button.button == 3: # right click if event.button.button == 3: # right click
self.show_context_menu(event, kind, word) self.show_context_menu(uri)
return True return True
self.plugin_modified = False self.plugin_modified = False
app.plugin_manager.extension_point( app.plugin_manager.extension_point(
'hyperlink_handler', word, kind, self, 'hyperlink_handler', uri, self, self.get_toplevel())
self.get_toplevel())
if self.plugin_modified: if self.plugin_modified:
return return
# we launch the correct application open_uri(uri, account=self.account)
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)
def display_html(self, html, textview, conv_textview, iter_=None): def display_html(self, html, textview, conv_textview, iter_=None):
buffer_ = self.get_buffer() buffer_ = self.get_buffer()

View file

@ -1957,10 +1957,6 @@ class Interface:
self.systray_enabled = False self.systray_enabled = False
self.systray.hide_icon() self.systray.hide_icon()
@staticmethod
def on_launch_browser_mailer(widget, url, kind):
helpers.launch_browser_mailer(kind, url)
def process_connections(self): def process_connections(self):
""" """
Called each foo (200) milliseconds. Check for idlequeue timeouts Called each foo (200) milliseconds. Check for idlequeue timeouts
@ -2381,11 +2377,6 @@ class Interface:
app.config.get_per('accounts', account, 'active'): app.config.get_per('accounts', account, 'active'):
app.connections[account] = Connection(account) 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 = {} self.instances = {}
for a in app.connections: for a in app.connections:

View file

@ -25,6 +25,8 @@ from gajim.common import app
from gajim.common import helpers from gajim.common import helpers
from gajim.common.i18n import ngettext from gajim.common.i18n import ngettext
from gajim.common.i18n import _ from gajim.common.i18n import _
from gajim.common.const import URIType
from gajim.common.const import URIAction
from gajim.gtk.util import get_builder from gajim.gtk.util import get_builder
@ -918,9 +920,9 @@ def get_encryption_menu(control_id, type_id, zeroconf=False):
return menu return menu
def get_conv_context_menu(account, kind, text): def get_conv_context_menu(account, uri):
if kind == 'xmpp': if uri.type == URIType.XMPP:
if '?join' in text: if uri.action == URIAction.JOIN:
context_menu = [ context_menu = [
('copy-text', _('Copy JID')), ('copy-text', _('Copy JID')),
('-join-groupchat', _('Join Groupchat')), ('-join-groupchat', _('Join Groupchat')),
@ -932,19 +934,25 @@ def get_conv_context_menu(account, kind, text):
('-add-contact', _('Add to Roster…')), ('-add-contact', _('Add to Roster…')),
] ]
elif kind == 'url': elif uri.type == URIType.WEB:
context_menu = [ context_menu = [
('copy-text', _('Copy Link Location')), ('copy-text', _('Copy Link Location')),
('open-link', _('Open Link in Browser')), ('open-link', _('Open Link in Browser')),
] ]
elif kind == 'mail': elif uri.type == URIType.MAIL:
context_menu = [ context_menu = [
('copy-text', _('Copy Email Address')), ('copy-text', _('Copy Email Address')),
('open-link', _('Open Email Composer')), ('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 = [ context_menu = [
('copy-text', _('Copy JID/Email')), ('copy-text', _('Copy JID/Email')),
('open-link', _('Open Email Composer')), ('open-link', _('Open Email Composer')),
@ -953,6 +961,7 @@ def get_conv_context_menu(account, kind, text):
('-add-contact', _('Add to Roster…')), ('-add-contact', _('Add to Roster…')),
] ]
else: else:
log.warning('No handler for URI type: %s', uri)
return return
menu = Gtk.Menu() menu = Gtk.Menu()
@ -962,19 +971,15 @@ def get_conv_context_menu(account, kind, text):
menuitem.set_label(label) menuitem.set_label(label)
if action.startswith('-'): if action.startswith('-'):
text = text.replace('xmpp:', '')
text = text.split('?')[0]
action = 'app.%s%s' % (account, action) action = 'app.%s%s' % (account, action)
else: else:
action = 'app.%s' % action action = 'app.%s' % action
menuitem.set_action_name(action) menuitem.set_action_name(action)
if action == 'app.open-link': if action in ('app.open-link', 'app.copy-text'):
value = GLib.Variant.new_strv([kind, text]) value = GLib.Variant.new_string(uri.data)
elif action == 'app.copy-text':
value = GLib.Variant.new_string(text)
else: else:
value = GLib.Variant.new_strv([account, text]) value = GLib.Variant.new_strv([account, uri.data])
menuitem.set_action_target_value(value) menuitem.set_action_target_value(value)
menuitem.show() menuitem.show()
menu.append(menuitem) menu.append(menuitem)

View file

@ -32,7 +32,7 @@ from gi.repository import Gdk
from gajim.common import app from gajim.common import app
from gajim.common import configpaths from gajim.common import configpaths
from gajim.common.exceptions import PluginsystemError 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 log_calls
from gajim.plugins.helpers import GajimPluginActivateException from gajim.plugins.helpers import GajimPluginActivateException
@ -250,7 +250,7 @@ class PluginsWindow:
@log_calls('PluginsWindow') @log_calls('PluginsWindow')
def on_install_plugin_button_clicked(self, widget): def on_install_plugin_button_clicked(self, widget):
if app.is_flatpak(): 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 return
def show_warn_dialog(): def show_warn_dialog():