qutebrowser/qutebrowser/app.py

639 lines
25 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
import os
2014-05-13 18:01:10 +02:00
import sys
2014-05-15 10:33:11 +02:00
import types
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 base64 import b64encode
from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
from PyQt5.QtCore import (pyqtSlot, QTimer, QEventLoop, Qt, QStandardPaths,
qInstallMessageHandler)
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-22 16:44:47 +02:00
import qutebrowser.browser.quickmarks as quickmarks
2014-05-23 16:11:55 +02:00
import qutebrowser.utils.log as log
2014-05-27 15:46:21 +02:00
import qutebrowser.utils.version as version
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-05-20 12:05:14 +02:00
from qutebrowser.keyinput.modeparsers import (NormalKeyParser, HintKeyParser,
PromptKeyParser)
from qutebrowser.keyinput.keyparser import PassthroughKeyParser
from qutebrowser.commands.managers import CommandManager, SearchManager
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 get_standard_dir, actute_warning
2014-05-22 10:49:19 +02:00
from qutebrowser.utils.readline import ReadlineBridge
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-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.
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-05-22 15:44:16 +02:00
rl_bridge: The ReadlineBridge being used.
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
def __init__(self, args):
"""Constructor.
Args:
Argument namespace from argparse.
"""
2014-06-06 16:32:57 +02:00
super().__init__(self._get_qt_args(args))
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
2014-05-22 15:44:16 +02:00
self.rl_bridge = 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
self._args = args
2014-05-05 16:42:41 +02:00
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-22 16:44:47 +02:00
quickmarks.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.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-06-06 16:32:57 +02:00
def _get_qt_args(self, namespace):
"""Get the Qt QApplication arguments based on an argparse namespace.
Args:
namespace: The argparse namespace.
Return:
The argv list to be passed to Qt.
"""
argv = [sys.argv[0]]
qt_args = ['style', 'stylesheet', 'widget-count', 'reverse',
'qmljsdebugger']
for argname in qt_args:
2014-06-06 16:40:49 +02:00
val = getattr(namespace, 'qt_' + argname, None)
if val is None:
# flag/argument not given
continue
elif val is True:
argv.append('-' + argname)
2014-06-06 16:32:57 +02:00
else:
2014-06-06 16:40:49 +02:00
argv.append('-' + argname)
argv.append(val)
2014-06-06 16:32:57 +02:00
log.init.debug("Qt arguments: {}, based on {}".format(argv, namespace))
return argv
2014-05-05 16:42:41 +02:00
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-05-20 17:50:09 +02:00
'prompt': PassthroughKeyParser('keybind.prompt', self, warn=False),
2014-05-20 12:05:14 +02:00
'yesno': PromptKeyParser(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-19 17:01:05 +02:00
self.modeman.register('prompt', self._keyparsers['prompt'].handle,
passthrough=True)
2014-05-20 12:05:14 +02:00
self.modeman.register('yesno', self._keyparsers['yesno'].handle)
2014-05-05 16:42:41 +02:00
def _init_misc(self):
"""Initialize misc things."""
2014-05-27 15:46:21 +02:00
if self._args.version:
print(version.version())
print()
print()
print(qutebrowser.__copyright__)
print()
print(version.GPL_BOILERPLATE.strip())
sys.exit(0)
self.setOrganizationName("qutebrowser")
self.setApplicationName("qutebrowser")
self.setApplicationVersion(qutebrowser.__version__)
self.messagebridge = MessageBridge()
2014-05-22 10:49:19 +02:00
self.rl_bridge = ReadlineBridge()
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:
2014-05-23 16:11:55 +02:00
log.init.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.
2014-05-23 16:11:55 +02:00
log.init.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 _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-05-23 16:11:55 +02:00
log.init.debug("Startup cmd {}".format(e))
self.commandmanager.run_safely_init(e.lstrip(':'))
else:
2014-05-23 16:11:55 +02:00
log.init.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-05-23 16:11:55 +02:00
log.init.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
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
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)
self.modeman.left.connect(status.prompt.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)
2014-05-17 22:38:07 +02:00
self.searchmanager.do_search.connect(tabs.search)
2014-04-25 16:53:23 +02:00
kp['normal'].keystring_updated.connect(status.keystring.setText)
2014-05-21 19:53:58 +02:00
tabs.got_cmd.connect(self.commandmanager.run_safely)
2014-04-22 10:34:43 +02:00
# hints
2014-05-17 22:38:07 +02:00
kp['hint'].fire_hint.connect(tabs.fire_hint)
kp['hint'].filter_hints.connect(tabs.filter_hints)
kp['hint'].keystring_updated.connect(tabs.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-05-20 11:03:55 +02:00
self.messagebridge.question.connect(status.prompt.ask_question,
Qt.DirectConnection)
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,
websettings, kp['normal'], self.modeman, status,
status.txt]:
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-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-06-03 17:59:15 +02:00
tabs.currentChanged.connect(status.txt.on_tab_changed)
tabs.cur_statusbar_message.connect(status.txt.on_statusbar_message)
tabs.cur_load_started.connect(status.txt.on_load_started)
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)
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-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
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-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-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:
2014-06-02 23:29:01 +02:00
log.destroy.warning("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-05-23 16:11:55 +02:00
log.destroy.debug("maybe_quit called from {}, quit status {}".format(
2014-02-17 22:21:11 +01:00
sender, self._quit_status))
2014-02-17 20:39:15 +01:00
if all(self._quit_status.values()):
2014-05-23 16:11:55 +02:00
log.destroy.debug("maybe_quit quitting.")
self.quit()
2014-01-28 14:44:12 +01:00
2014-06-04 23:17:22 +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)
# pythonpath = os.pathsep.join(sys.path)
# os.environ['PYTHONPATH'] = pythonpath
# argv = sys.argv[:]
# for page in self._opened_urls:
# try:
# argv.remove(page)
# except ValueError:
# pass
# argv = [sys.executable] + argv + pages
# log.procs.debug("Running {} with args {} (PYTHONPATH={})".format(
# sys.executable, argv, pythonpath))
# subprocess.Popen(argv)
# if shutdown:
# self.shutdown()
2014-05-14 08:56:42 +02:00
@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
2014-05-17 22:38:07 +02:00
self.mainwindow.tabs.cmd.openurl('qute:pyeval')
2014-01-30 14:58:32 +01:00
@pyqtSlot()
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-23 16:11:55 +02:00
log.destroy.debug("Shutting down...")
2014-06-03 16:48:21 +02:00
to_save = []
# Save everything
if self.config.get('general', 'auto-save-config'):
2014-06-03 16:48:21 +02:00
to_save.append(("config", self.config.save))
to_save += [("command history", self.cmd_history.save),
("window geometry", self._save_geometry),
("window geometry", self.stateconfig.save),
("cookies", self.cookiejar.save),
("quickmarks", quickmarks.save)]
for what, handler in to_save:
log.destroy.debug("Saving {} (handler: {})".format(
what, handler.__qualname__))
2014-04-15 17:28:05 +02:00
try:
2014-06-03 16:48:21 +02:00
handler()
2014-04-15 17:28:05 +02:00
except AttributeError:
2014-06-03 16:48:21 +02:00
log.destroy.warning("Could not save {}.".format(what))
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
2014-06-02 23:29:01 +02:00
log.destroy.warning("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:
2014-06-02 23:29:01 +02:00
log.destroy.warning("No networkmanager to shut down.")
2014-05-06 10:53:38 +02:00
self._maybe_quit('networkmanager')
2014-06-03 16:48:21 +02:00
# Re-enable faulthandler to stdout, then remove crash log
if self._crashlogfile is not None:
if sys.stderr is not None:
faulthandler.enable()
else:
faulthandler.disable()
self._crashlogfile.close()
try:
os.remove(self._crashlogfile.name)
except PermissionError:
pass
# If we don't kill our custom handler here we might get segfaults
qInstallMessageHandler(None)
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().
"""
2014-05-23 16:11:55 +02:00
log.destroy.debug("Shutdown complete, quitting.")
self.quit()
2014-06-04 13:38:53 +02:00
@cmdutils.register(hide=True)
def crash(typ='exception'):
"""Crash for debugging purposes.
Args:
typ: either 'exception' or 'segfault'
Raises:
raises Exception when typ is not segfault.
segfaults when typ is (you don't say...)
"""
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'')
exec(co) # pylint: disable=exec-used
raise Exception("Segfault failed (wat.)")
else:
raise Exception("Forced crash")