qutebrowser/qutebrowser/app.py

885 lines
32 KiB
Python
Raw Normal View History

2014-06-19 09:04:37 +02:00
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
2016-01-04 07:12:39 +01:00
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
2014-02-06 14:01:23 +01:00
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
2014-02-17 12:23:52 +01:00
"""Initialization of qutebrowser and application-wide things."""
2014-05-09 11:06:05 +02:00
import os
2014-05-13 18:01:10 +02:00
import sys
2014-06-24 06:43:52 +02:00
import subprocess
2014-05-09 11:06:05 +02:00
import configparser
2014-08-26 19:10:14 +02:00
import functools
import json
2015-05-16 23:26:15 +02:00
import shutil
import tempfile
import atexit
2015-08-07 20:36:38 +02:00
import datetime
2015-10-05 06:53:56 +02:00
import tokenize
2014-05-09 11:06:05 +02:00
2016-05-24 21:35:48 +02:00
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon, QWindow
2014-10-06 22:14:57 +02:00
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl,
QObject, QEvent, pyqtSignal)
2015-03-31 19:02:42 +02:00
try:
import hunter
except ImportError:
hunter = None
2014-02-17 08:56:33 +01:00
import qutebrowser
2015-12-01 20:55:38 +01:00
import qutebrowser.resources
from qutebrowser.completion.models import instances as completionmodels
from qutebrowser.commands import cmdutils, runners, cmdexc
from qutebrowser.config import style, config, websettings, configexc
2016-11-08 20:36:49 +01:00
from qutebrowser.browser import (urlmarks, adblock, history, browsertab,
downloads)
from qutebrowser.browser.network import proxy
2016-11-01 17:50:29 +01:00
from qutebrowser.browser.webkit import cookies, cache
from qutebrowser.browser.webkit.network import networkmanager
from qutebrowser.keyinput import macros
from qutebrowser.mainwindow import mainwindow, prompt
2016-10-05 09:07:35 +02:00
from qutebrowser.misc import (readline, ipc, savemanager, sessions,
crashsignal, earlyinit)
from qutebrowser.misc import utilcmds # pylint: disable=unused-import
from qutebrowser.utils import (log, version, message, utils, qtutils, urlutils,
objreg, usertypes, standarddir, error, debug)
2014-09-29 08:52:00 +02:00
# We import utilcmds to run the cmdutils.register decorators.
2013-12-14 22:15:16 +01:00
2014-01-28 23:04:02 +01:00
2015-04-30 07:37:25 +02:00
qApp = None
2014-02-07 20:21:50 +01:00
2015-04-30 07:37:25 +02:00
def run(args):
2015-10-04 15:41:42 +02:00
"""Initialize everything and run the application."""
2015-05-16 23:26:15 +02:00
if args.temp_basedir:
2015-05-29 20:48:43 +02:00
args.basedir = tempfile.mkdtemp(prefix='qutebrowser-basedir-')
2015-05-16 23:26:15 +02:00
quitter = Quitter(args)
objreg.register('quitter', quitter)
2015-04-30 07:37:25 +02:00
global qApp
qApp = Application(args)
2015-09-08 22:22:00 +02:00
qApp.setOrganizationName("qutebrowser")
qApp.setApplicationName("qutebrowser")
qApp.setApplicationVersion(qutebrowser.__version__)
qApp.lastWindowClosed.connect(quitter.on_last_window_closed)
log.init.debug("Initializing directories...")
standarddir.init(args)
if args.version:
print(version.version())
sys.exit(usertypes.Exit.ok)
crash_handler = crashsignal.CrashHandler(
app=qApp, quitter=quitter, args=args, parent=qApp)
crash_handler.activate()
objreg.register('crash-handler', crash_handler)
signal_handler = crashsignal.SignalHandler(app=qApp, quitter=quitter,
parent=qApp)
signal_handler.activate()
objreg.register('signal-handler', signal_handler)
2015-04-30 07:37:25 +02:00
try:
server = ipc.send_or_listen(args)
except ipc.Error:
# ipc.send_or_listen already displays the error message for us.
2015-04-30 07:37:25 +02:00
# We didn't really initialize much so far, so we just quit hard.
sys.exit(usertypes.Exit.err_ipc)
if server is None:
sys.exit(usertypes.Exit.ok)
else:
server.got_args.connect(lambda args, target_arg, cwd:
process_pos_args(args, cwd=cwd, via_ipc=True,
target_arg=target_arg))
init(args, crash_handler)
2015-04-30 07:37:25 +02:00
ret = qt_mainloop()
return ret
2015-04-30 07:37:25 +02:00
def qt_mainloop():
"""Simple wrapper to get a nicer stack trace for segfaults.
2015-04-30 07:37:25 +02:00
WARNING: misc/crashdialog.py checks the stacktrace for this function
name, so if this is changed, it should be changed there as well!
"""
return qApp.exec_()
def init(args, crash_handler):
"""Initialize everything.
Args:
args: The argparse namespace.
crash_handler: The CrashHandler instance.
"""
2015-04-30 07:37:25 +02:00
log.init.debug("Starting init...")
qApp.setQuitOnLastWindowClosed(False)
_init_icon()
try:
_init_modules(args, crash_handler)
2015-04-30 07:37:25 +02:00
except (OSError, UnicodeDecodeError) as e:
error.handle_fatal_exc(e, args, "Error while initializing!",
pre_text="Error while initializing")
sys.exit(usertypes.Exit.err_init)
2015-04-30 07:37:25 +02:00
log.init.debug("Initializing eventfilter...")
event_filter = EventFilter(qApp)
qApp.installEventFilter(event_filter)
objreg.register('event-filter', event_filter)
log.init.debug("Connecting signals...")
config_obj = objreg.get('config')
config_obj.style_changed.connect(style.get_stylesheet.cache_clear)
qApp.focusChanged.connect(on_focus_changed)
_process_args(args)
2015-04-30 07:37:25 +02:00
QDesktopServices.setUrlHandler('http', open_desktopservices_url)
QDesktopServices.setUrlHandler('https', open_desktopservices_url)
QDesktopServices.setUrlHandler('qute', open_desktopservices_url)
QTimer.singleShot(10, functools.partial(_init_late_modules, args))
2015-04-30 07:37:25 +02:00
log.init.debug("Init done!")
crash_handler.raise_crashdlg()
2015-04-30 07:37:25 +02:00
def _init_icon():
"""Initialize the icon of qutebrowser."""
icon = QIcon()
fallback_icon = QIcon()
for size in [16, 24, 32, 48, 64, 96, 128, 256, 512]:
2015-04-30 07:37:25 +02:00
filename = ':/icons/qutebrowser-{}x{}.png'.format(size, size)
pixmap = QPixmap(filename)
qtutils.ensure_not_null(pixmap)
fallback_icon.addPixmap(pixmap)
qtutils.ensure_not_null(fallback_icon)
icon = QIcon.fromTheme('qutebrowser', fallback_icon)
2015-04-30 07:37:25 +02:00
qtutils.ensure_not_null(icon)
qApp.setWindowIcon(icon)
def _process_args(args):
"""Open startpage etc. and process commandline args."""
config_obj = objreg.get('config')
for sect, opt, val in args.temp_settings:
try:
2015-04-30 07:37:25 +02:00
config_obj.set('temp', sect, opt, val)
except (configexc.Error, configparser.Error) as e:
2016-09-14 20:52:32 +02:00
message.error("set: {} - {}".format(e.__class__.__name__, e))
2015-04-30 07:37:25 +02:00
if not args.override_restore:
_load_session(args.session)
session_manager = objreg.get('session-manager')
if not session_manager.did_load:
log.init.debug("Initializing main window...")
window = mainwindow.MainWindow()
if not args.nowindow:
window.show()
2015-04-30 07:37:25 +02:00
qApp.setActiveWindow(window)
2014-06-24 21:36:00 +02:00
2015-04-30 07:37:25 +02:00
process_pos_args(args.command)
_open_startpage()
_open_quickstart(args)
2016-10-04 10:26:44 +02:00
delta = datetime.datetime.now() - earlyinit.START_TIME
log.init.debug("Init finished after {}s".format(delta.total_seconds()))
2014-10-14 10:10:24 +02:00
2015-04-30 07:37:25 +02:00
def _load_session(name):
"""Load the default session.
2014-10-14 10:10:24 +02:00
2015-04-30 07:37:25 +02:00
Args:
name: The name of the session to load, or None to read state file.
"""
state_config = objreg.get('state-config')
session_manager = objreg.get('session-manager')
if name is None and session_manager.exists('_autosave'):
name = '_autosave'
elif name is None:
try:
2015-04-30 07:37:25 +02:00
name = state_config['general']['session']
except KeyError:
2015-04-30 07:37:25 +02:00
# No session given as argument and none in the session file ->
# start without loading a session
return
2015-04-30 07:37:25 +02:00
try:
session_manager.load(name)
except sessions.SessionNotFoundError:
2016-09-14 20:52:32 +02:00
message.error("Session {} not found!".format(name))
2015-04-30 07:37:25 +02:00
except sessions.SessionError as e:
2016-09-14 20:52:32 +02:00
message.error("Failed to load session {}: {}".format(name, e))
2015-04-30 07:37:25 +02:00
try:
del state_config['general']['session']
except KeyError:
pass
# If this was a _restart session, delete it.
if name == '_restart':
session_manager.delete('_restart')
def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
2015-04-30 07:37:25 +02:00
"""Process positional commandline args.
URLs to open have no prefix, commands to execute begin with a colon.
Args:
args: A list of arguments to process.
via_ipc: Whether the arguments were transmitted over IPC.
cwd: The cwd to use for fuzzy_url.
2015-10-06 22:59:49 +02:00
target_arg: Command line argument received by a running instance via
ipc. If the --target argument was not specified, target_arg
will be an empty string.
2015-04-30 07:37:25 +02:00
"""
if via_ipc and not args:
win_id = mainwindow.get_window(via_ipc, force_window=True)
_open_startpage(win_id)
return
win_id = None
for cmd in args:
if cmd.startswith(':'):
if win_id is None:
win_id = mainwindow.get_window(via_ipc, force_tab=True)
log.init.debug("Startup cmd {!r}".format(cmd))
2015-04-30 07:37:25 +02:00
commandrunner = runners.CommandRunner(win_id)
commandrunner.run_safely_init(cmd[1:])
elif not cmd:
log.init.debug("Empty argument")
win_id = mainwindow.get_window(via_ipc, force_window=True)
else:
if via_ipc and target_arg and target_arg != 'auto':
open_target = target_arg
else:
open_target = config.get('general', 'new-instance-open-target')
2015-10-06 22:59:49 +02:00
win_id = mainwindow.get_window(via_ipc, force_target=open_target)
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
2015-04-30 07:37:25 +02:00
log.init.debug("Startup URL {}".format(cmd))
if not cwd: # could also be an empty string due to the PyQt signal
cwd = None
2015-04-30 07:37:25 +02:00
try:
url = urlutils.fuzzy_url(cmd, cwd, relative=True)
except urlutils.InvalidUrlError as e:
2016-09-14 20:52:32 +02:00
message.error("Error in startup argument '{}': {}".format(
cmd, e))
2015-04-30 07:37:25 +02:00
else:
background = open_target in ['tab-bg', 'tab-bg-silent']
2016-04-15 10:50:42 +02:00
tabbed_browser.tabopen(url, background=background,
explicit=True)
2014-01-29 15:30:19 +01:00
2015-04-30 07:37:25 +02:00
def _open_startpage(win_id=None):
"""Open startpage.
2014-05-07 17:29:28 +02:00
2015-04-30 07:37:25 +02:00
The startpage is never opened if the given windows are not empty.
2015-04-30 07:37:25 +02:00
Args:
win_id: If None, open startpage in all empty windows.
If set, open the startpage in the given window.
"""
if win_id is not None:
window_ids = [win_id]
else:
window_ids = objreg.window_registry
for cur_win_id in list(window_ids): # Copying as the dict could change
2015-04-30 07:37:25 +02:00
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=cur_win_id)
if tabbed_browser.count() == 0:
log.init.debug("Opening startpage")
for urlstr in config.get('general', 'startpage'):
try:
url = urlutils.fuzzy_url(urlstr, do_search=False)
except urlutils.InvalidUrlError as e:
2016-09-14 20:52:32 +02:00
message.error("Error when opening startpage: {}".format(e))
2015-04-30 07:37:25 +02:00
tabbed_browser.tabopen(QUrl('about:blank'))
else:
tabbed_browser.tabopen(url)
2014-12-06 00:39:33 +01:00
def _open_quickstart(args):
"""Open quickstart if it's the first start.
Args:
args: The argparse namespace.
"""
if args.basedir is not None:
# With --basedir given, don't open quickstart.
return
2015-04-30 07:37:25 +02:00
state_config = objreg.get('state-config')
try:
quickstart_done = state_config['general']['quickstart-done'] == '1'
except KeyError:
quickstart_done = False
if not quickstart_done:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window='last-focused')
tabbed_browser.tabopen(
QUrl('https://www.qutebrowser.org/quickstart.html'))
2015-04-30 07:37:25 +02:00
state_config['general']['quickstart-done'] = '1'
2015-03-29 19:45:00 +02:00
2014-12-06 00:39:33 +01:00
2015-04-30 07:37:25 +02:00
def _save_version():
"""Save the current version to the state config."""
state_config = objreg.get('state-config', None)
if state_config is not None:
state_config['general']['version'] = qutebrowser.__version__
2014-05-07 17:29:28 +02:00
2014-01-30 20:42:47 +01:00
2015-04-30 07:37:25 +02:00
def on_focus_changed(_old, new):
"""Register currently focused main window in the object registry."""
if new is None:
return
if not isinstance(new, QWidget):
2016-05-24 21:35:48 +02:00
log.misc.debug("on_focus_changed called with non-QWidget {!r}".format(
new))
return
2016-05-24 21:35:48 +02:00
window = new.window()
if isinstance(window, mainwindow.MainWindow):
objreg.register('last-focused-main-window', window, update=True)
# A focused window must also be visible, and in this case we should
# consider it as the most recently looked-at window
objreg.register('last-visible-main-window', window, update=True)
2015-04-30 07:37:25 +02:00
def open_desktopservices_url(url):
"""Handler to open a URL via QDesktopServices."""
2015-04-30 07:37:25 +02:00
win_id = mainwindow.get_window(via_ipc=True, force_window=False)
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
tabbed_browser.tabopen(url)
def _init_modules(args, crash_handler):
"""Initialize all 'modules' which need to be initialized.
Args:
args: The argparse namespace.
crash_handler: The CrashHandler instance.
"""
2017-02-04 21:59:39 +01:00
# pylint: disable=too-many-statements
log.init.debug("Initializing prompts...")
prompt.init()
2015-04-30 07:37:25 +02:00
log.init.debug("Initializing save manager...")
save_manager = savemanager.SaveManager(qApp)
objreg.register('save-manager', save_manager)
save_manager.add_saveable('version', _save_version)
2015-04-30 07:37:25 +02:00
log.init.debug("Initializing network...")
networkmanager.init()
if qtutils.version_check('5.8'):
# Otherwise we can only initialize it for QtWebKit because of crashes
log.init.debug("Initializing proxy...")
proxy.init()
2015-04-30 07:37:25 +02:00
log.init.debug("Initializing readline-bridge...")
readline_bridge = readline.ReadlineBridge()
objreg.register('readline-bridge', readline_bridge)
2015-04-30 07:37:25 +02:00
log.init.debug("Initializing config...")
config.init(qApp)
save_manager.init_autosave()
2015-04-30 07:37:25 +02:00
log.init.debug("Initializing web history...")
history.init(qApp)
2015-04-30 07:37:25 +02:00
log.init.debug("Initializing crashlog...")
if not args.no_err_windows:
crash_handler.handle_segfault()
2015-04-30 07:37:25 +02:00
log.init.debug("Initializing sessions...")
sessions.init(qApp)
2015-04-30 07:37:25 +02:00
log.init.debug("Initializing websettings...")
websettings.init(args)
2015-04-30 07:37:25 +02:00
log.init.debug("Initializing adblock...")
host_blocker = adblock.HostBlocker()
host_blocker.read_hosts()
objreg.register('host-blocker', host_blocker)
2015-04-30 07:37:25 +02:00
log.init.debug("Initializing quickmarks...")
quickmark_manager = urlmarks.QuickmarkManager(qApp)
2015-04-30 07:37:25 +02:00
objreg.register('quickmark-manager', quickmark_manager)
2015-05-22 00:17:22 +02:00
log.init.debug("Initializing bookmarks...")
bookmark_manager = urlmarks.BookmarkManager(qApp)
2015-05-22 00:17:22 +02:00
objreg.register('bookmark-manager', bookmark_manager)
2015-04-30 07:37:25 +02:00
log.init.debug("Initializing cookies...")
cookie_jar = cookies.CookieJar(qApp)
ram_cookie_jar = cookies.RAMCookieJar(qApp)
2015-04-30 07:37:25 +02:00
objreg.register('cookie-jar', cookie_jar)
objreg.register('ram-cookie-jar', ram_cookie_jar)
2015-04-30 07:37:25 +02:00
log.init.debug("Initializing cache...")
2015-10-20 22:40:43 +02:00
diskcache = cache.DiskCache(standarddir.cache(), parent=qApp)
2015-04-30 07:37:25 +02:00
objreg.register('cache', diskcache)
2015-04-30 07:37:25 +02:00
log.init.debug("Initializing completions...")
completionmodels.init()
2015-04-30 07:37:25 +02:00
log.init.debug("Misc initialization...")
2015-09-16 08:52:51 +02:00
if config.get('ui', 'hide-wayland-decoration'):
os.environ['QT_WAYLAND_DISABLE_WINDOWDECORATION'] = '1'
else:
os.environ.pop('QT_WAYLAND_DISABLE_WINDOWDECORATION', None)
macros.init()
2016-09-07 17:27:21 +02:00
# Init backend-specific stuff
browsertab.init()
2015-04-30 07:37:25 +02:00
def _init_late_modules(args):
"""Initialize modules which can be inited after the window is shown."""
log.init.debug("Reading web history...")
reader = objreg.get('web-history').async_read()
with debug.log_time(log.init, 'Reading history'):
while True:
QApplication.processEvents()
try:
next(reader)
except StopIteration:
break
except (OSError, UnicodeDecodeError) as e:
error.handle_fatal_exc(e, args, "Error while initializing!",
pre_text="Error while initializing")
sys.exit(usertypes.Exit.err_init)
2015-04-30 07:37:25 +02:00
class Quitter:
"""Utility class to quit/restart the QApplication.
2014-01-30 20:42:47 +01:00
2015-04-30 07:37:25 +02:00
Attributes:
quit_status: The current quitting status.
_shutting_down: Whether we're currently shutting down.
_args: The argparse namespace.
"""
2014-06-17 23:04:58 +02:00
2015-04-30 07:37:25 +02:00
def __init__(self, args):
self.quit_status = {
'crash': True,
'tabs': False,
'main': False,
}
self._shutting_down = False
self._args = args
2015-04-30 07:37:25 +02:00
def on_last_window_closed(self):
"""Slot which gets invoked when the last window was closed."""
self.shutdown(last_window=True)
2014-02-17 20:39:15 +01:00
def _compile_modules(self):
"""Compile all modules to catch SyntaxErrors."""
if os.path.basename(sys.argv[0]) == 'qutebrowser':
# Launched via launcher script
return
elif hasattr(sys, 'frozen'):
return
else:
path = os.path.abspath(os.path.dirname(qutebrowser.__file__))
if not os.path.isdir(path):
# Probably running from a python egg.
return
for dirpath, _dirnames, filenames in os.walk(path):
for fn in filenames:
if os.path.splitext(fn)[1] == '.py' and os.path.isfile(fn):
2015-10-05 06:53:56 +02:00
with tokenize.open(os.path.join(dirpath, fn)) as f:
compile(f.read(), fn, 'exec')
def _get_restart_args(self, pages=(), session=None):
"""Get the current working directory and args to relaunch qutebrowser.
Args:
pages: The pages to re-open.
session: The session to load, or None.
Return:
An (args, cwd) tuple.
args: The commandline as a list of strings.
cwd: The current working directory as a string.
"""
if os.path.basename(sys.argv[0]) == 'qutebrowser':
# Launched via launcher script
args = [sys.argv[0]]
cwd = None
elif hasattr(sys, 'frozen'):
2014-06-24 06:52:49 +02:00
args = [sys.executable]
cwd = os.path.abspath(os.path.dirname(sys.executable))
else:
args = [sys.executable, '-m', 'qutebrowser']
cwd = os.path.join(os.path.abspath(os.path.dirname(
qutebrowser.__file__)), '..')
if not os.path.isdir(cwd):
# Probably running from a python egg. Let's fallback to
# cwd=None and see if that works out.
2017-02-05 00:13:11 +01:00
# See https://github.com/qutebrowser/qutebrowser/issues/323
cwd = None
2014-06-24 06:43:52 +02:00
# Add all open pages so they get reopened.
page_args = []
for win in pages:
page_args.extend(win)
page_args.append('')
# Serialize the argparse namespace into json and pass that to the new
# process via --json-args.
# We do this as there's no way to "unparse" the namespace while
# ignoring some arguments.
argdict = vars(self._args)
argdict['session'] = None
argdict['url'] = []
argdict['command'] = page_args[:-1]
argdict['json_args'] = None
# Ensure the given session (or none at all) gets opened.
if session is None:
argdict['session'] = None
argdict['override_restore'] = True
else:
argdict['session'] = session
argdict['override_restore'] = False
# Ensure :restart works with --temp-basedir
if self._args.temp_basedir:
argdict['temp_basedir'] = False
argdict['temp_basedir_restarted'] = True
# Dump the data
data = json.dumps(argdict)
args += ['--json-args', data]
2014-06-24 06:52:49 +02:00
log.destroy.debug("args: {}".format(args))
log.destroy.debug("cwd: {}".format(cwd))
return args, cwd
2015-04-30 07:37:25 +02:00
@cmdutils.register(instance='quitter', name='restart')
def restart_cmd(self):
"""Restart qutebrowser while keeping existing tabs open."""
try:
2015-04-30 07:37:25 +02:00
ok = self.restart(session='_restart')
except sessions.SessionError as e:
log.destroy.exception("Failed to save session!")
raise cmdexc.CommandError("Failed to save session: {}!".format(e))
except SyntaxError as e:
log.destroy.exception("Got SyntaxError")
raise cmdexc.CommandError("SyntaxError in {}:{}: {}".format(
e.filename, e.lineno, e))
if ok:
self.shutdown(restart=True)
2015-04-30 07:37:25 +02:00
def restart(self, pages=(), session=None):
"""Inner logic to restart qutebrowser.
The "better" way to restart is to pass a session (_restart usually) as
that'll save the complete state.
However we don't do that (and pass a list of pages instead) when we
restart because of an exception, as that's a lot simpler and we don't
want to risk anything going wrong.
Args:
pages: A list of URLs to open.
session: The session to load, or None.
Return:
True if the restart succeeded, False otherwise.
"""
self._compile_modules()
log.destroy.debug("sys.executable: {}".format(sys.executable))
log.destroy.debug("sys.path: {}".format(sys.path))
log.destroy.debug("sys.argv: {}".format(sys.argv))
log.destroy.debug("frozen: {}".format(hasattr(sys, 'frozen')))
# Save the session if one is given.
if session is not None:
session_manager = objreg.get('session-manager')
session_manager.save(session)
2014-06-24 06:43:52 +02:00
# Open a new process and immediately shutdown the existing one
2014-11-23 17:56:14 +01:00
try:
args, cwd = self._get_restart_args(pages, session)
2014-11-23 17:56:14 +01:00
if cwd is None:
subprocess.Popen(args)
else:
subprocess.Popen(args, cwd=cwd)
2014-12-26 15:19:52 +01:00
except OSError:
log.destroy.exception("Failed to restart")
return False
else:
return True
2015-04-30 07:37:25 +02:00
@cmdutils.register(instance='quitter', name=['quit', 'q'],
ignore_args=True)
def shutdown(self, status=0, session=None, last_window=False,
restart=False):
2015-02-17 07:45:06 +01:00
"""Quit qutebrowser.
2014-07-30 18:11:22 +02:00
Args:
status: The status code to exit with.
2015-02-17 07:45:06 +01:00
session: A session name if saving should be forced.
last_window: If the shutdown was triggered due to the last window
2015-04-30 07:37:25 +02:00
closing.
restart: If we're planning to restart.
"""
if self._shutting_down:
return
self._shutting_down = True
2015-04-30 07:37:25 +02:00
log.destroy.debug("Shutting down with status {}, session {}...".format(
status, session))
session_manager = objreg.get('session-manager', None)
if session_manager is not None:
if session is not None:
session_manager.save(session, last_window=last_window,
load_next_time=True)
elif config.get('general', 'save-session'):
session_manager.save(sessions.default, last_window=last_window,
load_next_time=True)
if prompt.prompt_queue.shutdown():
# If shutdown was called while we were asking a question, we're in
2015-03-31 20:49:29 +02:00
# a still sub-eventloop (which gets quit now) and not in the main
# one.
# This means we need to defer the real shutdown to when we're back
# in the real main event loop, or we'll get a segfault.
log.destroy.debug("Deferring real shutdown because question was "
"active.")
QTimer.singleShot(0, functools.partial(self._shutdown, status,
restart=restart))
else:
# If we have no questions to shut down, we are already in the real
# event loop, so we can shut down immediately.
self._shutdown(status, restart=restart)
def _shutdown(self, status, restart):
"""Second stage of shutdown."""
log.destroy.debug("Stage 2 of shutting down...")
if qApp is None:
# No QApplication exists yet, so quit hard.
sys.exit(status)
2014-07-31 23:09:59 +02:00
# Remove eventfilter
2014-09-23 19:57:51 +02:00
try:
2014-07-31 23:09:59 +02:00
log.destroy.debug("Removing eventfilter...")
event_filter = objreg.get('event-filter', None)
if event_filter is not None:
qApp.removeEventFilter(event_filter)
except AttributeError:
2014-09-23 19:57:51 +02:00
pass
# Close all windows
QApplication.closeAllWindows()
2014-10-13 20:36:23 +02:00
# Shut down IPC
try:
objreg.get('ipc-server').shutdown()
except KeyError:
pass
2014-06-03 16:48:21 +02:00
# Save everything
2014-09-23 22:28:28 +02:00
try:
save_manager = objreg.get('save-manager')
2014-09-23 22:28:28 +02:00
except KeyError:
log.destroy.debug("Save manager not initialized yet, so not "
"saving anything.")
2014-09-23 22:28:28 +02:00
else:
for key in save_manager.saveables:
try:
save_manager.save(key, is_exit=True)
except OSError as e:
2015-03-27 07:59:13 +01:00
error.handle_fatal_exc(
e, self._args, "Error while saving!",
pre_text="Error while saving {}".format(key))
# Disable storage so removing tempdir will work
2016-09-05 17:58:56 +02:00
websettings.shutdown()
# Re-enable faulthandler to stdout, then remove crash log
log.destroy.debug("Deactivating crash log...")
objreg.get('crash-handler').destroy_crashlogfile()
2015-05-16 23:26:15 +02:00
# Delete temp basedir
if ((self._args.temp_basedir or self._args.temp_basedir_restarted) and
not restart):
atexit.register(shutil.rmtree, self._args.basedir,
ignore_errors=True)
# Delete temp download dir
2016-11-08 20:36:49 +01:00
downloads.temp_download_manager.cleanup()
# If we don't kill our custom handler here we might get segfaults
2015-10-04 15:41:42 +02:00
log.destroy.debug("Deactivating message handler...")
qInstallMessageHandler(None)
# Now we can hopefully quit without segfaults
2014-08-02 19:40:24 +02:00
log.destroy.debug("Deferring QApplication::exit...")
objreg.get('signal-handler').deactivate()
session_manager = objreg.get('session-manager', None)
if session_manager is not None:
session_manager.delete_autosave()
2015-03-31 20:49:29 +02:00
# We use a singleshot timer to exit here to minimize the likelihood of
# segfaults.
2015-04-30 07:37:25 +02:00
QTimer.singleShot(0, functools.partial(qApp.exit, status))
2014-08-02 19:40:24 +02:00
2016-05-10 23:04:52 +02:00
@cmdutils.register(instance='quitter', name='wq')
@cmdutils.argument('name', completion=usertypes.Completion.sessions)
2015-04-30 07:37:25 +02:00
def save_and_quit(self, name=sessions.default):
"""Save open pages and quit.
2015-04-30 07:37:25 +02:00
Args:
name: The name of the session.
"""
2015-05-06 07:35:11 +02:00
self.shutdown(session=name)
2015-04-30 07:37:25 +02:00
class Application(QApplication):
"""Main application instance.
Attributes:
_args: ArgumentParser instance.
_last_focus_object: The last focused object's repr.
2015-04-30 07:37:25 +02:00
"""
new_window = pyqtSignal(mainwindow.MainWindow)
2015-04-30 07:37:25 +02:00
def __init__(self, args):
"""Constructor.
Args:
Argument namespace from argparse.
"""
self._last_focus_object = None
2015-04-30 07:37:25 +02:00
qt_args = qtutils.get_args(args)
log.init.debug("Qt arguments: {}, based on {}".format(qt_args, args))
super().__init__(qt_args)
log.init.debug("Initializing application...")
self._args = args
objreg.register('args', args)
objreg.register('app', self)
2015-08-07 20:36:38 +02:00
self.launch_time = datetime.datetime.now()
self.focusObjectChanged.connect(self.on_focus_object_changed)
@pyqtSlot(QObject)
def on_focus_object_changed(self, obj):
"""Log when the focus object changed."""
output = repr(obj)
if self._last_focus_object != output:
log.misc.debug("Focus object changed: {}".format(output))
self._last_focus_object = output
2015-08-07 20:36:38 +02:00
2015-04-30 07:37:25 +02:00
def __repr__(self):
return utils.get_repr(self)
2014-08-02 19:40:24 +02:00
def exit(self, status):
"""Extend QApplication::exit to log the event."""
log.destroy.debug("Now calling QApplication::exit.")
2014-09-23 22:13:10 +02:00
if self._args.debug_exit:
2015-03-31 19:02:42 +02:00
if hunter is None:
print("Not logging late shutdown because hunter could not be "
"imported!", file=sys.stderr)
else:
print("Now logging late shutdown.", file=sys.stderr)
hunter.trace()
2014-08-02 19:40:24 +02:00
super().exit(status)
2015-04-09 20:18:23 +02:00
class EventFilter(QObject):
"""Global Qt event filter.
Attributes:
_activated: Whether the EventFilter is currently active.
_handlers; A {QEvent.Type: callable} dict with the handlers for an
event.
"""
def __init__(self, parent=None):
super().__init__(parent)
self._activated = True
self._handlers = {
QEvent.MouseButtonDblClick: self._handle_mouse_event,
QEvent.MouseButtonPress: self._handle_mouse_event,
QEvent.MouseButtonRelease: self._handle_mouse_event,
QEvent.MouseMove: self._handle_mouse_event,
QEvent.KeyPress: self._handle_key_event,
QEvent.KeyRelease: self._handle_key_event,
}
def _handle_key_event(self, event):
"""Handle a key press/release event.
Args:
event: The QEvent which is about to be delivered.
Return:
True if the event should be filtered, False if it's passed through.
"""
2015-04-30 07:37:25 +02:00
if qApp.activeWindow() not in objreg.window_registry.values():
2015-04-09 20:18:23 +02:00
# Some other window (print dialog, etc.) is focused so we pass the
# event through.
return False
try:
man = objreg.get('mode-manager', scope='window', window='current')
return man.eventFilter(event)
except objreg.RegistryUnavailableError:
# No window available yet, or not a MainWindow
return False
def _handle_mouse_event(self, _event):
"""Handle a mouse event.
Args:
_event: The QEvent which is about to be delivered.
Return:
True if the event should be filtered, False if it's passed through.
"""
2015-11-13 22:27:41 +01:00
# Mouse cursor shown (overrideCursor None) -> don't filter event
# Mouse cursor hidden (overrideCursor not None) -> filter event
return qApp.overrideCursor() is not None
2015-04-09 20:18:23 +02:00
def eventFilter(self, obj, event):
"""Handle an event.
Args:
obj: The object which will get the event.
event: The QEvent which is about to be delivered.
Return:
True if the event should be filtered, False if it's passed through.
"""
try:
if not self._activated:
return False
if not isinstance(obj, QWindow):
# We already handled this same event at some point earlier, so
# we're not interested in it anymore.
return False
try:
handler = self._handlers[event.type()]
except KeyError:
return False
else:
return handler(event)
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