Merge branch 'multiwin'
Conflicts: qutebrowser/app.py qutebrowser/browser/commands.py qutebrowser/browser/hints.py qutebrowser/keyinput/modeman.py qutebrowser/network/networkmanager.py qutebrowser/widgets/mainwindow.py qutebrowser/widgets/statusbar/command.py qutebrowser/widgets/statusbar/prompt.py qutebrowser/widgets/statusbar/prompter.py qutebrowser/widgets/tabbedbrowser.py
This commit is contained in:
commit
105c25bc5f
@ -301,17 +301,17 @@ There are currently these object registries, also called 'scopes':
|
||||
* The `global` scope, with objects which are used globally (`config`,
|
||||
`cookie-jar`, etc.)
|
||||
* The `tab` scope with objects which are per-tab (`hintmanager`, `webview`,
|
||||
etc.). Passing this scope to `objreg.get()` always selects the object in the
|
||||
currently focused tab.
|
||||
* The `meta` scope which is an object registry of all other object registries,
|
||||
mainly intended for debugging.
|
||||
etc.). Passing this scope to `objreg.get()` selects the object in the currently
|
||||
focused tab by default. A tab can be explicitely selected by passing
|
||||
+tab=_tab-id_, window=_win-id_+ to it.
|
||||
|
||||
A new object can be registered by using
|
||||
+objreg.register(_name_, _object_[, scope=_scope_])+. An object should not be
|
||||
registered twice. To update it, `update=True` has to be given.
|
||||
+objreg.register(_name_, _object_[, scope=_scope_, window=_win-id_,
|
||||
tab=_tab-id_])+. An object should not be registered twice. To update it,
|
||||
`update=True` has to be given.
|
||||
|
||||
An object can be retrieved by using +objreg.get(_name_[, scope=_scope_])+. The
|
||||
default scope is `global`.
|
||||
An object can be retrieved by using +objreg.get(_name_[, scope=_scope_,
|
||||
window=_win-id_, tab=_tab-id_])+. The default scope is `global`.
|
||||
|
||||
All objects can be printed by starting with the `--debug` flag and using the
|
||||
`:debug-all-objects` command.
|
||||
|
@ -8,6 +8,7 @@
|
||||
|<<back,back>>|Go back in the history of the current tab.
|
||||
|<<bind,bind>>|Bind a key to a command.
|
||||
|<<cancel-download,cancel-download>>|Cancel the first/[count]th download.
|
||||
|<<close,close>>|Close the current window.
|
||||
|<<download-page,download-page>>|Download the current page.
|
||||
|<<forward,forward>>|Go forward in the history of the current tab.
|
||||
|<<help,help>>|Show help about a command or setting.
|
||||
@ -49,13 +50,14 @@
|
||||
|==============
|
||||
[[back]]
|
||||
=== back
|
||||
Syntax: +:back [*--tab*] [*--bg*]+
|
||||
Syntax: +:back [*--tab*] [*--bg*] [*--window*]+
|
||||
|
||||
Go back in the history of the current tab.
|
||||
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--tab*+: Go back in a new tab.
|
||||
* +*-b*+, +*--bg*+: Go back in a background tab.
|
||||
* +*-w*+, +*--window*+: Go back in a new window.
|
||||
|
||||
==== count
|
||||
How many pages to go back.
|
||||
@ -81,26 +83,31 @@ Cancel the first/[count]th download.
|
||||
==== count
|
||||
The index of the download to cancel.
|
||||
|
||||
[[close]]
|
||||
=== close
|
||||
Close the current window.
|
||||
|
||||
[[download-page]]
|
||||
=== download-page
|
||||
Download the current page.
|
||||
|
||||
[[forward]]
|
||||
=== forward
|
||||
Syntax: +:forward [*--tab*] [*--bg*]+
|
||||
Syntax: +:forward [*--tab*] [*--bg*] [*--window*]+
|
||||
|
||||
Go forward in the history of the current tab.
|
||||
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--tab*+: Go forward in a new tab.
|
||||
* +*-b*+, +*--bg*+: Go back in a background tab.
|
||||
* +*-b*+, +*--bg*+: Go forward in a background tab.
|
||||
* +*-w*+, +*--window*+: Go forward in a new window.
|
||||
|
||||
==== count
|
||||
How many pages to go forward.
|
||||
|
||||
[[help]]
|
||||
=== help
|
||||
Syntax: +:help ['topic']+
|
||||
Syntax: +:help [*--tab*] [*--bg*] [*--window*] ['topic']+
|
||||
|
||||
Show help about a command or setting.
|
||||
|
||||
@ -111,6 +118,11 @@ Show help about a command or setting.
|
||||
- __section__\->__option__ for settings.
|
||||
|
||||
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--tab*+: Open in a new tab.
|
||||
* +*-b*+, +*--bg*+: Open in a background tab.
|
||||
* +*-w*+, +*--window*+: Open in a new window.
|
||||
|
||||
[[hint]]
|
||||
=== hint
|
||||
Syntax: +:hint ['group'] ['target'] ['args' ['args' ...]]+
|
||||
@ -131,11 +143,14 @@ Start hinting.
|
||||
- `normal`: Open the link in the current tab.
|
||||
- `tab`: Open the link in a new tab.
|
||||
- `tab-bg`: Open the link in a new background tab.
|
||||
- `window`: Open the link in a new window.
|
||||
- `yank`: Yank the link to the clipboard.
|
||||
- `yank-primary`: Yank the link to the primary selection.
|
||||
- `fill`: Fill the commandline with the command given as
|
||||
argument.
|
||||
- `rapid`: Open the link in a new tab and stay in hinting mode.
|
||||
- `rapid-win`: Open the link in a new window and stay in
|
||||
hinting mode.
|
||||
- `download`: Download the link.
|
||||
- `userscript`: Call an userscript with `$QUTE_URL` set to the
|
||||
link.
|
||||
@ -174,7 +189,7 @@ Execute a command after some time.
|
||||
|
||||
[[navigate]]
|
||||
=== navigate
|
||||
Syntax: +:navigate [*--tab*] 'where'+
|
||||
Syntax: +:navigate [*--tab*] [*--bg*] [*--window*] 'where'+
|
||||
|
||||
Open typical prev/next links or navigate using the URL path.
|
||||
|
||||
@ -194,10 +209,12 @@ This tries to automatically click on typical _Previous Page_ or _Next Page_ link
|
||||
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--tab*+: Open in a new tab.
|
||||
* +*-b*+, +*--bg*+: Open in a background tab.
|
||||
* +*-w*+, +*--window*+: Open in a new window.
|
||||
|
||||
[[open]]
|
||||
=== open
|
||||
Syntax: +:open [*--bg*] [*--tab*] 'url'+
|
||||
Syntax: +:open [*--bg*] [*--tab*] [*--window*] 'url'+
|
||||
|
||||
Open a URL in the current/[count]th tab.
|
||||
|
||||
@ -207,13 +224,14 @@ Open a URL in the current/[count]th tab.
|
||||
==== optional arguments
|
||||
* +*-b*+, +*--bg*+: Open in a new background tab.
|
||||
* +*-t*+, +*--tab*+: Open in a new tab.
|
||||
* +*-w*+, +*--window*+: Open in a new window.
|
||||
|
||||
==== count
|
||||
The tab index to open the URL in.
|
||||
|
||||
[[paste]]
|
||||
=== paste
|
||||
Syntax: +:paste [*--sel*] [*--tab*] [*--bg*]+
|
||||
Syntax: +:paste [*--sel*] [*--tab*] [*--bg*] [*--window*]+
|
||||
|
||||
Open a page from the clipboard.
|
||||
|
||||
@ -221,6 +239,7 @@ Open a page from the clipboard.
|
||||
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
|
||||
* +*-t*+, +*--tab*+: Open in a new tab.
|
||||
* +*-b*+, +*--bg*+: Open in a background tab.
|
||||
* +*-w*+, +*--window*+: Open in new window.
|
||||
|
||||
[[print]]
|
||||
=== print
|
||||
@ -246,7 +265,7 @@ Add a new quickmark.
|
||||
|
||||
[[quickmark-load]]
|
||||
=== quickmark-load
|
||||
Syntax: +:quickmark-load [*--tab*] [*--bg*] 'name'+
|
||||
Syntax: +:quickmark-load [*--tab*] [*--bg*] [*--window*] 'name'+
|
||||
|
||||
Load a quickmark.
|
||||
|
||||
@ -256,6 +275,7 @@ Load a quickmark.
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--tab*+: Load the quickmark in a new tab.
|
||||
* +*-b*+, +*--bg*+: Load the quickmark in a new background tab.
|
||||
* +*-w*+, +*--window*+: Load the quickmark in a new window.
|
||||
|
||||
[[quickmark-save]]
|
||||
=== quickmark-save
|
||||
@ -339,12 +359,13 @@ The tab index to stop.
|
||||
|
||||
[[tab-clone]]
|
||||
=== tab-clone
|
||||
Syntax: +:tab-clone [*--bg*]+
|
||||
Syntax: +:tab-clone [*--bg*] [*--window*]+
|
||||
|
||||
Duplicate the current tab.
|
||||
|
||||
==== optional arguments
|
||||
* +*-b*+, +*--bg*+: Open in a background tab.
|
||||
* +*-w*+, +*--window*+: Open in a new window.
|
||||
|
||||
[[tab-close]]
|
||||
=== tab-close
|
||||
|
@ -28,7 +28,7 @@ It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
|
||||
Commands to execute on startup.
|
||||
|
||||
*'URL'*::
|
||||
URLs to open on startup.
|
||||
URLs to open on startup (empty as a window separator).
|
||||
|
||||
=== optional arguments
|
||||
*-h*, *--help*::
|
||||
|
@ -36,15 +36,16 @@ from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, Qt, QUrl,
|
||||
QStandardPaths, QObject)
|
||||
|
||||
import qutebrowser
|
||||
from qutebrowser.commands import userscripts, runners, cmdutils
|
||||
from qutebrowser.commands import cmdutils, runners
|
||||
from qutebrowser.config import style, config, websettings
|
||||
from qutebrowser.network import qutescheme, proxy
|
||||
from qutebrowser.browser import quickmarks, cookies, downloads, cache, hints
|
||||
from qutebrowser.browser import quickmarks, cookies, downloads, cache
|
||||
from qutebrowser.widgets import mainwindow, console, crash
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.utils import (log, version, message, utilcmds, readline,
|
||||
utils, qtutils, urlutils, debug, objreg,
|
||||
usertypes)
|
||||
from qutebrowser.utils import (log, version, message, readline, utils, qtutils,
|
||||
urlutils, debug, objreg, usertypes)
|
||||
# We import utilcmds to run the cmdutils.register decorators.
|
||||
from qutebrowser.utils import utilcmds # pylint: disable=unused-import
|
||||
|
||||
|
||||
class Application(QApplication):
|
||||
@ -53,11 +54,11 @@ class Application(QApplication):
|
||||
|
||||
Attributes:
|
||||
_args: ArgumentParser instance.
|
||||
_commandrunner: The main CommandRunner instance.
|
||||
_shutting_down: True if we're currently shutting down.
|
||||
_quit_status: The current quitting status.
|
||||
_crashdlg: The crash dialog currently open.
|
||||
_crashlogfile: A file handler to the fatal crash logfile.
|
||||
_event_filter: The EventFilter for the application.
|
||||
"""
|
||||
|
||||
def __init__(self, args):
|
||||
@ -74,7 +75,6 @@ class Application(QApplication):
|
||||
self._shutting_down = False
|
||||
self._crashdlg = None
|
||||
self._crashlogfile = None
|
||||
self._commandrunner = None
|
||||
|
||||
if args.debug:
|
||||
# We don't enable this earlier because some imports trigger
|
||||
@ -110,16 +110,11 @@ class Application(QApplication):
|
||||
self._init_modules()
|
||||
|
||||
log.init.debug("Initializing eventfilter...")
|
||||
mode_manager = objreg.get('mode-manager')
|
||||
self.installEventFilter(mode_manager)
|
||||
self._event_filter = modeman.EventFilter(self)
|
||||
self.installEventFilter(self._event_filter)
|
||||
|
||||
log.init.debug("Connecting signals...")
|
||||
self._connect_signals()
|
||||
modeman.enter(usertypes.KeyMode.normal, 'init')
|
||||
|
||||
log.init.debug("Showing mainwindow...")
|
||||
if not args.nowindow:
|
||||
objreg.get('main-window').show()
|
||||
|
||||
log.init.debug("Applying python hacks...")
|
||||
self._python_hacks()
|
||||
@ -134,10 +129,6 @@ class Application(QApplication):
|
||||
|
||||
def _init_modules(self):
|
||||
"""Initialize all 'modules' which need to be initialized."""
|
||||
log.init.debug("Initializing message-bridge...")
|
||||
message_bridge = message.MessageBridge(self)
|
||||
objreg.register('message-bridge', message_bridge)
|
||||
|
||||
log.init.debug("Initializing readline-bridge...")
|
||||
readline_bridge = readline.ReadlineBridge()
|
||||
objreg.register('readline-bridge', readline_bridge)
|
||||
@ -146,35 +137,23 @@ class Application(QApplication):
|
||||
config.init(self._args)
|
||||
log.init.debug("Initializing crashlog...")
|
||||
self._handle_segfault()
|
||||
log.init.debug("Initializing modes...")
|
||||
modeman.init()
|
||||
log.init.debug("Initializing websettings...")
|
||||
websettings.init()
|
||||
log.init.debug("Initializing quickmarks...")
|
||||
quickmarks.init()
|
||||
log.init.debug("Initializing proxy...")
|
||||
proxy.init()
|
||||
log.init.debug("Initializing userscripts...")
|
||||
userscripts.init()
|
||||
log.init.debug("Initializing utility commands...")
|
||||
utilcmds.init()
|
||||
log.init.debug("Initializing cookies...")
|
||||
cookie_jar = cookies.CookieJar(self)
|
||||
objreg.register('cookie-jar', cookie_jar)
|
||||
log.init.debug("Initializing cache...")
|
||||
diskcache = cache.DiskCache(self)
|
||||
objreg.register('cache', diskcache)
|
||||
log.init.debug("Initializing commands...")
|
||||
self._commandrunner = runners.CommandRunner()
|
||||
log.init.debug("Initializing search...")
|
||||
search_runner = runners.SearchRunner(self)
|
||||
objreg.register('search-runner', search_runner)
|
||||
log.init.debug("Initializing downloads...")
|
||||
download_manager = downloads.DownloadManager(self)
|
||||
objreg.register('download-manager', download_manager)
|
||||
log.init.debug("Initializing main window...")
|
||||
main_window = mainwindow.MainWindow()
|
||||
objreg.register('main-window', main_window)
|
||||
mainwindow.MainWindow.spawn(False if self._args.nowindow else True)
|
||||
log.init.debug("Initializing debug console...")
|
||||
debug_console = console.ConsoleWidget()
|
||||
objreg.register('debug-console', debug_console)
|
||||
@ -238,30 +217,41 @@ class Application(QApplication):
|
||||
|
||||
URLs to open have no prefix, commands to execute begin with a colon.
|
||||
"""
|
||||
tabbed_browser = objreg.get('tabbed-browser')
|
||||
win_id = 0
|
||||
for cmd in self._args.command:
|
||||
if cmd.startswith(':'):
|
||||
log.init.debug("Startup cmd {}".format(cmd))
|
||||
self._commandrunner.run_safely_init(cmd.lstrip(':'))
|
||||
commandrunner = runners.CommandRunner(win_id)
|
||||
commandrunner.run_safely_init(cmd.lstrip(':'))
|
||||
elif not cmd:
|
||||
log.init.debug("Empty argument")
|
||||
win_id = mainwindow.MainWindow.spawn()
|
||||
else:
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
log.init.debug("Startup URL {}".format(cmd))
|
||||
try:
|
||||
url = urlutils.fuzzy_url(cmd)
|
||||
except urlutils.FuzzyUrlError as e:
|
||||
message.error("Error in startup argument '{}': {}".format(
|
||||
cmd, e))
|
||||
message.error(0, "Error in startup argument '{}': "
|
||||
"{}".format(cmd, e))
|
||||
else:
|
||||
tabbed_browser.tabopen(url)
|
||||
|
||||
if tabbed_browser.count() == 0:
|
||||
log.init.debug("Opening startpage")
|
||||
for urlstr in config.get('general', 'startpage'):
|
||||
try:
|
||||
url = urlutils.fuzzy_url(urlstr)
|
||||
except urlutils.FuzzyUrlError as e:
|
||||
message.error("Error when opening startpage: {}".format(e))
|
||||
else:
|
||||
tabbed_browser.tabopen(url)
|
||||
for win_id in objreg.window_registry:
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
if tabbed_browser.count() == 0:
|
||||
log.init.debug("Opening startpage")
|
||||
for urlstr in config.get('general', 'startpage'):
|
||||
try:
|
||||
url = urlutils.fuzzy_url(urlstr)
|
||||
except urlutils.FuzzyUrlError as e:
|
||||
message.error(0, "Error when opening startpage: "
|
||||
"{}".format(e))
|
||||
tabbed_browser.tabopen(QUrl('about:blank'))
|
||||
else:
|
||||
tabbed_browser.tabopen(url)
|
||||
|
||||
# Open quickstart if it's the first start
|
||||
state_config = objreg.get('state-config')
|
||||
@ -294,89 +284,9 @@ class Application(QApplication):
|
||||
|
||||
def _connect_signals(self):
|
||||
"""Connect all signals to their slots."""
|
||||
# pylint: disable=too-many-statements, too-many-locals
|
||||
# syntactic sugar
|
||||
kp = objreg.get('keyparsers')
|
||||
main_window = objreg.get('main-window')
|
||||
status = main_window.status
|
||||
completion = objreg.get('completion')
|
||||
tabs = objreg.get('tabbed-browser')
|
||||
cmd = objreg.get('status-command')
|
||||
completer = objreg.get('completer')
|
||||
search_runner = objreg.get('search-runner')
|
||||
message_bridge = objreg.get('message-bridge')
|
||||
mode_manager = objreg.get('mode-manager')
|
||||
prompter = objreg.get('prompter')
|
||||
download_manager = objreg.get('download-manager')
|
||||
config_obj = objreg.get('config')
|
||||
key_config = objreg.get('key-config')
|
||||
|
||||
# misc
|
||||
self.lastWindowClosed.connect(self.shutdown)
|
||||
tabs.quit.connect(self.shutdown)
|
||||
mode_manager.entered.connect(hints.on_mode_entered)
|
||||
|
||||
# status bar
|
||||
mode_manager.entered.connect(status.on_mode_entered)
|
||||
mode_manager.left.connect(status.on_mode_left)
|
||||
mode_manager.left.connect(cmd.on_mode_left)
|
||||
mode_manager.left.connect(prompter.on_mode_left)
|
||||
|
||||
# commands
|
||||
cmd.got_cmd.connect(self._commandrunner.run_safely)
|
||||
cmd.got_search.connect(search_runner.search)
|
||||
cmd.got_search_rev.connect(search_runner.search_rev)
|
||||
cmd.returnPressed.connect(tabs.setFocus)
|
||||
search_runner.do_search.connect(tabs.search)
|
||||
kp[usertypes.KeyMode.normal].keystring_updated.connect(
|
||||
status.keystring.setText)
|
||||
tabs.got_cmd.connect(self._commandrunner.run_safely)
|
||||
|
||||
# messages
|
||||
message_bridge.s_error.connect(status.disp_error)
|
||||
message_bridge.s_info.connect(status.disp_temp_text)
|
||||
message_bridge.s_set_text.connect(status.set_text)
|
||||
message_bridge.s_maybe_reset_text.connect(status.txt.maybe_reset_text)
|
||||
message_bridge.s_set_cmd_text.connect(cmd.set_cmd_text)
|
||||
message_bridge.s_question.connect(prompter.ask_question,
|
||||
Qt.DirectConnection)
|
||||
|
||||
# config
|
||||
config_obj.style_changed.connect(style.get_stylesheet.cache_clear)
|
||||
for obj in kp.values():
|
||||
key_config.changed.connect(obj.on_keyconfig_changed)
|
||||
|
||||
# statusbar
|
||||
# FIXME some of these probably only should be triggered on mainframe
|
||||
# loadStarted.
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/112
|
||||
tabs.current_tab_changed.connect(status.prog.on_tab_changed)
|
||||
tabs.cur_progress.connect(status.prog.setValue)
|
||||
tabs.cur_load_finished.connect(status.prog.hide)
|
||||
tabs.cur_load_started.connect(status.prog.on_load_started)
|
||||
|
||||
tabs.current_tab_changed.connect(status.percentage.on_tab_changed)
|
||||
tabs.cur_scroll_perc_changed.connect(status.percentage.set_perc)
|
||||
|
||||
tabs.current_tab_changed.connect(status.txt.on_tab_changed)
|
||||
tabs.cur_statusbar_message.connect(status.txt.on_statusbar_message)
|
||||
tabs.cur_load_started.connect(status.txt.on_load_started)
|
||||
|
||||
tabs.current_tab_changed.connect(status.url.on_tab_changed)
|
||||
tabs.cur_url_text_changed.connect(status.url.set_url)
|
||||
tabs.cur_link_hovered.connect(status.url.set_hover_url)
|
||||
tabs.cur_load_status_changed.connect(status.url.on_load_status_changed)
|
||||
|
||||
# command input / completion
|
||||
mode_manager.left.connect(tabs.on_mode_left)
|
||||
cmd.clear_completion_selection.connect(
|
||||
completion.on_clear_completion_selection)
|
||||
cmd.hide_completion.connect(completion.hide)
|
||||
cmd.update_completion.connect(completer.on_update_completion)
|
||||
completer.change_completed_part.connect(cmd.on_change_completed_part)
|
||||
|
||||
# downloads
|
||||
tabs.start_download.connect(download_manager.fetch)
|
||||
|
||||
def _get_widgets(self):
|
||||
"""Get a string list of all widgets."""
|
||||
@ -390,20 +300,6 @@ class Application(QApplication):
|
||||
lines.append(' ' * depth + repr(kid))
|
||||
self._get_pyqt_objects(lines, kid, depth + 1)
|
||||
|
||||
def _get_registered_objects(self):
|
||||
"""Get all registered objects in all registries as a string."""
|
||||
blocks = []
|
||||
lines = []
|
||||
for name, registry in objreg.meta_registry.items():
|
||||
blocks.append((name, registry.dump_objects()))
|
||||
for name, data in sorted(blocks, key=lambda e: e[0]):
|
||||
lines.append("")
|
||||
lines.append("{} object registry - {} objects:".format(
|
||||
name, len(data)))
|
||||
for line in data:
|
||||
lines.append(" {}".format(line))
|
||||
return lines
|
||||
|
||||
def get_all_objects(self):
|
||||
"""Get all children of an object recursively as a string."""
|
||||
output = ['']
|
||||
@ -419,36 +315,47 @@ class Application(QApplication):
|
||||
len(pyqt_lines)))
|
||||
output += pyqt_lines
|
||||
output += ['']
|
||||
output += self._get_registered_objects()
|
||||
output += objreg.dump_objects()
|
||||
return '\n'.join(output)
|
||||
|
||||
def _recover_pages(self):
|
||||
def _recover_pages(self, forgiving=False):
|
||||
"""Try to recover all open pages.
|
||||
|
||||
Called from _exception_hook, so as forgiving as possible.
|
||||
|
||||
Args:
|
||||
forgiving: Whether to ignore exceptions.
|
||||
|
||||
Return:
|
||||
A list of open pages, or an empty list.
|
||||
A list containing a list for each window, which in turn contain the
|
||||
opened URLs.
|
||||
"""
|
||||
try:
|
||||
tabbed_browser = objreg.get('tabbed-browser')
|
||||
except KeyError:
|
||||
return []
|
||||
pages = []
|
||||
for tab in tabbed_browser.widgets():
|
||||
try:
|
||||
url = tab.cur_url.toString(
|
||||
QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
if url:
|
||||
pages.append(url)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
log.destroy.exception("Error while recovering tab")
|
||||
for win_id in objreg.window_registry:
|
||||
win_pages = []
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
for tab in tabbed_browser.widgets():
|
||||
try:
|
||||
urlstr = tab.cur_url.toString(
|
||||
QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
if urlstr:
|
||||
win_pages.append(urlstr)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
if forgiving:
|
||||
log.destroy.exception("Error while recovering tab")
|
||||
else:
|
||||
raise
|
||||
pages.append(win_pages)
|
||||
return pages
|
||||
|
||||
def _save_geometry(self):
|
||||
"""Save the window geometry to the state config."""
|
||||
last_win_id = sorted(objreg.window_registry)[-1]
|
||||
main_window = objreg.get('main-window', scope='window',
|
||||
window=last_win_id)
|
||||
state_config = objreg.get('state-config')
|
||||
data = bytes(objreg.get('main-window').saveGeometry())
|
||||
data = bytes(main_window.saveGeometry())
|
||||
geom = base64.b64encode(data).decode('ASCII')
|
||||
try:
|
||||
state_config.add_section('geometry')
|
||||
@ -497,13 +404,13 @@ class Application(QApplication):
|
||||
self._quit_status['crash'] = False
|
||||
|
||||
try:
|
||||
pages = self._recover_pages()
|
||||
pages = self._recover_pages(forgiving=True)
|
||||
except Exception:
|
||||
log.destroy.exception("Error while recovering pages")
|
||||
pages = []
|
||||
|
||||
try:
|
||||
history = objreg.get('status-command').history[-5:]
|
||||
history = objreg.get('command-history')[-5:]
|
||||
except Exception:
|
||||
log.destroy.exception("Error while getting history: {}")
|
||||
history = []
|
||||
@ -531,18 +438,16 @@ class Application(QApplication):
|
||||
self._destroy_crashlogfile()
|
||||
sys.exit(1)
|
||||
|
||||
@cmdutils.register(instance='app', name=['quit', 'q'])
|
||||
def quit(self):
|
||||
"""Quit qutebrowser."""
|
||||
QApplication.closeAllWindows()
|
||||
|
||||
@cmdutils.register(instance='app', ignore_args=True)
|
||||
def restart(self, shutdown=True, pages=None):
|
||||
"""Restart qutebrowser while keeping existing tabs open."""
|
||||
# We don't use _recover_pages here as it's too forgiving when
|
||||
# exceptions occur.
|
||||
if pages is None:
|
||||
pages = []
|
||||
for tab in objreg.get('tabbed-browser').widgets():
|
||||
urlstr = tab.cur_url.toString(
|
||||
QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
if urlstr:
|
||||
pages.append(urlstr)
|
||||
pages = self._recover_pages()
|
||||
log.destroy.debug("sys.executable: {}".format(sys.executable))
|
||||
log.destroy.debug("sys.path: {}".format(sys.path))
|
||||
log.destroy.debug("sys.argv: {}".format(sys.argv))
|
||||
@ -563,7 +468,12 @@ class Application(QApplication):
|
||||
# We only want to preserve options on a restart.
|
||||
args.append(arg)
|
||||
# Add all open pages so they get reopened.
|
||||
args += pages
|
||||
page_args = []
|
||||
for win in pages:
|
||||
page_args.extend(win)
|
||||
page_args.append('')
|
||||
if page_args:
|
||||
args.extend(page_args[:-1])
|
||||
log.destroy.debug("args: {}".format(args))
|
||||
log.destroy.debug("cwd: {}".format(cwd))
|
||||
# Open a new process and immediately shutdown the existing one
|
||||
@ -592,13 +502,15 @@ class Application(QApplication):
|
||||
except Exception: # pylint: disable=broad-except
|
||||
out = traceback.format_exc()
|
||||
qutescheme.pyeval_output = out
|
||||
objreg.get('tabbed-browser').openurl(QUrl('qute:pyeval'), newtab=True)
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window='current')
|
||||
tabbed_browser.openurl(QUrl('qute:pyeval'), newtab=True)
|
||||
|
||||
@cmdutils.register(instance='app')
|
||||
def report(self):
|
||||
"""Report a bug in qutebrowser."""
|
||||
pages = self._recover_pages()
|
||||
history = objreg.get('status-command').history[-5:]
|
||||
history = objreg.get('command-history')[-5:]
|
||||
objects = self.get_all_objects()
|
||||
self._crashdlg = crash.ReportDialog(pages, history, objects)
|
||||
self._crashdlg.show()
|
||||
@ -654,8 +566,13 @@ class Application(QApplication):
|
||||
return
|
||||
self._shutting_down = True
|
||||
log.destroy.debug("Shutting down with status {}...".format(status))
|
||||
prompter = objreg.get('prompter', None)
|
||||
if prompter is not None and prompter.shutdown():
|
||||
deferrer = False
|
||||
for win_id in objreg.window_registry:
|
||||
prompter = objreg.get('prompter', None, scope='window',
|
||||
window=win_id)
|
||||
if prompter is not None and prompter.shutdown():
|
||||
deferrer = True
|
||||
if deferrer:
|
||||
# If shutdown was called while we were asking a question, we're in
|
||||
# a still sub-eventloop (which gets quitted now) and not in the
|
||||
# main one.
|
||||
@ -678,15 +595,15 @@ class Application(QApplication):
|
||||
# Remove eventfilter
|
||||
try:
|
||||
log.destroy.debug("Removing eventfilter...")
|
||||
self.removeEventFilter(objreg.get('mode-manager'))
|
||||
self.removeEventFilter(self._event_filter)
|
||||
except KeyError:
|
||||
pass
|
||||
# Close all tabs
|
||||
try:
|
||||
log.destroy.debug("Closing tabs...")
|
||||
objreg.get('tabbed-browser').shutdown()
|
||||
except KeyError:
|
||||
pass
|
||||
for win_id in objreg.window_registry:
|
||||
log.destroy.debug("Closing tabs in window {}...".format(win_id))
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
tabbed_browser.shutdown()
|
||||
# Save everything
|
||||
try:
|
||||
config_obj = objreg.get('config')
|
||||
|
@ -23,7 +23,7 @@ import re
|
||||
import os
|
||||
import subprocess
|
||||
import posixpath
|
||||
from functools import partial
|
||||
import functools
|
||||
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtCore import Qt, QUrl
|
||||
@ -53,53 +53,73 @@ class CommandDispatcher:
|
||||
|
||||
Attributes:
|
||||
_editor: The ExternalEditor object.
|
||||
_win_id: The window ID the CommandDispatcher is associated with.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, win_id):
|
||||
self._editor = None
|
||||
self._win_id = win_id
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self)
|
||||
|
||||
def _tabbed_browser(self, window=False):
|
||||
"""Convienence method to get the right tabbed-browser.
|
||||
|
||||
Args:
|
||||
window: If True, open a new window.
|
||||
"""
|
||||
if window:
|
||||
main_window = objreg.get('main-window', scope='window',
|
||||
window=self._win_id)
|
||||
win_id = main_window.spawn()
|
||||
else:
|
||||
win_id = self._win_id
|
||||
return objreg.get('tabbed-browser', scope='window', window=win_id)
|
||||
|
||||
def _count(self):
|
||||
"""Convenience method to get the widget count."""
|
||||
return objreg.get('tabbed-browser').count()
|
||||
return self._tabbed_browser().count()
|
||||
|
||||
def _set_current_index(self, idx):
|
||||
"""Convenience method to set the current widget index."""
|
||||
return objreg.get('tabbed-browser').setCurrentIndex(idx)
|
||||
return self._tabbed_browser().setCurrentIndex(idx)
|
||||
|
||||
def _current_index(self):
|
||||
"""Convenience method to get the current widget index."""
|
||||
return objreg.get('tabbed-browser').currentIndex()
|
||||
return self._tabbed_browser().currentIndex()
|
||||
|
||||
def _current_url(self):
|
||||
"""Convenience method to get the current url."""
|
||||
return objreg.get('tabbed-browser').current_url()
|
||||
return self._tabbed_browser().current_url()
|
||||
|
||||
def _current_widget(self):
|
||||
"""Get the currently active widget from a command."""
|
||||
widget = objreg.get('tabbed-browser').currentWidget()
|
||||
widget = self._tabbed_browser().currentWidget()
|
||||
if widget is None:
|
||||
raise cmdexc.CommandError("No WebView available yet!")
|
||||
return widget
|
||||
|
||||
def _open(self, url, tab, background):
|
||||
def _open(self, url, tab, background, window):
|
||||
"""Helper function to open a page.
|
||||
|
||||
Args:
|
||||
url: The URL to open as QUrl.
|
||||
tab: Whether to open in a new tab.
|
||||
background: Whether to open in the background.
|
||||
window: Whether to open in a new window
|
||||
"""
|
||||
if not url.isValid():
|
||||
errstr = "Invalid URL {}"
|
||||
if url.errorString():
|
||||
errstr += " - {}".format(url.errorString())
|
||||
raise cmdexc.CommandError(errstr)
|
||||
tabbed_browser = objreg.get('tabbed-browser')
|
||||
if tab and background:
|
||||
raise cmdexc.CommandError("Only one of -t/-b can be given!")
|
||||
tabbed_browser = self._tabbed_browser()
|
||||
if sum(1 for e in (tab, background, window) if e) > 1:
|
||||
raise cmdexc.CommandError("Only one of -t/-b/-w can be given!")
|
||||
elif window:
|
||||
tabbed_browser = self._tabbed_browser(window=True)
|
||||
tabbed_browser.tabopen(url)
|
||||
elif tab:
|
||||
tabbed_browser.tabopen(url, background=False, explicit=True)
|
||||
elif background:
|
||||
@ -119,7 +139,7 @@ class CommandDispatcher:
|
||||
The widget with the given tab ID if count is given.
|
||||
None if no widget was found.
|
||||
"""
|
||||
tabbed_browser = objreg.get('tabbed-browser')
|
||||
tabbed_browser = self._tabbed_browser()
|
||||
if count is None:
|
||||
return tabbed_browser.currentWidget()
|
||||
elif 1 <= count <= self._count():
|
||||
@ -179,10 +199,11 @@ class CommandDispatcher:
|
||||
def _tab_focus_last(self):
|
||||
"""Select the tab which was last focused."""
|
||||
try:
|
||||
tab = objreg.get('last-focused-tab')
|
||||
tab = objreg.get('last-focused-tab', scope='window',
|
||||
window=self._win_id)
|
||||
except KeyError:
|
||||
raise cmdexc.CommandError("No last focused tab!")
|
||||
idx = objreg.get('tabbed-browser').indexOf(tab)
|
||||
idx = self._tabbed_browser().indexOf(tab)
|
||||
if idx == -1:
|
||||
raise cmdexc.CommandError("Last focused tab vanished!")
|
||||
self._set_current_index(idx)
|
||||
@ -195,7 +216,7 @@ class CommandDispatcher:
|
||||
except PermissionError:
|
||||
raise cmdexc.CommandError("Failed to delete tempfile...")
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def tab_close(self, count=None):
|
||||
"""Close the current/[count]th tab.
|
||||
|
||||
@ -209,39 +230,41 @@ class CommandDispatcher:
|
||||
tab = self._cntwidget(count)
|
||||
if tab is None:
|
||||
return
|
||||
objreg.get('tabbed-browser').close_tab(tab)
|
||||
self._tabbed_browser().close_tab(tab)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', name='open',
|
||||
split=False)
|
||||
def openurl(self, url, bg=False, tab=False, count=None):
|
||||
split=False, scope='window')
|
||||
def openurl(self, url, bg=False, tab=False, window=False, count=None):
|
||||
"""Open a URL in the current/[count]th tab.
|
||||
|
||||
Args:
|
||||
url: The URL to open.
|
||||
bg: Open in a new background tab.
|
||||
tab: Open in a new tab.
|
||||
window: Open in a new window.
|
||||
count: The tab index to open the URL in, or None.
|
||||
"""
|
||||
try:
|
||||
url = urlutils.fuzzy_url(url)
|
||||
except urlutils.FuzzyUrlError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
if tab or bg:
|
||||
self._open(url, tab, bg)
|
||||
if tab or bg or window:
|
||||
self._open(url, tab, bg, window)
|
||||
else:
|
||||
curtab = self._cntwidget(count)
|
||||
if curtab is None:
|
||||
if count is None:
|
||||
# We want to open a URL in the current tab, but none exists
|
||||
# yet.
|
||||
objreg.get('tabbed-browser').tabopen(url)
|
||||
self._tabbed_browser().tabopen(url)
|
||||
else:
|
||||
# Explicit count with a tab that doesn't exist.
|
||||
return
|
||||
else:
|
||||
curtab.openurl(url)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', name='reload')
|
||||
@cmdutils.register(instance='command-dispatcher', name='reload',
|
||||
scope='window')
|
||||
def reloadpage(self, count=None):
|
||||
"""Reload the current/[count]th tab.
|
||||
|
||||
@ -252,7 +275,7 @@ class CommandDispatcher:
|
||||
if tab is not None:
|
||||
tab.reload()
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def stop(self, count=None):
|
||||
"""Stop loading in the current/[count]th tab.
|
||||
|
||||
@ -263,7 +286,8 @@ class CommandDispatcher:
|
||||
if tab is not None:
|
||||
tab.stop()
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', name='print')
|
||||
@cmdutils.register(instance='command-dispatcher', name='print',
|
||||
scope='window')
|
||||
def printpage(self, preview=False, count=None):
|
||||
"""Print the current/[count]th tab.
|
||||
|
||||
@ -287,64 +311,77 @@ class CommandDispatcher:
|
||||
diag.setAttribute(Qt.WA_DeleteOnClose)
|
||||
diag.open(lambda: tab.print(diag.printer()))
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
def tab_clone(self, bg=False):
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def tab_clone(self, bg=False, window=False):
|
||||
"""Duplicate the current tab.
|
||||
|
||||
Args:
|
||||
bg: Open in a background tab.
|
||||
window: Open in a new window.
|
||||
|
||||
Return:
|
||||
The new QWebView.
|
||||
"""
|
||||
if bg and window:
|
||||
raise cmdexc.CommandError("Only one of -b/-w can be given!")
|
||||
curtab = self._current_widget()
|
||||
tabbed_browser = objreg.get('tabbed-browser')
|
||||
tabbed_browser = self._tabbed_browser(window)
|
||||
newtab = tabbed_browser.tabopen(background=bg, explicit=True)
|
||||
history = qtutils.serialize(curtab.history())
|
||||
qtutils.deserialize(history, newtab.history())
|
||||
return newtab
|
||||
|
||||
def _back_forward(self, tab, bg, count, forward):
|
||||
def _back_forward(self, tab, bg, window, count, forward):
|
||||
"""Helper function for :back/:forward."""
|
||||
if tab or bg:
|
||||
widget = self.tab_clone(bg)
|
||||
if (not forward and not
|
||||
self._current_widget().page().history().canGoBack()):
|
||||
raise cmdexc.CommandError("At beginning of history.")
|
||||
if (forward and not
|
||||
self._current_widget().page().history().canGoForward()):
|
||||
raise cmdexc.CommandError("At end of history.")
|
||||
if tab or bg or window:
|
||||
widget = self.tab_clone(bg, window)
|
||||
else:
|
||||
widget = self._current_widget()
|
||||
for _ in range(count):
|
||||
if forward:
|
||||
widget.go_forward()
|
||||
widget.forward()
|
||||
else:
|
||||
widget.go_back()
|
||||
widget.back()
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
def back(self, tab=False, bg=False, count=1):
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def back(self, tab=False, bg=False, window=False, count=1):
|
||||
"""Go back in the history of the current tab.
|
||||
|
||||
Args:
|
||||
tab: Go back in a new tab.
|
||||
bg: Go back in a background tab.
|
||||
window: Go back in a new window.
|
||||
count: How many pages to go back.
|
||||
"""
|
||||
self._back_forward(tab, bg, count, forward=False)
|
||||
self._back_forward(tab, bg, window, count, forward=False)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
def forward(self, tab=False, bg=False, count=1):
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def forward(self, tab=False, bg=False, window=False, count=1):
|
||||
"""Go forward in the history of the current tab.
|
||||
|
||||
Args:
|
||||
tab: Go forward in a new tab.
|
||||
bg: Go back in a background tab.
|
||||
bg: Go forward in a background tab.
|
||||
window: Go forward in a new window.
|
||||
count: How many pages to go forward.
|
||||
"""
|
||||
self._back_forward(tab, bg, count, forward=True)
|
||||
self._back_forward(tab, bg, window, count, forward=True)
|
||||
|
||||
def _navigate_incdec(self, url, tab, incdec):
|
||||
def _navigate_incdec(self, url, incdec, tab, background, window):
|
||||
"""Helper method for :navigate when `where' is increment/decrement.
|
||||
|
||||
Args:
|
||||
url: The current url.
|
||||
tab: Whether to open the link in a new tab.
|
||||
incdec: Either 'increment' or 'decrement'.
|
||||
tab: Whether to open the link in a new tab.
|
||||
background: Open the link in a new background tab.
|
||||
window: Open the link in a new window.
|
||||
"""
|
||||
encoded = bytes(url.toEncoded()).decode('ascii')
|
||||
# Get the last number in a string
|
||||
@ -369,25 +406,27 @@ class CommandDispatcher:
|
||||
raise ValueError("Invalid value {} for indec!".format(incdec))
|
||||
urlstr = ''.join([pre, str(val), post]).encode('ascii')
|
||||
new_url = QUrl.fromEncoded(urlstr)
|
||||
self._open(new_url, tab, background=False)
|
||||
self._open(new_url, tab, background, window)
|
||||
|
||||
def _navigate_up(self, url, tab):
|
||||
def _navigate_up(self, url, tab, background, window):
|
||||
"""Helper method for :navigate when `where' is up.
|
||||
|
||||
Args:
|
||||
url: The current url.
|
||||
tab: Whether to open the link in a new tab.
|
||||
background: Open the link in a new background tab.
|
||||
window: Open the link in a new window.
|
||||
"""
|
||||
path = url.path()
|
||||
if not path or path == '/':
|
||||
raise cmdexc.CommandError("Can't go up!")
|
||||
new_path = posixpath.join(path, posixpath.pardir)
|
||||
url.setPath(new_path)
|
||||
self._open(url, tab, background=False)
|
||||
self._open(url, tab, background, window)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def navigate(self, where: ('prev', 'next', 'up', 'increment', 'decrement'),
|
||||
tab=False):
|
||||
tab=False, bg=False, window=False):
|
||||
"""Open typical prev/next links or navigate using the URL path.
|
||||
|
||||
This tries to automatically click on typical _Previous Page_ or
|
||||
@ -405,7 +444,11 @@ class CommandDispatcher:
|
||||
- `decrement`: Decrement the last number in the URL.
|
||||
|
||||
tab: Open in a new tab.
|
||||
bg: Open in a background tab.
|
||||
window: Open in a new window.
|
||||
"""
|
||||
if sum(1 for e in (tab, bg, window) if e) > 1:
|
||||
raise cmdexc.CommandError("Only one of -t/-b/-w can be given!")
|
||||
widget = self._current_widget()
|
||||
frame = widget.page().currentFrame()
|
||||
url = self._current_url()
|
||||
@ -413,18 +456,21 @@ class CommandDispatcher:
|
||||
raise cmdexc.CommandError("No frame focused!")
|
||||
hintmanager = objreg.get('hintmanager', scope='tab')
|
||||
if where == 'prev':
|
||||
hintmanager.follow_prevnext(frame, url, prev=True, newtab=tab)
|
||||
hintmanager.follow_prevnext(frame, url, prev=True, tab=tab,
|
||||
background=bg, window=window)
|
||||
elif where == 'next':
|
||||
hintmanager.follow_prevnext(frame, url, prev=False, newtab=tab)
|
||||
hintmanager.follow_prevnext(frame, url, prev=False, tab=tab,
|
||||
background=bg, window=window)
|
||||
elif where == 'up':
|
||||
self._navigate_up(url, tab)
|
||||
self._navigate_up(url, tab, bg, window)
|
||||
elif where in ('decrement', 'increment'):
|
||||
self._navigate_incdec(url, tab, where)
|
||||
self._navigate_incdec(url, where, tab, bg, window)
|
||||
else:
|
||||
raise ValueError("Got called with invalid value {} for "
|
||||
"`where'.".format(where))
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True)
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window')
|
||||
def scroll(self, dx: float, dy: float, count=1):
|
||||
"""Scroll the current tab by 'count * dx/dy'.
|
||||
|
||||
@ -439,7 +485,8 @@ class CommandDispatcher:
|
||||
cmdutils.check_overflow(dy, 'int')
|
||||
self._current_widget().page().currentFrame().scroll(dx, dy)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True)
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window')
|
||||
def scroll_perc(self, perc: float=None,
|
||||
horizontal: {'flag': 'x'}=False, count=None):
|
||||
"""Scroll to a specific percentage of the page.
|
||||
@ -455,7 +502,8 @@ class CommandDispatcher:
|
||||
self._scroll_percent(perc, count,
|
||||
Qt.Horizontal if horizontal else Qt.Vertical)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True)
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window')
|
||||
def scroll_page(self, x: float, y: float, count=1):
|
||||
"""Scroll the frame page-wise.
|
||||
|
||||
@ -472,7 +520,7 @@ class CommandDispatcher:
|
||||
cmdutils.check_overflow(dy, 'int')
|
||||
frame.scroll(dx, dy)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def yank(self, title=False, sel=False):
|
||||
"""Yank the current URL/title to the clipboard or primary selection.
|
||||
|
||||
@ -482,7 +530,7 @@ class CommandDispatcher:
|
||||
"""
|
||||
clipboard = QApplication.clipboard()
|
||||
if title:
|
||||
s = objreg.get('tabbed-browser').tabText(self._current_index())
|
||||
s = self._tabbed_browser().tabText(self._current_index())
|
||||
else:
|
||||
s = self._current_url().toString(
|
||||
QUrl.FullyEncoded | QUrl.RemovePassword)
|
||||
@ -495,9 +543,9 @@ class CommandDispatcher:
|
||||
log.misc.debug("Yanking to {}: '{}'".format(target, s))
|
||||
clipboard.setText(s, mode)
|
||||
what = 'Title' if title else 'URL'
|
||||
message.info("{} yanked to {}".format(what, target))
|
||||
message.info(self._win_id, "{} yanked to {}".format(what, target))
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def zoom_in(self, count=1):
|
||||
"""Increase the zoom level for the current tab.
|
||||
|
||||
@ -507,7 +555,7 @@ class CommandDispatcher:
|
||||
tab = self._current_widget()
|
||||
tab.zoom(count)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def zoom_out(self, count=1):
|
||||
"""Decrease the zoom level for the current tab.
|
||||
|
||||
@ -517,7 +565,7 @@ class CommandDispatcher:
|
||||
tab = self._current_widget()
|
||||
tab.zoom(-count)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def zoom(self, zoom=None, count=None):
|
||||
"""Set the zoom level for the current tab.
|
||||
|
||||
@ -535,24 +583,24 @@ class CommandDispatcher:
|
||||
tab = self._current_widget()
|
||||
tab.zoom_perc(level)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def tab_only(self):
|
||||
"""Close all tabs except for the current one."""
|
||||
tabbed_browser = objreg.get('tabbed-browser')
|
||||
tabbed_browser = self._tabbed_browser()
|
||||
for tab in tabbed_browser.widgets():
|
||||
if tab is self._current_widget():
|
||||
continue
|
||||
tabbed_browser.close_tab(tab)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def undo(self):
|
||||
"""Re-open a closed tab (optionally skipping [count] closed tabs)."""
|
||||
try:
|
||||
objreg.get('tabbed-browser').undo()
|
||||
self._tabbed_browser().undo()
|
||||
except IndexError:
|
||||
raise cmdexc.CommandError("Nothing to undo!")
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def tab_prev(self, count=1):
|
||||
"""Switch to the previous tab, or switch [count] tabs back.
|
||||
|
||||
@ -567,7 +615,7 @@ class CommandDispatcher:
|
||||
else:
|
||||
raise cmdexc.CommandError("First tab")
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def tab_next(self, count=1):
|
||||
"""Switch to the next tab, or switch [count] tabs forward.
|
||||
|
||||
@ -582,14 +630,15 @@ class CommandDispatcher:
|
||||
else:
|
||||
raise cmdexc.CommandError("Last tab")
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
def paste(self, sel=False, tab=False, bg=False):
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def paste(self, sel=False, tab=False, bg=False, window=False):
|
||||
"""Open a page from the clipboard.
|
||||
|
||||
Args:
|
||||
sel: Use the primary selection instead of the clipboard.
|
||||
tab: Open in a new tab.
|
||||
bg: Open in a background tab.
|
||||
window: Open in new window.
|
||||
"""
|
||||
clipboard = QApplication.clipboard()
|
||||
if sel and clipboard.supportsSelection():
|
||||
@ -606,9 +655,9 @@ class CommandDispatcher:
|
||||
url = urlutils.fuzzy_url(text)
|
||||
except urlutils.FuzzyUrlError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
self._open(url, tab, bg)
|
||||
self._open(url, tab, bg, window)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def tab_focus(self, index: (int, 'last')=None, count=None):
|
||||
"""Select the tab given as argument/[count].
|
||||
|
||||
@ -632,7 +681,7 @@ class CommandDispatcher:
|
||||
raise cmdexc.CommandError("There's no tab with index {}!".format(
|
||||
idx))
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def tab_move(self, direction: ('+', '-')=None, count=None):
|
||||
"""Move the current tab.
|
||||
|
||||
@ -656,7 +705,7 @@ class CommandDispatcher:
|
||||
if not 0 <= new_idx < self._count():
|
||||
raise cmdexc.CommandError("Can't move tab to position {}!".format(
|
||||
new_idx))
|
||||
tabbed_browser = objreg.get('tabbed-browser')
|
||||
tabbed_browser = self._tabbed_browser()
|
||||
tab = self._current_widget()
|
||||
cur_idx = self._current_index()
|
||||
icon = tabbed_browser.tabIcon(cur_idx)
|
||||
@ -671,7 +720,8 @@ class CommandDispatcher:
|
||||
finally:
|
||||
tabbed_browser.setUpdatesEnabled(True)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', split=False)
|
||||
@cmdutils.register(instance='command-dispatcher', split=False,
|
||||
scope='window')
|
||||
def spawn(self, *args):
|
||||
"""Spawn a command in a shell.
|
||||
|
||||
@ -689,12 +739,12 @@ class CommandDispatcher:
|
||||
log.procs.debug("Executing: {}".format(args))
|
||||
subprocess.Popen(args)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def home(self):
|
||||
"""Open main startpage in current tab."""
|
||||
self.openurl(config.get('general', 'startpage')[0])
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def run_userscript(self, cmd, *args: {'nargs': '*'}):
|
||||
"""Run an userscript given as argument.
|
||||
|
||||
@ -702,27 +752,30 @@ class CommandDispatcher:
|
||||
cmd: The userscript to run.
|
||||
args: Arguments to pass to the userscript.
|
||||
"""
|
||||
userscripts.run(cmd, *args, url=self._current_url())
|
||||
userscripts.run(cmd, *args, url=self._current_url(),
|
||||
win_id=self._win_id)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def quickmark_save(self):
|
||||
"""Save the current page as a quickmark."""
|
||||
quickmarks.prompt_save(self._current_url())
|
||||
quickmarks.prompt_save(self._win_id, self._current_url())
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
def quickmark_load(self, name, tab=False, bg=False):
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def quickmark_load(self, name, tab=False, bg=False, window=False):
|
||||
"""Load a quickmark.
|
||||
|
||||
Args:
|
||||
name: The name of the quickmark to load.
|
||||
tab: Load the quickmark in a new tab.
|
||||
bg: Load the quickmark in a new background tab.
|
||||
window: Load the quickmark in a new window.
|
||||
"""
|
||||
urlstr = quickmarks.get(name)
|
||||
url = QUrl(urlstr)
|
||||
self._open(url, tab, bg)
|
||||
self._open(url, tab, bg, window)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', name='inspector')
|
||||
@cmdutils.register(instance='command-dispatcher', name='inspector',
|
||||
scope='window')
|
||||
def toggle_inspector(self):
|
||||
"""Toggle the web inspector."""
|
||||
cur = self._current_widget()
|
||||
@ -744,13 +797,13 @@ class CommandDispatcher:
|
||||
else:
|
||||
cur.inspector.show()
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def download_page(self):
|
||||
"""Download the current page."""
|
||||
page = self._current_widget().page()
|
||||
objreg.get('download-manager').get(self._current_url(), page)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def view_source(self):
|
||||
"""Show the source of the current page."""
|
||||
# pylint doesn't seem to like pygments...
|
||||
@ -766,15 +819,20 @@ class CommandDispatcher:
|
||||
highlighted = pygments.highlight(html, lexer, formatter)
|
||||
current_url = self._current_url()
|
||||
tab = objreg.get('tabbed-browser').tabopen(explicit=True)
|
||||
tab = self._tabbed_browser().tabopen(explicit=True)
|
||||
tab.setHtml(highlighted, current_url)
|
||||
tab.viewing_source = True
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', name='help',
|
||||
completion=[usertypes.Completion.helptopic])
|
||||
def show_help(self, topic=None):
|
||||
completion=[usertypes.Completion.helptopic],
|
||||
scope='window')
|
||||
def show_help(self, tab=False, bg=False, window=False, topic=None):
|
||||
r"""Show help about a command or setting.
|
||||
|
||||
Args:
|
||||
tab: Open in a new tab.
|
||||
bg: Open in a background tab.
|
||||
window: Open in a new window.
|
||||
topic: The topic to show help for.
|
||||
|
||||
- :__command__ for commands.
|
||||
@ -804,11 +862,12 @@ class CommandDispatcher:
|
||||
path = 'settings.html#{}'.format(topic.replace('->', '-'))
|
||||
else:
|
||||
raise cmdexc.CommandError("Invalid help topic {}!".format(topic))
|
||||
self.openurl('qute://help/{}'.format(path))
|
||||
url = QUrl('qute://help/{}'.format(path))
|
||||
self._open(url, tab, bg, window)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher',
|
||||
modes=[usertypes.KeyMode.insert],
|
||||
hide=True)
|
||||
hide=True, scope='window')
|
||||
def open_editor(self):
|
||||
"""Open an external editor with the currently selected form field.
|
||||
|
||||
@ -832,9 +891,10 @@ class CommandDispatcher:
|
||||
text = str(elem)
|
||||
else:
|
||||
text = elem.evaluateJavaScript('this.value')
|
||||
self._editor = editor.ExternalEditor(objreg.get('tabbed-browser'))
|
||||
self._editor = editor.ExternalEditor(
|
||||
self._win_id, self._tabbed_browser())
|
||||
self._editor.editing_finished.connect(
|
||||
partial(self.on_editing_finished, elem))
|
||||
functools.partial(self.on_editing_finished, elem))
|
||||
self._editor.edit(text)
|
||||
|
||||
def on_editing_finished(self, elem, text):
|
||||
|
@ -26,6 +26,8 @@ import collections
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QTimer, QStandardPaths
|
||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
|
||||
# We need this import so PyQt can use it inside pyqtSlot
|
||||
from PyQt5.QtWebKitWidgets import QWebPage # pylint: disable=unused-import
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
@ -412,7 +414,9 @@ class DownloadManager(QObject):
|
||||
q.destroyed.connect(functools.partial(self.questions.remove, q))
|
||||
self.questions.append(q)
|
||||
download.cancelled.connect(q.abort)
|
||||
objreg.get('message-bridge').ask(q, blocking=False)
|
||||
message_bridge = objreg.get('message-bridge', scope='window',
|
||||
window='current')
|
||||
message_bridge.ask(q, blocking=False)
|
||||
|
||||
@pyqtSlot(DownloadItem)
|
||||
def on_finished(self, download):
|
||||
@ -433,4 +437,4 @@ class DownloadManager(QObject):
|
||||
@pyqtSlot(str)
|
||||
def on_error(self, msg):
|
||||
"""Display error message on download errors."""
|
||||
message.error("Download error: {}".format(msg))
|
||||
message.error('current', "Download error: {}".format(msg))
|
||||
|
@ -37,16 +37,17 @@ from qutebrowser.utils import usertypes, log, qtutils, message, objreg
|
||||
ElemTuple = collections.namedtuple('ElemTuple', ['elem', 'label'])
|
||||
|
||||
|
||||
Target = usertypes.enum('Target', ['normal', 'tab', 'tab_bg', 'yank',
|
||||
'yank_primary', 'fill', 'rapid', 'download',
|
||||
'userscript', 'spawn'])
|
||||
Target = usertypes.enum('Target', ['normal', 'tab', 'tab_bg', 'window', 'yank',
|
||||
'yank_primary', 'fill', 'rapid',
|
||||
'rapid_win', 'download', 'userscript',
|
||||
'spawn'])
|
||||
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_entered(mode):
|
||||
def on_mode_entered(mode, win_id):
|
||||
"""Stop hinting when insert mode was entered."""
|
||||
if mode == usertypes.KeyMode.insert:
|
||||
modeman.maybe_leave(usertypes.KeyMode.hint, 'insert mode')
|
||||
modeman.maybe_leave(win_id, usertypes.KeyMode.hint, 'insert mode')
|
||||
|
||||
|
||||
class HintContext:
|
||||
@ -58,7 +59,7 @@ class HintContext:
|
||||
elems: A mapping from keystrings to (elem, label) namedtuples.
|
||||
baseurl: The URL of the current page.
|
||||
target: What to do with the opened links.
|
||||
normal/tab/tab_bg: Get passed to BrowserTab.
|
||||
normal/tab/tab_bg/window: Get passed to BrowserTab.
|
||||
yank/yank_primary: Yank to clipboard/primary selection
|
||||
fill: Fill commandline with link.
|
||||
rapid: Rapid mode with background tabs
|
||||
@ -100,6 +101,8 @@ class HintManager(QObject):
|
||||
|
||||
Attributes:
|
||||
_context: The HintContext for the current invocation.
|
||||
_win_id: The window ID this HintManager is associated with.
|
||||
_tab_id: The tab ID this HintManager is associated with.
|
||||
|
||||
Signals:
|
||||
mouse_event: Mouse event to be posted in the web view.
|
||||
@ -125,10 +128,12 @@ class HintManager(QObject):
|
||||
Target.normal: "Follow hint...",
|
||||
Target.tab: "Follow hint in new tab...",
|
||||
Target.tab_bg: "Follow hint in background tab...",
|
||||
Target.window: "Follow hint in new window...",
|
||||
Target.yank: "Yank hint to clipboard...",
|
||||
Target.yank_primary: "Yank hint to primary selection...",
|
||||
Target.fill: "Set hint in commandline...",
|
||||
Target.rapid: "Follow hint (rapid mode)...",
|
||||
Target.rapid_win: "Follow hint in new window (rapid mode)...",
|
||||
Target.download: "Download hint...",
|
||||
Target.userscript: "Call userscript via hint...",
|
||||
Target.spawn: "Spawn command via hint...",
|
||||
@ -137,15 +142,15 @@ class HintManager(QObject):
|
||||
mouse_event = pyqtSignal('QMouseEvent')
|
||||
set_open_target = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
frame: The QWebFrame to use for finding elements and drawing.
|
||||
"""
|
||||
def __init__(self, win_id, tab_id, parent=None):
|
||||
"""Constructor."""
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self._tab_id = tab_id
|
||||
self._context = None
|
||||
objreg.get('mode-manager').left.connect(self.on_mode_left)
|
||||
mode_manager = objreg.get('mode-manager', scope='window',
|
||||
window=win_id)
|
||||
mode_manager.left.connect(self.on_mode_left)
|
||||
|
||||
def _cleanup(self):
|
||||
"""Clean up after hinting."""
|
||||
@ -155,7 +160,9 @@ class HintManager(QObject):
|
||||
except webelem.IsNullError:
|
||||
pass
|
||||
text = self.HINT_TEXTS[self._context.target]
|
||||
objreg.get('message-bridge').maybe_reset_text(text)
|
||||
message_bridge = objreg.get('message-bridge', scope='window',
|
||||
window=self._win_id)
|
||||
message_bridge.maybe_reset_text(text)
|
||||
self._context = None
|
||||
|
||||
def _hint_strings(self, elems):
|
||||
@ -300,6 +307,8 @@ class HintManager(QObject):
|
||||
"""
|
||||
if self._context.target == Target.rapid:
|
||||
target = Target.tab_bg
|
||||
elif self._context.target == Target.rapid_win:
|
||||
target = Target.window
|
||||
else:
|
||||
target = self._context.target
|
||||
self.set_open_target.emit(target.name)
|
||||
@ -331,8 +340,8 @@ class HintManager(QObject):
|
||||
mode = QClipboard.Selection if sel else QClipboard.Clipboard
|
||||
urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
|
||||
QApplication.clipboard().setText(urlstr, mode)
|
||||
message.info("URL yanked to {}".format("primary selection" if sel
|
||||
else "clipboard"))
|
||||
message.info(self._win_id, "URL yanked to {}".format(
|
||||
"primary selection" if sel else "clipboard"))
|
||||
|
||||
def _preset_cmd_text(self, url):
|
||||
"""Preset a commandline text based on a hint URL.
|
||||
@ -342,7 +351,7 @@ class HintManager(QObject):
|
||||
"""
|
||||
urlstr = url.toDisplayString(QUrl.FullyEncoded)
|
||||
args = self._context.get_args(urlstr)
|
||||
message.set_cmd_text(' '.join(args))
|
||||
message.set_cmd_text(self._win_id, ' '.join(args))
|
||||
|
||||
def _download(self, elem):
|
||||
"""Download a hint URL.
|
||||
@ -352,7 +361,8 @@ class HintManager(QObject):
|
||||
"""
|
||||
url = self._resolve_url(elem)
|
||||
if url is None:
|
||||
message.error("No suitable link found for this element.",
|
||||
message.error(self._win_id,
|
||||
"No suitable link found for this element.",
|
||||
immediately=True)
|
||||
return
|
||||
objreg.get('download-manager').get(url, elem.webFrame().page())
|
||||
@ -361,7 +371,7 @@ class HintManager(QObject):
|
||||
"""Call an userscript from a hint."""
|
||||
cmd = self._context.args[0]
|
||||
args = self._context.args[1:]
|
||||
userscripts.run(cmd, *args, url=url)
|
||||
userscripts.run(cmd, *args, url=url, win_id=self._win_id)
|
||||
|
||||
def _spawn(self, url):
|
||||
"""Spawn a simple command from a hint."""
|
||||
@ -487,17 +497,22 @@ class HintManager(QObject):
|
||||
for e, string in zip(elems, strings):
|
||||
label = self._draw_label(e, string)
|
||||
self._context.elems[string] = ElemTuple(e, label)
|
||||
keyparser = objreg.get('keyparsers')[usertypes.KeyMode.hint]
|
||||
keyparsers = objreg.get('keyparsers', scope='window',
|
||||
window=self._win_id)
|
||||
keyparser = keyparsers[usertypes.KeyMode.hint]
|
||||
keyparser.update_bindings(strings)
|
||||
|
||||
def follow_prevnext(self, frame, baseurl, prev=False, newtab=False):
|
||||
def follow_prevnext(self, frame, baseurl, prev=False, tab=False,
|
||||
background=False, window=False):
|
||||
"""Click a "previous"/"next" element on the page.
|
||||
|
||||
Args:
|
||||
frame: The frame where the element is in.
|
||||
baseurl: The base URL of the current tab.
|
||||
prev: True to open a "previous" link, False to open a "next" link.
|
||||
newtab: True to open in a new tab, False for the current tab.
|
||||
tab: True to open in a new tab, False for the current tab.
|
||||
background: True to open in a background tab.
|
||||
window: True to open in a new window, False for the current one.
|
||||
"""
|
||||
elem = self._find_prevnext(frame, prev)
|
||||
if elem is None:
|
||||
@ -507,10 +522,22 @@ class HintManager(QObject):
|
||||
if url is None:
|
||||
raise cmdexc.CommandError("No {} links found!".format(
|
||||
"prev" if prev else "forward"))
|
||||
if newtab:
|
||||
objreg.get('tabbed-browser').tabopen(url, background=False)
|
||||
qtutils.ensure_valid(url)
|
||||
if window:
|
||||
main_window = objreg.get('main-window', scope='window',
|
||||
window=self._win_id)
|
||||
win_id = main_window.spawn()
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
tabbed_browser.tabopen(url, background=False)
|
||||
elif tab:
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._win_id)
|
||||
tabbed_browser.tabopen(url, background=background)
|
||||
else:
|
||||
objreg.get('webview', scope='tab').openurl(url)
|
||||
webview = objreg.get('webview', scope='tab', window=self._win_id,
|
||||
tab=self._tab_id)
|
||||
webview.openurl(url)
|
||||
|
||||
@cmdutils.register(instance='hintmanager', scope='tab', name='hint')
|
||||
def start(self, group=webelem.Group.all, target=Target.normal,
|
||||
@ -529,11 +556,14 @@ class HintManager(QObject):
|
||||
- `normal`: Open the link in the current tab.
|
||||
- `tab`: Open the link in a new tab.
|
||||
- `tab-bg`: Open the link in a new background tab.
|
||||
- `window`: Open the link in a new window.
|
||||
- `yank`: Yank the link to the clipboard.
|
||||
- `yank-primary`: Yank the link to the primary selection.
|
||||
- `fill`: Fill the commandline with the command given as
|
||||
argument.
|
||||
- `rapid`: Open the link in a new tab and stay in hinting mode.
|
||||
- `rapid-win`: Open the link in a new window and stay in
|
||||
hinting mode.
|
||||
- `download`: Download the link.
|
||||
- `userscript`: Call an userscript with `$QUTE_URL` set to the
|
||||
link.
|
||||
@ -549,7 +579,8 @@ class HintManager(QObject):
|
||||
`{hint-url}` will get replaced by the selected
|
||||
URL.
|
||||
"""
|
||||
tabbed_browser = objreg.get('tabbed-browser')
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._win_id)
|
||||
widget = tabbed_browser.currentWidget()
|
||||
if widget is None:
|
||||
raise cmdexc.CommandError("No WebView available yet!")
|
||||
@ -563,10 +594,13 @@ class HintManager(QObject):
|
||||
self._context.frames = webelem.get_child_frames(mainframe)
|
||||
self._context.args = args
|
||||
self._init_elements(mainframe, group)
|
||||
objreg.get('message-bridge').set_text(self.HINT_TEXTS[target])
|
||||
message_bridge = objreg.get('message-bridge', scope='window',
|
||||
window=self._win_id)
|
||||
message_bridge.set_text(self.HINT_TEXTS[target])
|
||||
self._connect_frame_signals()
|
||||
try:
|
||||
modeman.enter(usertypes.KeyMode.hint, 'HintManager.start')
|
||||
modeman.enter(self._win_id, usertypes.KeyMode.hint,
|
||||
'HintManager.start')
|
||||
except modeman.ModeLockedError:
|
||||
self._cleanup()
|
||||
|
||||
@ -610,7 +644,7 @@ class HintManager(QObject):
|
||||
visible[k] = e
|
||||
if not visible:
|
||||
# Whoops, filtered all hints
|
||||
modeman.leave(usertypes.KeyMode.hint, 'all filtered')
|
||||
modeman.leave(self._win_id, usertypes.KeyMode.hint, 'all filtered')
|
||||
elif len(visible) == 1 and config.get('hints', 'auto-follow'):
|
||||
# unpacking gets us the first (and only) key in the dict.
|
||||
self.fire(*visible)
|
||||
@ -631,7 +665,9 @@ class HintManager(QObject):
|
||||
Target.normal: self._click,
|
||||
Target.tab: self._click,
|
||||
Target.tab_bg: self._click,
|
||||
Target.window: self._click,
|
||||
Target.rapid: self._click,
|
||||
Target.rapid_win: self._click,
|
||||
# _download needs a QWebElement to get the frame.
|
||||
Target.download: self._download,
|
||||
}
|
||||
@ -649,14 +685,16 @@ class HintManager(QObject):
|
||||
elif self._context.target in url_handlers:
|
||||
url = self._resolve_url(elem)
|
||||
if url is None:
|
||||
message.error("No suitable link found for this element.",
|
||||
message.error(self._win_id,
|
||||
"No suitable link found for this element.",
|
||||
immediately=True)
|
||||
return
|
||||
url_handlers[self._context.target](url)
|
||||
else:
|
||||
raise ValueError("No suitable handler found!")
|
||||
if self._context.target != Target.rapid:
|
||||
modeman.maybe_leave(usertypes.KeyMode.hint, 'followed')
|
||||
if self._context.target not in (Target.rapid, Target.rapid_win):
|
||||
modeman.maybe_leave(self._win_id, usertypes.KeyMode.hint,
|
||||
'followed')
|
||||
|
||||
@cmdutils.register(instance='hintmanager', scope='tab', hide=True)
|
||||
def follow_hint(self):
|
||||
|
@ -47,7 +47,7 @@ def init():
|
||||
try:
|
||||
key, url = line.split(maxsplit=1)
|
||||
except ValueError:
|
||||
message.error("Invalid quickmark '{}'".format(line))
|
||||
message.error(0, "Invalid quickmark '{}'".format(line))
|
||||
else:
|
||||
marks[key] = url
|
||||
|
||||
@ -58,22 +58,23 @@ def save():
|
||||
linecp.save()
|
||||
|
||||
|
||||
def prompt_save(url):
|
||||
def prompt_save(win_id, url):
|
||||
"""Prompt for a new quickmark name to be added and add it.
|
||||
|
||||
Args:
|
||||
win_id: The current window ID.
|
||||
url: The quickmark url as a QUrl.
|
||||
"""
|
||||
if not url.isValid():
|
||||
urlutils.invalid_url_error(url, "save quickmark")
|
||||
return
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
message.ask_async("Add quickmark:", usertypes.PromptMode.text,
|
||||
message.ask_async(win_id, "Add quickmark:", usertypes.PromptMode.text,
|
||||
functools.partial(quickmark_add, urlstr))
|
||||
|
||||
|
||||
@cmdutils.register()
|
||||
def quickmark_add(url, name):
|
||||
def quickmark_add(url, name, win_id):
|
||||
"""Add a new quickmark.
|
||||
|
||||
Args:
|
||||
@ -83,10 +84,10 @@ def quickmark_add(url, name):
|
||||
# We don't raise cmdexc.CommandError here as this can be called async via
|
||||
# prompt_save.
|
||||
if not name:
|
||||
message.error("Can't set mark with empty name!")
|
||||
message.error(win_id, "Can't set mark with empty name!")
|
||||
return
|
||||
if not url:
|
||||
message.error("Can't set mark with empty URL!")
|
||||
message.error(win_id, "Can't set mark with empty URL!")
|
||||
return
|
||||
|
||||
def set_mark():
|
||||
@ -94,7 +95,7 @@ def quickmark_add(url, name):
|
||||
marks[name] = url
|
||||
|
||||
if name in marks:
|
||||
message.confirm_async("Override existing quickmark?", set_mark,
|
||||
message.confirm_async(win_id, "Override existing quickmark?", set_mark,
|
||||
default=True)
|
||||
else:
|
||||
set_mark()
|
||||
|
@ -34,12 +34,19 @@ class SignalFilter(QObject):
|
||||
Signals are only passed to the parent TabbedBrowser if they originated in
|
||||
the currently shown widget.
|
||||
|
||||
Attributes:
|
||||
_win_id: The window ID this SignalFilter is associated with.
|
||||
|
||||
Class attributes:
|
||||
BLACKLIST: List of signal names which should not be logged.
|
||||
"""
|
||||
|
||||
BLACKLIST = ['cur_scroll_perc_changed', 'cur_progress']
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
|
||||
def create(self, signal, tab):
|
||||
"""Factory for partial _filter_signals functions.
|
||||
|
||||
@ -73,7 +80,8 @@ class SignalFilter(QObject):
|
||||
The target signal if the sender was the current widget.
|
||||
"""
|
||||
log_signal = debug.signal_name(signal) not in self.BLACKLIST
|
||||
tabbed_browser = objreg.get('tabbed-browser')
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._win_id)
|
||||
try:
|
||||
tabidx = tabbed_browser.indexOf(tab)
|
||||
except RuntimeError:
|
||||
|
@ -41,6 +41,7 @@ class BrowserPage(QWebPage):
|
||||
Attributes:
|
||||
_extension_handlers: Mapping of QWebPage extensions to their handlers.
|
||||
_networkmnager: The NetworkManager used.
|
||||
_win_id: The window ID this BrowserPage is associated with.
|
||||
|
||||
Signals:
|
||||
start_download: Emitted when a file should be downloaded.
|
||||
@ -48,13 +49,14 @@ class BrowserPage(QWebPage):
|
||||
|
||||
start_download = pyqtSignal('QNetworkReply*')
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self._extension_handlers = {
|
||||
QWebPage.ErrorPageExtension: self._handle_errorpage,
|
||||
QWebPage.ChooseMultipleFilesExtension: self._handle_multiple_files,
|
||||
}
|
||||
self._networkmanager = networkmanager.NetworkManager(self)
|
||||
self._networkmanager = networkmanager.NetworkManager(win_id, self)
|
||||
self.setNetworkAccessManager(self._networkmanager)
|
||||
self.setForwardUnsupportedContent(True)
|
||||
self.printRequested.connect(self.on_print_requested)
|
||||
@ -75,8 +77,8 @@ class BrowserPage(QWebPage):
|
||||
|
||||
http://www.riverbankcomputing.com/pipermail/pyqt/2014-June/034385.html
|
||||
"""
|
||||
answer = message.ask("js: {}".format(msg), usertypes.PromptMode.text,
|
||||
default)
|
||||
answer = message.ask(self._win_id, "js: {}".format(msg),
|
||||
usertypes.PromptMode.text, default)
|
||||
if answer is None:
|
||||
return (False, "")
|
||||
else:
|
||||
@ -155,8 +157,8 @@ class BrowserPage(QWebPage):
|
||||
def on_print_requested(self, frame):
|
||||
"""Handle printing when requested via javascript."""
|
||||
if not qtutils.check_print_compat():
|
||||
message.error("Printing on Qt < 5.3.0 on Windows is broken, "
|
||||
"please upgrade!", immediately=True)
|
||||
message.error(self._win_id, "Printing on Qt < 5.3.0 on Windows is "
|
||||
"broken, please upgrade!", immediately=True)
|
||||
return
|
||||
printdiag = QPrintDialog()
|
||||
printdiag.setAttribute(Qt.WA_DeleteOnClose)
|
||||
@ -245,11 +247,12 @@ class BrowserPage(QWebPage):
|
||||
|
||||
def javaScriptAlert(self, _frame, msg):
|
||||
"""Override javaScriptAlert to use the statusbar."""
|
||||
message.ask("[js alert] {}".format(msg), usertypes.PromptMode.alert)
|
||||
message.ask(self._win_id, "[js alert] {}".format(msg),
|
||||
usertypes.PromptMode.alert)
|
||||
|
||||
def javaScriptConfirm(self, _frame, msg):
|
||||
"""Override javaScriptConfirm to use the statusbar."""
|
||||
ans = message.ask("[js confirm] {}".format(msg),
|
||||
ans = message.ask(self._win_id, "[js confirm] {}".format(msg),
|
||||
usertypes.PromptMode.yesno)
|
||||
return bool(ans)
|
||||
|
||||
@ -269,8 +272,8 @@ class BrowserPage(QWebPage):
|
||||
|
||||
def shouldInterruptJavaScript(self):
|
||||
"""Override shouldInterruptJavaScript to use the statusbar."""
|
||||
answer = message.ask("Interrupt long-running javascript?",
|
||||
usertypes.PromptMode.yesno)
|
||||
answer = message.ask(self._win_id, "Interrupt long-running "
|
||||
"javascript?", usertypes.PromptMode.yesno)
|
||||
if answer is None:
|
||||
answer = True
|
||||
return answer
|
||||
@ -295,14 +298,26 @@ class BrowserPage(QWebPage):
|
||||
url = request.url()
|
||||
urlstr = url.toDisplayString()
|
||||
if not url.isValid():
|
||||
message.error("Invalid link {} clicked!".format(urlstr))
|
||||
message.error(self._win_id, "Invalid link {} clicked!".format(
|
||||
urlstr))
|
||||
log.webview.debug(url.errorString())
|
||||
return False
|
||||
if self.view().open_target == usertypes.ClickTarget.tab:
|
||||
objreg.get('tabbed-browser').tabopen(url, False)
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._win_id)
|
||||
open_target = self.view().open_target
|
||||
if open_target == usertypes.ClickTarget.tab:
|
||||
tabbed_browser.tabopen(url, False)
|
||||
return False
|
||||
elif self.view().open_target == usertypes.ClickTarget.tab_bg:
|
||||
objreg.get('tabbed-browser').tabopen(url, True)
|
||||
elif open_target == usertypes.ClickTarget.tab_bg:
|
||||
tabbed_browser.tabopen(url, True)
|
||||
return False
|
||||
elif open_target == usertypes.ClickTarget.window:
|
||||
main_window = objreg.get('main-window', scope='window',
|
||||
window=self._win_id)
|
||||
win_id = main_window.spawn()
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
tabbed_browser.tabopen(url, False)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
@ -58,7 +58,9 @@ class HelpAction(argparse.Action):
|
||||
"""
|
||||
|
||||
def __call__(self, parser, _namespace, _values, _option_string=None):
|
||||
objreg.get('tabbed-browser').tabopen(
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window='current')
|
||||
tabbed_browser.tabopen(
|
||||
QUrl('qute://help/commands.html#{}'.format(parser.name)))
|
||||
parser.exit()
|
||||
|
||||
|
@ -95,13 +95,18 @@ class Command:
|
||||
self._type_conv = type_conv
|
||||
self._name_conv = name_conv
|
||||
|
||||
def _check_prerequisites(self):
|
||||
def _check_prerequisites(self, win_id):
|
||||
"""Check if the command is permitted to run currently.
|
||||
|
||||
Args:
|
||||
win_id: The window ID the command is run in.
|
||||
|
||||
Raise:
|
||||
PrerequisitesError if the command can't be called currently.
|
||||
"""
|
||||
curmode = objreg.get('mode-manager').mode()
|
||||
mode_manager = objreg.get('mode-manager', scope='window',
|
||||
window=win_id)
|
||||
curmode = mode_manager.mode()
|
||||
if self._modes is not None and curmode not in self._modes:
|
||||
mode_names = '/'.join(mode.name for mode in self._modes)
|
||||
raise cmdexc.PrerequisitesError(
|
||||
@ -179,7 +184,7 @@ class Command:
|
||||
desc = ""
|
||||
if not self.ignore_args:
|
||||
for param in signature.parameters.values():
|
||||
if param.name in ('self', 'count'):
|
||||
if param.name in ('self', 'count', 'win_id'):
|
||||
continue
|
||||
annotation_info = self._parse_annotation(param)
|
||||
typ = self._get_type(param, annotation_info)
|
||||
@ -294,15 +299,19 @@ class Command:
|
||||
else:
|
||||
return type(param.default)
|
||||
|
||||
def _get_self_arg(self, param, args):
|
||||
def _get_self_arg(self, win_id, param, args):
|
||||
"""Get the self argument for a function call.
|
||||
|
||||
Arguments:
|
||||
win_id: The window id this command should be executed in.
|
||||
param: The count parameter.
|
||||
args: The positional argument list. Gets modified directly.
|
||||
"""
|
||||
assert param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
|
||||
obj = objreg.get(self._instance, scope=self._scope)
|
||||
if self._scope is not 'window':
|
||||
win_id = None
|
||||
obj = objreg.get(self._instance, scope=self._scope,
|
||||
window=win_id)
|
||||
args.append(obj)
|
||||
|
||||
def _get_count_arg(self, param, args, kwargs):
|
||||
@ -328,6 +337,23 @@ class Command:
|
||||
raise TypeError("{}: invalid parameter type {} for argument "
|
||||
"'count'!".format(self.name, param.kind))
|
||||
|
||||
def _get_win_id_arg(self, win_id, param, args, kwargs):
|
||||
"""Add the win_id argument to a function call.
|
||||
|
||||
Arguments:
|
||||
win_id: The window ID to add.
|
||||
param: The count parameter.
|
||||
args: The positional argument list. Gets modified directly.
|
||||
kwargs: The keyword argument dict. Gets modified directly.
|
||||
"""
|
||||
if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
|
||||
args.append(win_id)
|
||||
elif param.kind == inspect.Parameter.KEYWORD_ONLY:
|
||||
kwargs['win_id'] = win_id
|
||||
else:
|
||||
raise TypeError("{}: invalid parameter type {} for argument "
|
||||
"'count'!".format(self.name, param.kind))
|
||||
|
||||
def _get_param_name_and_value(self, param):
|
||||
"""Get the converted name and value for an inspect.Parameter."""
|
||||
name = self._name_conv.get(param.name, param.name)
|
||||
@ -340,9 +366,12 @@ class Command:
|
||||
value = self._type_conv[param.name](value)
|
||||
return name, value
|
||||
|
||||
def _get_call_args(self):
|
||||
def _get_call_args(self, win_id): # noqa
|
||||
"""Get arguments for a function call.
|
||||
|
||||
Args:
|
||||
win_id: The window id this command should be executed in.
|
||||
|
||||
Return:
|
||||
An (args, kwargs) tuple.
|
||||
"""
|
||||
@ -354,18 +383,22 @@ class Command:
|
||||
if self.ignore_args:
|
||||
if self._instance is not None:
|
||||
param = list(signature.parameters.values())[0]
|
||||
self._get_self_arg(param, args)
|
||||
self._get_self_arg(win_id, param, args)
|
||||
return args, kwargs
|
||||
|
||||
for i, param in enumerate(signature.parameters.values()):
|
||||
if i == 0 and self._instance is not None:
|
||||
# Special case for 'self'.
|
||||
self._get_self_arg(param, args)
|
||||
self._get_self_arg(win_id, param, args)
|
||||
continue
|
||||
elif param.name == 'count':
|
||||
# Special case for 'count'.
|
||||
self._get_count_arg(param, args, kwargs)
|
||||
continue
|
||||
elif param.name == 'win_id':
|
||||
# Special case for 'win_id'.
|
||||
self._get_win_id_arg(win_id, param, args, kwargs)
|
||||
continue
|
||||
name, value = self._get_param_name_and_value(param)
|
||||
if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
|
||||
args.append(value)
|
||||
@ -380,12 +413,13 @@ class Command:
|
||||
self.name, param.kind, param.name))
|
||||
return args, kwargs
|
||||
|
||||
def run(self, args=None, count=None):
|
||||
def run(self, win_id, args=None, count=None):
|
||||
"""Run the command.
|
||||
|
||||
Note we don't catch CommandError here as it might happen async.
|
||||
|
||||
Args:
|
||||
win_id: The window ID the command is run in.
|
||||
args: Arguments to the command.
|
||||
count: Command repetition count.
|
||||
"""
|
||||
@ -398,15 +432,15 @@ class Command:
|
||||
try:
|
||||
self.namespace = self.parser.parse_args(args)
|
||||
except argparser.ArgumentParserError as e:
|
||||
message.error('{}: {}'.format(self.name, e))
|
||||
message.error(win_id, '{}: {}'.format(self.name, e))
|
||||
return
|
||||
except argparser.ArgumentParserExit as e:
|
||||
log.commands.debug("argparser exited with status {}: {}".format(
|
||||
e.status, e))
|
||||
return
|
||||
self._count = count
|
||||
posargs, kwargs = self._get_call_args()
|
||||
self._check_prerequisites()
|
||||
posargs, kwargs = self._get_call_args(win_id)
|
||||
self._check_prerequisites(win_id)
|
||||
log.commands.debug('Calling {}'.format(
|
||||
debug.format_call(self.handler, posargs, kwargs)))
|
||||
self.handler(*posargs, **kwargs)
|
||||
|
@ -27,13 +27,17 @@ from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.utils import message, log, utils, objreg
|
||||
|
||||
|
||||
def replace_variables(arglist):
|
||||
def replace_variables(win_id, arglist):
|
||||
"""Utility function to replace variables like {url} in a list of args."""
|
||||
args = []
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
for arg in arglist:
|
||||
if arg == '{url}':
|
||||
url = objreg.get('tabbed-browser').current_url().toString(
|
||||
QUrl.FullyEncoded | QUrl.RemovePassword)
|
||||
# Note we have to do this in here as the user gets an error message
|
||||
# by current_url if no URL is open yet.
|
||||
url = tabbed_browser.current_url().toString(QUrl.FullyEncoded |
|
||||
QUrl.RemovePassword)
|
||||
args.append(url)
|
||||
else:
|
||||
args.append(arg)
|
||||
@ -114,7 +118,7 @@ class SearchRunner(QObject):
|
||||
"""
|
||||
self._search(text, rev=True)
|
||||
|
||||
@cmdutils.register(instance='search-runner', hide=True)
|
||||
@cmdutils.register(instance='search-runner', hide=True, scope='window')
|
||||
def search_next(self, count=1):
|
||||
"""Continue the search to the ([count]th) next term.
|
||||
|
||||
@ -128,7 +132,7 @@ class SearchRunner(QObject):
|
||||
for _ in range(count):
|
||||
self.do_search.emit(self._text, self._flags)
|
||||
|
||||
@cmdutils.register(instance='search-runner', hide=True)
|
||||
@cmdutils.register(instance='search-runner', hide=True, scope='window')
|
||||
def search_prev(self, count=1):
|
||||
"""Continue the search to the ([count]th) previous term.
|
||||
|
||||
@ -152,18 +156,21 @@ class SearchRunner(QObject):
|
||||
self.do_search.emit(self._text, flags)
|
||||
|
||||
|
||||
class CommandRunner:
|
||||
class CommandRunner(QObject):
|
||||
|
||||
"""Parse and run qutebrowser commandline commands.
|
||||
|
||||
Attributes:
|
||||
_cmd: The command which was parsed.
|
||||
_args: The arguments which were parsed.
|
||||
_win_id: The window this CommandRunner is associated with.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._cmd = None
|
||||
self._args = []
|
||||
self._win_id = win_id
|
||||
|
||||
def _get_alias(self, text, alias_no_args):
|
||||
"""Get an alias from the config.
|
||||
@ -278,11 +285,11 @@ class CommandRunner:
|
||||
self.run(sub, count)
|
||||
return
|
||||
self.parse(text)
|
||||
args = replace_variables(self._args)
|
||||
args = replace_variables(self._win_id, self._args)
|
||||
if count is not None:
|
||||
self._cmd.run(args, count=count)
|
||||
self._cmd.run(self._win_id, args, count=count)
|
||||
else:
|
||||
self._cmd.run(args)
|
||||
self._cmd.run(self._win_id, args)
|
||||
|
||||
@pyqtSlot(str, int)
|
||||
def run_safely(self, text, count=None):
|
||||
@ -290,7 +297,7 @@ class CommandRunner:
|
||||
try:
|
||||
self.run(text, count)
|
||||
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
|
||||
message.error(e, immediately=True)
|
||||
message.error(self._win_id, e, immediately=True)
|
||||
|
||||
@pyqtSlot(str, int)
|
||||
def run_safely_init(self, text, count=None):
|
||||
@ -301,4 +308,4 @@ class CommandRunner:
|
||||
try:
|
||||
self.run(text, count)
|
||||
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
|
||||
message.error(e)
|
||||
message.error(self._win_id, e)
|
||||
|
@ -17,29 +17,20 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Functions to execute an userscript.
|
||||
|
||||
Module attributes:
|
||||
_runners: Active userscript runners from run_userscript.
|
||||
"""
|
||||
"""Functions to execute an userscript."""
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
import select
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import (pyqtSignal, QObject, QThread, QStandardPaths,
|
||||
QProcessEnvironment, QProcess, QUrl)
|
||||
|
||||
from qutebrowser.utils import message, log, utils
|
||||
from qutebrowser.utils import message, log, utils, objreg
|
||||
from qutebrowser.commands import runners, cmdexc
|
||||
|
||||
|
||||
_runners = []
|
||||
_commandrunner = None
|
||||
|
||||
|
||||
class _BlockingFIFOReader(QObject):
|
||||
|
||||
"""A worker which reads commands from a FIFO endlessly.
|
||||
@ -97,6 +88,7 @@ class _BaseUserscriptRunner(QObject):
|
||||
Attributes:
|
||||
_filepath: The path of the file/FIFO which is being read.
|
||||
_proc: The QProcess which is being executed.
|
||||
_win_id: The window ID this runner is associated with.
|
||||
|
||||
Class attributes:
|
||||
PROCESS_MESSAGES: A mapping of QProcess::ProcessError members to
|
||||
@ -121,8 +113,9 @@ class _BaseUserscriptRunner(QObject):
|
||||
QProcess.UnknownError: "An unknown error occurred.",
|
||||
}
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self._filepath = None
|
||||
self._proc = None
|
||||
|
||||
@ -152,7 +145,8 @@ class _BaseUserscriptRunner(QObject):
|
||||
except PermissionError as e:
|
||||
# NOTE: Do not replace this with "raise CommandError" as it's
|
||||
# executed async.
|
||||
message.error("Failed to delete tempfile... ({})".format(e))
|
||||
message.error(self._win_id,
|
||||
"Failed to delete tempfile... ({})".format(e))
|
||||
self._filepath = None
|
||||
self._proc = None
|
||||
|
||||
@ -180,7 +174,8 @@ class _BaseUserscriptRunner(QObject):
|
||||
msg = self.PROCESS_MESSAGES[error]
|
||||
# NOTE: Do not replace this with "raise CommandError" as it's
|
||||
# executed async.
|
||||
message.error("Error while calling userscript: {}".format(msg))
|
||||
message.error(self._win_id,
|
||||
"Error while calling userscript: {}".format(msg))
|
||||
|
||||
|
||||
class _POSIXUserscriptRunner(_BaseUserscriptRunner):
|
||||
@ -195,8 +190,8 @@ class _POSIXUserscriptRunner(_BaseUserscriptRunner):
|
||||
_thread: The QThread where reader runs.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(win_id, parent)
|
||||
self._reader = None
|
||||
self._thread = None
|
||||
|
||||
@ -262,8 +257,8 @@ class _WindowsUserscriptRunner(_BaseUserscriptRunner):
|
||||
_oshandle: The oshandle of the temp file.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(win_id, parent)
|
||||
self._oshandle = None
|
||||
|
||||
def _cleanup(self):
|
||||
@ -326,19 +321,16 @@ else:
|
||||
UserscriptRunner = _DummyUserscriptRunner
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize the global _commandrunner."""
|
||||
global _commandrunner
|
||||
_commandrunner = runners.CommandRunner()
|
||||
|
||||
|
||||
def run(cmd, *args, url):
|
||||
def run(cmd, *args, url, win_id):
|
||||
"""Convenience method to run an userscript."""
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
# We don't remove the password in the URL here, as it's probably safe to
|
||||
# pass via env variable..
|
||||
urlstr = url.toString(QUrl.FullyEncoded)
|
||||
runner = UserscriptRunner()
|
||||
runner.got_cmd.connect(_commandrunner.run_safely)
|
||||
commandrunner = runners.CommandRunner(win_id, tabbed_browser)
|
||||
runner = UserscriptRunner(win_id, tabbed_browser)
|
||||
runner.got_cmd.connect(commandrunner.run_safely)
|
||||
runner.run(cmd, *args, env={'QUTE_URL': urlstr})
|
||||
_runners.append(runner)
|
||||
runner.finished.connect(functools.partial(_runners.remove, runner))
|
||||
runner.finished.connect(commandrunner.deleteLater)
|
||||
runner.finished.connect(runner.deleteLater)
|
||||
|
@ -435,7 +435,7 @@ class ConfigManager(QObject):
|
||||
@cmdutils.register(name='set', instance='config',
|
||||
completion=[Completion.section, Completion.option,
|
||||
Completion.value])
|
||||
def set_command(self, sectname: {'name': 'section'},
|
||||
def set_command(self, win_id, sectname: {'name': 'section'},
|
||||
optname: {'name': 'option'}, value=None, temp=False):
|
||||
"""Set an option.
|
||||
|
||||
@ -455,8 +455,8 @@ class ConfigManager(QObject):
|
||||
try:
|
||||
if optname.endswith('?'):
|
||||
val = self.get(sectname, optname[:-1], transformed=False)
|
||||
message.info("{} {} = {}".format(sectname, optname[:-1], val),
|
||||
immediately=True)
|
||||
message.info(win_id, "{} {} = {}".format(
|
||||
sectname, optname[:-1], val), immediately=True)
|
||||
else:
|
||||
if value is None:
|
||||
raise cmdexc.CommandError("set: The following arguments "
|
||||
|
@ -878,6 +878,8 @@ KEY_DATA = collections.OrderedDict([
|
||||
('set-cmd-text ":open -t {url}"', ['gO']),
|
||||
('set-cmd-text ":open -b "', ['xo']),
|
||||
('set-cmd-text ":open -b {url}"', ['xO']),
|
||||
('set-cmd-text ":open -w "', ['wo']),
|
||||
('set-cmd-text ":open -w {url}"', ['wO']),
|
||||
('open -t about:blank', ['ga']),
|
||||
('tab-close', ['d', '<Ctrl-W>']),
|
||||
('tab-only', ['co']),
|
||||
@ -891,10 +893,13 @@ KEY_DATA = collections.OrderedDict([
|
||||
('reload', ['r']),
|
||||
('back', ['H', '<Backspace>']),
|
||||
('back -t', ['th']),
|
||||
('back -w', ['wh']),
|
||||
('forward', ['L']),
|
||||
('forward -t', ['tl']),
|
||||
('forward -w', ['wl']),
|
||||
('hint', ['f']),
|
||||
('hint all tab', ['F']),
|
||||
('hint all window', ['wf']),
|
||||
('hint all tab-bg', [';b']),
|
||||
('hint images', [';i']),
|
||||
('hint images tab', [';I']),
|
||||
@ -905,6 +910,7 @@ KEY_DATA = collections.OrderedDict([
|
||||
('hint links yank', [';y']),
|
||||
('hint links yank-primary', [';Y']),
|
||||
('hint links rapid', [';r']),
|
||||
('hint links rapid-win', [';R']),
|
||||
('hint links download', [';d']),
|
||||
('scroll -50 0', ['h']),
|
||||
('scroll 0 50', ['j']),
|
||||
@ -924,9 +930,12 @@ KEY_DATA = collections.OrderedDict([
|
||||
('paste -s', ['pP']),
|
||||
('paste -t', ['Pp']),
|
||||
('paste -ts', ['PP']),
|
||||
('paste -w', ['wp']),
|
||||
('paste -ws', ['wP']),
|
||||
('quickmark-save', ['m']),
|
||||
('set-cmd-text ":quickmark-load "', ['b']),
|
||||
('set-cmd-text ":quickmark-load -t "', ['B']),
|
||||
('set-cmd-text ":quickmark-load -w"', ['wb']),
|
||||
('save', ['sf']),
|
||||
('set-cmd-text ":set "', ['ss']),
|
||||
('set-cmd-text ":set -t "', ['sl']),
|
||||
|
@ -21,12 +21,13 @@
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import collections
|
||||
|
||||
from qutebrowser.utils import log, utils
|
||||
from qutebrowser.config import config
|
||||
|
||||
|
||||
class LineConfigParser:
|
||||
class LineConfigParser(collections.UserList):
|
||||
|
||||
"""Parser for configuration files which are simply line-based.
|
||||
|
||||
@ -47,6 +48,7 @@ class LineConfigParser:
|
||||
limit: Config tuple (section, option) which contains a limit.
|
||||
binary: Whether to open the file in binary mode.
|
||||
"""
|
||||
super().__init__()
|
||||
self._configdir = configdir
|
||||
self._configfile = os.path.join(self._configdir, fname)
|
||||
self._fname = fname
|
||||
@ -65,10 +67,6 @@ class LineConfigParser:
|
||||
configdir=self._configdir, fname=self._fname,
|
||||
limit=self._limit, binary=self._binary)
|
||||
|
||||
def __iter__(self):
|
||||
"""Iterate over the set data."""
|
||||
return self.data.__iter__()
|
||||
|
||||
def read(self, filename):
|
||||
"""Read the data from a file."""
|
||||
if self._binary:
|
||||
|
@ -53,6 +53,7 @@ class BaseKeyParser(QObject):
|
||||
Attributes:
|
||||
bindings: Bound keybindings
|
||||
special_bindings: Bound special bindings (<Foo>).
|
||||
_win_id: The window ID this keyparser is associated with.
|
||||
_warn_on_keychains: Whether a warning should be logged when binding
|
||||
keychains in a section which does not support them.
|
||||
_keystring: The currently entered key sequence
|
||||
@ -73,9 +74,10 @@ class BaseKeyParser(QObject):
|
||||
'none'])
|
||||
Type = usertypes.enum('Type', ['chain', 'special'])
|
||||
|
||||
def __init__(self, parent=None, supports_count=None,
|
||||
def __init__(self, win_id, parent=None, supports_count=None,
|
||||
supports_chains=False):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self._timer = None
|
||||
self._modename = None
|
||||
self._keystring = ''
|
||||
|
@ -32,16 +32,16 @@ class CommandKeyParser(BaseKeyParser):
|
||||
_commandrunner: CommandRunner instance.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None, supports_count=None,
|
||||
def __init__(self, win_id, parent=None, supports_count=None,
|
||||
supports_chains=False):
|
||||
super().__init__(parent, supports_count, supports_chains)
|
||||
self._commandrunner = runners.CommandRunner()
|
||||
super().__init__(win_id, parent, supports_count, supports_chains)
|
||||
self._commandrunner = runners.CommandRunner(win_id)
|
||||
|
||||
def execute(self, cmdstr, _keytype, count=None):
|
||||
try:
|
||||
self._commandrunner.run(cmdstr, count)
|
||||
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
|
||||
message.error(e, immediately=True)
|
||||
message.error(self._win_id, e, immediately=True)
|
||||
|
||||
|
||||
class PassthroughKeyParser(CommandKeyParser):
|
||||
@ -56,7 +56,7 @@ class PassthroughKeyParser(CommandKeyParser):
|
||||
|
||||
do_log = False
|
||||
|
||||
def __init__(self, mode, parent=None, warn=True):
|
||||
def __init__(self, win_id, mode, parent=None, warn=True):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
@ -64,7 +64,7 @@ class PassthroughKeyParser(CommandKeyParser):
|
||||
parent: Qt parent.
|
||||
warn: Whether to warn if an ignored key was bound.
|
||||
"""
|
||||
super().__init__(parent, supports_chains=False)
|
||||
super().__init__(win_id, parent, supports_chains=False)
|
||||
self._warn_on_keychains = warn
|
||||
self.read_config(mode)
|
||||
self._mode = mode
|
||||
|
@ -23,6 +23,8 @@ Module attributes:
|
||||
manager: The ModeManager instance.
|
||||
"""
|
||||
|
||||
import functools
|
||||
|
||||
from PyQt5.QtGui import QWindow
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, QEvent
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
@ -43,22 +45,26 @@ class NotInModeError(Exception):
|
||||
"""Exception raised when we want to leave a mode we're not in."""
|
||||
|
||||
|
||||
def init():
|
||||
"""Inizialize the mode manager and the keyparsers."""
|
||||
def init(win_id, parent):
|
||||
"""Inizialize the mode manager and the keyparsers for the given win_id."""
|
||||
KM = usertypes.KeyMode # pylint: disable=invalid-name
|
||||
modeman = ModeManager(objreg.get('app'))
|
||||
objreg.register('mode-manager', modeman)
|
||||
modeman = ModeManager(win_id, parent)
|
||||
objreg.register('mode-manager', modeman, scope='window', window=win_id)
|
||||
keyparsers = {
|
||||
KM.normal: modeparsers.NormalKeyParser(modeman),
|
||||
KM.hint: modeparsers.HintKeyParser(modeman),
|
||||
KM.insert: keyparser.PassthroughKeyParser('insert', modeman),
|
||||
KM.passthrough: keyparser.PassthroughKeyParser('passthrough', modeman),
|
||||
KM.command: keyparser.PassthroughKeyParser('command', modeman),
|
||||
KM.prompt: keyparser.PassthroughKeyParser('prompt', modeman,
|
||||
KM.normal: modeparsers.NormalKeyParser(win_id, modeman),
|
||||
KM.hint: modeparsers.HintKeyParser(win_id, modeman),
|
||||
KM.insert: keyparser.PassthroughKeyParser(win_id, 'insert', modeman),
|
||||
KM.passthrough: keyparser.PassthroughKeyParser(win_id, 'passthrough',
|
||||
modeman),
|
||||
KM.command: keyparser.PassthroughKeyParser(win_id, 'command', modeman),
|
||||
KM.prompt: keyparser.PassthroughKeyParser(win_id, 'prompt', modeman,
|
||||
warn=False),
|
||||
KM.yesno: modeparsers.PromptKeyParser(modeman),
|
||||
KM.yesno: modeparsers.PromptKeyParser(win_id, modeman),
|
||||
}
|
||||
objreg.register('keyparsers', keyparsers)
|
||||
objreg.register('keyparsers', keyparsers, scope='window', window=win_id)
|
||||
modeman.destroyed.connect(
|
||||
functools.partial(objreg.delete, 'keyparsers', scope='window',
|
||||
window=win_id))
|
||||
modeman.register(KM.normal, keyparsers[KM.normal].handle)
|
||||
modeman.register(KM.hint, keyparsers[KM.hint].handle)
|
||||
modeman.register(KM.insert, keyparsers[KM.insert].handle, passthrough=True)
|
||||
@ -68,35 +74,66 @@ def init():
|
||||
passthrough=True)
|
||||
modeman.register(KM.prompt, keyparsers[KM.prompt].handle, passthrough=True)
|
||||
modeman.register(KM.yesno, keyparsers[KM.yesno].handle)
|
||||
return modeman
|
||||
|
||||
|
||||
def enter(mode, reason=None, override=False):
|
||||
def _get_modeman(win_id):
|
||||
"""Get a modemanager object."""
|
||||
return objreg.get('mode-manager', scope='window', window=win_id)
|
||||
|
||||
def enter(win_id, mode, reason=None, override=False):
|
||||
"""Enter the mode 'mode'."""
|
||||
objreg.get('mode-manager').enter(mode, reason, override)
|
||||
_get_modeman(win_id).enter(mode, reason, override)
|
||||
|
||||
|
||||
def leave(mode, reason=None):
|
||||
def leave(win_id, mode, reason=None):
|
||||
"""Leave the mode 'mode'."""
|
||||
objreg.get('mode-manager').leave(mode, reason)
|
||||
_get_modeman(win_id).leave(mode, reason)
|
||||
|
||||
|
||||
def maybe_enter(mode, reason=None, override=False):
|
||||
def maybe_enter(win_id, mode, reason=None, override=False):
|
||||
"""Convenience method to enter 'mode' without exceptions."""
|
||||
try:
|
||||
objreg.get('mode-manager').enter(mode, reason, override)
|
||||
_get_modeman(win_id).enter(mode, reason, override)
|
||||
except ModeLockedError:
|
||||
pass
|
||||
|
||||
|
||||
def maybe_leave(mode, reason=None):
|
||||
def maybe_leave(win_id, mode, reason=None):
|
||||
"""Convenience method to leave 'mode' without exceptions."""
|
||||
try:
|
||||
objreg.get('mode-manager').leave(mode, reason)
|
||||
_get_modeman(win_id).leave(mode, reason)
|
||||
except NotInModeError as e:
|
||||
# This is rather likely to happen, so we only log to debug log.
|
||||
log.modes.debug("{} (leave reason: {})".format(e, reason))
|
||||
|
||||
|
||||
class EventFilter(QObject):
|
||||
|
||||
"""Event filter which passes the event to the corrent ModeManager."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._activated = True
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Forward events to the correct modeman."""
|
||||
if not self._activated:
|
||||
return False
|
||||
try:
|
||||
modeman = objreg.get('mode-manager', scope='window',
|
||||
window='current')
|
||||
return modeman.eventFilter(obj, event)
|
||||
except objreg.RegistryUnavailableError:
|
||||
# No window available yet, or not a MainWindow
|
||||
return False
|
||||
except:
|
||||
# If there is an exception in here and we leave the eventfilter
|
||||
# activated, we'll get an infinite loop and a stack overflow.
|
||||
self._activated = False
|
||||
raise
|
||||
|
||||
|
||||
class ModeManager(QObject):
|
||||
|
||||
"""Manager for keyboard modes.
|
||||
@ -106,6 +143,7 @@ class ModeManager(QObject):
|
||||
locked: Whether current mode is locked. This means the current mode can
|
||||
still be left (then locked will be reset), but no new mode can
|
||||
be entered while the current mode is active.
|
||||
_win_id: The window ID of this ModeManager
|
||||
_handlers: A dictionary of modes and their handlers.
|
||||
_mode_stack: A list of the modes we're currently in, with the active
|
||||
one on the right.
|
||||
@ -116,16 +154,19 @@ class ModeManager(QObject):
|
||||
|
||||
Signals:
|
||||
entered: Emitted when a mode is entered.
|
||||
arg: The mode which has been entered.
|
||||
arg1: The mode which has been entered.
|
||||
arg2: The window ID of this mode manager.
|
||||
left: Emitted when a mode is left.
|
||||
arg: The mode which has been left.
|
||||
arg1: The mode which has been left.
|
||||
arg2: The window ID of this mode manager.
|
||||
"""
|
||||
|
||||
entered = pyqtSignal(usertypes.KeyMode)
|
||||
left = pyqtSignal(usertypes.KeyMode)
|
||||
entered = pyqtSignal(usertypes.KeyMode, int)
|
||||
left = pyqtSignal(usertypes.KeyMode, int)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self.locked = False
|
||||
self._handlers = {}
|
||||
self.passthrough = []
|
||||
@ -251,9 +292,9 @@ class ModeManager(QObject):
|
||||
return
|
||||
self._mode_stack.append(mode)
|
||||
log.modes.debug("New mode stack: {}".format(self._mode_stack))
|
||||
self.entered.emit(mode)
|
||||
self.entered.emit(mode, self._win_id)
|
||||
|
||||
@cmdutils.register(instance='mode-manager', hide=True)
|
||||
@cmdutils.register(instance='mode-manager', hide=True, scope='window')
|
||||
def enter_mode(self, mode):
|
||||
"""Enter a key mode.
|
||||
|
||||
@ -284,10 +325,11 @@ class ModeManager(QObject):
|
||||
log.modes.debug("Leaving mode {}{}, new mode stack {}".format(
|
||||
mode, '' if reason is None else ' (reason: {})'.format(reason),
|
||||
self._mode_stack))
|
||||
self.left.emit(mode)
|
||||
self.left.emit(mode, self._win_id)
|
||||
|
||||
@cmdutils.register(instance='mode-manager', name='leave-mode',
|
||||
not_modes=[usertypes.KeyMode.normal], hide=True)
|
||||
not_modes=[usertypes.KeyMode.normal], hide=True,
|
||||
scope='window')
|
||||
def leave_current_mode(self):
|
||||
"""Leave the mode we're currently in."""
|
||||
if self.mode() == usertypes.KeyMode.normal:
|
||||
@ -321,8 +363,8 @@ class ModeManager(QObject):
|
||||
# We already handled this same event at some point earlier, so
|
||||
# we're not interested in it anymore.
|
||||
return False
|
||||
if (QApplication.instance().activeWindow() is not
|
||||
objreg.get('main-window')):
|
||||
if (QApplication.instance().activeWindow() not in
|
||||
objreg.window_registry.values()):
|
||||
# Some other window (print dialog, etc.) is focused so we pass
|
||||
# the event through.
|
||||
return False
|
||||
|
@ -39,8 +39,9 @@ class NormalKeyParser(keyparser.CommandKeyParser):
|
||||
|
||||
"""KeyParser for normalmode with added STARTCHARS detection."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent, supports_count=True, supports_chains=True)
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(win_id, parent, supports_count=True,
|
||||
supports_chains=True)
|
||||
self.read_config('normal')
|
||||
|
||||
def __repr__(self):
|
||||
@ -57,7 +58,7 @@ class NormalKeyParser(keyparser.CommandKeyParser):
|
||||
"""
|
||||
txt = e.text().strip()
|
||||
if not self._keystring and any(txt == c for c in STARTCHARS):
|
||||
message.set_cmd_text(txt)
|
||||
message.set_cmd_text(self._win_id, txt)
|
||||
return True
|
||||
return super()._handle_single_key(e)
|
||||
|
||||
@ -66,8 +67,9 @@ class PromptKeyParser(keyparser.CommandKeyParser):
|
||||
|
||||
"""KeyParser for yes/no prompts."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent, supports_count=False, supports_chains=True)
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(win_id, parent, supports_count=False,
|
||||
supports_chains=True)
|
||||
# We don't want an extra section for this in the config, so we just
|
||||
# abuse the prompt section.
|
||||
self.read_config('prompt')
|
||||
@ -85,8 +87,9 @@ class HintKeyParser(keyparser.CommandKeyParser):
|
||||
_last_press: The nature of the last keypress, a LastPress member.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent, supports_count=False, supports_chains=True)
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(win_id, parent, supports_count=False,
|
||||
supports_chains=True)
|
||||
self._filtertext = ''
|
||||
self._last_press = LastPress.none
|
||||
self.read_config('hint')
|
||||
@ -108,7 +111,8 @@ class HintKeyParser(keyparser.CommandKeyParser):
|
||||
"""
|
||||
log.keyboard.debug("Got special key 0x{:x} text {}".format(
|
||||
e.key(), e.text()))
|
||||
hintmanager = objreg.get('hintmanager', scope='tab')
|
||||
hintmanager = objreg.get('hintmanager', scope='tab',
|
||||
window=self._win_id, tab='current')
|
||||
if e.key() == Qt.Key_Backspace:
|
||||
log.keyboard.debug("Got backspace, mode {}, filtertext '{}', "
|
||||
"keystring '{}'".format(self._last_press,
|
||||
@ -163,7 +167,9 @@ class HintKeyParser(keyparser.CommandKeyParser):
|
||||
if not isinstance(keytype, self.Type):
|
||||
raise TypeError("Type {} is no Type member!".format(keytype))
|
||||
if keytype == self.Type.chain:
|
||||
objreg.get('hintmanager', scope='tab').fire(cmdstr)
|
||||
hintmanager = objreg.get('hintmanager', scope='tab',
|
||||
window=self._win_id, tab='current')
|
||||
hintmanager.fire(cmdstr)
|
||||
else:
|
||||
# execute as command
|
||||
super().execute(cmdstr, keytype, count)
|
||||
@ -180,4 +186,6 @@ class HintKeyParser(keyparser.CommandKeyParser):
|
||||
@pyqtSlot(str)
|
||||
def on_keystring_updated(self, keystr):
|
||||
"""Update hintmanager when the keystring was updated."""
|
||||
objreg.get('hintmanager', scope='tab').handle_partial_key(keystr)
|
||||
hintmanager = objreg.get('hintmanager', scope='tab',
|
||||
window=self._win_id, tab='current')
|
||||
hintmanager.handle_partial_key(keystr)
|
||||
|
@ -42,14 +42,16 @@ class NetworkManager(QNetworkAccessManager):
|
||||
_requests: Pending requests.
|
||||
_scheme_handlers: A dictionary (scheme -> handler) of supported custom
|
||||
schemes.
|
||||
_win_id: The window ID this NetworkManager is associated with.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, win_id, parent=None):
|
||||
log.init.debug("Initializing NetworkManager")
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self._requests = []
|
||||
self._scheme_handlers = {
|
||||
'qute': qutescheme.QuteSchemeHandler(),
|
||||
'qute': qutescheme.QuteSchemeHandler(win_id),
|
||||
}
|
||||
|
||||
# We have a shared cookie jar and cache - we restore their parents so
|
||||
@ -101,20 +103,22 @@ class NetworkManager(QNetworkAccessManager):
|
||||
for err in errors:
|
||||
# FIXME we might want to use warn here (non-fatal error)
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/114
|
||||
message.error('SSL error: {}'.format(err.errorString()))
|
||||
message.error(self._win_id,
|
||||
'SSL error: {}'.format(err.errorString()))
|
||||
reply.ignoreSslErrors()
|
||||
|
||||
@pyqtSlot('QNetworkReply', 'QAuthenticator')
|
||||
def on_authentication_required(self, _reply, authenticator):
|
||||
"""Called when a website needs authentication."""
|
||||
answer = message.ask("Username ({}):".format(authenticator.realm()),
|
||||
answer = message.ask(self._win_id,
|
||||
"Username ({}):".format(authenticator.realm()),
|
||||
mode=usertypes.PromptMode.user_pwd)
|
||||
self._fill_authenticator(authenticator, answer)
|
||||
|
||||
@pyqtSlot('QNetworkProxy', 'QAuthenticator')
|
||||
def on_proxy_authentication_required(self, _proxy, authenticator):
|
||||
"""Called when a proxy needs authentication."""
|
||||
answer = message.ask("Proxy username ({}):".format(
|
||||
answer = message.ask(self._win_id, "Proxy username ({}):".format(
|
||||
authenticator.realm()), mode=usertypes.PromptMode.user_pwd)
|
||||
self._fill_authenticator(authenticator, answer)
|
||||
|
||||
|
@ -69,7 +69,7 @@ class QuteSchemeHandler(schemehandler.SchemeHandler):
|
||||
request, errorstr, QNetworkReply.ContentNotFoundError,
|
||||
self.parent())
|
||||
try:
|
||||
data = handler(request)
|
||||
data = handler(self._win_id, request)
|
||||
except IOError as e:
|
||||
return schemehandler.ErrorNetworkReply(
|
||||
request, str(e), QNetworkReply.ContentNotFoundError,
|
||||
@ -78,14 +78,14 @@ class QuteSchemeHandler(schemehandler.SchemeHandler):
|
||||
request, data, 'text/html', self.parent())
|
||||
|
||||
|
||||
def qute_pyeval(_request):
|
||||
def qute_pyeval(_win_id, _request):
|
||||
"""Handler for qute:pyeval. Return HTML content as bytes."""
|
||||
html = jinja.env.get_template('pre.html').render(
|
||||
title='pyeval', content=pyeval_output)
|
||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
|
||||
def qute_version(_request):
|
||||
def qute_version(_win_id, _request):
|
||||
"""Handler for qute:version. Return HTML content as bytes."""
|
||||
html = jinja.env.get_template('version.html').render(
|
||||
title='Version info', version=version.version(),
|
||||
@ -93,7 +93,7 @@ def qute_version(_request):
|
||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
|
||||
def qute_plainlog(_request):
|
||||
def qute_plainlog(_win_id, _request):
|
||||
"""Handler for qute:plainlog. Return HTML content as bytes."""
|
||||
if log.ram_handler is None:
|
||||
text = "Log output was disabled."
|
||||
@ -103,7 +103,7 @@ def qute_plainlog(_request):
|
||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
|
||||
def qute_log(_request):
|
||||
def qute_log(_win_id, _request):
|
||||
"""Handler for qute:log. Return HTML content as bytes."""
|
||||
if log.ram_handler is None:
|
||||
html_log = None
|
||||
@ -114,12 +114,12 @@ def qute_log(_request):
|
||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
|
||||
def qute_gpl(_request):
|
||||
def qute_gpl(_win_id, _request):
|
||||
"""Handler for qute:gpl. Return HTML content as bytes."""
|
||||
return utils.read_file('html/COPYING.html').encode('ASCII')
|
||||
|
||||
|
||||
def qute_help(request):
|
||||
def qute_help(win_id, request):
|
||||
"""Handler for qute:help. Return HTML content as bytes."""
|
||||
try:
|
||||
utils.read_file('html/doc/index.html')
|
||||
@ -140,8 +140,8 @@ def qute_help(request):
|
||||
else:
|
||||
urlpath = urlpath.lstrip('/')
|
||||
if not docutils.docs_up_to_date(urlpath):
|
||||
message.error("Your documentation is outdated! Please re-run scripts/"
|
||||
"asciidoc2html.py.")
|
||||
message.error(win_id, "Your documentation is outdated! Please re-run "
|
||||
"scripts/asciidoc2html.py.")
|
||||
path = 'html/doc/{}'.format(urlpath)
|
||||
return utils.read_file(path).encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
|
@ -28,7 +28,15 @@ from PyQt5.QtCore import pyqtSlot, QObject, QIODevice, QByteArray, QTimer
|
||||
|
||||
class SchemeHandler(QObject):
|
||||
|
||||
"""Abstract base class for custom scheme handlers."""
|
||||
"""Abstract base class for custom scheme handlers.
|
||||
|
||||
Attributes:
|
||||
_win_id: The window ID this scheme handler is associated with.
|
||||
"""
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
|
||||
def createRequest(self, op, request, outgoing_data):
|
||||
"""Create a new request.
|
||||
|
@ -91,7 +91,8 @@ def get_argparser():
|
||||
parser.add_argument('command', nargs='*', help="Commands to execute on "
|
||||
"startup.", metavar=':command')
|
||||
# URLs will actually be in command
|
||||
parser.add_argument('url', nargs='*', help="URLs to open on startup.")
|
||||
parser.add_argument('url', nargs='*', help="URLs to open on startup "
|
||||
"(empty as a window separator).")
|
||||
return parser
|
||||
|
||||
|
||||
|
@ -67,7 +67,7 @@ class SplitCountTests(unittest.TestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.kp = basekeyparser.BaseKeyParser(supports_count=True)
|
||||
self.kp = basekeyparser.BaseKeyParser(0, supports_count=True)
|
||||
|
||||
def test_onlycount(self):
|
||||
"""Test split_count with only a count."""
|
||||
@ -114,13 +114,13 @@ class ReadConfigTests(unittest.TestCase):
|
||||
|
||||
def test_read_config_invalid(self):
|
||||
"""Test reading config without setting it before."""
|
||||
kp = basekeyparser.BaseKeyParser()
|
||||
kp = basekeyparser.BaseKeyParser(0)
|
||||
with self.assertRaises(ValueError):
|
||||
kp.read_config()
|
||||
|
||||
def test_read_config_valid(self):
|
||||
"""Test reading config."""
|
||||
kp = basekeyparser.BaseKeyParser(supports_count=True,
|
||||
kp = basekeyparser.BaseKeyParser(0, supports_count=True,
|
||||
supports_chains=True)
|
||||
kp.read_config('test')
|
||||
self.assertIn('ccc', kp.bindings)
|
||||
@ -147,7 +147,7 @@ class SpecialKeysTests(unittest.TestCase):
|
||||
patcher.start()
|
||||
objreg.register('key-config', fake_keyconfig)
|
||||
self.addCleanup(patcher.stop)
|
||||
self.kp = basekeyparser.BaseKeyParser()
|
||||
self.kp = basekeyparser.BaseKeyParser(0)
|
||||
self.kp.execute = mock.Mock()
|
||||
self.kp.read_config('test')
|
||||
|
||||
@ -187,7 +187,7 @@ class KeyChainTests(unittest.TestCase):
|
||||
objreg.register('key-config', fake_keyconfig)
|
||||
self.timermock = mock.Mock()
|
||||
basekeyparser.usertypes.Timer = mock.Mock(return_value=self.timermock)
|
||||
self.kp = basekeyparser.BaseKeyParser(supports_chains=True,
|
||||
self.kp = basekeyparser.BaseKeyParser(0, supports_chains=True,
|
||||
supports_count=False)
|
||||
self.kp.execute = mock.Mock()
|
||||
self.kp.read_config('test')
|
||||
@ -254,7 +254,7 @@ class CountTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
objreg.register('key-config', fake_keyconfig)
|
||||
basekeyparser.usertypes.Timer = mock.Mock()
|
||||
self.kp = basekeyparser.BaseKeyParser(supports_chains=True,
|
||||
self.kp = basekeyparser.BaseKeyParser(0, supports_chains=True,
|
||||
supports_count=True)
|
||||
self.kp.execute = mock.Mock()
|
||||
self.kp.read_config('test')
|
||||
|
@ -54,7 +54,7 @@ class ArgTests(unittest.TestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.editor = editor.ExternalEditor()
|
||||
self.editor = editor.ExternalEditor(0)
|
||||
|
||||
def test_simple_start_args(self):
|
||||
"""Test starting editor without arguments."""
|
||||
@ -102,7 +102,7 @@ class FileHandlingTests(unittest.TestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.editor = editor.ExternalEditor()
|
||||
self.editor = editor.ExternalEditor(0)
|
||||
editor.config = stubs.ConfigStub(
|
||||
{'general': {'editor': [''], 'editor-encoding': 'utf-8'}})
|
||||
|
||||
@ -141,7 +141,7 @@ class TextModifyTests(unittest.TestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.editor = editor.ExternalEditor()
|
||||
self.editor = editor.ExternalEditor(0)
|
||||
self.editor.editing_finished = mock.Mock()
|
||||
editor.config = stubs.ConfigStub(
|
||||
{'general': {'editor': [''], 'editor-encoding': 'utf-8'}})
|
||||
@ -211,7 +211,7 @@ class ErrorMessageTests(unittest.TestCase):
|
||||
# pylint: disable=maybe-no-member
|
||||
|
||||
def setUp(self):
|
||||
self.editor = editor.ExternalEditor()
|
||||
self.editor = editor.ExternalEditor(0)
|
||||
editor.config = stubs.ConfigStub(
|
||||
{'general': {'editor': [''], 'editor-encoding': 'utf-8'}})
|
||||
|
||||
|
@ -35,6 +35,7 @@ class Completer(QObject):
|
||||
Attributes:
|
||||
_ignore_change: Whether to ignore the next completion update.
|
||||
_models: dict of available completion models.
|
||||
_win_id: The window ID this completer is in.
|
||||
|
||||
Signals:
|
||||
change_completed_part: Text which should be substituted for the word
|
||||
@ -47,8 +48,9 @@ class Completer(QObject):
|
||||
|
||||
change_completed_part = pyqtSignal(str, bool)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self._ignore_change = False
|
||||
|
||||
self._models = {
|
||||
@ -63,7 +65,9 @@ class Completer(QObject):
|
||||
|
||||
def _model(self):
|
||||
"""Convienience method to get the current completion model."""
|
||||
return objreg.get('completion').model()
|
||||
completion = objreg.get('completion', scope='window',
|
||||
window=self._win_id)
|
||||
return completion.model()
|
||||
|
||||
def _init_static_completions(self):
|
||||
"""Initialize the static completion models."""
|
||||
@ -192,7 +196,8 @@ class Completer(QObject):
|
||||
log.completion.debug("Ignoring completion update")
|
||||
return
|
||||
|
||||
completion = objreg.get('completion')
|
||||
completion = objreg.get('completion', scope='window',
|
||||
window=self._win_id)
|
||||
|
||||
if prefix != ':':
|
||||
# This is a search or gibberish, so we don't need to complete
|
||||
|
@ -37,16 +37,18 @@ class ExternalEditor(QObject):
|
||||
_oshandle: The OS level handle to the tmpfile.
|
||||
_filehandle: The file handle to the tmpfile.
|
||||
_proc: The QProcess of the editor.
|
||||
_win_id: The window ID the ExternalEditor is associated with.
|
||||
"""
|
||||
|
||||
editing_finished = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._text = None
|
||||
self._oshandle = None
|
||||
self._filename = None
|
||||
self._proc = None
|
||||
self._win_id = win_id
|
||||
|
||||
def _cleanup(self):
|
||||
"""Clean up temporary files after the editor closed."""
|
||||
@ -56,7 +58,8 @@ class ExternalEditor(QObject):
|
||||
except PermissionError as e:
|
||||
# NOTE: Do not replace this with "raise CommandError" as it's
|
||||
# executed async.
|
||||
message.error("Failed to delete tempfile... ({})".format(e))
|
||||
message.error(self._win_id,
|
||||
"Failed to delete tempfile... ({})".format(e))
|
||||
|
||||
def on_proc_closed(self, exitcode, exitstatus):
|
||||
"""Write the editor text into the form field and clean up tempfile.
|
||||
@ -75,8 +78,9 @@ class ExternalEditor(QObject):
|
||||
if exitcode != 0:
|
||||
# NOTE: Do not replace this with "raise CommandError" as it's
|
||||
# executed async.
|
||||
message.error("Editor did quit abnormally (status {})!".format(
|
||||
exitcode))
|
||||
message.error(
|
||||
self._win_id, "Editor did quit abnormally (status "
|
||||
"{})!".format(exitcode))
|
||||
return
|
||||
encoding = config.get('general', 'editor-encoding')
|
||||
with open(self._filename, 'r', encoding=encoding) as f:
|
||||
@ -100,7 +104,8 @@ class ExternalEditor(QObject):
|
||||
}
|
||||
# NOTE: Do not replace this with "raise CommandError" as it's
|
||||
# executed async.
|
||||
message.error("Error while calling editor: {}".format(messages[error]))
|
||||
message.error(self._win_id,
|
||||
"Error while calling editor: {}".format(messages[error]))
|
||||
self._cleanup()
|
||||
|
||||
def edit(self, text):
|
||||
|
@ -24,33 +24,41 @@ from PyQt5.QtCore import pyqtSignal, QObject, QTimer
|
||||
from qutebrowser.utils import usertypes, log, objreg, utils
|
||||
|
||||
|
||||
def error(message, immediately=False):
|
||||
def _get_bridge(win_id):
|
||||
"""Get the correct MessageBridge instance for a window."""
|
||||
return objreg.get('message-bridge', scope='window', window=win_id)
|
||||
|
||||
|
||||
def error(win_id, message, immediately=False):
|
||||
"""Convienience function to display an error message in the statusbar.
|
||||
|
||||
Args:
|
||||
See MessageBridge.error.
|
||||
win_id: The ID of the window which is calling this function.
|
||||
others: See MessageBridge.error.
|
||||
"""
|
||||
objreg.get('message-bridge').error(message, immediately)
|
||||
_get_bridge(win_id).error(message, immediately)
|
||||
|
||||
|
||||
def info(message, immediately=True):
|
||||
def info(win_id, message, immediately=True):
|
||||
"""Convienience function to display an info message in the statusbar.
|
||||
|
||||
Args:
|
||||
See MessageBridge.info.
|
||||
win_id: The ID of the window which is calling this function.
|
||||
others: See MessageBridge.info.
|
||||
"""
|
||||
objreg.get('message-bridge').info(message, immediately)
|
||||
_get_bridge(win_id).info(message, immediately)
|
||||
|
||||
|
||||
def set_cmd_text(txt):
|
||||
def set_cmd_text(win_id, txt):
|
||||
"""Convienience function to Set the statusbar command line to a text."""
|
||||
objreg.get('message-bridge').set_cmd_text(txt)
|
||||
_get_bridge(win_id).set_cmd_text(txt)
|
||||
|
||||
|
||||
def ask(message, mode, default=None):
|
||||
def ask(win_id, message, mode, default=None):
|
||||
"""Ask a modular question in the statusbar (blocking).
|
||||
|
||||
Args:
|
||||
win_id: The ID of the window which is calling this function.
|
||||
message: The message to display to the user.
|
||||
mode: A PromptMode.
|
||||
default: The default value to display.
|
||||
@ -62,24 +70,30 @@ def ask(message, mode, default=None):
|
||||
q.text = message
|
||||
q.mode = mode
|
||||
q.default = default
|
||||
objreg.get('message-bridge').ask(q, blocking=True)
|
||||
_get_bridge(win_id).ask(q, blocking=True)
|
||||
q.deleteLater()
|
||||
return q.answer
|
||||
|
||||
|
||||
def alert(message):
|
||||
"""Display an alert which needs to be confirmed."""
|
||||
def alert(win_id, message):
|
||||
"""Display an alert which needs to be confirmed.
|
||||
|
||||
Args:
|
||||
win_id: The ID of the window which is calling this function.
|
||||
message: The message to show.
|
||||
"""
|
||||
q = usertypes.Question()
|
||||
q.text = message
|
||||
q.mode = usertypes.PromptMode.alert
|
||||
objreg.get('message-bridge').ask(q, blocking=True)
|
||||
_get_bridge(win_id).ask(q, blocking=True)
|
||||
q.deleteLater()
|
||||
|
||||
|
||||
def ask_async(message, mode, handler, default=None):
|
||||
def ask_async(win_id, message, mode, handler, default=None):
|
||||
"""Ask an async question in the statusbar.
|
||||
|
||||
Args:
|
||||
win_id: The ID of the window which is calling this function.
|
||||
message: The message to display to the user.
|
||||
mode: A PromptMode.
|
||||
handler: The function to get called with the answer as argument.
|
||||
@ -87,7 +101,7 @@ def ask_async(message, mode, handler, default=None):
|
||||
"""
|
||||
if not isinstance(mode, usertypes.PromptMode):
|
||||
raise TypeError("Mode {} is no PromptMode member!".format(mode))
|
||||
bridge = objreg.get('message-bridge')
|
||||
bridge = _get_bridge(win_id)
|
||||
q = usertypes.Question(bridge)
|
||||
q.text = message
|
||||
q.mode = mode
|
||||
@ -97,16 +111,17 @@ def ask_async(message, mode, handler, default=None):
|
||||
bridge.ask(q, blocking=False)
|
||||
|
||||
|
||||
def confirm_async(message, yes_action, no_action=None, default=None):
|
||||
def confirm_async(win_id, message, yes_action, no_action=None, default=None):
|
||||
"""Ask a yes/no question to the user and execute the given actions.
|
||||
|
||||
Args:
|
||||
win_id: The ID of the window which is calling this function.
|
||||
message: The message to display to the user.
|
||||
yes_action: Callable to be called when the user answered yes.
|
||||
no_action: Callable to be called when the user answered no.
|
||||
default: True/False to set a default value, or None.
|
||||
"""
|
||||
bridge = objreg.get('message-bridge')
|
||||
bridge = _get_bridge(win_id)
|
||||
q = usertypes.Question(bridge)
|
||||
q.text = message
|
||||
q.mode = usertypes.PromptMode.yesno
|
||||
|
@ -23,7 +23,9 @@
|
||||
import collections
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import QObject
|
||||
from PyQt5.QtCore import QObject, QTimer
|
||||
|
||||
from qutebrowser.utils import log
|
||||
|
||||
|
||||
class UnsetObject:
|
||||
@ -58,12 +60,28 @@ class ObjectRegistry(collections.UserDict):
|
||||
|
||||
Sets a slot to remove QObjects when they are destroyed.
|
||||
"""
|
||||
if name is None:
|
||||
raise TypeError("Registering '{}' with name 'None'!".format(obj))
|
||||
if obj is None:
|
||||
raise TypeError("Registering object None with name '{}'!".format(
|
||||
name))
|
||||
if isinstance(obj, QObject):
|
||||
obj.destroyed.connect(functools.partial(self.on_destroyed, name))
|
||||
super().__setitem__(name, obj)
|
||||
|
||||
def on_destroyed(self, name):
|
||||
"""Schedule removing of a destroyed QObject.
|
||||
|
||||
We don't remove the destroyed object immediately because it might still
|
||||
be destroying its children, which might still use the object
|
||||
registry.
|
||||
"""
|
||||
log.misc.debug("schedule destroyed: {}".format(name))
|
||||
QTimer.singleShot(0, functools.partial(self._on_destroyed, name))
|
||||
|
||||
def _on_destroyed(self, name):
|
||||
"""Remove a destroyed QObject."""
|
||||
log.misc.debug("destroyed: {}".format(name))
|
||||
try:
|
||||
del self[name]
|
||||
except KeyError:
|
||||
@ -79,33 +97,79 @@ class ObjectRegistry(collections.UserDict):
|
||||
|
||||
# The registry for global objects
|
||||
global_registry = ObjectRegistry()
|
||||
# The object registry of object registries.
|
||||
meta_registry = ObjectRegistry()
|
||||
meta_registry['global'] = global_registry
|
||||
# The window registry.
|
||||
window_registry = ObjectRegistry()
|
||||
|
||||
|
||||
def _get_registry(scope):
|
||||
def _get_tab_registry(win_id, tab_id):
|
||||
"""Get the registry of a tab."""
|
||||
if tab_id is None:
|
||||
tab_id = 'current'
|
||||
if tab_id == 'current' and win_id is None:
|
||||
app = get('app')
|
||||
window = app.activeWindow()
|
||||
if window is None or not hasattr(window, 'win_id'):
|
||||
raise RegistryUnavailableError('tab')
|
||||
win_id = window.win_id
|
||||
elif win_id is not None:
|
||||
window = window_registry[win_id]
|
||||
|
||||
if tab_id == 'current':
|
||||
tabbed_browser = get('tabbed-browser', scope='window', window=win_id)
|
||||
tab = tabbed_browser.currentWidget()
|
||||
if tab is None:
|
||||
raise RegistryUnavailableError('window')
|
||||
tab_id = tab.tab_id
|
||||
tab_registry = get('tab-registry', scope='window', window=win_id)
|
||||
try:
|
||||
return tab_registry[tab_id].registry
|
||||
except AttributeError:
|
||||
raise RegistryUnavailableError('tab')
|
||||
|
||||
|
||||
def _get_window_registry(window):
|
||||
"""Get the registry of a window."""
|
||||
if window is None:
|
||||
raise TypeError("window is None with scope window!")
|
||||
if window == 'current':
|
||||
app = get('app')
|
||||
win = app.activeWindow()
|
||||
if win is None or not hasattr(win, 'win_id'):
|
||||
raise RegistryUnavailableError('window')
|
||||
else:
|
||||
try:
|
||||
win = window_registry[window]
|
||||
except KeyError:
|
||||
raise RegistryUnavailableError('window')
|
||||
try:
|
||||
return win.registry
|
||||
except AttributeError:
|
||||
raise RegistryUnavailableError('window')
|
||||
|
||||
|
||||
def _get_registry(scope, window=None, tab=None):
|
||||
"""Get the correct registry for a given scope."""
|
||||
if window is not None and scope not in ('window', 'tab'):
|
||||
raise TypeError("window is set with scope {}".format(scope))
|
||||
if tab is not None and scope != 'tab':
|
||||
raise TypeError("tab is set with scope {}".format(scope))
|
||||
if scope == 'global':
|
||||
return global_registry
|
||||
elif scope == 'tab':
|
||||
widget = get('tabbed-browser').currentWidget()
|
||||
if widget is None:
|
||||
raise RegistryUnavailableError(scope)
|
||||
return widget.registry
|
||||
elif scope == 'meta':
|
||||
return meta_registry
|
||||
return _get_tab_registry(window, tab)
|
||||
elif scope == 'window':
|
||||
return _get_window_registry(window)
|
||||
else:
|
||||
raise ValueError("Invalid scope '{}'!".format(scope))
|
||||
|
||||
|
||||
def get(name, default=_UNSET, scope='global'):
|
||||
def get(name, default=_UNSET, scope='global', window=None, tab=None):
|
||||
"""Helper function to get an object.
|
||||
|
||||
Args:
|
||||
default: A default to return if the object does not exist.
|
||||
"""
|
||||
reg = _get_registry(scope)
|
||||
reg = _get_registry(scope, window, tab)
|
||||
try:
|
||||
return reg[name]
|
||||
except KeyError:
|
||||
@ -115,7 +179,8 @@ def get(name, default=_UNSET, scope='global'):
|
||||
raise
|
||||
|
||||
|
||||
def register(name, obj, update=False, scope=None, registry=None):
|
||||
def register(name, obj, update=False, scope=None, registry=None, window=None,
|
||||
tab=None):
|
||||
"""Helper function to register an object.
|
||||
|
||||
Args:
|
||||
@ -131,14 +196,36 @@ def register(name, obj, update=False, scope=None, registry=None):
|
||||
else:
|
||||
if scope is None:
|
||||
scope = 'global'
|
||||
reg = _get_registry(scope)
|
||||
reg = _get_registry(scope, window, tab)
|
||||
if not update and name in reg:
|
||||
raise KeyError("Object '{}' is already registered ({})!".format(
|
||||
name, repr(reg[name])))
|
||||
reg[name] = obj
|
||||
|
||||
|
||||
def delete(name, scope='global'):
|
||||
def delete(name, scope='global', window=None, tab=None):
|
||||
"""Helper function to unregister an object."""
|
||||
reg = _get_registry(scope)
|
||||
reg = _get_registry(scope, window, tab)
|
||||
del reg[name]
|
||||
|
||||
|
||||
def dump_objects():
|
||||
"""Get all registered objects in all registries as a string."""
|
||||
blocks = []
|
||||
lines = []
|
||||
blocks.append(('global', global_registry.dump_objects()))
|
||||
for win_id in window_registry:
|
||||
registry = _get_registry('window', window=win_id)
|
||||
blocks.append(('window-{}'.format(win_id), registry.dump_objects()))
|
||||
tab_registry = get('tab-registry', scope='window', window=win_id)
|
||||
for tab_id, tab in tab_registry.items():
|
||||
dump = tab.registry.dump_objects()
|
||||
data = [' ' + line for line in dump]
|
||||
blocks.append((' tab-{}'.format(tab_id), data))
|
||||
for name, data in blocks:
|
||||
lines.append("")
|
||||
lines.append("{} object registry - {} objects:".format(
|
||||
name, len(data)))
|
||||
for line in data:
|
||||
lines.append(" {}".format(line))
|
||||
return lines
|
||||
|
@ -228,7 +228,7 @@ PromptMode = enum('PromptMode', ['yesno', 'text', 'user_pwd', 'alert'])
|
||||
|
||||
|
||||
# Where to open a clicked link.
|
||||
ClickTarget = enum('ClickTarget', ['normal', 'tab', 'tab_bg'])
|
||||
ClickTarget = enum('ClickTarget', ['normal', 'tab', 'tab_bg', 'window'])
|
||||
|
||||
|
||||
# Key input modes
|
||||
|
@ -19,36 +19,26 @@
|
||||
|
||||
"""Misc. utility commands exposed to the user."""
|
||||
|
||||
import types
|
||||
import functools
|
||||
|
||||
import types
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
from qutebrowser.utils import usertypes, log, objreg
|
||||
from qutebrowser.commands import runners, cmdexc, cmdutils
|
||||
from qutebrowser.utils import log, objreg, usertypes
|
||||
from qutebrowser.commands import cmdutils, runners, cmdexc
|
||||
from qutebrowser.config import style
|
||||
|
||||
|
||||
_timers = []
|
||||
_commandrunner = None
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize the global _commandrunner."""
|
||||
global _commandrunner
|
||||
_commandrunner = runners.CommandRunner()
|
||||
|
||||
|
||||
@cmdutils.register()
|
||||
def later(ms: int, *command):
|
||||
@cmdutils.register(scope='window')
|
||||
def later(ms: int, *command, win_id):
|
||||
"""Execute a command after some time.
|
||||
|
||||
Args:
|
||||
ms: How many milliseconds to wait.
|
||||
*command: The command to run, with optional args.
|
||||
"""
|
||||
timer = usertypes.Timer(name='later')
|
||||
app = objreg.get('app')
|
||||
timer = usertypes.Timer(name='later', parent=app)
|
||||
timer.setSingleShot(True)
|
||||
if ms < 0:
|
||||
raise cmdexc.CommandError("I can't run something in the past!")
|
||||
@ -57,11 +47,11 @@ def later(ms: int, *command):
|
||||
except OverflowError:
|
||||
raise cmdexc.CommandError("Numeric argument is too large for internal "
|
||||
"int representation.")
|
||||
_timers.append(timer)
|
||||
cmdline = ' '.join(command)
|
||||
timer.timeout.connect(functools.partial(
|
||||
_commandrunner.run_safely, cmdline))
|
||||
timer.timeout.connect(lambda: _timers.remove(timer))
|
||||
commandrunner = runners.CommandRunner(win_id)
|
||||
timer.timeout.connect(
|
||||
functools.partial(commandrunner.run_safely, cmdline))
|
||||
timer.timeout.connect(timer.deleteLater)
|
||||
timer.start()
|
||||
|
||||
|
||||
|
@ -46,6 +46,7 @@ class CompletionView(QTreeView):
|
||||
|
||||
Attributes:
|
||||
enabled: Whether showing the CompletionView is enabled.
|
||||
_win_id: The ID of the window this CompletionView is associated with.
|
||||
_height: The height to use for the CompletionView.
|
||||
_height_perc: Either None or a percentage if height should be relative.
|
||||
_delegate: The item delegate used.
|
||||
@ -90,11 +91,13 @@ class CompletionView(QTreeView):
|
||||
|
||||
resize_completion = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
objreg.register('completion', self)
|
||||
completer_obj = completer.Completer()
|
||||
objreg.register('completer', completer_obj)
|
||||
self._win_id = win_id
|
||||
objreg.register('completion', self, scope='window', window=win_id)
|
||||
completer_obj = completer.Completer(win_id, self)
|
||||
objreg.register('completer', completer_obj, scope='window',
|
||||
window=win_id)
|
||||
self.enabled = config.get('completion', 'show')
|
||||
config.on_change(self.set_enabled, 'completion', 'show')
|
||||
# FIXME
|
||||
@ -213,13 +216,13 @@ class CompletionView(QTreeView):
|
||||
selmod.clearCurrentIndex()
|
||||
|
||||
@cmdutils.register(instance='completion', hide=True,
|
||||
modes=[usertypes.KeyMode.command])
|
||||
modes=[usertypes.KeyMode.command], scope='window')
|
||||
def completion_item_prev(self):
|
||||
"""Select the previous completion item."""
|
||||
self._next_prev_item(prev=True)
|
||||
|
||||
@cmdutils.register(instance='completion', hide=True,
|
||||
modes=[usertypes.KeyMode.command])
|
||||
modes=[usertypes.KeyMode.command], scope='window')
|
||||
def completion_item_next(self):
|
||||
"""Select the next completion item."""
|
||||
self._next_prev_item(prev=False)
|
||||
@ -227,7 +230,9 @@ class CompletionView(QTreeView):
|
||||
def selectionChanged(self, selected, deselected):
|
||||
"""Extend selectionChanged to call completers selection_changed."""
|
||||
super().selectionChanged(selected, deselected)
|
||||
objreg.get('completer').selection_changed(selected, deselected)
|
||||
completer_obj = objreg.get('completer', scope='window',
|
||||
window=self._win_id)
|
||||
completer_obj.selection_changed(selected, deselected)
|
||||
|
||||
def resizeEvent(self, e):
|
||||
"""Extend resizeEvent to adjust column size."""
|
||||
|
@ -113,7 +113,7 @@ class _CrashDialog(QDialog):
|
||||
"""Gather crash information to display.
|
||||
|
||||
Args:
|
||||
pages: A list of the open pages (URLs as strings)
|
||||
pages: A list of lists of the open pages (URLs as strings)
|
||||
cmdhist: A list with the command history (as strings)
|
||||
exc: An exception tuple (type, value, traceback)
|
||||
"""
|
||||
@ -172,7 +172,7 @@ class ExceptionCrashDialog(_CrashDialog):
|
||||
_btn_quit: The quit button
|
||||
_btn_restore: the restore button
|
||||
_btn_pastebin: the pastebin button
|
||||
_pages: A list of the open pages (URLs as strings)
|
||||
_pages: A list of lists of the open pages (URLs as strings)
|
||||
_cmdhist: A list with the command history (as strings)
|
||||
_exc: An exception tuple (type, value, traceback)
|
||||
_objects: A list of all QObjects as string.
|
||||
@ -220,7 +220,7 @@ class ExceptionCrashDialog(_CrashDialog):
|
||||
self._crash_info += [
|
||||
("Exception", ''.join(traceback.format_exception(*self._exc))),
|
||||
("Commandline args", ' '.join(sys.argv[1:])),
|
||||
("Open Pages", '\n'.join(self._pages)),
|
||||
("Open Pages", '\n\n'.join('\n'.join(e) for e in self._pages)),
|
||||
("Command history", '\n'.join(self._cmdhist)),
|
||||
("Objects", self._objects),
|
||||
]
|
||||
@ -316,7 +316,7 @@ class ReportDialog(_CrashDialog):
|
||||
super()._gather_crash_info()
|
||||
self._crash_info += [
|
||||
("Commandline args", ' '.join(sys.argv[1:])),
|
||||
("Open Pages", '\n'.join(self._pages)),
|
||||
("Open Pages", '\n\n'.join('\n'.join(e) for e in self._pages)),
|
||||
("Command history", '\n'.join(self._cmdhist)),
|
||||
("Objects", self._objects),
|
||||
]
|
||||
|
@ -21,15 +21,22 @@
|
||||
|
||||
import binascii
|
||||
import base64
|
||||
import itertools
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QRect, QPoint, QTimer, QEventLoop
|
||||
from PyQt5.QtCore import pyqtSlot, QRect, QPoint, QTimer, Qt
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout
|
||||
|
||||
from qutebrowser.commands import cmdutils
|
||||
from qutebrowser.commands import runners, cmdutils
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import message, log, usertypes, qtutils, objreg, utils
|
||||
from qutebrowser.widgets import tabbedbrowser, completion, downloads
|
||||
from qutebrowser.widgets.statusbar import bar
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.browser import hints
|
||||
|
||||
|
||||
win_id_gen = itertools.count(0)
|
||||
|
||||
|
||||
class MainWindow(QWidget):
|
||||
@ -44,12 +51,102 @@ class MainWindow(QWidget):
|
||||
_downloadview: The DownloadView widget.
|
||||
_tabbed_browser: The TabbedBrowser widget.
|
||||
_vbox: The main QVBoxLayout.
|
||||
_commandrunner: The main CommandRunner instance.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self._commandrunner = None
|
||||
self.win_id = win_id
|
||||
self.registry = objreg.ObjectRegistry()
|
||||
objreg.window_registry[win_id] = self
|
||||
objreg.register('main-window', self, scope='window', window=win_id)
|
||||
tab_registry = objreg.ObjectRegistry()
|
||||
objreg.register('tab-registry', tab_registry, scope='window',
|
||||
window=win_id)
|
||||
|
||||
message_bridge = message.MessageBridge(self)
|
||||
objreg.register('message-bridge', message_bridge, scope='window',
|
||||
window=win_id)
|
||||
|
||||
self.setWindowTitle('qutebrowser')
|
||||
if win_id == 0:
|
||||
self._load_geometry()
|
||||
else:
|
||||
self._set_default_geometry()
|
||||
log.init.debug("Initial mainwindow geometry: {}".format(
|
||||
self.geometry()))
|
||||
self._vbox = QVBoxLayout(self)
|
||||
self._vbox.setContentsMargins(0, 0, 0, 0)
|
||||
self._vbox.setSpacing(0)
|
||||
|
||||
self._downloadview = downloads.DownloadView()
|
||||
self._vbox.addWidget(self._downloadview)
|
||||
self._downloadview.show()
|
||||
|
||||
self._tabbed_browser = tabbedbrowser.TabbedBrowser(win_id)
|
||||
self._tabbed_browser.title_changed.connect(self.setWindowTitle)
|
||||
objreg.register('tabbed-browser', self._tabbed_browser, scope='window',
|
||||
window=win_id)
|
||||
self._vbox.addWidget(self._tabbed_browser)
|
||||
|
||||
self._completion = completion.CompletionView(win_id, self)
|
||||
|
||||
self.status = bar.StatusBar(win_id)
|
||||
self._vbox.addWidget(self.status)
|
||||
objreg.register('status-command', self.status.cmd, scope='window',
|
||||
window=win_id)
|
||||
|
||||
self._commandrunner = runners.CommandRunner(win_id)
|
||||
|
||||
log.init.debug("Initializing search...")
|
||||
search_runner = runners.SearchRunner(self)
|
||||
objreg.register('search-runner', search_runner, scope='window',
|
||||
window=win_id)
|
||||
|
||||
log.init.debug("Initializing modes...")
|
||||
mode_manager = modeman.init(self.win_id, self)
|
||||
|
||||
log.init.debug("Initializing eventfilter...")
|
||||
self.installEventFilter(mode_manager)
|
||||
|
||||
self._connect_signals()
|
||||
QTimer.singleShot(0, functools.partial(
|
||||
modeman.enter, win_id, usertypes.KeyMode.normal, 'init'))
|
||||
|
||||
# When we're here the statusbar might not even really exist yet, so
|
||||
# resizing will fail. Therefore, we use singleShot QTimers to make sure
|
||||
# we defer this until everything else is initialized.
|
||||
QTimer.singleShot(0, self._connect_resize_completion)
|
||||
config.on_change(self.resize_completion, 'completion', 'height')
|
||||
config.on_change(self.resize_completion, 'completion', 'shrink')
|
||||
|
||||
#self.retranslateUi(MainWindow)
|
||||
#self.tabWidget.setCurrentIndex(0)
|
||||
#QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self)
|
||||
|
||||
@classmethod
|
||||
def spawn(cls, show=True):
|
||||
"""Create a new main window.
|
||||
|
||||
Args:
|
||||
show: Show the window after creating.
|
||||
|
||||
Return:
|
||||
The new window id.
|
||||
"""
|
||||
win_id = next(win_id_gen)
|
||||
win = MainWindow(win_id)
|
||||
if show:
|
||||
win.show()
|
||||
return win_id
|
||||
|
||||
def _load_geometry(self):
|
||||
"""Load the geometry from the state file."""
|
||||
state_config = objreg.get('state-config')
|
||||
try:
|
||||
data = state_config['geometry']['mainwindow']
|
||||
@ -71,40 +168,6 @@ class MainWindow(QWidget):
|
||||
log.init.warning("Error while restoring geometry.")
|
||||
self._set_default_geometry()
|
||||
|
||||
log.init.debug("Initial mainwindow geometry: {}".format(
|
||||
self.geometry()))
|
||||
self._vbox = QVBoxLayout(self)
|
||||
self._vbox.setContentsMargins(0, 0, 0, 0)
|
||||
self._vbox.setSpacing(0)
|
||||
|
||||
self._downloadview = downloads.DownloadView()
|
||||
self._vbox.addWidget(self._downloadview)
|
||||
self._downloadview.show()
|
||||
|
||||
self._tabbed_browser = tabbedbrowser.TabbedBrowser()
|
||||
self._tabbed_browser.title_changed.connect(self.setWindowTitle)
|
||||
objreg.register('tabbed-browser', self._tabbed_browser)
|
||||
self._vbox.addWidget(self._tabbed_browser)
|
||||
|
||||
self._completion = completion.CompletionView(self)
|
||||
|
||||
self.status = bar.StatusBar()
|
||||
self._vbox.addWidget(self.status)
|
||||
|
||||
# When we're here the statusbar might not even really exist yet, so
|
||||
# resizing will fail. Therefore, we use singleShot QTimers to make sure
|
||||
# we defer this until everything else is initialized.
|
||||
QTimer.singleShot(0, self._connect_resize_completion)
|
||||
config.on_change(self.resize_completion, 'completion', 'height')
|
||||
config.on_change(self.resize_completion, 'completion', 'shrink')
|
||||
|
||||
#self.retranslateUi(MainWindow)
|
||||
#self.tabWidget.setCurrentIndex(0)
|
||||
#QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self)
|
||||
|
||||
def _connect_resize_completion(self):
|
||||
"""Connect the resize_completion signal and resize it once."""
|
||||
self._completion.resize_completion.connect(self.resize_completion)
|
||||
@ -114,6 +177,93 @@ class MainWindow(QWidget):
|
||||
"""Set some sensible default geometry."""
|
||||
self.setGeometry(QRect(50, 50, 800, 600))
|
||||
|
||||
def _get_object(self, name):
|
||||
"""Get an object for this window in the object registry."""
|
||||
return objreg.get(name, scope='window', window=self.win_id)
|
||||
|
||||
def _connect_signals(self):
|
||||
"""Connect all mainwindow signals."""
|
||||
# pylint: disable=too-many-locals,too-many-statements
|
||||
app = objreg.get('app')
|
||||
download_manager = objreg.get('download-manager')
|
||||
key_config = objreg.get('key-config')
|
||||
|
||||
status = self._get_object('statusbar')
|
||||
keyparsers = self._get_object('keyparsers')
|
||||
completion_obj = self._get_object('completion')
|
||||
tabs = self._get_object('tabbed-browser')
|
||||
cmd = self._get_object('status-command')
|
||||
completer = self._get_object('completer')
|
||||
search_runner = self._get_object('search-runner')
|
||||
message_bridge = self._get_object('message-bridge')
|
||||
mode_manager = self._get_object('mode-manager')
|
||||
prompter = self._get_object('prompter')
|
||||
|
||||
# misc
|
||||
self._tabbed_browser.quit.connect(app.shutdown)
|
||||
mode_manager.entered.connect(hints.on_mode_entered)
|
||||
|
||||
# status bar
|
||||
mode_manager.entered.connect(status.on_mode_entered)
|
||||
mode_manager.left.connect(status.on_mode_left)
|
||||
mode_manager.left.connect(cmd.on_mode_left)
|
||||
mode_manager.left.connect(prompter.on_mode_left)
|
||||
|
||||
# commands
|
||||
keyparsers[usertypes.KeyMode.normal].keystring_updated.connect(
|
||||
status.keystring.setText)
|
||||
cmd.got_cmd.connect(self._commandrunner.run_safely)
|
||||
cmd.got_search.connect(search_runner.search)
|
||||
cmd.got_search_rev.connect(search_runner.search_rev)
|
||||
cmd.returnPressed.connect(tabs.setFocus)
|
||||
search_runner.do_search.connect(tabs.search)
|
||||
tabs.got_cmd.connect(self._commandrunner.run_safely)
|
||||
|
||||
# config
|
||||
for obj in keyparsers.values():
|
||||
key_config.changed.connect(obj.on_keyconfig_changed)
|
||||
|
||||
# messages
|
||||
message_bridge.s_error.connect(status.disp_error)
|
||||
message_bridge.s_info.connect(status.disp_temp_text)
|
||||
message_bridge.s_set_text.connect(status.set_text)
|
||||
message_bridge.s_maybe_reset_text.connect(status.txt.maybe_reset_text)
|
||||
message_bridge.s_set_cmd_text.connect(cmd.set_cmd_text)
|
||||
message_bridge.s_question.connect(prompter.ask_question,
|
||||
Qt.DirectConnection)
|
||||
|
||||
# statusbar
|
||||
# FIXME some of these probably only should be triggered on mainframe
|
||||
# loadStarted.
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/112
|
||||
tabs.current_tab_changed.connect(status.prog.on_tab_changed)
|
||||
tabs.cur_progress.connect(status.prog.setValue)
|
||||
tabs.cur_load_finished.connect(status.prog.hide)
|
||||
tabs.cur_load_started.connect(status.prog.on_load_started)
|
||||
|
||||
tabs.current_tab_changed.connect(status.percentage.on_tab_changed)
|
||||
tabs.cur_scroll_perc_changed.connect(status.percentage.set_perc)
|
||||
|
||||
tabs.current_tab_changed.connect(status.txt.on_tab_changed)
|
||||
tabs.cur_statusbar_message.connect(status.txt.on_statusbar_message)
|
||||
tabs.cur_load_started.connect(status.txt.on_load_started)
|
||||
|
||||
tabs.current_tab_changed.connect(status.url.on_tab_changed)
|
||||
tabs.cur_url_text_changed.connect(status.url.set_url)
|
||||
tabs.cur_link_hovered.connect(status.url.set_hover_url)
|
||||
tabs.cur_load_status_changed.connect(status.url.on_load_status_changed)
|
||||
|
||||
# command input / completion
|
||||
mode_manager.left.connect(tabs.on_mode_left)
|
||||
cmd.clear_completion_selection.connect(
|
||||
completion_obj.on_clear_completion_selection)
|
||||
cmd.hide_completion.connect(completion_obj.hide)
|
||||
cmd.update_completion.connect(completer.on_update_completion)
|
||||
completer.change_completed_part.connect(cmd.on_change_completed_part)
|
||||
|
||||
# downloads
|
||||
tabs.start_download.connect(download_manager.fetch)
|
||||
|
||||
@pyqtSlot()
|
||||
def resize_completion(self):
|
||||
"""Adjust completion according to config."""
|
||||
@ -143,9 +293,9 @@ class MainWindow(QWidget):
|
||||
if rect.isValid():
|
||||
self._completion.setGeometry(rect)
|
||||
|
||||
@cmdutils.register(instance='main-window', name=['quit', 'q'])
|
||||
@cmdutils.register(instance='main-window', scope='window')
|
||||
def close(self):
|
||||
"""Quit qutebrowser.
|
||||
"""Close the current window.
|
||||
|
||||
//
|
||||
|
||||
@ -169,15 +319,19 @@ class MainWindow(QWidget):
|
||||
confirm_quit = config.get('ui', 'confirm-quit')
|
||||
count = self._tabbed_browser.count()
|
||||
if confirm_quit == 'never':
|
||||
e.accept()
|
||||
pass
|
||||
elif confirm_quit == 'multiple-tabs' and count <= 1:
|
||||
e.accept()
|
||||
pass
|
||||
else:
|
||||
text = "Close {} {}?".format(
|
||||
count, "tab" if count == 1 else "tabs")
|
||||
confirmed = message.ask(text, usertypes.PromptMode.yesno,
|
||||
default=True)
|
||||
if confirmed:
|
||||
e.accept()
|
||||
else:
|
||||
confirmed = message.ask(self.win_id, text,
|
||||
usertypes.PromptMode.yesno, default=True)
|
||||
if not confirmed:
|
||||
e.ignore()
|
||||
return
|
||||
e.accept()
|
||||
mode_manager = objreg.get('mode-manager', scope='window',
|
||||
window=self.win_id)
|
||||
log.destroy.debug("Removing eventfilter...")
|
||||
self.removeEventFilter(mode_manager)
|
||||
|
@ -46,7 +46,7 @@ class StatusBar(QWidget):
|
||||
percentage: The Percentage widget in the statusbar.
|
||||
url: The UrlText widget in the statusbar.
|
||||
prog: The Progress widget in the statusbar.
|
||||
_cmd: The Command widget in the statusbar.
|
||||
cmd: The Command widget in the statusbar.
|
||||
_hbox: The main QHBoxLayout.
|
||||
_stack: The QStackedLayout with cmd/txt widgets.
|
||||
_text_queue: A deque of (error, text) tuples to be displayed.
|
||||
@ -57,6 +57,7 @@ class StatusBar(QWidget):
|
||||
the command widget.
|
||||
_previous_widget: A PreviousWidget member - the widget which was
|
||||
displayed when an error interrupted it.
|
||||
_win_id: The window ID the statusbar is associated with.
|
||||
|
||||
Class attributes:
|
||||
_error: If there currently is an error, accessed through the error
|
||||
@ -113,14 +114,16 @@ class StatusBar(QWidget):
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
objreg.register('statusbar', self, scope='window', window=win_id)
|
||||
self.setObjectName(self.__class__.__name__)
|
||||
self.setAttribute(Qt.WA_StyledBackground)
|
||||
style.set_register_stylesheet(self)
|
||||
|
||||
self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
|
||||
|
||||
self._win_id = win_id
|
||||
self._option = None
|
||||
self._last_text_time = None
|
||||
|
||||
@ -132,9 +135,8 @@ class StatusBar(QWidget):
|
||||
self._hbox.addLayout(self._stack)
|
||||
self._stack.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self._cmd = command.Command()
|
||||
objreg.register('status-command', self._cmd)
|
||||
self._stack.addWidget(self._cmd)
|
||||
self.cmd = command.Command(win_id)
|
||||
self._stack.addWidget(self.cmd)
|
||||
|
||||
self.txt = textwidget.Text()
|
||||
self._stack.addWidget(self.txt)
|
||||
@ -145,14 +147,14 @@ class StatusBar(QWidget):
|
||||
self.set_pop_timer_interval()
|
||||
config.on_change(self.set_pop_timer_interval, 'ui', 'message-timeout')
|
||||
|
||||
self.prompt = prompt.Prompt()
|
||||
self.prompt = prompt.Prompt(win_id)
|
||||
self._stack.addWidget(self.prompt)
|
||||
self._previous_widget = PreviousWidget.none
|
||||
|
||||
self._cmd.show_cmd.connect(self._show_cmd_widget)
|
||||
self._cmd.hide_cmd.connect(self._hide_cmd_widget)
|
||||
self.cmd.show_cmd.connect(self._show_cmd_widget)
|
||||
self.cmd.hide_cmd.connect(self._hide_cmd_widget)
|
||||
self._hide_cmd_widget()
|
||||
prompter = objreg.get('prompter')
|
||||
prompter = objreg.get('prompter', scope='window', window=self._win_id)
|
||||
prompter.show_prompt.connect(self._show_prompt_widget)
|
||||
prompter.hide_prompt.connect(self._hide_prompt_widget)
|
||||
self._hide_prompt_widget()
|
||||
@ -263,7 +265,7 @@ class StatusBar(QWidget):
|
||||
if self._text_pop_timer.isActive():
|
||||
self._timer_was_active = True
|
||||
self._text_pop_timer.stop()
|
||||
self._stack.setCurrentWidget(self._cmd)
|
||||
self._stack.setCurrentWidget(self.cmd)
|
||||
|
||||
def _hide_cmd_widget(self):
|
||||
"""Show temporary text instead of command widget."""
|
||||
@ -382,7 +384,9 @@ class StatusBar(QWidget):
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_entered(self, mode):
|
||||
"""Mark certain modes in the commandline."""
|
||||
if mode in objreg.get('mode-manager').passthrough:
|
||||
mode_manager = objreg.get('mode-manager', scope='window',
|
||||
window=self._win_id)
|
||||
if mode in mode_manager.passthrough:
|
||||
text = "-- {} MODE --".format(mode.name.upper())
|
||||
self.txt.set_text(self.txt.Text.normal, text)
|
||||
if mode == usertypes.KeyMode.insert:
|
||||
@ -391,7 +395,9 @@ class StatusBar(QWidget):
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_left(self, mode):
|
||||
"""Clear marked mode."""
|
||||
if mode in objreg.get('mode-manager').passthrough:
|
||||
mode_manager = objreg.get('mode-manager', scope='window',
|
||||
window=self._win_id)
|
||||
if mode in mode_manager.passthrough:
|
||||
self.txt.set_text(self.txt.Text.normal, '')
|
||||
if mode == usertypes.KeyMode.insert:
|
||||
self._set_insert_active(False)
|
||||
|
@ -35,6 +35,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
|
||||
Attributes:
|
||||
_cursor_part: The part the cursor is currently over.
|
||||
_win_id: The window ID this widget is associated with.
|
||||
|
||||
Signals:
|
||||
got_cmd: Emitted when a command is triggered by the user.
|
||||
@ -64,9 +65,10 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
show_cmd = pyqtSignal()
|
||||
hide_cmd = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, win_id, parent=None):
|
||||
misc.CommandLineEdit.__init__(self, parent)
|
||||
misc.MinimalLineEditMixin.__init__(self)
|
||||
self._win_id = win_id
|
||||
self._cursor_part = 0
|
||||
self.history.history = objreg.get('command-history').data
|
||||
self._empty_item_idx = None
|
||||
@ -96,7 +98,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
# Text is only whitespace so we treat this as a single element with
|
||||
# the whitespace.
|
||||
return [text]
|
||||
runner = runners.CommandRunner()
|
||||
runner = runners.CommandRunner(self._win_id)
|
||||
parts = runner.parse(text, fallback=True, alias_no_args=False)
|
||||
if self._empty_item_idx is not None:
|
||||
log.completion.debug("Empty element queued at {}, "
|
||||
@ -156,7 +158,8 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
self.setFocus()
|
||||
self.show_cmd.emit()
|
||||
|
||||
@cmdutils.register(instance='status-command', name='set-cmd-text')
|
||||
@cmdutils.register(instance='status-command', name='set-cmd-text',
|
||||
scope='window')
|
||||
def set_cmd_text_command(self, text):
|
||||
"""Preset the statusbar to some text.
|
||||
|
||||
@ -168,7 +171,9 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
Args:
|
||||
text: The commandline to set.
|
||||
"""
|
||||
url = objreg.get('tabbed-browser').current_url().toString(
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._win_id)
|
||||
url = tabbed_browser.current_url().toString(
|
||||
QUrl.FullyEncoded | QUrl.RemovePassword)
|
||||
# FIXME we currently replace the URL in any place in the arguments,
|
||||
# rather than just replacing it if it is a dedicated argument. We could
|
||||
@ -213,7 +218,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
self.show_cmd.emit()
|
||||
|
||||
@cmdutils.register(instance='status-command', hide=True,
|
||||
modes=[usertypes.KeyMode.command])
|
||||
modes=[usertypes.KeyMode.command], scope='window')
|
||||
def command_history_prev(self):
|
||||
"""Go back in the commandline history."""
|
||||
try:
|
||||
@ -228,7 +233,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
self.set_cmd_text(item)
|
||||
|
||||
@cmdutils.register(instance='status-command', hide=True,
|
||||
modes=[usertypes.KeyMode.command])
|
||||
modes=[usertypes.KeyMode.command], scope='window')
|
||||
def command_history_next(self):
|
||||
"""Go forward in the commandline history."""
|
||||
if not self.history.is_browsing():
|
||||
@ -241,7 +246,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
self.set_cmd_text(item)
|
||||
|
||||
@cmdutils.register(instance='status-command', hide=True,
|
||||
modes=[usertypes.KeyMode.command])
|
||||
modes=[usertypes.KeyMode.command], scope='window')
|
||||
def command_accept(self):
|
||||
"""Execute the command currently in the commandline.
|
||||
|
||||
@ -257,7 +262,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
}
|
||||
text = self.text()
|
||||
self.history.append(text)
|
||||
modeman.leave(usertypes.KeyMode.command, 'cmd accept')
|
||||
modeman.leave(self._win_id, usertypes.KeyMode.command, 'cmd accept')
|
||||
if text[0] in signals:
|
||||
signals[text[0]].emit(text.lstrip(text[0]))
|
||||
|
||||
@ -294,7 +299,8 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
|
||||
def focusInEvent(self, e):
|
||||
"""Extend focusInEvent to enter command mode."""
|
||||
modeman.maybe_enter(usertypes.KeyMode.command, 'cmd focus')
|
||||
modeman.maybe_enter(self._win_id, usertypes.KeyMode.command,
|
||||
'cmd focus')
|
||||
super().focusInEvent(e)
|
||||
|
||||
def setText(self, text):
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
"""Prompt shown in the statusbar."""
|
||||
|
||||
import functools
|
||||
|
||||
from PyQt5.QtWidgets import QHBoxLayout, QWidget, QLineEdit
|
||||
|
||||
from qutebrowser.widgets import misc
|
||||
@ -45,9 +47,9 @@ class Prompt(QWidget):
|
||||
_hbox: The QHBoxLayout used to display the text and prompt.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
objreg.register('prompt', self)
|
||||
objreg.register('prompt', self, scope='window', window=win_id)
|
||||
self._hbox = QHBoxLayout(self)
|
||||
self._hbox.setContentsMargins(0, 0, 0, 0)
|
||||
self._hbox.setSpacing(5)
|
||||
@ -58,8 +60,12 @@ class Prompt(QWidget):
|
||||
self.lineedit = PromptLineEdit()
|
||||
self._hbox.addWidget(self.lineedit)
|
||||
|
||||
prompter_obj = prompter.Prompter(self)
|
||||
objreg.register('prompter', prompter_obj)
|
||||
prompter_obj = prompter.Prompter(win_id)
|
||||
objreg.register('prompter', prompter_obj, scope='window',
|
||||
window=win_id)
|
||||
self.destroyed.connect(
|
||||
functools.partial(objreg.delete, 'prompter', scope='window',
|
||||
window=win_id))
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self)
|
||||
|
@ -60,6 +60,7 @@ class Prompter(QObject):
|
||||
_question: A Question object with the question to be asked to the user.
|
||||
_loops: A list of local EventLoops to spin in when blocking.
|
||||
_queue: A deque of waiting questions.
|
||||
_win_id: The window ID this object is associated with.
|
||||
|
||||
Signals:
|
||||
show_prompt: Emitted when the prompt widget should be shown.
|
||||
@ -69,12 +70,13 @@ class Prompter(QObject):
|
||||
show_prompt = pyqtSignal()
|
||||
hide_prompt = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._question = None
|
||||
self._loops = []
|
||||
self._queue = collections.deque()
|
||||
self._busy = False
|
||||
self._win_id = win_id
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, loops=len(self._loops),
|
||||
@ -95,7 +97,7 @@ class Prompter(QObject):
|
||||
"""Get a PromptContext based on the current state."""
|
||||
if not self._busy:
|
||||
return None
|
||||
prompt = objreg.get('prompt')
|
||||
prompt = objreg.get('prompt', scope='window', window=self._win_id)
|
||||
ctx = PromptContext(question=self._question,
|
||||
text=prompt.txt.text(),
|
||||
input_text=prompt.lineedit.text(),
|
||||
@ -112,7 +114,7 @@ class Prompter(QObject):
|
||||
Return: True if a context was restored, False otherwise.
|
||||
"""
|
||||
log.statusbar.debug("Restoring context {}".format(ctx))
|
||||
prompt = objreg.get('prompt')
|
||||
prompt = objreg.get('prompt', scope='window', window=self._win_id)
|
||||
if ctx is None:
|
||||
self.hide_prompt.emit()
|
||||
self._busy = False
|
||||
@ -134,7 +136,7 @@ class Prompter(QObject):
|
||||
Raise:
|
||||
ValueError if the set PromptMode is invalid.
|
||||
"""
|
||||
prompt = objreg.get('prompt')
|
||||
prompt = objreg.get('prompt', scope='window', window=self._win_id)
|
||||
if self._question.mode == usertypes.PromptMode.yesno:
|
||||
if self._question.default is None:
|
||||
suffix = ""
|
||||
@ -188,7 +190,7 @@ class Prompter(QObject):
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_left(self, mode):
|
||||
"""Clear and reset input when the mode was left."""
|
||||
prompt = objreg.get('prompt')
|
||||
prompt = objreg.get('prompt', scope='window', window=self._win_id)
|
||||
if mode in (usertypes.KeyMode.prompt, usertypes.KeyMode.yesno):
|
||||
prompt.txt.setText('')
|
||||
prompt.lineedit.clear()
|
||||
@ -198,7 +200,7 @@ class Prompter(QObject):
|
||||
if self._question.answer is None and not self._question.is_aborted:
|
||||
self._question.cancel()
|
||||
|
||||
@cmdutils.register(instance='prompter', hide=True,
|
||||
@cmdutils.register(instance='prompter', hide=True, scope='window',
|
||||
modes=[usertypes.KeyMode.prompt,
|
||||
usertypes.KeyMode.yesno])
|
||||
def prompt_accept(self):
|
||||
@ -209,7 +211,7 @@ class Prompter(QObject):
|
||||
This executes the next action depending on the question mode, e.g. asks
|
||||
for the password or leaves the mode.
|
||||
"""
|
||||
prompt = objreg.get('prompt')
|
||||
prompt = objreg.get('prompt', scope='window', window=self._win_id)
|
||||
if (self._question.mode == usertypes.PromptMode.user_pwd and
|
||||
self._question.user is None):
|
||||
# User just entered an username
|
||||
@ -221,27 +223,31 @@ class Prompter(QObject):
|
||||
# User just entered a password
|
||||
password = prompt.lineedit.text()
|
||||
self._question.answer = (self._question.user, password)
|
||||
modeman.leave(usertypes.KeyMode.prompt, 'prompt accept')
|
||||
modeman.leave(self._win_id, usertypes.KeyMode.prompt,
|
||||
'prompt accept')
|
||||
self._question.done()
|
||||
elif self._question.mode == usertypes.PromptMode.text:
|
||||
# User just entered text.
|
||||
self._question.answer = prompt.lineedit.text()
|
||||
modeman.leave(usertypes.KeyMode.prompt, 'prompt accept')
|
||||
modeman.leave(self._win_id, usertypes.KeyMode.prompt,
|
||||
'prompt accept')
|
||||
self._question.done()
|
||||
elif self._question.mode == usertypes.PromptMode.yesno:
|
||||
# User wants to accept the default of a yes/no question.
|
||||
self._question.answer = self._question.default
|
||||
modeman.leave(usertypes.KeyMode.yesno, 'yesno accept')
|
||||
modeman.leave(self._win_id, usertypes.KeyMode.yesno,
|
||||
'yesno accept')
|
||||
self._question.done()
|
||||
elif self._question.mode == usertypes.PromptMode.alert:
|
||||
# User acknowledged an alert
|
||||
self._question.answer = None
|
||||
modeman.leave(usertypes.KeyMode.prompt, 'alert accept')
|
||||
modeman.leave(self._win_id, usertypes.KeyMode.prompt,
|
||||
'alert accept')
|
||||
self._question.done()
|
||||
else:
|
||||
raise ValueError("Invalid question mode!")
|
||||
|
||||
@cmdutils.register(instance='prompter', hide=True,
|
||||
@cmdutils.register(instance='prompter', hide=True, scope='window',
|
||||
modes=[usertypes.KeyMode.yesno])
|
||||
def prompt_yes(self):
|
||||
"""Answer yes to a yes/no prompt."""
|
||||
@ -249,10 +255,10 @@ class Prompter(QObject):
|
||||
# We just ignore this if we don't have a yes/no question.
|
||||
return
|
||||
self._question.answer = True
|
||||
modeman.leave(usertypes.KeyMode.yesno, 'yesno accept')
|
||||
modeman.leave(self._win_id, usertypes.KeyMode.yesno, 'yesno accept')
|
||||
self._question.done()
|
||||
|
||||
@cmdutils.register(instance='prompter', hide=True,
|
||||
@cmdutils.register(instance='prompter', hide=True, scope='window',
|
||||
modes=[usertypes.KeyMode.yesno])
|
||||
def prompt_no(self):
|
||||
"""Answer no to a yes/no prompt."""
|
||||
@ -260,7 +266,7 @@ class Prompter(QObject):
|
||||
# We just ignore this if we don't have a yes/no question.
|
||||
return
|
||||
self._question.answer = False
|
||||
modeman.leave(usertypes.KeyMode.yesno, 'prompt accept')
|
||||
modeman.leave(self._win_id, usertypes.KeyMode.yesno, 'prompt accept')
|
||||
self._question.done()
|
||||
|
||||
@pyqtSlot(usertypes.Question, bool)
|
||||
@ -293,10 +299,12 @@ class Prompter(QObject):
|
||||
|
||||
self._question = question
|
||||
mode = self._display_question()
|
||||
question.aborted.connect(lambda: modeman.maybe_leave(mode, 'aborted'))
|
||||
mode_manager = objreg.get('mode-manager')
|
||||
question.aborted.connect(
|
||||
lambda: modeman.maybe_leave(self._win_id, mode, 'aborted'))
|
||||
mode_manager = objreg.get('mode-manager', scope='window',
|
||||
window=self._win_id)
|
||||
try:
|
||||
modeman.enter(mode, 'question asked', override=True)
|
||||
modeman.enter(self._win_id, mode, 'question asked', override=True)
|
||||
except modeman.ModeLockedError:
|
||||
if mode_manager.mode() != usertypes.KeyMode.prompt:
|
||||
question.abort()
|
||||
|
@ -51,7 +51,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
emitted if the signal occured in the current tab.
|
||||
|
||||
Attributes:
|
||||
_tabs: A list of open tabs.
|
||||
_win_id: The window ID this tabbedbrowser is associated with.
|
||||
_filter: A SignalFilter instance.
|
||||
_now_focused: The tab which is focused now.
|
||||
_tab_insert_idx_left: Where to insert a new tab with
|
||||
@ -97,19 +97,23 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
current_tab_changed = pyqtSignal(webview.WebView)
|
||||
title_changed = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(win_id, parent)
|
||||
self._win_id = win_id
|
||||
self._tab_insert_idx_left = 0
|
||||
self._tab_insert_idx_right = -1
|
||||
self.tabCloseRequested.connect(self.on_tab_close_requested)
|
||||
self.currentChanged.connect(self.on_current_changed)
|
||||
self.cur_load_started.connect(self.on_cur_load_started)
|
||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
self._tabs = []
|
||||
self._undo_stack = []
|
||||
self._filter = signalfilter.SignalFilter(self)
|
||||
dispatcher = commands.CommandDispatcher()
|
||||
objreg.register('command-dispatcher', dispatcher)
|
||||
self._filter = signalfilter.SignalFilter(win_id, self)
|
||||
dispatcher = commands.CommandDispatcher(win_id)
|
||||
objreg.register('command-dispatcher', dispatcher, scope='window',
|
||||
window=win_id)
|
||||
self.destroyed.connect(
|
||||
functools.partial(objreg.delete, 'command-dispatcher',
|
||||
scope='window', window=win_id))
|
||||
self._now_focused = None
|
||||
# FIXME adjust this to font size
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/119
|
||||
@ -245,8 +249,10 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
tab))
|
||||
if tab is self._now_focused:
|
||||
self._now_focused = None
|
||||
if tab is objreg.get('last-focused-tab', None):
|
||||
objreg.delete('last-focused-tab')
|
||||
if tab is objreg.get('last-focused-tab', None, scope='window',
|
||||
window=self._win_id):
|
||||
objreg.delete('last-focused-tab', scope='window',
|
||||
window=self._win_id)
|
||||
if tab.cur_url.isValid():
|
||||
history_data = qtutils.serialize(tab.history())
|
||||
entry = UndoEntry(tab.cur_url, history_data)
|
||||
@ -255,7 +261,6 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
urlutils.invalid_url_error(url, "saving tab")
|
||||
return
|
||||
tab.shutdown()
|
||||
self._tabs.remove(tab)
|
||||
self.removeTab(idx)
|
||||
tab.deleteLater()
|
||||
|
||||
@ -318,9 +323,8 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
if url is not None:
|
||||
qtutils.ensure_valid(url)
|
||||
log.webview.debug("Creating new tab with URL {}".format(url))
|
||||
tab = webview.WebView(self)
|
||||
tab = webview.WebView(self._win_id, self)
|
||||
self._connect_tab_signals(tab)
|
||||
self._tabs.append(tab)
|
||||
if explicit:
|
||||
pos = config.get('tabs', 'new-tab-position-explicit')
|
||||
else:
|
||||
@ -369,19 +373,19 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
old_scroll_pos = widget.scroll_pos
|
||||
found = widget.findText(text, flags)
|
||||
if not found and not flags & QWebPage.HighlightAllOccurrences and text:
|
||||
message.error("Text '{}' not found on page!".format(text),
|
||||
immediately=True)
|
||||
message.error(self._win_id, "Text '{}' not found on "
|
||||
"page!".format(text), immediately=True)
|
||||
else:
|
||||
backward = int(flags) & QWebPage.FindBackward
|
||||
|
||||
def check_scroll_pos():
|
||||
"""Check if the scroll position got smaller and show info."""
|
||||
if not backward and widget.scroll_pos < old_scroll_pos:
|
||||
message.info("Search hit BOTTOM, continuing at TOP",
|
||||
immediately=True)
|
||||
message.info(self._win_id, "Search hit BOTTOM, continuing "
|
||||
"at TOP", immediately=True)
|
||||
elif backward and widget.scroll_pos > old_scroll_pos:
|
||||
message.info("Search hit TOP, continuing at BOTTOM",
|
||||
immediately=True)
|
||||
message.info(self._win_id, "Search hit TOP, continuing at "
|
||||
"BOTTOM", immediately=True)
|
||||
# We first want QWebPage to refresh.
|
||||
QTimer.singleShot(0, check_scroll_pos)
|
||||
|
||||
@ -414,8 +418,10 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
@pyqtSlot()
|
||||
def on_cur_load_started(self):
|
||||
"""Leave insert/hint mode when loading started."""
|
||||
modeman.maybe_leave(usertypes.KeyMode.insert, 'load started')
|
||||
modeman.maybe_leave(usertypes.KeyMode.hint, 'load started')
|
||||
modeman.maybe_leave(self._win_id, usertypes.KeyMode.insert,
|
||||
'load started')
|
||||
modeman.maybe_leave(self._win_id, usertypes.KeyMode.hint,
|
||||
'load started')
|
||||
|
||||
@pyqtSlot(webview.WebView, str)
|
||||
def on_title_changed(self, tab, text):
|
||||
@ -501,9 +507,11 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
return
|
||||
tab = self.widget(idx)
|
||||
tab.setFocus()
|
||||
modeman.maybe_leave(usertypes.KeyMode.hint, 'tab changed')
|
||||
modeman.maybe_leave(self._win_id, usertypes.KeyMode.hint,
|
||||
'tab changed')
|
||||
if self._now_focused is not None:
|
||||
objreg.register('last-focused-tab', self._now_focused, update=True)
|
||||
objreg.register('last-focused-tab', self._now_focused, update=True,
|
||||
scope='window', window=self._win_id)
|
||||
self._now_focused = tab
|
||||
self.current_tab_changed.emit(tab)
|
||||
self._change_app_title(self.tabText(idx))
|
||||
|
@ -42,9 +42,9 @@ class TabWidget(QTabWidget):
|
||||
|
||||
"""The tabwidget used for TabbedBrowser."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
bar = TabBar()
|
||||
bar = TabBar(win_id)
|
||||
self.setTabBar(bar)
|
||||
bar.tabCloseRequested.connect(self.tabCloseRequested)
|
||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
@ -92,10 +92,12 @@ class TabBar(QTabBar):
|
||||
|
||||
Attributes:
|
||||
vertical: When the tab bar is currently vertical.
|
||||
win_id: The window ID this TabBar belongs to.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self.setStyle(TabBarStyle(self.style()))
|
||||
self.set_font()
|
||||
config.on_change(self.set_font, 'fonts', 'tabbar')
|
||||
@ -202,8 +204,10 @@ class TabBar(QTabBar):
|
||||
if self.vertical:
|
||||
confwidth = str(config.get('tabs', 'width'))
|
||||
if confwidth.endswith('%'):
|
||||
main_window = objreg.get('main-window', scope='window',
|
||||
window=self._win_id)
|
||||
perc = int(confwidth.rstrip('%'))
|
||||
width = objreg.get('main-window').width() * perc / 100
|
||||
width = main_window.width() * perc / 100
|
||||
else:
|
||||
width = int(confwidth)
|
||||
size = QSize(max(minimum_size.width(), width), height)
|
||||
|
@ -57,6 +57,7 @@ class WebView(QWebView):
|
||||
viewing_source: Whether the webview is currently displaying source
|
||||
code.
|
||||
registry: The ObjectRegistry associated with this tab.
|
||||
tab_id: The tab ID of the view.
|
||||
_cur_url: The current URL (accessed via cur_url property).
|
||||
_has_ssl_errors: Whether SSL errors occured during loading.
|
||||
_zoom: A NeighborList with the zoom levels.
|
||||
@ -64,6 +65,7 @@ class WebView(QWebView):
|
||||
_force_open_target: Override for open_target.
|
||||
_check_insertmode: If True, in mouseReleaseEvent we should check if we
|
||||
need to enter/leave insert mode.
|
||||
_win_id: The window ID of the view.
|
||||
|
||||
Signals:
|
||||
scroll_pos_changed: Scroll percentage of current tab changed.
|
||||
@ -79,8 +81,9 @@ class WebView(QWebView):
|
||||
load_status_changed = pyqtSignal(str)
|
||||
url_text_changed = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self.load_status = LoadStatus.none
|
||||
self._check_insertmode = False
|
||||
self.inspector = None
|
||||
@ -99,15 +102,16 @@ class WebView(QWebView):
|
||||
self.progress = 0
|
||||
self.registry = objreg.ObjectRegistry()
|
||||
self.tab_id = next(tab_id_gen)
|
||||
tab_registry = objreg.get('tab-registry', scope='window',
|
||||
window=win_id)
|
||||
tab_registry[self.tab_id] = self
|
||||
objreg.register('webview', self, registry=self.registry)
|
||||
page = webpage.BrowserPage(self)
|
||||
page = webpage.BrowserPage(win_id, self)
|
||||
self.setPage(page)
|
||||
hintmanager = hints.HintManager(self)
|
||||
hintmanager = hints.HintManager(win_id, self.tab_id, self)
|
||||
hintmanager.mouse_event.connect(self.on_mouse_event)
|
||||
hintmanager.set_open_target.connect(self.set_force_open_target)
|
||||
objreg.register('hintmanager', hintmanager, registry=self.registry)
|
||||
objreg.register('tab-{}'.format(self.tab_id),
|
||||
self.registry, scope='meta')
|
||||
page.linkHovered.connect(self.linkHovered)
|
||||
page.mainFrame().loadStarted.connect(self.on_load_started)
|
||||
self.urlChanged.connect(self.on_url_changed)
|
||||
@ -150,16 +154,18 @@ class WebView(QWebView):
|
||||
"""
|
||||
if e.button() == Qt.XButton1:
|
||||
# Back button on mice which have it.
|
||||
try:
|
||||
self.go_back()
|
||||
except cmdexc.CommandError as ex:
|
||||
message.error(ex, immediately=True)
|
||||
if self.page().history().canGoBack():
|
||||
self.back()
|
||||
else:
|
||||
message.error(self._win_id, "At beginning of history.",
|
||||
immediately=True)
|
||||
elif e.button() == Qt.XButton2:
|
||||
# Forward button on mice which have it.
|
||||
try:
|
||||
self.go_forward()
|
||||
except cmdexc.CommandError as ex:
|
||||
message.error(ex, immediately=True)
|
||||
if self.page().history().canGoForward():
|
||||
self.forward()
|
||||
else:
|
||||
message.error(self._win_id, "At end of history.",
|
||||
immediately=True)
|
||||
|
||||
def _mousepress_insertmode(self, e):
|
||||
"""Switch to insert mode when an editable element was clicked.
|
||||
@ -200,11 +206,13 @@ class WebView(QWebView):
|
||||
if ((hitresult.isContentEditable() and elem.is_writable()) or
|
||||
elem.is_editable()):
|
||||
log.mouse.debug("Clicked editable element!")
|
||||
modeman.maybe_enter(usertypes.KeyMode.insert, 'click')
|
||||
modeman.maybe_enter(self._win_id, usertypes.KeyMode.insert,
|
||||
'click')
|
||||
else:
|
||||
log.mouse.debug("Clicked non-editable element!")
|
||||
if config.get('input', 'auto-leave-insert-mode'):
|
||||
modeman.maybe_leave(usertypes.KeyMode.insert, 'click')
|
||||
modeman.maybe_leave(self._win_id, usertypes.KeyMode.insert,
|
||||
'click')
|
||||
|
||||
def mouserelease_insertmode(self):
|
||||
"""If we have an insertmode check scheduled, handle it."""
|
||||
@ -218,11 +226,13 @@ class WebView(QWebView):
|
||||
return
|
||||
if elem.is_editable():
|
||||
log.mouse.debug("Clicked editable element (delayed)!")
|
||||
modeman.maybe_enter(usertypes.KeyMode.insert, 'click-delayed')
|
||||
modeman.maybe_enter(self._win_id, usertypes.KeyMode.insert,
|
||||
'click-delayed')
|
||||
else:
|
||||
log.mouse.debug("Clicked non-editable element (delayed)!")
|
||||
if config.get('input', 'auto-leave-insert-mode'):
|
||||
modeman.maybe_leave(usertypes.KeyMode.insert, 'click-delayed')
|
||||
modeman.maybe_leave(self._win_id, usertypes.KeyMode.insert,
|
||||
'click-delayed')
|
||||
|
||||
def _mousepress_opentarget(self, e):
|
||||
"""Set the open target when something was clicked.
|
||||
@ -293,7 +303,7 @@ class WebView(QWebView):
|
||||
if perc < 0:
|
||||
raise cmdexc.CommandError("Can't zoom {}%!".format(perc))
|
||||
self.setZoomFactor(float(perc) / 100)
|
||||
message.info("Zoom level: {}%".format(perc))
|
||||
message.info(self._win_id, "Zoom level: {}%".format(perc))
|
||||
|
||||
def zoom(self, offset):
|
||||
"""Increase/Decrease the zoom level.
|
||||
@ -304,30 +314,6 @@ class WebView(QWebView):
|
||||
level = self._zoom.getitem(offset)
|
||||
self.zoom_perc(level, fuzzyval=False)
|
||||
|
||||
@pyqtSlot(str, int)
|
||||
def search(self, text, flags):
|
||||
"""Search for text in the current page.
|
||||
|
||||
Args:
|
||||
text: The text to search for.
|
||||
flags: The QWebPage::FindFlags.
|
||||
"""
|
||||
self._tabs.currentWidget().findText(text, flags)
|
||||
|
||||
def go_back(self):
|
||||
"""Go back a page in the history."""
|
||||
if self.page().history().canGoBack():
|
||||
self.back()
|
||||
else:
|
||||
raise cmdexc.CommandError("At beginning of history.")
|
||||
|
||||
def go_forward(self):
|
||||
"""Go forward a page in the history."""
|
||||
if self.page().history().canGoForward():
|
||||
self.forward()
|
||||
else:
|
||||
raise cmdexc.CommandError("At end of history.")
|
||||
|
||||
@pyqtSlot('QUrl')
|
||||
def on_url_changed(self, url):
|
||||
"""Update cur_url when URL has changed.
|
||||
@ -363,7 +349,9 @@ class WebView(QWebView):
|
||||
self._set_load_status(LoadStatus.error)
|
||||
if not config.get('input', 'auto-insert-mode'):
|
||||
return
|
||||
cur_mode = objreg.get('mode-manager').mode()
|
||||
mode_manager = objreg.get('mode-manager', scope='window',
|
||||
window=self._win_id)
|
||||
cur_mode = mode_manager.mode()
|
||||
if cur_mode == usertypes.KeyMode.insert or not ok:
|
||||
return
|
||||
frame = self.page().currentFrame()
|
||||
@ -374,7 +362,8 @@ class WebView(QWebView):
|
||||
return
|
||||
log.modes.debug("focus element: {}".format(repr(elem)))
|
||||
if elem.is_editable():
|
||||
modeman.maybe_enter(usertypes.KeyMode.insert, 'load finished')
|
||||
modeman.maybe_enter(self._win_id, usertypes.KeyMode.insert,
|
||||
'load finished')
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_force_open_target(self, target):
|
||||
@ -409,7 +398,9 @@ class WebView(QWebView):
|
||||
if wintype == QWebPage.WebModalDialog:
|
||||
log.webview.warning("WebModalDialog requested, but we don't "
|
||||
"support that!")
|
||||
return objreg.get('tabbed-browser').tabopen()
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._win_id)
|
||||
return tabbed_browser.tabopen()
|
||||
|
||||
def paintEvent(self, e):
|
||||
"""Extend paintEvent to emit a signal if the scroll position changed.
|
||||
|
Loading…
Reference in New Issue
Block a user