qutebrowser/qutebrowser/app.py

678 lines
26 KiB
Python
Raw Normal View History

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
### Things we want to do before normal imports ###
2014-05-13 19:46:57 +02:00
from qutebrowser.utils.checkpyver import check_python_version
check_python_version()
2014-05-13 18:01:10 +02:00
import qutebrowser.utils.earlyinit as earlyinit
earlyinit.init_faulthandler()
earlyinit.fix_harfbuzz()
earlyinit.check_pyqt_core()
earlyinit.check_pyqt_webkit()
2014-05-09 11:06:05 +02:00
import os
2014-05-13 18:01:10 +02:00
import sys
2014-05-15 10:33:11 +02:00
import types
2014-05-09 11:06:05 +02:00
import logging
import subprocess
import faulthandler
2014-05-09 11:06:05 +02:00
import configparser
from bdb import BdbQuit
from functools import partial
from signal import signal, SIGINT
from argparse import ArgumentParser
from base64 import b64encode
from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
2014-04-24 06:45:38 +02:00
from PyQt5.QtCore import pyqtSlot, QTimer, QEventLoop
2014-05-08 22:33:24 +02:00
from PyQt5.QtCore import QStandardPaths
2014-02-17 08:56:33 +01:00
import qutebrowser
import qutebrowser.commands.utils as cmdutils
import qutebrowser.config.style as style
2014-02-23 18:07:17 +01:00
import qutebrowser.config.config as config
2014-02-21 07:18:04 +01:00
import qutebrowser.network.qutescheme as qutescheme
2014-05-05 16:42:41 +02:00
import qutebrowser.config.websettings as websettings
2014-05-05 22:07:41 +02:00
import qutebrowser.network.proxy as proxy
2014-05-14 18:00:40 +02:00
import qutebrowser.utils.message as message
2014-05-05 17:56:14 +02:00
from qutebrowser.network.networkmanager import NetworkManager
from qutebrowser.config.config import ConfigManager
from qutebrowser.keyinput.modeman import ModeManager
from qutebrowser.widgets.mainwindow import MainWindow
from qutebrowser.widgets.crash import ExceptionCrashDialog, FatalCrashDialog
2014-04-25 12:21:01 +02:00
from qutebrowser.keyinput.modeparsers import NormalKeyParser, HintKeyParser
from qutebrowser.keyinput.keyparser import PassthroughKeyParser
from qutebrowser.commands.managers import CommandManager, SearchManager
2014-05-14 18:00:40 +02:00
from qutebrowser.commands.exceptions import CommandError
2014-05-05 17:56:14 +02:00
from qutebrowser.config.iniparsers import ReadWriteConfigParser
from qutebrowser.config.lineparser import LineConfigParser
from qutebrowser.browser.cookies import CookieJar
from qutebrowser.utils.message import MessageBridge
from qutebrowser.utils.misc import (dotted_getattr, get_standard_dir,
actute_warning)
2014-04-16 10:02:34 +02:00
from qutebrowser.utils.debug import set_trace # pylint: disable=unused-import
2013-12-14 22:15:16 +01:00
2014-01-28 23:04:02 +01:00
2014-01-19 18:20:57 +01:00
class QuteBrowser(QApplication):
2014-02-07 20:21:50 +01:00
2014-01-29 15:30:19 +01:00
"""Main object for qutebrowser.
Can be used like this:
>>> app = QuteBrowser()
>>> sys.exit(app.exec_())
2014-02-07 20:21:50 +01:00
2014-02-18 16:38:13 +01:00
Attributes:
mainwindow: The MainWindow QWidget.
commandmanager: The main CommandManager instance.
searchmanager: The main SearchManager instance.
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.
networkmanager: The global NetworkManager instance.
cookiejar: The global CookieJar instance.
2014-04-21 15:20:41 +02:00
_keyparsers: A mapping from modes to keyparsers.
2014-02-18 16:38:13 +01:00
_args: ArgumentParser instance.
_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-04-22 14:02:29 +02:00
_opened_urls: List of opened URLs.
_crashdlg: The crash dialog currently open.
_crashlogfile: A file handler to the fatal crash logfile.
2014-02-18 16:38:13 +01:00
"""
# This also holds all our globals, so we're a bit over the top here.
# pylint: disable=too-many-instance-attributes
2014-01-19 18:20:57 +01:00
def __init__(self):
super().__init__(sys.argv)
2014-05-06 10:53:38 +02:00
self._quit_status = {
'crash': True,
'tabs': False,
'networkmanager': False,
'main': False,
2014-05-06 10:53:38 +02:00
}
2014-02-18 16:38:13 +01:00
self._timers = []
2014-04-22 14:02:29 +02:00
self._opened_urls = []
2014-02-18 16:38:13 +01:00
self._shutting_down = False
2014-05-05 17:56:14 +02:00
self._keyparsers = None
self._crashdlg = None
self._crashlogfile = None
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-05-05 16:42:41 +02:00
self._args = self._parse_args()
self._init_log()
self._init_misc()
actute_warning()
2014-05-05 16:42:41 +02:00
self._init_config()
self._handle_segfault()
2014-05-05 16:42:41 +02:00
self._init_modes()
2014-05-08 22:33:24 +02:00
websettings.init()
2014-05-05 22:07:41 +02:00
proxy.init()
2014-05-08 22:33:24 +02:00
self.cookiejar = CookieJar()
self.networkmanager = NetworkManager(self.cookiejar)
self.commandmanager = CommandManager()
self.searchmanager = SearchManager()
2014-05-05 16:42:41 +02:00
self._init_cmds()
self.mainwindow = MainWindow()
self.modeman.mainwindow = self.mainwindow
self.installEventFilter(self.modeman)
2014-05-05 16:42:41 +02:00
self.setQuitOnLastWindowClosed(False)
self._connect_signals()
2014-05-09 16:03:46 +02:00
self.modeman.enter('normal', 'init')
2014-05-05 16:42:41 +02:00
self.mainwindow.show()
self._python_hacks()
timer = QTimer.singleShot(0, self._process_init_args)
self._timers.append(timer)
if self._crashdlg is not None:
self._crashdlg.raise_()
2014-05-05 16:42:41 +02:00
def _parse_args(self):
"""Parse command line options.
2014-01-17 20:03:21 +01:00
2014-05-05 16:42:41 +02:00
Return:
Argument namespace from argparse.
"""
parser = ArgumentParser("usage: %(prog)s [options]")
parser.add_argument('-l', '--log', dest='loglevel',
help="Set loglevel", default='info')
parser.add_argument('-c', '--confdir', help="Set config directory "
"(empty for no config storage)")
parser.add_argument('-d', '--debug', help="Turn on debugging options.",
action='store_true')
parser.add_argument('command', nargs='*', help="Commands to execute "
"on startup.", metavar=':command')
# URLs will actually be in command
parser.add_argument('url', nargs='*', help="URLs to open on startup.")
return parser.parse_args()
def _init_config(self):
"""Inizialize and read the config."""
2014-02-18 16:38:13 +01:00
if self._args.confdir is None:
2014-05-08 22:33:24 +02:00
confdir = get_standard_dir(QStandardPaths.ConfigLocation)
2014-02-18 16:38:13 +01:00
elif self._args.confdir == '':
confdir = None
else:
2014-02-18 16:38:13 +01:00
confdir = self._args.confdir
try:
self.config = ConfigManager(confdir, 'qutebrowser.conf')
2014-04-25 13:55:26 +02:00
except (config.ValidationError,
config.NoOptionError,
2014-04-25 13:55:26 +02:00
configparser.InterpolationError,
configparser.DuplicateSectionError,
configparser.DuplicateOptionError,
2014-05-01 19:57:14 +02:00
configparser.ParsingError,
ValueError) as 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)
msgbox.exec_()
# We didn't really initialize much so far, so we just quit hard.
sys.exit(1)
self.stateconfig = ReadWriteConfigParser(confdir, 'state')
self.cmd_history = 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 = {
'normal': NormalKeyParser(self),
2014-04-24 21:12:55 +02:00
'hint': HintKeyParser(self),
'insert': PassthroughKeyParser('keybind.insert', self),
2014-04-25 07:13:57 +02:00
'passthrough': PassthroughKeyParser('keybind.passthrough', self),
2014-04-25 07:50:21 +02:00
'command': PassthroughKeyParser('keybind.command', self),
2014-04-21 15:20:41 +02:00
}
self.modeman = ModeManager()
self.modeman.register('normal', self._keyparsers['normal'].handle)
self.modeman.register('hint', self._keyparsers['hint'].handle)
self.modeman.register('insert', self._keyparsers['insert'].handle,
passthrough=True)
self.modeman.register('passthrough',
self._keyparsers['passthrough'].handle,
passthrough=True)
self.modeman.register('command', self._keyparsers['command'].handle,
passthrough=True)
2014-05-05 16:42:41 +02:00
def _init_log(self):
2014-04-17 17:44:27 +02:00
"""Initialisation of the logging output.
Raise:
ValueError if there was an invalid loglevel.
"""
loglevel = 'debug' if self._args.debug else self._args.loglevel
numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int):
2014-04-25 16:53:23 +02:00
raise ValueError("Invalid log level: {}".format(loglevel))
logging.basicConfig(
level=numeric_level,
format='%(asctime)s [%(levelname)s] '
'[%(module)s:%(funcName)s:%(lineno)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
2014-05-05 16:42:41 +02:00
def _init_misc(self):
"""Initialize misc things."""
if self._args.debug:
os.environ['QT_FATAL_WARNINGS'] = '1'
self.setApplicationName("qutebrowser")
self.setApplicationVersion(qutebrowser.__version__)
self.messagebridge = MessageBridge()
def _handle_segfault(self):
"""Handle a segfault from a previous run."""
# 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.
logname = os.path.join(get_standard_dir(QStandardPaths.DataLocation),
'crash.log')
# First check if an old logfile exists.
if os.path.exists(logname):
with open(logname, 'r') as f:
data = f.read()
if data:
# Crashlog exists and has data in it, so something crashed
# previously.
try:
os.remove(logname)
except PermissionError:
logging.warn("Could not remove crash log!")
else:
self._init_crashlogfile()
self._crashdlg = FatalCrashDialog(data)
self._crashdlg.show()
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.
logging.warn("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 it).")
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."""
logname = os.path.join(get_standard_dir(QStandardPaths.DataLocation),
'crash.log')
self._crashlogfile = open(logname, 'w')
faulthandler.enable(self._crashlogfile)
if (hasattr(faulthandler, 'register') and
hasattr(signal, 'SIGUSR1')):
# If available, we also want a traceback on SIGUSR1.
# pylint: disable=no-member
faulthandler.register(signal.SIGUSR1)
def _init_cmds(self):
"""Initialisation of the qutebrowser commands.
2014-04-17 17:44:27 +02:00
Registers all commands and connects their signals.
"""
2014-03-03 21:06:10 +01:00
for key, cmd in sorted(cmdutils.cmd_dict.items()):
cmd.signal.connect(self.command_handler)
if cmd.instance is not None:
func = '.'.join([cmd.instance if cmd.instance else 'app',
cmd.handler.__name__])
else:
func = cmd.handler.__name__
logging.debug("Registered command: {} -> {}".format(key, func))
def _process_init_args(self):
"""Process initial positional args.
URLs to open have no prefix, commands to execute begin with a colon.
"""
# 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-02-18 16:38:13 +01:00
for e in self._args.command:
if e.startswith(':'):
2014-04-25 16:53:23 +02:00
logging.debug("Startup cmd {}".format(e))
self.commandmanager.run_safely(e.lstrip(':'))
else:
logging.debug("Startup URL {}".format(e))
2014-04-22 14:02:29 +02:00
self._opened_urls.append(e)
self.mainwindow.tabs.tabopen(e)
2014-02-21 20:06:42 +01:00
if self.mainwindow.tabs.count() == 0:
2014-04-25 16:53:23 +02:00
logging.debug("Opening startpage")
for url in self.config.get('general', 'startpage'):
self.mainwindow.tabs.tabopen(url)
2014-01-29 15:30:19 +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.
"""
signal(SIGINT, lambda *args: self.exit(128 + SIGINT))
timer = QTimer()
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-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
# 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)
tabs.currentChanged.connect(self.mainwindow.update_inspector)
# status bar
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-04-22 10:34:43 +02:00
# commands
cmd.got_cmd.connect(self.commandmanager.run_safely)
cmd.got_search.connect(self.searchmanager.search)
cmd.got_search_rev.connect(self.searchmanager.search_rev)
2014-04-22 10:34:43 +02:00
cmd.returnPressed.connect(tabs.setFocus)
self.searchmanager.do_search.connect(tabs.cur.search)
2014-04-25 16:53:23 +02:00
kp['normal'].keystring_updated.connect(status.keystring.setText)
2014-04-22 10:34:43 +02:00
# hints
kp['hint'].fire_hint.connect(tabs.cur.fire_hint)
kp['hint'].filter_hints.connect(tabs.cur.filter_hints)
kp['hint'].keystring_updated.connect(tabs.cur.handle_hint_key)
tabs.hint_strings_updated.connect(kp['hint'].on_hint_strings_updated)
2014-04-22 10:34:43 +02:00
# messages
self.messagebridge.error.connect(status.disp_error)
2014-05-16 15:33:36 +02:00
self.messagebridge.info.connect(status.disp_temp_text)
self.messagebridge.text.connect(status.set_text)
self.messagebridge.set_cmd_text.connect(cmd.set_cmd_text)
2014-04-22 10:34:43 +02:00
# config
self.config.style_changed.connect(style.invalidate_caches)
for obj in [tabs, completion, self.mainwindow, self.cmd_history,
2014-05-16 15:33:36 +02:00
websettings, kp['normal'], self.modeman, status]:
self.config.changed.connect(obj.on_config_changed)
2014-04-22 10:34:43 +02:00
# statusbar
2014-05-15 19:02:20 +02:00
tabs.currentChanged.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 19:02:20 +02:00
tabs.currentChanged.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-16 15:33:36 +02:00
tabs.cur_statusbar_message.connect(status.on_statusbar_message)
2014-05-15 19:02:20 +02:00
tabs.currentChanged.connect(status.url.on_tab_changed)
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)
tabs.cur_load_status_changed.connect(status.url.on_load_status_changed)
2014-04-22 10:34:43 +02:00
# command input / completion
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)
cmd.textEdited.connect(completion.on_cmd_text_changed)
2014-04-22 10:34:43 +02:00
completion.change_completed_part.connect(cmd.on_change_completed_part)
2014-04-21 15:20:41 +02:00
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-05-13 21:25:16 +02:00
for tab in self.mainwindow.tabs.widgets:
2014-02-17 20:30:09 +01:00
try:
2014-05-13 21:25:16 +02:00
url = tab.url().toString()
2014-02-17 20:30:09 +01:00
if url:
pages.append(url)
except Exception: # pylint: disable=broad-except
pass
return pages
def _save_geometry(self):
"""Save the window geometry to the state config."""
geom = b64encode(bytes(self.mainwindow.saveGeometry())).decode('ASCII')
try:
self.stateconfig.add_section('geometry')
except configparser.DuplicateSectionError:
pass
self.stateconfig['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
exc = (exctype, excvalue, tb)
2014-02-06 10:25:22 +01:00
sys.__excepthook__(*exc)
2014-01-30 20:42:47 +01:00
2014-05-06 12:11:00 +02:00
if exctype is BdbQuit or not issubclass(exctype, Exception):
# pdb exit, KeyboardInterrupt, ...
2014-02-17 20:30:09 +01:00
try:
self.shutdown()
return
except Exception:
self.quit()
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-01-28 14:44:12 +01:00
except Exception:
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:]
except Exception:
history = []
# Try to shutdown gracefully
try:
self.shutdown(do_quit=False)
except Exception:
pass
try:
self.lastWindowClosed.disconnect(self.shutdown)
except TypeError:
logging.exception("Preventing shutdown failed.")
2014-02-05 11:40:30 +01:00
QApplication.closeAllWindows()
self._crashdlg = ExceptionCrashDialog(pages, history, exc)
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.
sys.exit(1)
2014-02-17 20:39:15 +01:00
def _maybe_quit(self, sender):
"""Maybe quit qutebrowser.
This only quits if both the ExceptionCrashDialog was ready to quit AND
the shutdown is complete.
2014-02-17 22:21:11 +01:00
2014-02-19 10:58:32 +01:00
Args:
The sender of the quit signal (string)
2014-02-17 20:39:15 +01:00
"""
self._quit_status[sender] = True
2014-02-17 22:21:11 +01:00
logging.debug("maybe_quit called from {}, quit status {}".format(
sender, self._quit_status))
2014-02-17 20:39:15 +01:00
if all(self._quit_status.values()):
2014-02-18 13:05:42 +01:00
logging.debug("maybe_quit quitting.")
self.quit()
2014-01-28 14:44:12 +01:00
2014-05-14 08:56:42 +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 = []
for tab in self.mainwindow.tabs.widgets:
url = tab.url().toString()
if url:
pages.append(url)
os.environ['PYTHONPATH'] = os.pathsep.join(sys.path)
argv = sys.argv[:]
for page in self._opened_urls:
try:
argv.remove(page)
except ValueError:
pass
argv = [sys.executable] + argv + pages
logging.debug("Running {} with args {}".format(sys.executable,
argv))
subprocess.Popen(argv)
if shutdown:
self.shutdown()
@cmdutils.register(instance='', split=False)
2014-01-19 23:54:22 +01:00
def pyeval(self, s):
2014-01-29 15:30:19 +01:00
"""Evaluate a python string and display the results as a webpage.
:pyeval command handler.
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
self.mainwindow.tabs.cur.openurl('qute:pyeval')
2014-01-30 14:58:32 +01:00
2014-03-03 21:06:10 +01:00
@cmdutils.register(instance='', hide=True)
2014-05-15 10:33:11 +02:00
def crash(self, typ='exception'):
2014-01-30 14:58:32 +01:00
"""Crash for debugging purposes.
:crash command handler.
2014-02-07 20:21:50 +01:00
2014-05-15 10:33:11 +02:00
Args:
typ: either 'exception' or 'segfault'
2014-02-19 10:58:32 +01:00
Raises:
2014-05-15 10:33:11 +02:00
raises Exception when typ is not segfault.
segfaults when typ is (you don't say...)
2014-01-30 14:58:32 +01:00
"""
2014-05-15 10:33:11 +02:00
if typ == 'segfault':
# From python's Lib/test/crashers/bogus_code_obj.py
co = types.CodeType(0, 0, 0, 0, 0, b'\x04\x71\x00\x00', (), (), (),
'', '', 1, b'')
2014-05-15 10:56:28 +02:00
exec(co) # pylint: disable=exec-used
2014-05-15 10:33:11 +02:00
raise Exception("Segfault failed (wat.)")
else:
raise Exception("Forced crash")
@pyqtSlot()
2014-03-03 21:11:02 +01:00
@cmdutils.register(instance='', name=['quit', 'q'], nargs=0)
2014-05-06 10:53:38 +02:00
def shutdown(self):
"""Try to shutdown everything cleanly.
For some reason lastWindowClosing sometimes seem to get emitted twice,
so we make sure we only run once here.
"""
if self._shutting_down:
return
self._shutting_down = True
2014-05-06 10:53:38 +02:00
logging.debug("Shutting down...")
# Save config
if self.config.get('general', 'auto-save-config'):
2014-04-15 17:28:05 +02:00
try:
self.config.save()
2014-04-15 17:28:05 +02:00
except AttributeError:
logging.exception("Could not save config.")
2014-05-06 10:53:38 +02:00
# Save command history
2014-04-15 18:02:07 +02:00
try:
self.cmd_history.save()
2014-04-15 18:02:07 +02:00
except AttributeError:
logging.exception("Could not save command history.")
2014-05-06 10:53:38 +02:00
# Save window state
try:
self._save_geometry()
self.stateconfig.save()
except AttributeError:
logging.exception("Could not save window geometry.")
2014-05-06 10:53:38 +02:00
# Save cookies
2014-05-05 16:28:43 +02:00
try:
self.cookiejar.save()
2014-05-05 16:28:43 +02:00
except AttributeError:
logging.exception("Could not save cookies.")
2014-05-06 10:53:38 +02:00
# Shut down tabs
try:
2014-05-06 10:53:38 +02:00
self.mainwindow.tabs.shutdown_complete.connect(partial(
self._maybe_quit, 'tabs'))
self.mainwindow.tabs.shutdown()
except AttributeError: # mainwindow or tabs could still be None
logging.exception("No mainwindow/tabs to shut down.")
2014-05-06 10:53:38 +02:00
self._maybe_quit('tabs')
# Shut down networkmanager
try:
self.networkmanager.abort_requests()
self.networkmanager.destroyed.connect(partial(
self._maybe_quit, 'networkmanager'))
self.networkmanager.deleteLater()
except AttributeError:
logging.exception("No networkmanager to shut down.")
self._maybe_quit('networkmanager')
if self._crashlogfile is not None:
# Re-enable faulthandler to stdout, then remove crash log
faulthandler.enable()
self._crashlogfile.close()
try:
os.remove(self._crashlogfile.name)
except PermissionError:
pass
self._maybe_quit('main')
@pyqtSlot()
def on_tab_shutdown_complete(self):
"""Quit application after a shutdown.
Gets called when all tabs finished shutting down after shutdown().
"""
logging.debug("Shutdown complete, quitting.")
self.quit()
2014-03-03 21:06:10 +01:00
@pyqtSlot(tuple)
def command_handler(self, tpl):
"""Handle commands which need an instance..
Args:
tpl: An (instance, func, count, args) tuple.
instance: How to get the current instance of the target object
from app.py, as a dotted string, e.g.
'mainwindow.tabs.cur'.
func: The function name to be called (as string).
count: The count given to the command, or None.
args: A list of arguments given to the command.
"""
(instance, func, count, args) = tpl
if instance == '':
obj = self
else:
obj = dotted_getattr(self, instance)
handler = getattr(obj, func)
2014-05-14 18:00:40 +02:00
try:
if count is not None:
handler(*args, count=count)
else:
handler(*args)
except CommandError as e:
message.error(e)