diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index a185212c1..fb664c324 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -252,12 +252,13 @@ class CommandDispatcher: self._tabs.currentWidget().go_forward() @cmdutils.register(instance='mainwindow.tabs.cmd') - def hint(self, groupstr='all', targetstr='normal'): + def hint(self, groupstr='all', targetstr='normal', *args): """Start hinting. Args: groupstr: The hinting mode to use. targetstr: Where to open the links. + *args: Arguments for spawn/userscript. """ widget = self._tabs.currentWidget() frame = widget.page().mainFrame() @@ -272,7 +273,7 @@ class CommandDispatcher: except AttributeError: raise CommandError("Unknown hinting target {}!".format(targetstr)) widget.hintmanager.start(frame, self._tabs.current_url(), group, - target) + target, *args) @cmdutils.register(instance='mainwindow.tabs.cmd', hide=True) def follow_hint(self): @@ -602,11 +603,19 @@ class CommandDispatcher: self.openurl(config.get('general', 'startpage')[0]) @cmdutils.register(instance='mainwindow.tabs.cmd') - def run_userscript(self, cmd, *args): - """Run an userscript given as argument.""" + def run_userscript(self, cmd, *args, url=None): + """Run an userscript given as argument. + + Args: + cmd: The userscript to run. + args: Arguments to pass to the userscript. + url: A custom QUrl to use instead of the current url. + """ + if url is None: + url = self._tabs.current_url() # We don't remove the password in the URL here, as it's probably safe # to pass via env variable. - urlstr = self._tabs.current_url().toString(QUrl.FullyEncoded) + urlstr = url.toString(QUrl.FullyEncoded) runner = UserscriptRunner(self._tabs) runner.got_cmd.connect(self._tabs.got_cmd) runner.run(cmd, *args, env={'QUTE_URL': urlstr}) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 2f107839f..e07626233 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -20,6 +20,7 @@ """A HintManager to draw hints over links.""" import math +import subprocess from collections import namedtuple from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QEvent, Qt, QUrl @@ -40,7 +41,8 @@ ElemTuple = namedtuple('ElemTuple', 'elem, label') Target = enum('Target', 'normal', 'tab', 'tab_bg', 'yank', 'yank_primary', - 'cmd', 'cmd_tab', 'cmd_tab_bg', 'rapid', 'download') + 'cmd', 'cmd_tab', 'cmd_tab_bg', 'rapid', 'download', + 'userscript', 'spawn') class HintContext: @@ -57,8 +59,11 @@ class HintContext: cmd/cmd_tab/cmd_tab_bg: Enter link to commandline rapid: Rapid mode with background tabs download: Download the link. + userscript: Call a custom userscript. + spawn: Spawn a simple command. to_follow: The link to follow when enter is pressed. connected_frames: The QWebFrames which are connected to a signal. + args: Custom arguments for userscript/spawn """ def __init__(self): @@ -68,6 +73,7 @@ class HintContext: self.to_follow = None self.frames = [] self.connected_frames = [] + self.args = [] @property def target(self): @@ -105,6 +111,9 @@ class HintManager(QObject): download_get: Download an URL. arg 0: The URL to download, as QUrl. arg 1: The QWebPage to download the URL in. + run_userscript: Emitted when a custom userscript should be run. + arg 0: The URL which was selected. + arg 1: The userscript/args to run. """ HINT_CSS = """ @@ -132,6 +141,8 @@ class HintManager(QObject): Target.cmd_tab_bg: "Set hint in commandline as background tab...", Target.rapid: "Follow hint (rapid mode)...", Target.download: "Download hint...", + Target.userscript: "Call userscript via hint...", + Target.spawn: "Spawn command via hint...", } hint_strings_updated = pyqtSignal(list) @@ -139,6 +150,7 @@ class HintManager(QObject): openurl = pyqtSignal('QUrl', bool) set_open_target = pyqtSignal(str) download_get = pyqtSignal('QUrl', 'QWebPage') + run_userscript = pyqtSignal('QUrl', list) def __init__(self, parent=None): """Constructor. @@ -354,6 +366,23 @@ class HintManager(QObject): qt_ensure_valid(url) self.download_get.emit(url, elem.webFrame().page()) + def _call_userscript(self, url): + """Call an userscript from a hint.""" + qt_ensure_valid(url) + self.run_userscript.emit(url, list(self._context.args)) + + def _spawn(self, url): + """Spawn a simple command from a hint.""" + qt_ensure_valid(url) + urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword) + args = [] + for arg in self._context.args: + if arg == '{hint-url}': + args.append(urlstr) + else: + args.append(arg) + subprocess.Popen(args) + def _resolve_url(self, elem, baseurl=None): """Resolve a URL and check if we want to keep it. @@ -433,7 +462,7 @@ class HintManager(QObject): self.openurl.emit(url, newtab) def start(self, mainframe, baseurl, group=webelem.Group.all, - target=Target.normal): + target=Target.normal, *args): """Start hinting. Args: @@ -441,6 +470,7 @@ class HintManager(QObject): baseurl: URL of the current page. group: Which group of elements to hint. target: What to do with the link. See attribute docstring. + *args: Arguments for userscript/download Emit: hint_strings_updated: Emitted to update keypraser. @@ -452,6 +482,14 @@ class HintManager(QObject): # start. But since we had a bug where frame is None in # on_mode_left, we are extra careful here. raise ValueError("start() was called with frame=None") + if target in (Target.userscript, Target.spawn): + if not args: + raise CommandError("Additional arguments are required with " + "target userscript/spawn.") + else: + if args: + raise CommandError("Arguments are only allowed with target " + "userscript/spawn.") elems = [] ctx = HintContext() ctx.frames = webelem.get_child_frames(mainframe) @@ -464,6 +502,7 @@ class HintManager(QObject): raise CommandError("No elements found.") ctx.target = target ctx.baseurl = baseurl + ctx.args = args message.instance().set_text(self.HINT_TEXTS[target]) strings = self._hint_strings(visible_elems) for e, string in zip(visible_elems, strings): @@ -546,6 +585,8 @@ class HintManager(QObject): Target.cmd: self._preset_cmd_text, Target.cmd_tab: self._preset_cmd_text, Target.cmd_tab_bg: self._preset_cmd_text, + Target.userscript: self._call_userscript, + Target.spawn: self._spawn, } elem = self._context.elems[keystr].elem if self._context.target in elem_handlers: diff --git a/qutebrowser/widgets/webview.py b/qutebrowser/widgets/webview.py index 975f932c6..a6ddf4ff5 100644 --- a/qutebrowser/widgets/webview.py +++ b/qutebrowser/widgets/webview.py @@ -112,6 +112,7 @@ class WebView(QWebView): self.setPage(self._page) self.hintmanager = HintManager(self) self.hintmanager.mouse_event.connect(self.on_mouse_event) + self.hintmanager.run_userscript.connect(self.run_hint_userscript) self.hintmanager.set_open_target.connect(self.set_force_open_target) self._page.linkHovered.connect(self.linkHovered) self._page.mainFrame().loadStarted.connect(self.on_load_started) @@ -343,6 +344,11 @@ class WebView(QWebView): """ self._tabs.currentWidget().findText(text, flags) + @pyqtSlot('QUrl', list) + def run_hint_userscript(self, url, args): + """Glue slot to run an userscript via HintManager.""" + self.tabbedbrowser.cmd.run_userscript(args[0], *args[1:], url=url) + def go_back(self): """Go back a page in the history.""" if self.page().history().canGoBack():