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