qutebrowser/qutebrowser/app.py

716 lines
27 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:
2014-02-06 14:01:23 +01:00
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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-07-30 17:50:12 +02:00
import signal
2014-08-05 22:33:08 +02:00
import warnings
2014-08-26 19:10:14 +02:00
import bdb
import base64
import functools
import traceback
2014-05-09 11:06:05 +02:00
2014-09-28 00:27:22 +02:00
from PyQt5.QtWidgets import QApplication, QDialog
2014-10-06 22:14:57 +02:00
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl,
2014-10-14 07:37:12 +02:00
QStandardPaths, QObject, Qt)
2014-02-17 08:56:33 +01:00
import qutebrowser
2014-09-29 19:56:13 +02:00
from qutebrowser.commands import cmdutils, runners
2014-09-28 00:27:22 +02:00
from qutebrowser.config import style, config, websettings
2014-08-26 19:10:14 +02:00
from qutebrowser.network import qutescheme, proxy
2014-09-28 22:13:14 +02:00
from qutebrowser.browser import quickmarks, cookies, downloads, cache
2014-10-10 07:45:44 +02:00
from qutebrowser.widgets import mainwindow, crash
2014-09-28 00:18:57 +02:00
from qutebrowser.keyinput import modeman
2014-09-28 22:23:37 +02:00
from qutebrowser.utils import (log, version, message, readline, utils, qtutils,
2014-10-13 07:06:57 +02:00
urlutils, debug, objreg, usertypes, standarddir,
ipc, earlyinit)
2014-09-29 08:52:00 +02:00
# We import utilcmds to run the cmdutils.register decorators.
from qutebrowser.utils import utilcmds # pylint: disable=unused-import
2013-12-14 22:15:16 +01:00
2014-01-28 23:04:02 +01:00
2014-06-04 13:38:53 +02:00
class Application(QApplication):
2014-02-07 20:21:50 +01:00
2014-06-04 13:38:53 +02:00
"""Main application instance.
2014-02-07 20:21:50 +01:00
2014-02-18 16:38:13 +01:00
Attributes:
2014-09-23 22:13:10 +02:00
_args: ArgumentParser instance.
2014-02-18 16:38:13 +01:00
_shutting_down: True if we're currently shutting down.
_quit_status: The current quitting status.
_crashdlg: The crash dialog currently open.
_crashlogfile: A file handler to the fatal crash logfile.
2014-09-28 22:13:14 +02:00
_event_filter: The EventFilter for the application.
geometry: The geometry of the last closed main window.
2014-02-18 16:38:13 +01:00
"""
def __init__(self, args):
"""Constructor.
Args:
Argument namespace from argparse.
"""
2014-05-06 10:53:38 +02:00
self._quit_status = {
'crash': True,
'tabs': False,
'main': False,
2014-05-06 10:53:38 +02:00
}
self.geometry = None
2014-02-18 16:38:13 +01:00
self._shutting_down = False
self._crashdlg = None
self._crashlogfile = None
2014-09-28 00:18:57 +02:00
if args.debug:
# We don't enable this earlier because some imports trigger
# warnings (which are not our fault).
warnings.simplefilter('default')
2014-02-18 16:38:13 +01:00
2014-09-28 00:18:57 +02:00
qt_args = qtutils.get_args(args)
log.init.debug("Qt arguments: {}, based on {}".format(qt_args, args))
super().__init__(qt_args)
2014-02-06 10:25:22 +01:00
sys.excepthook = self._exception_hook
2014-01-28 14:44:12 +01:00
2014-09-23 22:13:10 +02:00
self._args = args
2014-09-24 07:10:17 +02:00
objreg.register('args', args)
2014-09-28 00:18:57 +02:00
objreg.register('app', self)
if self._args.version:
print(version.version())
print()
print()
print(qutebrowser.__copyright__)
print()
print(version.GPL_BOILERPLATE.strip())
sys.exit(0)
2014-10-13 07:06:57 +02:00
sent = ipc.send_to_running_instance(self._args.command)
if sent:
2014-06-24 21:36:00 +02:00
sys.exit(0)
2014-06-23 06:37:47 +02:00
log.init.debug("Starting init...")
2014-09-28 00:18:57 +02:00
self.setQuitOnLastWindowClosed(False)
self.setOrganizationName("qutebrowser")
self.setApplicationName("qutebrowser")
self.setApplicationVersion(qutebrowser.__version__)
2014-08-26 19:10:14 +02:00
utils.actute_warning()
2014-09-28 00:18:57 +02:00
self._init_modules()
QTimer.singleShot(0, self._open_pages)
2014-09-28 00:18:57 +02:00
log.init.debug("Initializing eventfilter...")
2014-09-28 22:13:14 +02:00
self._event_filter = modeman.EventFilter(self)
self.installEventFilter(self._event_filter)
2014-09-28 00:18:57 +02:00
log.init.debug("Connecting signals...")
self._connect_signals()
log.init.debug("Applying python hacks...")
self._python_hacks()
2014-10-13 07:42:06 +02:00
log.init.debug("Starting IPC server...")
ipc.init()
2014-10-13 07:42:06 +02:00
2014-09-28 00:18:57 +02:00
log.init.debug("Init done!")
if self._crashdlg is not None:
self._crashdlg.raise_()
def __repr__(self):
return utils.get_repr(self)
def _init_modules(self):
"""Initialize all 'modules' which need to be initialized."""
log.init.debug("Initializing readline-bridge...")
readline_bridge = readline.ReadlineBridge()
objreg.register('readline-bridge', readline_bridge)
log.init.debug("Initializing directories...")
standarddir.init()
2014-06-23 06:37:47 +02:00
log.init.debug("Initializing config...")
2014-09-28 00:27:22 +02:00
config.init(self._args)
2014-06-23 06:37:47 +02:00
log.init.debug("Initializing crashlog...")
self._handle_segfault()
2014-06-23 06:37:47 +02:00
log.init.debug("Initializing websettings...")
2014-05-08 22:33:24 +02:00
websettings.init()
2014-06-23 06:37:47 +02:00
log.init.debug("Initializing quickmarks...")
quickmark_manager = quickmarks.QuickmarkManager()
objreg.register('quickmark-manager', quickmark_manager)
2014-06-23 06:37:47 +02:00
log.init.debug("Initializing proxy...")
2014-05-05 22:07:41 +02:00
proxy.init()
2014-06-23 06:37:47 +02:00
log.init.debug("Initializing cookies...")
2014-09-23 23:05:55 +02:00
cookie_jar = cookies.CookieJar(self)
2014-09-24 07:10:17 +02:00
objreg.register('cookie-jar', cookie_jar)
2014-09-01 19:42:21 +02:00
log.init.debug("Initializing cache...")
diskcache = cache.DiskCache(self)
2014-09-24 07:10:17 +02:00
objreg.register('cache', diskcache)
2014-06-23 06:37:47 +02:00
log.init.debug("Initializing downloads...")
2014-09-23 23:05:55 +02:00
download_manager = downloads.DownloadManager(self)
2014-09-24 07:10:17 +02:00
objreg.register('download-manager', download_manager)
2014-06-23 06:37:47 +02:00
log.init.debug("Initializing main window...")
2014-10-06 22:29:18 +02:00
win_id = mainwindow.MainWindow.spawn(
False if self._args.nowindow else True)
main_window = objreg.get('main-window', scope='window', window=win_id)
self.setActiveWindow(main_window)
2014-09-23 23:31:17 +02:00
def _handle_segfault(self):
"""Handle a segfault from a previous run."""
path = standarddir.get(QStandardPaths.DataLocation)
2014-08-26 19:10:14 +02:00
logname = os.path.join(path, 'crash.log')
# First check if an old logfile exists.
if os.path.exists(logname):
2014-08-20 20:33:14 +02:00
with open(logname, 'r', encoding='ascii') as f:
data = f.read()
try:
os.remove(logname)
except PermissionError:
log.init.warning("Could not remove crash log!")
else:
self._init_crashlogfile()
if data:
# Crashlog exists and has data in it, so something crashed
# previously.
2014-08-26 19:10:14 +02:00
self._crashdlg = crash.FatalCrashDialog(data)
self._crashdlg.show()
else:
# There's no log file, so we can use this to display crashes to the
# user on the next start.
self._init_crashlogfile()
def _init_crashlogfile(self):
"""Start a new logfile and redirect faulthandler to it."""
path = standarddir.get(QStandardPaths.DataLocation)
2014-08-26 19:10:14 +02:00
logname = os.path.join(path, 'crash.log')
2014-08-20 20:33:14 +02:00
self._crashlogfile = open(logname, 'w', encoding='ascii')
earlyinit.init_faulthandler(self._crashlogfile)
def _open_pages(self):
"""Open startpage etc. and process commandline args."""
2014-10-13 07:06:57 +02:00
self.process_args(self._args.command)
self._open_startpage()
self._open_quickstart()
2014-10-14 10:10:24 +02:00
def _get_window(self, via_ipc, args):
"""Helper function for process_args to get a window id.
Args:
via_ipc: Whether the request was made via IPC.
args: The argument list.
"""
if not via_ipc:
2014-10-14 10:10:24 +02:00
# Initial main window
2014-10-14 07:37:12 +02:00
return 0
2014-10-14 10:10:24 +02:00
window_to_raise = None
open_target = config.get('general', 'new-instance-open-target')
2014-10-14 10:10:24 +02:00
if open_target == 'window' or not args:
win_id = mainwindow.MainWindow.spawn()
window = objreg.get('main-window', scope='window', window=win_id)
window_to_raise = window
else:
2014-10-14 07:37:12 +02:00
try:
window = objreg.get('last-main-window')
except KeyError:
# We can't display an error here because... duh, there is no
# window.
log.ipc.error("No main window found!")
2014-10-14 07:37:12 +02:00
return None
2014-10-14 10:10:24 +02:00
win_id = window.win_id
if open_target != 'tab-silent':
window_to_raise = window
if window_to_raise is not None:
window_to_raise.setWindowState(window.windowState() &
~Qt.WindowMinimized |
Qt.WindowActive)
window_to_raise.raise_()
window_to_raise.activateWindow()
self.alert(window_to_raise)
2014-10-14 10:10:24 +02:00
return win_id
2014-10-14 07:37:12 +02:00
def process_args(self, args, via_ipc=False):
"""Process commandline args.
URLs to open have no prefix, commands to execute begin with a colon.
2014-06-24 21:36:00 +02:00
Args:
args: A list of arguments to process.
via_ipc: Whether the arguments were transmitted over IPC.
"""
2014-10-14 10:10:24 +02:00
win_id = self._get_window(via_ipc, args)
2014-10-14 07:37:12 +02:00
if win_id is None:
return
2014-10-14 10:10:24 +02:00
if ipc and not args:
self._open_startpage(win_id)
2014-06-24 21:36:00 +02:00
for cmd in args:
if cmd.startswith(':'):
log.init.debug("Startup cmd {}".format(cmd))
2014-09-29 19:56:13 +02:00
commandrunner = runners.CommandRunner(win_id)
commandrunner.run_safely_init(cmd.lstrip(':'))
elif not cmd:
log.init.debug("Empty argument")
2014-10-06 21:24:07 +02:00
win_id = mainwindow.MainWindow.spawn()
else:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
log.init.debug("Startup URL {}".format(cmd))
try:
url = urlutils.fuzzy_url(cmd)
2014-06-20 23:57:52 +02:00
except urlutils.FuzzyUrlError as e:
2014-09-28 22:13:14 +02:00
message.error(0, "Error in startup argument '{}': "
"{}".format(cmd, e))
else:
2014-09-23 23:05:55 +02:00
tabbed_browser.tabopen(url)
2014-10-14 10:10:24 +02:00
def _open_startpage(self, win_id=None):
"""Open startpage.
The startpage is never opened if the given windows are not empty.
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 window_ids:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
2014-10-14 10:10:24 +02:00
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)
except urlutils.FuzzyUrlError as e:
message.error(0, "Error when opening startpage: "
2014-09-29 20:36:53 +02:00
"{}".format(e))
tabbed_browser.tabopen(QUrl('about:blank'))
else:
tabbed_browser.tabopen(url)
2014-01-29 15:30:19 +01:00
def _open_quickstart(self):
"""Open quickstart if it's the first start."""
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='current')
tabbed_browser.tabopen(
QUrl('http://www.qutebrowser.org/quickstart.html'))
try:
state_config.add_section('general')
except configparser.DuplicateSectionError:
pass
state_config['general']['quickstart-done'] = '1'
def _python_hacks(self):
"""Get around some PyQt-oddities by evil hacks.
This sets up the uncaught exception hook, quits with an appropriate
exit status, and handles Ctrl+C properly by passing control to the
Python interpreter once all 500ms.
"""
2014-07-30 18:11:22 +02:00
signal.signal(signal.SIGINT, self.interrupt)
signal.signal(signal.SIGTERM, self.interrupt)
2014-09-28 00:18:57 +02:00
timer = usertypes.Timer(self, 'python_hacks')
timer.start(500)
timer.timeout.connect(lambda: None)
2014-09-24 07:10:17 +02:00
objreg.register('python-hack-timer', timer)
2014-04-21 15:20:41 +02:00
def _connect_signals(self):
"""Connect all signals to their slots."""
2014-09-24 07:10:17 +02:00
config_obj = objreg.get('config')
2014-04-21 15:20:41 +02:00
self.lastWindowClosed.connect(self.shutdown)
2014-09-23 22:28:28 +02:00
config_obj.style_changed.connect(style.get_stylesheet.cache_clear)
def _get_widgets(self):
2014-06-17 23:04:58 +02:00
"""Get a string list of all widgets."""
widgets = self.allWidgets()
widgets.sort(key=lambda e: repr(e))
return [repr(w) for w in widgets]
2014-06-17 23:04:58 +02:00
2014-09-23 06:39:23 +02:00
def _get_pyqt_objects(self, lines, obj, depth=0):
"""Recursive method for get_all_objects to get Qt objects."""
2014-06-17 23:04:58 +02:00
for kid in obj.findChildren(QObject):
lines.append(' ' * depth + repr(kid))
2014-09-23 06:39:23 +02:00
self._get_pyqt_objects(lines, kid, depth + 1)
def get_all_objects(self):
"""Get all children of an object recursively as a string."""
2014-09-23 07:48:34 +02:00
output = ['']
2014-09-25 08:03:04 +02:00
widget_lines = self._get_widgets()
widget_lines = [' ' + e for e in widget_lines]
widget_lines.insert(0, "Qt widgets - {} objects".format(
len(widget_lines)))
output += widget_lines
pyqt_lines = []
self._get_pyqt_objects(pyqt_lines, self)
2014-09-23 07:48:34 +02:00
pyqt_lines = [' ' + e for e in pyqt_lines]
pyqt_lines.insert(0, 'Qt objects - {} objects:'.format(
len(pyqt_lines)))
output += pyqt_lines
output += ['']
2014-10-05 21:50:14 +02:00
output += objreg.dump_objects()
2014-09-23 07:48:34 +02:00
return '\n'.join(output)
2014-06-17 23:04:58 +02:00
2014-09-29 20:36:53 +02:00
def _recover_pages(self, forgiving=False):
2014-02-17 20:30:09 +01:00
"""Try to recover all open pages.
Called from _exception_hook, so as forgiving as possible.
2014-09-29 20:36:53 +02:00
Args:
forgiving: Whether to ignore exceptions.
2014-02-19 10:58:32 +01:00
Return:
A list containing a list for each window, which in turn contain the
opened URLs.
2014-02-17 20:30:09 +01:00
"""
pages = []
for win_id in objreg.window_registry:
win_pages = []
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
for tab in tabbed_browser.widgets():
try:
2014-09-29 20:36:53 +02:00
urlstr = tab.cur_url.toString(
QUrl.RemovePassword | QUrl.FullyEncoded)
2014-09-29 20:36:53 +02:00
if urlstr:
win_pages.append(urlstr)
except Exception: # pylint: disable=broad-except
2014-09-29 20:36:53 +02:00
if forgiving:
log.destroy.exception("Error while recovering tab")
else:
raise
pages.append(win_pages)
2014-02-17 20:30:09 +01:00
return pages
def _save_geometry(self):
"""Save the window geometry to the state config."""
if self.geometry is not None:
state_config = objreg.get('state-config')
geom = base64.b64encode(self.geometry).decode('ASCII')
try:
state_config.add_section('geometry')
except configparser.DuplicateSectionError:
pass
state_config['geometry']['mainwindow'] = geom
2014-01-30 20:42:47 +01:00
def _exception_hook(self, exctype, excvalue, tb):
2014-01-29 15:30:19 +01:00
"""Handle uncaught python exceptions.
It'll try very hard to write all open tabs to a file, and then exit
gracefully.
"""
2014-01-28 19:52:09 +01:00
# pylint: disable=broad-except
2014-01-30 20:42:47 +01:00
2014-08-26 19:10:14 +02:00
if exctype is bdb.BdbQuit or not issubclass(exctype, Exception):
2014-05-06 12:11:00 +02:00
# pdb exit, KeyboardInterrupt, ...
2014-02-17 20:30:09 +01:00
try:
self.shutdown()
return
except Exception:
log.init.exception("Error while shutting down")
2014-02-17 20:30:09 +01:00
self.quit()
2014-06-02 23:29:01 +02:00
return
exc = (exctype, excvalue, tb)
sys.__excepthook__(*exc)
2014-05-07 17:29:28 +02:00
if not self._quit_status['crash']:
log.misc.error("ARGH, there was an exception while the crash "
"dialog is already shown:", exc_info=exc)
return
2014-05-07 17:29:28 +02:00
self._quit_status['crash'] = False
2014-01-28 14:44:12 +01:00
try:
2014-09-29 20:36:53 +02:00
pages = self._recover_pages(forgiving=True)
except Exception:
log.destroy.exception("Error while recovering pages")
2014-02-17 20:30:09 +01:00
pages = []
2014-01-30 20:42:47 +01:00
try:
history = objreg.get('command-history')[-5:]
except Exception:
log.destroy.exception("Error while getting history: {}")
2014-01-30 20:42:47 +01:00
history = []
2014-06-17 23:04:58 +02:00
try:
objects = self.get_all_objects()
except Exception:
log.destroy.exception("Error while getting objects")
2014-06-17 23:04:58 +02:00
objects = ""
try:
self.lastWindowClosed.disconnect(self.shutdown)
except TypeError:
log.destroy.exception("Error while preventing shutdown")
2014-02-05 11:40:30 +01:00
QApplication.closeAllWindows()
2014-08-26 19:10:14 +02:00
self._crashdlg = crash.ExceptionCrashDialog(pages, history, exc,
objects)
ret = self._crashdlg.exec_()
2014-01-30 20:42:47 +01:00
if ret == QDialog.Accepted: # restore
2014-05-14 08:56:42 +02:00
self.restart(shutdown=False, pages=pages)
2014-05-07 17:29:28 +02:00
# We might risk a segfault here, but that's better than continuing to
# run in some undefined state, so we only do the most needed shutdown
# here.
qInstallMessageHandler(None)
2014-05-07 17:29:28 +02:00
sys.exit(1)
2014-02-17 20:39:15 +01:00
2014-09-28 22:13:14 +02:00
@cmdutils.register(instance='app', name=['quit', 'q'])
def quit(self):
"""Quit qutebrowser."""
QApplication.closeAllWindows()
2014-09-23 22:06:46 +02:00
@cmdutils.register(instance='app', ignore_args=True)
2014-06-24 06:43:52 +02:00
def restart(self, shutdown=True, pages=None):
"""Restart qutebrowser while keeping existing tabs open."""
if pages is None:
2014-09-29 20:36:53 +02:00
pages = self._recover_pages()
2014-06-24 06:43:52 +02:00
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')))
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__)), '..')
2014-06-24 06:43:52 +02:00
for arg in sys.argv[1:]:
if arg.startswith('-'):
# We only want to preserve options on a restart.
args.append(arg)
# Add all open pages so they get reopened.
page_args = []
for win in pages:
page_args.extend(win)
page_args.append('')
if page_args:
args.extend(page_args[:-1])
2014-06-24 06:52:49 +02:00
log.destroy.debug("args: {}".format(args))
log.destroy.debug("cwd: {}".format(cwd))
2014-06-24 06:43:52 +02:00
# Open a new process and immediately shutdown the existing one
if cwd is None:
subprocess.Popen(args)
else:
subprocess.Popen(args, cwd=cwd)
2014-06-24 06:43:52 +02:00
if shutdown:
self.shutdown()
2014-05-14 08:56:42 +02:00
2014-09-23 22:06:46 +02:00
@cmdutils.register(instance='app', split=False, debug=True)
2014-06-17 11:12:55 +02:00
def debug_pyeval(self, s):
2014-01-29 15:30:19 +01:00
"""Evaluate a python string and display the results as a webpage.
//
2014-06-17 11:12:55 +02:00
We have this here rather in utils.debug so the context of eval makes
more sense and because we don't want to import much stuff in the utils.
2014-02-07 20:21:50 +01:00
2014-02-19 10:58:32 +01:00
Args:
s: The string to evaluate.
2014-01-29 15:30:19 +01:00
"""
2014-01-19 23:54:22 +01:00
try:
2014-04-28 00:05:14 +02:00
r = eval(s) # pylint: disable=eval-used
2014-01-19 23:54:22 +01:00
out = repr(r)
except Exception: # pylint: disable=broad-except
out = traceback.format_exc()
2014-02-21 07:18:04 +01:00
qutescheme.pyeval_output = out
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window='current')
tabbed_browser.openurl(QUrl('qute:pyeval'), newtab=True)
2014-01-30 14:58:32 +01:00
2014-09-23 22:06:46 +02:00
@cmdutils.register(instance='app')
2014-06-25 22:22:30 +02:00
def report(self):
"""Report a bug in qutebrowser."""
pages = self._recover_pages()
history = objreg.get('command-history')[-5:]
2014-06-25 22:22:30 +02:00
objects = self.get_all_objects()
self._crashdlg = crash.ReportDialog(pages, history, objects)
2014-06-25 22:22:30 +02:00
self._crashdlg.show()
2014-07-30 18:11:22 +02:00
def interrupt(self, signum, _frame):
2014-08-02 01:53:27 +02:00
"""Handler for signals to gracefully shutdown (SIGINT/SIGTERM).
This calls self.shutdown and remaps the signal to call
self.interrupt_forcefully the next time.
"""
2014-07-30 18:11:22 +02:00
log.destroy.info("SIGINT/SIGTERM received, shutting down!")
log.destroy.info("Do the same again to forcefully quit.")
2014-07-30 18:11:22 +02:00
signal.signal(signal.SIGINT, self.interrupt_forcefully)
signal.signal(signal.SIGTERM, self.interrupt_forcefully)
2014-08-02 00:53:30 +02:00
# If we call shutdown directly here, we get a segfault.
2014-08-26 19:10:14 +02:00
QTimer.singleShot(0, functools.partial(self.shutdown, 128 + signum))
2014-07-30 18:11:22 +02:00
2014-07-30 18:56:01 +02:00
def interrupt_forcefully(self, signum, _frame):
2014-08-02 01:53:27 +02:00
"""Interrupt forcefully on the second SIGINT/SIGTERM request.
This skips our shutdown routine and calls QApplication:exit instead.
It then remaps the signals to call self.interrupt_really_forcefully the
next time.
"""
2014-07-30 18:11:22 +02:00
log.destroy.info("Forceful quit requested, goodbye cruel world!")
log.destroy.info("Do the same again to quit with even more force.")
signal.signal(signal.SIGINT, self.interrupt_really_forcefully)
signal.signal(signal.SIGTERM, self.interrupt_really_forcefully)
# This *should* work without a QTimer, but because of the trouble in
# self.interrupt we're better safe than sorry.
2014-08-26 19:10:14 +02:00
QTimer.singleShot(0, functools.partial(self.exit, 128 + signum))
def interrupt_really_forcefully(self, signum, _frame):
2014-08-02 01:53:27 +02:00
"""Interrupt with even more force on the third SIGINT/SIGTERM request.
This doesn't run *any* Qt cleanup and simply exits via Python.
It will most likely lead to a segfault.
"""
log.destroy.info("WHY ARE YOU DOING THIS TO ME? :(")
sys.exit(128 + signum)
2014-07-30 18:11:22 +02:00
@pyqtSlot()
2014-07-30 18:11:22 +02:00
def shutdown(self, status=0):
"""Try to shutdown everything cleanly.
For some reason lastWindowClosing sometimes seem to get emitted twice,
so we make sure we only run once here.
2014-07-30 18:11:22 +02:00
Args:
status: The status code to exit with.
"""
if self._shutting_down:
return
self._shutting_down = True
2014-07-30 18:11:22 +02:00
log.destroy.debug("Shutting down with status {}...".format(status))
2014-09-28 22:13:14 +02:00
deferrer = False
for win_id in objreg.window_registry:
prompter = objreg.get('prompter', None, scope='window',
window=win_id)
if prompter is not None and prompter.shutdown():
deferrer = True
if deferrer:
# If shutdown was called while we were asking a question, we're in
# a still sub-eventloop (which gets quitted now) and not in the
# main one.
# 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.")
2014-08-26 19:10:14 +02:00
QTimer.singleShot(0, functools.partial(self._shutdown, status))
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)
def _shutdown(self, status): # noqa
"""Second stage of shutdown."""
2014-09-23 23:17:36 +02:00
# pylint: disable=too-many-branches, too-many-statements
# FIXME refactor this
2014-10-01 22:23:27 +02:00
# https://github.com/The-Compiler/qutebrowser/issues/113
log.destroy.debug("Stage 2 of shutting down...")
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...")
2014-09-28 22:13:14 +02:00
self.removeEventFilter(self._event_filter)
except AttributeError:
2014-09-23 19:57:51 +02:00
pass
2014-07-31 20:40:21 +02:00
# Close all tabs
for win_id in objreg.window_registry:
log.destroy.debug("Closing tabs in window {}...".format(win_id))
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
tabbed_browser.shutdown()
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:
2014-09-24 07:10:17 +02:00
config_obj = objreg.get('config')
2014-09-23 22:28:28 +02:00
except KeyError:
log.destroy.debug("Config not initialized yet, so not saving "
"anything.")
else:
to_save = []
2014-09-23 22:28:28 +02:00
if config.get('general', 'auto-save-config'):
2014-09-23 22:37:41 +02:00
to_save.append(("config", config_obj.save))
try:
2014-09-24 07:10:17 +02:00
key_config = objreg.get('key-config')
2014-09-23 22:37:41 +02:00
except KeyError:
pass
else:
2014-09-23 23:05:55 +02:00
to_save.append(("keyconfig", key_config.save))
to_save += [("window geometry", self._save_geometry)]
2014-09-23 22:17:25 +02:00
try:
2014-09-24 07:10:17 +02:00
command_history = objreg.get('command-history')
2014-09-23 22:17:25 +02:00
except KeyError:
pass
else:
2014-09-23 23:05:55 +02:00
to_save.append(("command history", command_history.save))
try:
quickmark_manager = objreg.get('quickmark-manager')
except KeyError:
pass
else:
to_save.append(("command history", quickmark_manager.save))
2014-09-23 19:22:55 +02:00
try:
2014-09-24 07:10:17 +02:00
state_config = objreg.get('state-config')
2014-09-23 19:22:55 +02:00
except KeyError:
pass
else:
2014-09-23 23:05:55 +02:00
to_save.append(("window geometry", state_config.save))
try:
2014-09-24 07:10:17 +02:00
cookie_jar = objreg.get('cookie-jar')
except KeyError:
pass
else:
2014-09-23 23:05:55 +02:00
to_save.append(("cookies", cookie_jar.save))
for what, handler in to_save:
log.destroy.debug("Saving {} (handler: {})".format(
2014-10-08 21:11:04 +02:00
what, utils.qualname(handler)))
try:
handler()
except AttributeError as e:
log.destroy.warning("Could not save {}.".format(what))
log.destroy.debug(e)
# If we don't kill our custom handler here we might get segfaults
2014-07-30 18:11:35 +02:00
log.destroy.debug("Deactiving 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...")
# We use a singleshot timer to exit here to minimize the likelyhood of
# segfaults.
2014-08-26 19:10:14 +02:00
QTimer.singleShot(0, functools.partial(self.exit, status))
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:
print("Now logging late shutdown.", file=sys.stderr)
debug.trace_lines(True)
2014-08-02 19:40:24 +02:00
super().exit(status)