Big refactoring of app.py.
This commit is contained in:
parent
1903792239
commit
d3a7b2e4ca
File diff suppressed because it is too large
Load Diff
@ -52,9 +52,10 @@ class change_filter: # pylint: disable=invalid-name
|
||||
Attributes:
|
||||
_sectname: The section to be filtered.
|
||||
_optname: The option to be filtered.
|
||||
_function: Whether a function rather than a method is decorated.
|
||||
"""
|
||||
|
||||
def __init__(self, sectname, optname=None):
|
||||
def __init__(self, sectname, optname=None, function=False):
|
||||
"""Save decorator arguments.
|
||||
|
||||
Gets called on parse-time with the decorator arguments.
|
||||
@ -62,6 +63,7 @@ class change_filter: # pylint: disable=invalid-name
|
||||
Args:
|
||||
sectname: The section to be filtered.
|
||||
optname: The option to be filtered.
|
||||
function: Whether a function rather than a method is decorated.
|
||||
"""
|
||||
if sectname not in configdata.DATA:
|
||||
raise configexc.NoSectionError(sectname)
|
||||
@ -69,6 +71,7 @@ class change_filter: # pylint: disable=invalid-name
|
||||
raise configexc.NoOptionError(optname, sectname)
|
||||
self._sectname = sectname
|
||||
self._optname = optname
|
||||
self._function = function
|
||||
|
||||
def __call__(self, func):
|
||||
"""Filter calls to the decorated function.
|
||||
@ -86,6 +89,21 @@ class change_filter: # pylint: disable=invalid-name
|
||||
Return:
|
||||
The decorated function.
|
||||
"""
|
||||
if self._function:
|
||||
@pyqtSlot(str, str)
|
||||
@functools.wraps(func)
|
||||
def wrapper(sectname=None, optname=None):
|
||||
# pylint: disable=missing-docstring
|
||||
if sectname is None and optname is None:
|
||||
# Called directly, not from a config change event.
|
||||
return func()
|
||||
elif sectname != self._sectname:
|
||||
return
|
||||
elif self._optname is not None and optname != self._optname:
|
||||
return
|
||||
else:
|
||||
return func()
|
||||
else:
|
||||
@pyqtSlot(str, str)
|
||||
@functools.wraps(func)
|
||||
def wrapper(wrapper_self, sectname=None, optname=None):
|
||||
|
@ -24,7 +24,7 @@ import base64
|
||||
import itertools
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QRect, QPoint, QTimer, Qt
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication
|
||||
|
||||
from qutebrowser.commands import runners, cmdutils
|
||||
from qutebrowser.config import config
|
||||
@ -39,6 +39,47 @@ from qutebrowser.browser import hints, downloads, downloadview
|
||||
win_id_gen = itertools.count(0)
|
||||
|
||||
|
||||
def get_window(via_ipc, force_window=False, force_tab=False):
|
||||
"""Helper function for app.py to get a window id.
|
||||
|
||||
Args:
|
||||
via_ipc: Whether the request was made via IPC.
|
||||
force_window: Whether to force opening in a window.
|
||||
force_tab: Whether to force opening in a tab.
|
||||
"""
|
||||
if force_window and force_tab:
|
||||
raise ValueError("force_window and force_tab are mutually exclusive!")
|
||||
if not via_ipc:
|
||||
# Initial main window
|
||||
return 0
|
||||
window_to_raise = None
|
||||
open_target = config.get('general', 'new-instance-open-target')
|
||||
if (open_target == 'window' or force_window) and not force_tab:
|
||||
window = MainWindow()
|
||||
window.show()
|
||||
win_id = window.win_id
|
||||
window_to_raise = window
|
||||
else:
|
||||
try:
|
||||
window = objreg.last_window()
|
||||
except objreg.NoWindow:
|
||||
# There is no window left, so we open a new one
|
||||
window = MainWindow()
|
||||
window.show()
|
||||
win_id = window.win_id
|
||||
window_to_raise = window
|
||||
win_id = window.win_id
|
||||
if open_target not in ('tab-silent', 'tab-bg-silent'):
|
||||
window_to_raise = window
|
||||
if window_to_raise is not None:
|
||||
window_to_raise.setWindowState(window.windowState() &
|
||||
~Qt.WindowMinimized | Qt.WindowActive)
|
||||
window_to_raise.raise_()
|
||||
window_to_raise.activateWindow()
|
||||
QApplication.instance().alert(window_to_raise)
|
||||
return win_id
|
||||
|
||||
|
||||
class MainWindow(QWidget):
|
||||
|
||||
"""The main window of qutebrowser.
|
||||
@ -173,6 +214,13 @@ class MainWindow(QWidget):
|
||||
else:
|
||||
self._load_geometry(geom)
|
||||
|
||||
def _save_geometry(self):
|
||||
"""Save the window geometry to the state config."""
|
||||
state_config = objreg.get('state-config')
|
||||
data = bytes(self.saveGeometry())
|
||||
geom = base64.b64encode(data).decode('ASCII')
|
||||
state_config['geometry']['mainwindow'] = geom
|
||||
|
||||
def _load_geometry(self, geom):
|
||||
"""Load geometry from a bytes object.
|
||||
|
||||
@ -370,6 +418,6 @@ class MainWindow(QWidget):
|
||||
e.accept()
|
||||
if len(objreg.window_registry) == 1:
|
||||
objreg.get('session-manager').save_last_window_session()
|
||||
objreg.get('app').geometry = bytes(self.saveGeometry())
|
||||
self._save_geometry()
|
||||
log.destroy.debug("Closing window {}".format(self.win_id))
|
||||
self._tabbed_browser.shutdown()
|
||||
|
357
qutebrowser/misc/crashsignal.py
Normal file
357
qutebrowser/misc/crashsignal.py
Normal file
@ -0,0 +1,357 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 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/>.
|
||||
|
||||
"""Handlers for crashes and OS signals."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import bdb
|
||||
import pdb
|
||||
import signal
|
||||
import functools
|
||||
import faulthandler
|
||||
import os.path
|
||||
|
||||
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QObject,
|
||||
QSocketNotifier, QTimer, QUrl)
|
||||
from PyQt5.QtWidgets import QApplication, QDialog
|
||||
|
||||
from qutebrowser.commands import cmdutils
|
||||
from qutebrowser.misc import earlyinit, crashdialog
|
||||
from qutebrowser.utils import usertypes, standarddir, log, objreg, debug
|
||||
|
||||
|
||||
class CrashHandler(QObject):
|
||||
|
||||
"""Handler for crashes, reports and exceptions.
|
||||
|
||||
Attributes:
|
||||
_app: The QApplication instance.
|
||||
_args: The argparse namespace.
|
||||
_crash_dialog: The CrashDialog currently being shown.
|
||||
_crash_log_file: The file handle for the faulthandler crash log.
|
||||
"""
|
||||
|
||||
def __init__(self, app, args, parent=None):
|
||||
super().__init__(parent)
|
||||
self._app = app
|
||||
self._args = args
|
||||
self._crash_log_file = None
|
||||
self._crash_dialog = None
|
||||
|
||||
def activate(self):
|
||||
"""Activate the exception hook."""
|
||||
sys.excepthook = self.exception_hook
|
||||
|
||||
def handle_segfault(self):
|
||||
"""Handle a segfault from a previous run."""
|
||||
logname = os.path.join(standarddir.data(), 'crash.log')
|
||||
try:
|
||||
# First check if an old logfile exists.
|
||||
if os.path.exists(logname):
|
||||
with open(logname, 'r', encoding='ascii') as f:
|
||||
data = f.read()
|
||||
os.remove(logname)
|
||||
self._init_crashlogfile()
|
||||
if data:
|
||||
# Crashlog exists and has data in it, so something crashed
|
||||
# previously.
|
||||
self._crash_dialog = crashdialog.get_fatal_crash_dialog(
|
||||
self._args.debug, data)
|
||||
self._crash_dialog.show()
|
||||
else:
|
||||
# There's no log file, so we can use this to display crashes to
|
||||
# the user on the next start.
|
||||
self._init_crashlogfile()
|
||||
except OSError:
|
||||
log.init.exception("Error while handling crash log file!")
|
||||
self._init_crashlogfile()
|
||||
|
||||
def _recover_pages(self, forgiving=False):
|
||||
"""Try to recover all open pages.
|
||||
|
||||
Called from exception_hook, so as forgiving as possible.
|
||||
|
||||
Args:
|
||||
forgiving: Whether to ignore exceptions.
|
||||
|
||||
Return:
|
||||
A list containing a list for each window, which in turn contain the
|
||||
opened URLs.
|
||||
"""
|
||||
pages = []
|
||||
for win_id in objreg.window_registry:
|
||||
win_pages = []
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
for tab in tabbed_browser.widgets():
|
||||
try:
|
||||
urlstr = tab.cur_url.toString(
|
||||
QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
if urlstr:
|
||||
win_pages.append(urlstr)
|
||||
except Exception:
|
||||
if forgiving:
|
||||
log.destroy.exception("Error while recovering tab")
|
||||
else:
|
||||
raise
|
||||
pages.append(win_pages)
|
||||
return pages
|
||||
|
||||
def _init_crashlogfile(self):
|
||||
"""Start a new logfile and redirect faulthandler to it."""
|
||||
logname = os.path.join(standarddir.data(), 'crash.log')
|
||||
try:
|
||||
self._crash_log_file = open(logname, 'w', encoding='ascii')
|
||||
except OSError:
|
||||
log.init.exception("Error while opening crash log file!")
|
||||
else:
|
||||
earlyinit.init_faulthandler(self._crash_log_file)
|
||||
|
||||
@cmdutils.register(instance='crash-handler')
|
||||
def report(self):
|
||||
"""Report a bug in qutebrowser."""
|
||||
pages = self._recover_pages()
|
||||
cmd_history = objreg.get('command-history')[-5:]
|
||||
objects = debug.get_all_objects()
|
||||
self._crash_dialog = crashdialog.ReportDialog(pages, cmd_history,
|
||||
objects)
|
||||
self._crash_dialog.show()
|
||||
|
||||
def destroy_crashlogfile(self):
|
||||
"""Clean up the crash log file and delete it."""
|
||||
if self._crash_log_file is None:
|
||||
return
|
||||
# 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__)
|
||||
else:
|
||||
faulthandler.disable()
|
||||
try:
|
||||
self._crash_log_file.close()
|
||||
os.remove(self._crash_log_file.name)
|
||||
except OSError:
|
||||
log.destroy.exception("Could not remove crash log!")
|
||||
|
||||
def exception_hook(self, exctype, excvalue, tb): # noqa
|
||||
"""Handle uncaught python exceptions.
|
||||
|
||||
It'll try very hard to write all open tabs to a file, and then exit
|
||||
gracefully.
|
||||
"""
|
||||
exc = (exctype, excvalue, tb)
|
||||
qapp = QApplication.instance()
|
||||
|
||||
if not qapp.quitter.quit_status['crash']:
|
||||
log.misc.error("ARGH, there was an exception while the crash "
|
||||
"dialog is already shown:", exc_info=exc)
|
||||
return
|
||||
|
||||
log.misc.error("Uncaught exception", exc_info=exc)
|
||||
|
||||
is_ignored_exception = (exctype is bdb.BdbQuit or
|
||||
not issubclass(exctype, Exception))
|
||||
|
||||
if self._args.pdb_postmortem:
|
||||
pdb.post_mortem(tb)
|
||||
|
||||
if (is_ignored_exception or self._args.no_crash_dialog or
|
||||
self._args.pdb_postmortem):
|
||||
# pdb exit, KeyboardInterrupt, ...
|
||||
status = 0 if is_ignored_exception else 2
|
||||
try:
|
||||
qapp.shutdown(status)
|
||||
return
|
||||
except Exception:
|
||||
log.init.exception("Error while shutting down")
|
||||
qapp.quit()
|
||||
return
|
||||
|
||||
qapp.quitter.quit_status['crash'] = False
|
||||
|
||||
try:
|
||||
pages = self._recover_pages(forgiving=True)
|
||||
except Exception:
|
||||
log.destroy.exception("Error while recovering pages")
|
||||
pages = []
|
||||
|
||||
try:
|
||||
cmd_history = objreg.get('command-history')[-5:]
|
||||
except Exception:
|
||||
log.destroy.exception("Error while getting history: {}")
|
||||
cmd_history = []
|
||||
|
||||
try:
|
||||
objects = debug.get_all_objects()
|
||||
except Exception:
|
||||
log.destroy.exception("Error while getting objects")
|
||||
objects = ""
|
||||
|
||||
try:
|
||||
objreg.get('ipc-server').ignored = True
|
||||
except Exception:
|
||||
log.destroy.exception("Error while ignoring ipc")
|
||||
|
||||
try:
|
||||
self._app.lastWindowClosed.disconnect(
|
||||
self._app.quitter.on_last_window_closed)
|
||||
except TypeError:
|
||||
log.destroy.exception("Error while preventing shutdown")
|
||||
self._app.closeAllWindows()
|
||||
self._crash_dialog = crashdialog.ExceptionCrashDialog(
|
||||
self._args.debug, pages, cmd_history, exc, objects)
|
||||
ret = self._crash_dialog.exec_()
|
||||
if ret == QDialog.Accepted: # restore
|
||||
self.restart(self._args, pages)
|
||||
|
||||
# We might risk a segfault here, but that's better than continuing to
|
||||
# run in some undefined state, so we only do the most needed shutdown
|
||||
# here.
|
||||
qInstallMessageHandler(None)
|
||||
self.destroy_crashlogfile()
|
||||
sys.exit(1)
|
||||
|
||||
def raise_crashdlg(self):
|
||||
"""Raise the crash dialog if one exists."""
|
||||
if self._crash_dialog is not None:
|
||||
self._crash_dialog.raise_()
|
||||
|
||||
|
||||
class SignalHandler(QObject):
|
||||
|
||||
"""Handler responsible for handling OS signals (SIGINT, SIGTERM, etc.).
|
||||
|
||||
Attributes:
|
||||
_app: The QApplication instance.
|
||||
_activated: Whether activate() was called.
|
||||
_notifier: A QSocketNotifier used for signals on Unix.
|
||||
_timer: A QTimer used to poll for signals on Windows.
|
||||
_orig_handlers: A {signal: handler} dict of original signal handlers.
|
||||
_orig_wakeup_fd: The original wakeup filedescriptor.
|
||||
"""
|
||||
|
||||
def __init__(self, app, parent=None):
|
||||
super().__init__(parent)
|
||||
self._app = app
|
||||
self._notifier = None
|
||||
self._timer = usertypes.Timer(self, 'python_hacks')
|
||||
self._orig_handlers = {}
|
||||
self._activated = False
|
||||
self._orig_wakeup_fd = None
|
||||
|
||||
def activate(self):
|
||||
"""Set up signal handlers.
|
||||
|
||||
On Windows this uses a QTimer to periodically hand control over to
|
||||
Python so it can handle signals.
|
||||
|
||||
On Unix, it uses a QSocketNotifier with os.set_wakeup_fd to get
|
||||
notified.
|
||||
"""
|
||||
self._orig_handlers[signal.SIGINT] = signal.signal(
|
||||
signal.SIGINT, self.interrupt)
|
||||
self._orig_handlers[signal.SIGTERM] = signal.signal(
|
||||
signal.SIGTERM, self.interrupt)
|
||||
|
||||
if os.name == 'posix' and hasattr(signal, 'set_wakeup_fd'):
|
||||
import fcntl
|
||||
read_fd, write_fd = os.pipe()
|
||||
for fd in (read_fd, write_fd):
|
||||
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
||||
self._notifier = QSocketNotifier(
|
||||
read_fd, QSocketNotifier.Read, self)
|
||||
self._notifier.activated.connect(self.handle_signal_wakeup)
|
||||
self._orig_wakeup_fd = signal.set_wakeup_fd(write_fd)
|
||||
else:
|
||||
self._timer.start(1000)
|
||||
self._timer.timeout.connect(lambda: None)
|
||||
self._activated = True
|
||||
|
||||
def deactivate(self):
|
||||
"""Deactivate all signal handlers."""
|
||||
if not self._activated:
|
||||
return
|
||||
if self._notifier is not None:
|
||||
self._notifier.setEnabled(False)
|
||||
rfd = self._notifier.socket()
|
||||
wfd = signal.set_wakeup_fd(self._orig_wakeup_fd)
|
||||
os.close(rfd)
|
||||
os.close(wfd)
|
||||
for sig, handler in self._orig_handlers.items():
|
||||
signal.signal(sig, handler)
|
||||
self._timer.stop()
|
||||
self._activated = False
|
||||
|
||||
@pyqtSlot()
|
||||
def handle_signal_wakeup(self):
|
||||
"""Handle a newly arrived signal.
|
||||
|
||||
This gets called via self._notifier when there's a signal.
|
||||
|
||||
Python will get control here, so the signal will get handled.
|
||||
"""
|
||||
log.destroy.debug("Handling signal wakeup!")
|
||||
self._notifier.setEnabled(False)
|
||||
read_fd = self._notifier.socket()
|
||||
try:
|
||||
os.read(read_fd, 1)
|
||||
except OSError:
|
||||
log.destroy.exception("Failed to read wakeup fd.")
|
||||
self._notifier.setEnabled(True)
|
||||
|
||||
def interrupt(self, signum, _frame):
|
||||
"""Handler for signals to gracefully shutdown (SIGINT/SIGTERM).
|
||||
|
||||
This calls shutdown and remaps the signal to call
|
||||
interrupt_forcefully the next time.
|
||||
"""
|
||||
log.destroy.info("SIGINT/SIGTERM received, shutting down!")
|
||||
log.destroy.info("Do the same again to forcefully quit.")
|
||||
signal.signal(signal.SIGINT, self.interrupt_forcefully)
|
||||
signal.signal(signal.SIGTERM, self.interrupt_forcefully)
|
||||
# If we call shutdown directly here, we get a segfault.
|
||||
QTimer.singleShot(0, functools.partial(
|
||||
self._app.quitter.shutdown, 128 + signum))
|
||||
|
||||
def interrupt_forcefully(self, signum, _frame):
|
||||
"""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.
|
||||
"""
|
||||
log.destroy.info("Forceful quit requested, goodbye cruel world!")
|
||||
log.destroy.info("Do the same again to quit with even more force.")
|
||||
signal.signal(signal.SIGINT, self.interrupt_really_forcefully)
|
||||
signal.signal(signal.SIGTERM, self.interrupt_really_forcefully)
|
||||
# This *should* work without a QTimer, but because of the trouble in
|
||||
# self.interrupt we're better safe than sorry.
|
||||
QTimer.singleShot(0, functools.partial(self._app.exit, 128 + signum))
|
||||
|
||||
def interrupt_really_forcefully(self, signum, _frame):
|
||||
"""Interrupt with even more force on the third SIGINT/SIGTERM request.
|
||||
|
||||
This doesn't run *any* Qt cleanup and simply exits via Python.
|
||||
It will most likely lead to a segfault.
|
||||
"""
|
||||
log.destroy.info("WHY ARE YOU DOING THIS TO ME? :(")
|
||||
sys.exit(128 + signum)
|
@ -21,18 +21,21 @@
|
||||
|
||||
import functools
|
||||
import types
|
||||
import traceback
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
try:
|
||||
import hunter
|
||||
except ImportError:
|
||||
hunter = None
|
||||
|
||||
from qutebrowser.utils import log, objreg, usertypes, message
|
||||
from qutebrowser.browser.network import qutescheme
|
||||
from qutebrowser.utils import log, objreg, usertypes, message, debug
|
||||
from qutebrowser.commands import cmdutils, runners, cmdexc
|
||||
from qutebrowser.config import style
|
||||
from qutebrowser.misc import consolewidget
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
|
||||
@cmdutils.register(maxsplit=1, no_cmd_split=True, win_id='win_id')
|
||||
def later(ms: {'type': int}, command, win_id):
|
||||
@ -128,7 +131,7 @@ def debug_crash(typ: {'type': ('exception', 'segfault')}='exception'):
|
||||
@cmdutils.register(debug=True)
|
||||
def debug_all_objects():
|
||||
"""Print a list of all objects to the debug log."""
|
||||
s = QCoreApplication.instance().get_all_objects()
|
||||
s = debug.get_all_objects()
|
||||
log.misc.debug(s)
|
||||
|
||||
|
||||
@ -166,3 +169,21 @@ def debug_trace(expr=""):
|
||||
eval('hunter.trace({})'.format(expr))
|
||||
except Exception as e:
|
||||
raise cmdexc.CommandError("{}: {}".format(e.__class__.__name__, e))
|
||||
|
||||
|
||||
@cmdutils.register(maxsplit=0, debug=True, no_cmd_split=True)
|
||||
def debug_pyeval(s):
|
||||
"""Evaluate a python string and display the results as a web page.
|
||||
|
||||
Args:
|
||||
s: The string to evaluate.
|
||||
"""
|
||||
try:
|
||||
r = eval(s)
|
||||
out = repr(r)
|
||||
except Exception:
|
||||
out = traceback.format_exc()
|
||||
qutescheme.pyeval_output = out
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window='last-focused')
|
||||
tabbed_browser.openurl(QUrl('qute:pyeval'), newtab=True)
|
||||
|
@ -138,24 +138,4 @@ def main():
|
||||
# We do this imports late as earlyinit needs to be run first (because of
|
||||
# the harfbuzz fix and version checking).
|
||||
from qutebrowser import app
|
||||
import PyQt5.QtWidgets as QtWidgets
|
||||
app = app.Application(args)
|
||||
|
||||
def qt_mainloop():
|
||||
"""Simple wrapper to get a nicer stack trace for segfaults.
|
||||
|
||||
WARNING: misc/crashdialog.py checks the stacktrace for this function
|
||||
name, so if this is changed, it should be changed there as well!
|
||||
"""
|
||||
return app.exec_()
|
||||
|
||||
# We set qApp explicitly here to reduce the risk of segfaults while
|
||||
# quitting.
|
||||
# See https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/561303/comments/7
|
||||
# While this is a workaround for PyQt4 which should be fixed in PyQt, it
|
||||
# seems this still reduces segfaults.
|
||||
# FIXME: We should do another attempt at contacting upstream about this.
|
||||
QtWidgets.qApp = app
|
||||
ret = qt_mainloop()
|
||||
QtWidgets.qApp = None
|
||||
return ret
|
||||
return app.run(args)
|
||||
|
@ -25,9 +25,10 @@ import functools
|
||||
import datetime
|
||||
import contextlib
|
||||
|
||||
from PyQt5.QtCore import QEvent, QMetaMethod
|
||||
from PyQt5.QtCore import QEvent, QMetaMethod, QObject
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from qutebrowser.utils import log, utils, qtutils
|
||||
from qutebrowser.utils import log, utils, qtutils, objreg
|
||||
|
||||
|
||||
def log_events(klass):
|
||||
@ -233,3 +234,36 @@ def log_time(logger, action='operation'):
|
||||
finished = datetime.datetime.now()
|
||||
delta = (finished - started).total_seconds()
|
||||
logger.debug("{} took {} seconds.".format(action.capitalize(), delta))
|
||||
|
||||
|
||||
def _get_widgets():
|
||||
"""Get a string list of all widgets."""
|
||||
widgets = QApplication.instance().allWidgets()
|
||||
widgets.sort(key=repr)
|
||||
return [repr(w) for w in widgets]
|
||||
|
||||
|
||||
def _get_pyqt_objects(lines, obj, depth=0):
|
||||
"""Recursive method for get_all_objects to get Qt objects."""
|
||||
for kid in obj.findChildren(QObject):
|
||||
lines.append(' ' * depth + repr(kid))
|
||||
_get_pyqt_objects(lines, kid, depth + 1)
|
||||
|
||||
|
||||
def get_all_objects():
|
||||
"""Get all children of an object recursively as a string."""
|
||||
output = ['']
|
||||
widget_lines = _get_widgets()
|
||||
widget_lines = [' ' + e for e in widget_lines]
|
||||
widget_lines.insert(0, "Qt widgets - {} objects".format(
|
||||
len(widget_lines)))
|
||||
output += widget_lines
|
||||
pyqt_lines = []
|
||||
_get_pyqt_objects(pyqt_lines, QApplication.instance())
|
||||
pyqt_lines = [' ' + e for e in pyqt_lines]
|
||||
pyqt_lines.insert(0, 'Qt objects - {} objects:'.format(
|
||||
len(pyqt_lines)))
|
||||
output += pyqt_lines
|
||||
output += ['']
|
||||
output += objreg.dump_objects()
|
||||
return '\n'.join(output)
|
||||
|
Loading…
Reference in New Issue
Block a user