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-15 12:20:03 +02:00
|
|
|
import faulthandler
|
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
|
2014-05-09 11:06:05 +02:00
|
|
|
|
2014-04-21 22:29:57 +02:00
|
|
|
from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
|
2014-06-03 15:19:48 +02:00
|
|
|
from PyQt5.QtCore import (pyqtSlot, QTimer, QEventLoop, Qt, QStandardPaths,
|
2014-06-20 16:33:01 +02:00
|
|
|
qInstallMessageHandler, QObject, QUrl)
|
2014-01-20 15:58:49 +01:00
|
|
|
|
2014-02-17 08:56:33 +01:00
|
|
|
import qutebrowser
|
2014-08-26 20:48:39 +02:00
|
|
|
from qutebrowser.commands import userscripts, runners, cmdutils
|
2014-08-26 19:10:14 +02:00
|
|
|
from qutebrowser.config import (style, config, websettings, iniparsers,
|
2014-08-26 21:41:41 +02:00
|
|
|
lineparser, configtypes)
|
2014-08-26 19:10:14 +02:00
|
|
|
from qutebrowser.network import qutescheme, proxy
|
2014-09-01 19:42:21 +02:00
|
|
|
from qutebrowser.browser import quickmarks, cookies, downloads, cache
|
2014-08-26 19:10:14 +02:00
|
|
|
from qutebrowser.widgets import mainwindow, console, crash
|
|
|
|
from qutebrowser.keyinput import modeparsers, keyparser, modeman
|
2014-08-26 20:25:11 +02:00
|
|
|
from qutebrowser.utils import (log, version, message, utilcmds, readline,
|
2014-09-15 21:55:38 +02:00
|
|
|
utils, qtutils, urlutils, debug)
|
2014-08-26 19:10:14 +02:00
|
|
|
from qutebrowser.utils import usertypes as utypes
|
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:
|
|
|
|
mainwindow: The MainWindow QWidget.
|
2014-08-07 14:43:45 +02:00
|
|
|
debugconsole: The ConsoleWidget for debugging.
|
2014-08-06 15:38:25 +02:00
|
|
|
commandrunner: The main CommandRunner instance.
|
|
|
|
searchrunner: The main SearchRunner instance.
|
2014-05-05 20:12:20 +02:00
|
|
|
config: The main ConfigManager
|
|
|
|
stateconfig: The "state" ReadWriteConfigParser instance.
|
|
|
|
cmd_history: The "cmd_history" LineConfigParser instance.
|
|
|
|
messagebridge: The global MessageBridge instance.
|
|
|
|
modeman: The global ModeManager instance.
|
|
|
|
cookiejar: The global CookieJar instance.
|
2014-09-01 19:42:21 +02:00
|
|
|
cache: The global DiskCache instance.
|
2014-05-22 15:44:16 +02:00
|
|
|
rl_bridge: The ReadlineBridge being used.
|
2014-06-16 09:44:11 +02:00
|
|
|
args: ArgumentParser instance.
|
2014-04-21 15:20:41 +02:00
|
|
|
_keyparsers: A mapping from modes to keyparsers.
|
2014-02-18 16:38:13 +01:00
|
|
|
_timers: List of used QTimers so they don't get GCed.
|
|
|
|
_shutting_down: True if we're currently shutting down.
|
|
|
|
_quit_status: The current quitting status.
|
2014-05-15 12:20:03 +02:00
|
|
|
_crashdlg: The crash dialog currently open.
|
|
|
|
_crashlogfile: A file handler to the fatal crash logfile.
|
2014-02-18 16:38:13 +01:00
|
|
|
"""
|
2014-01-20 15:58:49 +01:00
|
|
|
|
2014-06-04 13:51:47 +02:00
|
|
|
def __init__(self, args):
|
|
|
|
"""Constructor.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
Argument namespace from argparse.
|
|
|
|
"""
|
2014-06-23 07:12:19 +02:00
|
|
|
# pylint: disable=too-many-statements
|
2014-08-05 22:33:08 +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-08-26 19:23:06 +02:00
|
|
|
qt_args = qtutils.get_args(args)
|
2014-06-10 11:54:14 +02:00
|
|
|
log.init.debug("Qt arguments: {}, based on {}".format(qt_args, args))
|
2014-08-26 19:10:14 +02:00
|
|
|
super().__init__(qt_args)
|
2014-05-06 10:53:38 +02:00
|
|
|
self._quit_status = {
|
|
|
|
'crash': True,
|
|
|
|
'tabs': False,
|
2014-05-15 15:26:20 +02:00
|
|
|
'main': False,
|
2014-05-06 10:53:38 +02:00
|
|
|
}
|
2014-02-18 16:38:13 +01:00
|
|
|
self._timers = []
|
|
|
|
self._shutting_down = False
|
2014-05-05 17:56:14 +02:00
|
|
|
self._keyparsers = None
|
2014-05-15 12:20:03 +02:00
|
|
|
self._crashdlg = None
|
|
|
|
self._crashlogfile = None
|
2014-05-22 15:44:16 +02:00
|
|
|
self.rl_bridge = None
|
2014-05-05 20:12:20 +02:00
|
|
|
self.messagebridge = None
|
|
|
|
self.stateconfig = None
|
|
|
|
self.modeman = None
|
|
|
|
self.cmd_history = None
|
|
|
|
self.config = None
|
2014-02-18 16:38:13 +01:00
|
|
|
|
2014-02-06 10:25:22 +01:00
|
|
|
sys.excepthook = self._exception_hook
|
2014-01-28 14:44:12 +01:00
|
|
|
|
2014-06-16 09:44:11 +02:00
|
|
|
self.args = args
|
2014-06-23 06:37:47 +02:00
|
|
|
log.init.debug("Starting init...")
|
2014-05-05 16:42:41 +02:00
|
|
|
self._init_misc()
|
2014-08-26 19:10:14 +02:00
|
|
|
utils.actute_warning()
|
2014-06-23 06:37:47 +02:00
|
|
|
log.init.debug("Initializing config...")
|
2014-05-05 16:42:41 +02:00
|
|
|
self._init_config()
|
2014-06-23 06:37:47 +02:00
|
|
|
log.init.debug("Initializing crashlog...")
|
2014-05-15 12:20:03 +02:00
|
|
|
self._handle_segfault()
|
2014-06-23 06:37:47 +02:00
|
|
|
log.init.debug("Initializing modes...")
|
2014-05-05 16:42:41 +02:00
|
|
|
self._init_modes()
|
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...")
|
2014-05-22 16:44:47 +02:00
|
|
|
quickmarks.init()
|
2014-06-23 06:37:47 +02:00
|
|
|
log.init.debug("Initializing proxy...")
|
2014-05-05 22:07:41 +02:00
|
|
|
proxy.init()
|
2014-07-29 02:05:15 +02:00
|
|
|
log.init.debug("Initializing userscripts...")
|
|
|
|
userscripts.init()
|
2014-07-30 17:05:52 +02:00
|
|
|
log.init.debug("Initializing utility commands...")
|
|
|
|
utilcmds.init()
|
2014-06-23 06:37:47 +02:00
|
|
|
log.init.debug("Initializing cookies...")
|
2014-08-26 19:10:14 +02:00
|
|
|
self.cookiejar = cookies.CookieJar(self)
|
2014-09-01 19:42:21 +02:00
|
|
|
log.init.debug("Initializing cache...")
|
|
|
|
self.cache = cache.DiskCache(self)
|
2014-06-23 06:37:47 +02:00
|
|
|
log.init.debug("Initializing commands...")
|
2014-08-26 19:10:14 +02:00
|
|
|
self.commandrunner = runners.CommandRunner()
|
2014-06-23 06:37:47 +02:00
|
|
|
log.init.debug("Initializing search...")
|
2014-08-26 19:10:14 +02:00
|
|
|
self.searchrunner = runners.SearchRunner(self)
|
2014-06-23 06:37:47 +02:00
|
|
|
log.init.debug("Initializing downloads...")
|
2014-08-26 19:10:14 +02:00
|
|
|
self.downloadmanager = downloads.DownloadManager(self)
|
2014-06-23 06:37:47 +02:00
|
|
|
log.init.debug("Initializing main window...")
|
2014-08-26 19:10:14 +02:00
|
|
|
self.mainwindow = mainwindow.MainWindow()
|
2014-05-07 17:20:01 +02:00
|
|
|
self.modeman.mainwindow = self.mainwindow
|
2014-08-07 14:43:45 +02:00
|
|
|
log.init.debug("Initializing debug console...")
|
2014-08-26 19:10:14 +02:00
|
|
|
self.debugconsole = console.ConsoleWidget()
|
2014-06-23 06:37:47 +02:00
|
|
|
log.init.debug("Initializing eventfilter...")
|
2014-05-05 20:12:20 +02:00
|
|
|
self.installEventFilter(self.modeman)
|
2014-05-05 16:42:41 +02:00
|
|
|
self.setQuitOnLastWindowClosed(False)
|
|
|
|
|
2014-06-23 06:37:47 +02:00
|
|
|
log.init.debug("Connecting signals...")
|
2014-05-05 16:42:41 +02:00
|
|
|
self._connect_signals()
|
2014-08-26 19:10:14 +02:00
|
|
|
self.modeman.enter(utypes.KeyMode.normal, 'init')
|
2014-05-05 16:42:41 +02:00
|
|
|
|
2014-06-23 06:37:47 +02:00
|
|
|
log.init.debug("Showing mainwindow...")
|
2014-08-02 00:47:04 +02:00
|
|
|
if not args.nowindow:
|
|
|
|
self.mainwindow.show()
|
2014-06-23 06:37:47 +02:00
|
|
|
log.init.debug("Applying python hacks...")
|
2014-05-05 16:42:41 +02:00
|
|
|
self._python_hacks()
|
|
|
|
timer = QTimer.singleShot(0, self._process_init_args)
|
|
|
|
self._timers.append(timer)
|
|
|
|
|
2014-06-23 06:37:47 +02:00
|
|
|
log.init.debug("Init done!")
|
|
|
|
|
2014-05-15 12:20:03 +02:00
|
|
|
if self._crashdlg is not None:
|
|
|
|
self._crashdlg.raise_()
|
|
|
|
|
2014-05-05 16:42:41 +02:00
|
|
|
def _init_config(self):
|
|
|
|
"""Inizialize and read the config."""
|
2014-06-16 09:44:11 +02:00
|
|
|
if self.args.confdir is None:
|
2014-08-26 19:10:14 +02:00
|
|
|
confdir = utils.get_standard_dir(QStandardPaths.ConfigLocation)
|
2014-06-16 09:44:11 +02:00
|
|
|
elif self.args.confdir == '':
|
2014-01-22 17:04:10 +01:00
|
|
|
confdir = None
|
|
|
|
else:
|
2014-06-16 09:44:11 +02:00
|
|
|
confdir = self.args.confdir
|
2014-04-21 22:29:57 +02:00
|
|
|
try:
|
2014-08-26 19:10:14 +02:00
|
|
|
self.config = config.ConfigManager(confdir, 'qutebrowser.conf',
|
|
|
|
self)
|
2014-08-26 21:41:41 +02:00
|
|
|
except (configtypes.ValidationError,
|
2014-05-01 21:20:17 +02:00
|
|
|
config.NoOptionError,
|
2014-07-10 22:38:09 +02:00
|
|
|
config.InterpolationSyntaxError,
|
2014-04-25 13:55:26 +02:00
|
|
|
configparser.InterpolationError,
|
|
|
|
configparser.DuplicateSectionError,
|
|
|
|
configparser.DuplicateOptionError,
|
2014-07-10 22:38:09 +02:00
|
|
|
configparser.ParsingError) as e:
|
2014-07-10 22:39:24 +02:00
|
|
|
log.init.exception(e)
|
2014-04-25 13:55:26 +02:00
|
|
|
errstr = "Error while reading config:"
|
|
|
|
if hasattr(e, 'section') and hasattr(e, 'option'):
|
|
|
|
errstr += "\n\n{} -> {}:".format(e.section, e.option)
|
|
|
|
errstr += "\n{}".format(e)
|
|
|
|
msgbox = QMessageBox(QMessageBox.Critical,
|
|
|
|
"Error while reading config!", errstr)
|
2014-04-21 22:29:57 +02:00
|
|
|
msgbox.exec_()
|
|
|
|
# We didn't really initialize much so far, so we just quit hard.
|
|
|
|
sys.exit(1)
|
2014-08-26 19:10:14 +02:00
|
|
|
self.stateconfig = iniparsers.ReadWriteConfigParser(confdir, 'state')
|
|
|
|
self.cmd_history = lineparser.LineConfigParser(
|
|
|
|
confdir, 'cmd_history', ('completion', 'history-length'))
|
2014-01-20 12:26:02 +01:00
|
|
|
|
2014-05-05 16:42:41 +02:00
|
|
|
def _init_modes(self):
|
|
|
|
"""Inizialize the mode manager and the keyparsers."""
|
2014-04-21 15:20:41 +02:00
|
|
|
self._keyparsers = {
|
2014-08-26 19:10:14 +02:00
|
|
|
utypes.KeyMode.normal:
|
|
|
|
modeparsers.NormalKeyParser(self),
|
|
|
|
utypes.KeyMode.hint:
|
|
|
|
modeparsers.HintKeyParser(self),
|
|
|
|
utypes.KeyMode.insert:
|
|
|
|
keyparser.PassthroughKeyParser('keybind.insert', self),
|
|
|
|
utypes.KeyMode.passthrough:
|
|
|
|
keyparser.PassthroughKeyParser('keybind.passthrough', self),
|
|
|
|
utypes.KeyMode.command:
|
|
|
|
keyparser.PassthroughKeyParser('keybind.command', self),
|
|
|
|
utypes.KeyMode.prompt:
|
|
|
|
keyparser.PassthroughKeyParser('keybind.prompt', self,
|
|
|
|
warn=False),
|
|
|
|
utypes.KeyMode.yesno:
|
|
|
|
modeparsers.PromptKeyParser(self),
|
2014-04-21 15:20:41 +02:00
|
|
|
}
|
2014-08-26 19:10:14 +02:00
|
|
|
self.modeman = modeman.ModeManager(self)
|
|
|
|
self.modeman.register(utypes.KeyMode.normal,
|
|
|
|
self._keyparsers[utypes.KeyMode.normal].handle)
|
|
|
|
self.modeman.register(utypes.KeyMode.hint,
|
|
|
|
self._keyparsers[utypes.KeyMode.hint].handle)
|
|
|
|
self.modeman.register(utypes.KeyMode.insert,
|
|
|
|
self._keyparsers[utypes.KeyMode.insert].handle,
|
2014-05-05 20:12:20 +02:00
|
|
|
passthrough=True)
|
2014-08-26 19:10:14 +02:00
|
|
|
self.modeman.register(
|
|
|
|
utypes.KeyMode.passthrough,
|
|
|
|
self._keyparsers[utypes.KeyMode.passthrough].handle,
|
|
|
|
passthrough=True)
|
|
|
|
self.modeman.register(utypes.KeyMode.command,
|
|
|
|
self._keyparsers[utypes.KeyMode.command].handle,
|
2014-05-05 20:12:20 +02:00
|
|
|
passthrough=True)
|
2014-08-26 19:10:14 +02:00
|
|
|
self.modeman.register(utypes.KeyMode.prompt,
|
|
|
|
self._keyparsers[utypes.KeyMode.prompt].handle,
|
2014-05-05 20:12:20 +02:00
|
|
|
passthrough=True)
|
2014-08-26 19:10:14 +02:00
|
|
|
self.modeman.register(utypes.KeyMode.yesno,
|
|
|
|
self._keyparsers[utypes.KeyMode.yesno].handle)
|
2014-02-18 17:54:17 +01:00
|
|
|
|
2014-05-05 16:42:41 +02:00
|
|
|
def _init_misc(self):
|
2014-02-18 17:54:17 +01:00
|
|
|
"""Initialize misc things."""
|
2014-06-16 09:44:11 +02:00
|
|
|
if self.args.version:
|
2014-05-27 15:46:21 +02:00
|
|
|
print(version.version())
|
|
|
|
print()
|
|
|
|
print()
|
|
|
|
print(qutebrowser.__copyright__)
|
|
|
|
print()
|
|
|
|
print(version.GPL_BOILERPLATE.strip())
|
|
|
|
sys.exit(0)
|
2014-05-26 11:47:02 +02:00
|
|
|
self.setOrganizationName("qutebrowser")
|
2014-02-18 17:54:17 +01:00
|
|
|
self.setApplicationName("qutebrowser")
|
|
|
|
self.setApplicationVersion(qutebrowser.__version__)
|
2014-06-20 16:33:01 +02:00
|
|
|
self.messagebridge = message.MessageBridge(self)
|
2014-08-26 19:10:14 +02:00
|
|
|
self.rl_bridge = readline.ReadlineBridge()
|
2014-02-18 17:54:17 +01:00
|
|
|
|
2014-05-15 12:20:03 +02:00
|
|
|
def _handle_segfault(self):
|
|
|
|
"""Handle a segfault from a previous run."""
|
2014-05-15 15:26:20 +02:00
|
|
|
# FIXME If an empty logfile exists, we log to stdout instead, which is
|
|
|
|
# the only way to not break multiple instances.
|
|
|
|
# However this also means if the logfile is there for some weird
|
|
|
|
# reason, we'll *always* log to stderr, but that's still better than no
|
|
|
|
# dialogs at all.
|
2014-08-26 19:10:14 +02:00
|
|
|
path = utils.get_standard_dir(QStandardPaths.DataLocation)
|
|
|
|
logname = os.path.join(path, 'crash.log')
|
2014-05-15 12:20:03 +02:00
|
|
|
# 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:
|
2014-05-15 12:20:03 +02:00
|
|
|
data = f.read()
|
|
|
|
if data:
|
2014-05-15 15:26:20 +02:00
|
|
|
# Crashlog exists and has data in it, so something crashed
|
|
|
|
# previously.
|
|
|
|
try:
|
|
|
|
os.remove(logname)
|
|
|
|
except PermissionError:
|
2014-06-22 23:30:44 +02:00
|
|
|
log.init.warning("Could not remove crash log!")
|
2014-05-15 15:26:20 +02:00
|
|
|
else:
|
|
|
|
self._init_crashlogfile()
|
2014-08-26 19:10:14 +02:00
|
|
|
self._crashdlg = crash.FatalCrashDialog(data)
|
2014-05-15 12:20:03 +02:00
|
|
|
self._crashdlg.show()
|
2014-05-15 15:26:20 +02:00
|
|
|
else:
|
|
|
|
# Crashlog exists but without data.
|
|
|
|
# This means another instance is probably still running and
|
|
|
|
# didn't remove the file. As we can't write to the same file,
|
|
|
|
# we just leave faulthandler as it is and log to stderr.
|
2014-06-22 23:30:44 +02:00
|
|
|
log.init.warning("Empty crash log detected. This means either "
|
|
|
|
"another instance is running (then ignore "
|
|
|
|
"this warning) or the file is lying here "
|
|
|
|
"because of some earlier crash (then delete "
|
|
|
|
"{}).".format(logname))
|
2014-05-15 15:26:20 +02:00
|
|
|
self._crashlogfile = None
|
|
|
|
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."""
|
2014-08-26 19:10:14 +02:00
|
|
|
path = utils.get_standard_dir(QStandardPaths.DataLocation)
|
|
|
|
logname = os.path.join(path, 'crash.log')
|
2014-08-20 20:33:14 +02:00
|
|
|
self._crashlogfile = open(logname, 'w', encoding='ascii')
|
2014-05-15 12:20:03 +02:00
|
|
|
faulthandler.enable(self._crashlogfile)
|
2014-05-15 15:26:20 +02:00
|
|
|
if (hasattr(faulthandler, 'register') and
|
|
|
|
hasattr(signal, 'SIGUSR1')):
|
2014-05-15 12:20:03 +02:00
|
|
|
# If available, we also want a traceback on SIGUSR1.
|
2014-05-15 15:26:20 +02:00
|
|
|
# pylint: disable=no-member
|
|
|
|
faulthandler.register(signal.SIGUSR1)
|
2014-05-15 12:20:03 +02:00
|
|
|
|
2014-01-30 20:41:54 +01:00
|
|
|
def _process_init_args(self):
|
|
|
|
"""Process initial positional args.
|
|
|
|
|
|
|
|
URLs to open have no prefix, commands to execute begin with a colon.
|
|
|
|
"""
|
2014-02-17 17:47:21 +01:00
|
|
|
# QNetworkAccessManager::createRequest will hang for over a second, so
|
|
|
|
# we make sure the GUI is refreshed here, so the start seems faster.
|
|
|
|
self.processEvents(QEventLoop.ExcludeUserInputEvents |
|
|
|
|
QEventLoop.ExcludeSocketNotifiers)
|
2014-01-30 20:41:54 +01:00
|
|
|
|
2014-06-20 16:33:01 +02:00
|
|
|
for cmd in self.args.command:
|
|
|
|
if cmd.startswith(':'):
|
|
|
|
log.init.debug("Startup cmd {}".format(cmd))
|
2014-08-06 15:38:25 +02:00
|
|
|
self.commandrunner.run_safely_init(cmd.lstrip(':'))
|
2014-01-30 20:41:54 +01:00
|
|
|
else:
|
2014-06-20 16:33:01 +02:00
|
|
|
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-06-20 16:33:01 +02:00
|
|
|
message.error("Error in startup argument '{}': {}".format(
|
|
|
|
cmd, e))
|
|
|
|
else:
|
|
|
|
self.mainwindow.tabs.tabopen(url)
|
2014-01-30 20:41:54 +01:00
|
|
|
|
2014-02-21 20:06:42 +01:00
|
|
|
if self.mainwindow.tabs.count() == 0:
|
2014-05-23 16:11:55 +02:00
|
|
|
log.init.debug("Opening startpage")
|
2014-06-20 16:33:01 +02:00
|
|
|
for urlstr in self.config.get('general', 'startpage'):
|
|
|
|
try:
|
|
|
|
url = urlutils.fuzzy_url(urlstr)
|
2014-06-20 23:57:52 +02:00
|
|
|
except urlutils.FuzzyUrlError as e:
|
2014-06-20 16:33:01 +02:00
|
|
|
message.error("Error when opening startpage: {}".format(e))
|
|
|
|
else:
|
|
|
|
self.mainwindow.tabs.tabopen(url)
|
2014-01-29 15:30:19 +01:00
|
|
|
|
2014-02-18 17:54:17 +01:00
|
|
|
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-08-26 19:10:14 +02:00
|
|
|
timer = utypes.Timer(self, 'python_hacks')
|
2014-02-18 17:54:17 +01:00
|
|
|
timer.start(500)
|
|
|
|
timer.timeout.connect(lambda: None)
|
|
|
|
self._timers.append(timer)
|
|
|
|
|
2014-04-21 15:20:41 +02:00
|
|
|
def _connect_signals(self):
|
|
|
|
"""Connect all signals to their slots."""
|
2014-06-23 20:33:41 +02:00
|
|
|
# pylint: disable=too-many-statements
|
2014-04-22 10:34:43 +02:00
|
|
|
# syntactic sugar
|
|
|
|
kp = self._keyparsers
|
|
|
|
status = self.mainwindow.status
|
|
|
|
completion = self.mainwindow.completion
|
|
|
|
tabs = self.mainwindow.tabs
|
|
|
|
cmd = self.mainwindow.status.cmd
|
2014-06-03 13:37:11 +02:00
|
|
|
completer = self.mainwindow.completion.completer
|
2014-04-22 10:34:43 +02:00
|
|
|
|
|
|
|
# misc
|
2014-04-21 15:20:41 +02:00
|
|
|
self.lastWindowClosed.connect(self.shutdown)
|
2014-04-22 10:34:43 +02:00
|
|
|
tabs.quit.connect(self.shutdown)
|
|
|
|
|
|
|
|
# status bar
|
2014-05-05 20:12:20 +02:00
|
|
|
self.modeman.entered.connect(status.on_mode_entered)
|
|
|
|
self.modeman.left.connect(status.on_mode_left)
|
|
|
|
self.modeman.left.connect(status.cmd.on_mode_left)
|
2014-07-02 07:07:03 +02:00
|
|
|
self.modeman.left.connect(status.prompt.prompter.on_mode_left)
|
2014-04-22 10:34:43 +02:00
|
|
|
|
|
|
|
# commands
|
2014-08-06 15:38:25 +02:00
|
|
|
cmd.got_cmd.connect(self.commandrunner.run_safely)
|
|
|
|
cmd.got_search.connect(self.searchrunner.search)
|
|
|
|
cmd.got_search_rev.connect(self.searchrunner.search_rev)
|
2014-04-22 10:34:43 +02:00
|
|
|
cmd.returnPressed.connect(tabs.setFocus)
|
2014-08-06 15:38:25 +02:00
|
|
|
self.searchrunner.do_search.connect(tabs.search)
|
2014-08-26 19:10:14 +02:00
|
|
|
kp[utypes.KeyMode.normal].keystring_updated.connect(
|
|
|
|
status.keystring.setText)
|
2014-08-06 15:38:25 +02:00
|
|
|
tabs.got_cmd.connect(self.commandrunner.run_safely)
|
2014-04-22 10:34:43 +02:00
|
|
|
|
|
|
|
# hints
|
2014-08-26 19:10:14 +02:00
|
|
|
kp[utypes.KeyMode.hint].fire_hint.connect(tabs.fire_hint)
|
|
|
|
kp[utypes.KeyMode.hint].filter_hints.connect(tabs.filter_hints)
|
|
|
|
kp[utypes.KeyMode.hint].keystring_updated.connect(tabs.handle_hint_key)
|
2014-07-28 22:40:58 +02:00
|
|
|
tabs.hint_strings_updated.connect(
|
2014-08-26 19:10:14 +02:00
|
|
|
kp[utypes.KeyMode.hint].on_hint_strings_updated)
|
2014-04-22 10:34:43 +02:00
|
|
|
|
|
|
|
# messages
|
2014-06-26 07:58:00 +02:00
|
|
|
self.messagebridge.s_error.connect(status.disp_error)
|
|
|
|
self.messagebridge.s_info.connect(status.disp_temp_text)
|
|
|
|
self.messagebridge.s_set_text.connect(status.set_text)
|
2014-09-03 11:51:24 +02:00
|
|
|
self.messagebridge.s_maybe_reset_text.connect(
|
|
|
|
status.txt.maybe_reset_text)
|
2014-06-26 07:58:00 +02:00
|
|
|
self.messagebridge.s_set_cmd_text.connect(cmd.set_cmd_text)
|
2014-07-02 07:07:03 +02:00
|
|
|
self.messagebridge.s_question.connect(
|
|
|
|
status.prompt.prompter.ask_question, Qt.DirectConnection)
|
2014-04-22 10:34:43 +02:00
|
|
|
|
|
|
|
# config
|
2014-08-28 09:51:54 +02:00
|
|
|
self.config.style_changed.connect(style.get_stylesheet.cache_clear)
|
2014-06-06 17:12:54 +02:00
|
|
|
for obj in (tabs, completion, self.mainwindow, self.cmd_history,
|
2014-08-26 19:10:14 +02:00
|
|
|
websettings, kp[utypes.KeyMode.normal], self.modeman,
|
|
|
|
status, status.txt):
|
2014-05-05 20:12:20 +02:00
|
|
|
self.config.changed.connect(obj.on_config_changed)
|
2014-04-22 10:34:43 +02:00
|
|
|
|
|
|
|
# statusbar
|
2014-06-03 17:59:15 +02:00
|
|
|
# FIXME some of these probably only should be triggered on mainframe
|
|
|
|
# loadStarted.
|
2014-06-16 22:49:22 +02:00
|
|
|
tabs.current_tab_changed.connect(status.prog.on_tab_changed)
|
2014-04-22 10:34:43 +02:00
|
|
|
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)
|
2014-05-15 17:57:08 +02:00
|
|
|
|
2014-06-16 22:49:22 +02:00
|
|
|
tabs.current_tab_changed.connect(status.percentage.on_tab_changed)
|
2014-04-22 10:34:43 +02:00
|
|
|
tabs.cur_scroll_perc_changed.connect(status.percentage.set_perc)
|
2014-05-15 17:57:08 +02:00
|
|
|
|
2014-06-16 22:49:22 +02:00
|
|
|
tabs.current_tab_changed.connect(status.txt.on_tab_changed)
|
2014-06-03 17:59:15 +02:00
|
|
|
tabs.cur_statusbar_message.connect(status.txt.on_statusbar_message)
|
|
|
|
tabs.cur_load_started.connect(status.txt.on_load_started)
|
2014-05-15 17:57:08 +02:00
|
|
|
|
2014-06-16 22:49:22 +02:00
|
|
|
tabs.current_tab_changed.connect(status.url.on_tab_changed)
|
2014-05-15 22:31:01 +02:00
|
|
|
tabs.cur_url_text_changed.connect(status.url.set_url)
|
2014-04-22 10:34:43 +02:00
|
|
|
tabs.cur_link_hovered.connect(status.url.set_hover_url)
|
2014-05-16 07:11:39 +02:00
|
|
|
tabs.cur_load_status_changed.connect(status.url.on_load_status_changed)
|
2014-04-22 10:34:43 +02:00
|
|
|
|
|
|
|
# command input / completion
|
2014-05-05 20:12:20 +02:00
|
|
|
self.modeman.left.connect(tabs.on_mode_left)
|
2014-04-22 10:34:43 +02:00
|
|
|
cmd.clear_completion_selection.connect(
|
|
|
|
completion.on_clear_completion_selection)
|
|
|
|
cmd.hide_completion.connect(completion.hide)
|
2014-06-03 13:37:11 +02:00
|
|
|
cmd.update_completion.connect(completer.on_update_completion)
|
|
|
|
completer.change_completed_part.connect(cmd.on_change_completed_part)
|
2014-04-21 15:20:41 +02:00
|
|
|
|
2014-06-10 22:11:17 +02:00
|
|
|
# downloads
|
|
|
|
tabs.start_download.connect(self.downloadmanager.fetch)
|
2014-06-23 17:30:28 +02:00
|
|
|
tabs.download_get.connect(self.downloadmanager.get)
|
2014-06-10 22:11:17 +02:00
|
|
|
|
2014-06-17 23:04:58 +02:00
|
|
|
def get_all_widgets(self):
|
|
|
|
"""Get a string list of all widgets."""
|
|
|
|
lines = []
|
|
|
|
widgets = self.allWidgets()
|
|
|
|
widgets.sort(key=lambda e: repr(e))
|
|
|
|
lines.append("{} widgets".format(len(widgets)))
|
|
|
|
for w in widgets:
|
|
|
|
lines.append(repr(w))
|
|
|
|
return '\n'.join(lines)
|
|
|
|
|
|
|
|
def get_all_objects(self, obj=None, depth=0, lines=None):
|
|
|
|
"""Get all children of an object recursively as a string."""
|
|
|
|
if lines is None:
|
|
|
|
lines = []
|
|
|
|
if obj is None:
|
|
|
|
obj = self
|
|
|
|
for kid in obj.findChildren(QObject):
|
|
|
|
lines.append(' ' * depth + repr(kid))
|
|
|
|
self.get_all_objects(kid, depth + 1, lines)
|
|
|
|
if depth == 0:
|
|
|
|
lines.insert(0, '{} objects:'.format(len(lines)))
|
|
|
|
return '\n'.join(lines)
|
|
|
|
|
2014-02-17 20:30:09 +01:00
|
|
|
def _recover_pages(self):
|
|
|
|
"""Try to recover all open pages.
|
|
|
|
|
|
|
|
Called from _exception_hook, so as forgiving as possible.
|
|
|
|
|
2014-02-19 10:58:32 +01:00
|
|
|
Return:
|
|
|
|
A list of open pages, or an empty list.
|
2014-02-17 20:30:09 +01:00
|
|
|
"""
|
|
|
|
pages = []
|
|
|
|
if self.mainwindow is None:
|
|
|
|
return pages
|
|
|
|
if self.mainwindow.tabs is None:
|
|
|
|
return pages
|
2014-09-02 21:54:07 +02:00
|
|
|
for tab in self.mainwindow.tabs.widgets():
|
2014-02-17 20:30:09 +01:00
|
|
|
try:
|
2014-08-02 19:49:15 +02:00
|
|
|
url = tab.cur_url.toString(
|
|
|
|
QUrl.RemovePassword | QUrl.FullyEncoded)
|
2014-02-17 20:30:09 +01:00
|
|
|
if url:
|
|
|
|
pages.append(url)
|
2014-06-20 23:26:19 +02:00
|
|
|
except Exception as e: # pylint: disable=broad-except
|
2014-06-21 22:40:31 +02:00
|
|
|
log.destroy.debug("Error while recovering tab: {}: {}".format(
|
|
|
|
e.__class__.__name__, e))
|
2014-02-17 20:30:09 +01:00
|
|
|
return pages
|
|
|
|
|
2014-02-18 17:54:17 +01:00
|
|
|
def _save_geometry(self):
|
|
|
|
"""Save the window geometry to the state config."""
|
2014-08-26 19:10:14 +02:00
|
|
|
data = bytes(self.mainwindow.saveGeometry())
|
|
|
|
geom = base64.b64encode(data).decode('ASCII')
|
2014-02-18 17:54:17 +01:00
|
|
|
try:
|
2014-05-05 20:12:20 +02:00
|
|
|
self.stateconfig.add_section('geometry')
|
2014-02-18 17:54:17 +01:00
|
|
|
except configparser.DuplicateSectionError:
|
|
|
|
pass
|
2014-05-05 20:12:20 +02:00
|
|
|
self.stateconfig['geometry']['mainwindow'] = geom
|
2014-02-18 17:54:17 +01:00
|
|
|
|
2014-06-17 22:37:06 +02:00
|
|
|
def _destroy_crashlogfile(self):
|
|
|
|
"""Clean up the crash log file and delete it."""
|
|
|
|
if self._crashlogfile is None:
|
|
|
|
return
|
2014-08-01 00:55:18 +02:00
|
|
|
# We use sys.__stderr__ instead of sys.stderr here so this will still
|
|
|
|
# work when sys.stderr got replaced, e.g. by "Python Tools for Visual
|
|
|
|
# Studio".
|
|
|
|
if sys.__stderr__ is not None:
|
|
|
|
faulthandler.enable(sys.__stderr__)
|
2014-06-17 22:37:06 +02:00
|
|
|
else:
|
|
|
|
faulthandler.disable()
|
|
|
|
self._crashlogfile.close()
|
|
|
|
try:
|
|
|
|
os.remove(self._crashlogfile.name)
|
2014-07-30 18:45:56 +02:00
|
|
|
except (PermissionError, FileNotFoundError) as e:
|
2014-06-22 23:30:44 +02:00
|
|
|
log.destroy.warning("Could not remove crash log ({})!".format(e))
|
2014-06-17 22:37:06 +02:00
|
|
|
|
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
|
2014-06-20 23:26:19 +02:00
|
|
|
except Exception as e:
|
2014-06-21 22:40:31 +02:00
|
|
|
log.init.debug("Error while shutting down: {}: {}".format(
|
|
|
|
e.__class__.__name__, e))
|
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
|
|
|
|
|
|
|
self._quit_status['crash'] = False
|
|
|
|
|
2014-01-28 14:44:12 +01:00
|
|
|
try:
|
2014-02-17 20:30:09 +01:00
|
|
|
pages = self._recover_pages()
|
2014-06-20 23:26:19 +02:00
|
|
|
except Exception as e:
|
2014-06-21 22:40:31 +02:00
|
|
|
log.destroy.debug("Error while recovering pages: {}: {}".format(
|
|
|
|
e.__class__.__name__, e))
|
2014-02-17 20:30:09 +01:00
|
|
|
pages = []
|
2014-01-30 20:42:47 +01:00
|
|
|
|
|
|
|
try:
|
|
|
|
history = self.mainwindow.status.cmd.history[-5:]
|
2014-06-20 23:26:19 +02:00
|
|
|
except Exception as e:
|
2014-06-21 22:40:31 +02:00
|
|
|
log.destroy.debug("Error while getting history: {}: {}".format(
|
|
|
|
e.__class__.__name__, e))
|
2014-01-30 20:42:47 +01:00
|
|
|
history = []
|
|
|
|
|
2014-06-17 23:04:58 +02:00
|
|
|
try:
|
|
|
|
widgets = self.get_all_widgets()
|
2014-06-20 23:26:19 +02:00
|
|
|
except Exception as e:
|
2014-06-21 22:40:31 +02:00
|
|
|
log.destroy.debug("Error while getting widgets: {}: {}".format(
|
|
|
|
e.__class__.__name__, e))
|
2014-06-17 23:04:58 +02:00
|
|
|
widgets = ""
|
|
|
|
|
|
|
|
try:
|
|
|
|
objects = self.get_all_objects()
|
2014-06-20 23:26:19 +02:00
|
|
|
except Exception as e:
|
2014-06-21 22:40:31 +02:00
|
|
|
log.destroy.debug("Error while getting objects: {}: {}".format(
|
|
|
|
e.__class__.__name__, e))
|
2014-06-17 23:04:58 +02:00
|
|
|
objects = ""
|
|
|
|
|
2014-02-17 14:17:56 +01:00
|
|
|
try:
|
|
|
|
self.lastWindowClosed.disconnect(self.shutdown)
|
2014-06-20 23:26:19 +02:00
|
|
|
except TypeError as e:
|
2014-06-21 22:40:31 +02:00
|
|
|
log.destroy.debug("Error while preventing shutdown: {}: {}".format(
|
|
|
|
e.__class__.__name__, e))
|
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,
|
|
|
|
widgets, objects)
|
2014-05-15 12:20:03 +02:00
|
|
|
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
|
2014-06-17 22:37:06 +02:00
|
|
|
# run in some undefined state, so we only do the most needed shutdown
|
|
|
|
# here.
|
|
|
|
qInstallMessageHandler(None)
|
|
|
|
self._destroy_crashlogfile()
|
2014-05-07 17:29:28 +02:00
|
|
|
sys.exit(1)
|
2014-02-17 20:39:15 +01:00
|
|
|
|
2014-06-24 06:43:52 +02:00
|
|
|
@cmdutils.register(instance='', nargs=0)
|
|
|
|
def restart(self, shutdown=True, pages=None):
|
|
|
|
"""Restart qutebrowser while keeping existing tabs open."""
|
|
|
|
# We don't use _recover_pages here as it's too forgiving when
|
|
|
|
# exceptions occur.
|
|
|
|
if pages is None:
|
|
|
|
pages = []
|
2014-09-02 21:54:07 +02:00
|
|
|
for tab in self.mainwindow.tabs.widgets():
|
2014-08-02 19:49:15 +02:00
|
|
|
urlstr = tab.cur_url.toString(
|
|
|
|
QUrl.RemovePassword | QUrl.FullyEncoded)
|
2014-06-24 06:43:52 +02:00
|
|
|
if urlstr:
|
|
|
|
pages.append(urlstr)
|
|
|
|
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')))
|
2014-06-24 06:52:49 +02:00
|
|
|
if hasattr(sys, 'frozen'):
|
|
|
|
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.
|
|
|
|
args += pages
|
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
|
2014-06-24 06:52:49 +02:00
|
|
|
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-06-16 09:44:11 +02:00
|
|
|
@cmdutils.register(instance='', 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-07-16 20:09:41 +02:00
|
|
|
//
|
|
|
|
|
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)
|
2014-01-28 23:04:02 +01:00
|
|
|
except Exception as e: # pylint: disable=broad-except
|
2014-01-19 23:54:22 +01:00
|
|
|
out = ': '.join([e.__class__.__name__, str(e)])
|
2014-02-21 07:18:04 +01:00
|
|
|
qutescheme.pyeval_output = out
|
2014-06-20 16:33:01 +02:00
|
|
|
self.mainwindow.tabs.openurl(QUrl('qute:pyeval'), newtab=True)
|
2014-01-30 14:58:32 +01:00
|
|
|
|
2014-06-25 22:22:30 +02:00
|
|
|
@cmdutils.register(instance='')
|
|
|
|
def report(self):
|
|
|
|
"""Report a bug in qutebrowser."""
|
|
|
|
pages = self._recover_pages()
|
|
|
|
history = self.mainwindow.status.cmd.history[-5:]
|
|
|
|
widgets = self.get_all_widgets()
|
|
|
|
objects = self.get_all_objects()
|
2014-08-26 19:10:14 +02:00
|
|
|
self._crashdlg = crash.ReportDialog(pages, history, widgets, objects)
|
2014-06-25 22:22:30 +02:00
|
|
|
self._crashdlg.show()
|
|
|
|
|
2014-08-13 05:15:21 +02:00
|
|
|
@cmdutils.register(instance='', debug=True, name='debug-console')
|
2014-08-07 14:43:45 +02:00
|
|
|
def show_debugconsole(self):
|
|
|
|
"""Show the debugging console."""
|
|
|
|
self.debugconsole.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!")
|
2014-08-02 00:57:32 +02:00
|
|
|
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!")
|
2014-08-02 00:57:32 +02:00
|
|
|
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))
|
2014-08-02 00:57:32 +02:00
|
|
|
|
|
|
|
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.
|
|
|
|
"""
|
2014-08-02 00:57:32 +02:00
|
|
|
log.destroy.info("WHY ARE YOU DOING THIS TO ME? :(")
|
|
|
|
sys.exit(128 + signum)
|
2014-07-30 18:11:22 +02:00
|
|
|
|
2014-02-18 17:54:17 +01:00
|
|
|
@pyqtSlot()
|
2014-07-30 18:11:22 +02:00
|
|
|
def shutdown(self, status=0):
|
2014-02-18 17:54:17 +01:00
|
|
|
"""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.
|
2014-02-18 17:54:17 +01:00
|
|
|
"""
|
|
|
|
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-08-02 19:38:02 +02:00
|
|
|
if self.mainwindow.status.prompt.prompter.shutdown():
|
|
|
|
# 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))
|
2014-08-02 19:38:02 +02:00
|
|
|
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):
|
|
|
|
"""Second stage of shutdown."""
|
|
|
|
log.destroy.debug("Stage 2 of shutting down...")
|
2014-07-31 23:09:59 +02:00
|
|
|
# Remove eventfilter
|
|
|
|
if self.modeman is not None:
|
|
|
|
log.destroy.debug("Removing eventfilter...")
|
|
|
|
self.removeEventFilter(self.modeman)
|
2014-07-31 20:40:21 +02:00
|
|
|
# Close all tabs
|
2014-08-02 01:12:05 +02:00
|
|
|
if self.mainwindow is not None:
|
|
|
|
log.destroy.debug("Closing tabs...")
|
|
|
|
self.mainwindow.tabs.shutdown()
|
2014-06-03 16:48:21 +02:00
|
|
|
# Save everything
|
2014-07-30 18:21:40 +02:00
|
|
|
if hasattr(self, 'config') and self.config is not None:
|
2014-07-30 18:13:36 +02:00
|
|
|
to_save = []
|
|
|
|
if self.config.get('general', 'auto-save-config'):
|
|
|
|
to_save.append(("config", self.config.save))
|
2014-07-30 18:21:40 +02:00
|
|
|
to_save += [("window geometry", self._save_geometry),
|
2014-07-30 18:13:36 +02:00
|
|
|
("quickmarks", quickmarks.save)]
|
2014-07-30 18:21:40 +02:00
|
|
|
if hasattr(self, 'cmd_history'):
|
|
|
|
to_save.append(("command history", self.cmd_history.save))
|
|
|
|
if hasattr(self, 'stateconfig'):
|
|
|
|
to_save.append(("window geometry", self.stateconfig.save))
|
|
|
|
if hasattr(self, 'cookiejar'):
|
|
|
|
to_save.append(("cookies", self.cookiejar.save))
|
2014-07-30 18:13:36 +02:00
|
|
|
for what, handler in to_save:
|
|
|
|
log.destroy.debug("Saving {} (handler: {})".format(
|
|
|
|
what, handler.__qualname__))
|
|
|
|
try:
|
|
|
|
handler()
|
|
|
|
except AttributeError as e:
|
|
|
|
log.destroy.warning("Could not save {}.".format(what))
|
|
|
|
log.destroy.debug(e)
|
|
|
|
else:
|
|
|
|
log.destroy.debug("Config not initialized yet, so not saving "
|
|
|
|
"anything.")
|
2014-06-03 16:48:21 +02:00
|
|
|
# Re-enable faulthandler to stdout, then remove crash log
|
2014-07-30 18:11:35 +02:00
|
|
|
log.destroy.debug("Deactiving crash log...")
|
2014-06-17 22:37:06 +02:00
|
|
|
self._destroy_crashlogfile()
|
2014-06-03 15:19:48 +02:00
|
|
|
# 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...")
|
2014-06-03 15:19:48 +02:00
|
|
|
qInstallMessageHandler(None)
|
2014-07-30 18:13:36 +02:00
|
|
|
# Now we can hopefully quit without segfaults
|
2014-08-02 19:40:24 +02:00
|
|
|
log.destroy.debug("Deferring QApplication::exit...")
|
2014-08-01 23:26:33 +02:00
|
|
|
# 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-15 21:55:38 +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)
|