2014-02-07 20:21:50 +01:00
|
|
|
"""Initialization of qutebrowser and application-wide things."""
|
2014-01-29 15:30:19 +01:00
|
|
|
|
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-01-30 20:42:47 +01:00
|
|
|
import os
|
2013-12-14 22:15:16 +01:00
|
|
|
import sys
|
2014-01-17 20:03:21 +01:00
|
|
|
import logging
|
2014-02-05 11:40:30 +01:00
|
|
|
import subprocess
|
2014-01-28 14:44:12 +01:00
|
|
|
import faulthandler
|
2014-01-20 15:58:49 +01:00
|
|
|
from signal import signal, SIGINT
|
|
|
|
from argparse import ArgumentParser
|
|
|
|
|
2014-01-30 04:56:16 +01:00
|
|
|
# This is a really old place to do this, but we have to do this before
|
|
|
|
# importing PyQt or it won't work.
|
|
|
|
# See https://bugreports.qt-project.org/browse/QTBUG-36099
|
|
|
|
import qutebrowser.utils.harfbuzz as harfbuzz
|
|
|
|
harfbuzz.fix()
|
|
|
|
|
2014-01-30 20:42:47 +01:00
|
|
|
from PyQt5.QtWidgets import QApplication, QDialog
|
2014-02-07 11:01:58 +01:00
|
|
|
from PyQt5.QtCore import QTimer
|
2014-01-20 15:58:49 +01:00
|
|
|
|
|
|
|
import qutebrowser.commands.utils as cmdutils
|
2014-01-28 12:21:00 +01:00
|
|
|
import qutebrowser.utils.config as config
|
2014-02-07 10:07:13 +01:00
|
|
|
import qutebrowser.utils.about as about
|
2013-12-15 20:33:43 +01:00
|
|
|
from qutebrowser.widgets.mainwindow import MainWindow
|
2014-02-10 15:01:05 +01:00
|
|
|
from qutebrowser.widgets.crash import CrashDialog
|
2014-01-17 12:01:21 +01:00
|
|
|
from qutebrowser.commands.keys import KeyParser
|
2014-01-20 12:26:02 +01:00
|
|
|
from qutebrowser.utils.appdirs import AppDirs
|
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-01-29 15:30:19 +01:00
|
|
|
"""
|
2014-02-07 20:21:50 +01:00
|
|
|
|
2014-01-28 23:04:02 +01:00
|
|
|
dirs = None # AppDirs - config/cache directories
|
|
|
|
config = None # Config(Parser) object
|
2014-01-20 15:58:49 +01:00
|
|
|
mainwindow = None
|
|
|
|
commandparser = None
|
|
|
|
keyparser = None
|
2014-01-28 23:04:02 +01:00
|
|
|
args = None # ArgumentParser
|
|
|
|
timer = None # QTimer for python hacks
|
2014-01-20 15:58:49 +01:00
|
|
|
|
2014-01-19 18:20:57 +01:00
|
|
|
def __init__(self):
|
|
|
|
super().__init__(sys.argv)
|
2014-02-06 10:25:22 +01:00
|
|
|
sys.excepthook = self._exception_hook
|
2014-01-28 14:44:12 +01:00
|
|
|
|
|
|
|
# Handle segfaults
|
|
|
|
faulthandler.enable()
|
2014-01-20 13:50:33 +01:00
|
|
|
|
2014-01-29 15:30:19 +01:00
|
|
|
self._parseopts()
|
|
|
|
self._initlog()
|
2014-02-05 12:46:35 +01:00
|
|
|
self._initmisc()
|
2014-01-17 20:03:21 +01:00
|
|
|
|
2014-01-20 12:26:02 +01:00
|
|
|
self.dirs = AppDirs('qutebrowser')
|
2014-01-22 17:04:10 +01:00
|
|
|
if self.args.confdir is None:
|
2014-01-28 06:10:41 +01:00
|
|
|
confdir = self.dirs.user_config_dir
|
2014-01-22 17:04:10 +01:00
|
|
|
elif self.args.confdir == '':
|
|
|
|
confdir = None
|
|
|
|
else:
|
|
|
|
confdir = self.args.confdir
|
2014-01-28 12:21:00 +01:00
|
|
|
config.init(confdir)
|
2014-01-20 12:26:02 +01:00
|
|
|
|
2014-01-19 18:20:57 +01:00
|
|
|
self.commandparser = cmdutils.CommandParser()
|
2014-01-29 21:06:56 +01:00
|
|
|
self.searchparser = cmdutils.SearchParser()
|
2014-01-20 17:20:17 +01:00
|
|
|
self.keyparser = KeyParser(self.mainwindow)
|
2014-01-29 15:30:19 +01:00
|
|
|
self._init_cmds()
|
2014-01-27 21:35:12 +01:00
|
|
|
self.mainwindow = MainWindow()
|
2014-01-17 20:03:21 +01:00
|
|
|
|
2014-01-28 12:21:00 +01:00
|
|
|
self.aboutToQuit.connect(config.config.save)
|
2014-01-19 19:41:34 +01:00
|
|
|
self.mainwindow.tabs.keypress.connect(self.keyparser.handle)
|
2014-01-19 18:20:57 +01:00
|
|
|
self.keyparser.set_cmd_text.connect(self.mainwindow.status.cmd.set_cmd)
|
2014-02-06 13:34:49 +01:00
|
|
|
self.mainwindow.tabs.set_cmd_text.connect(
|
|
|
|
self.mainwindow.status.cmd.set_cmd)
|
2014-01-22 17:31:15 +01:00
|
|
|
self.mainwindow.status.cmd.got_cmd.connect(self.commandparser.run)
|
2014-01-29 21:06:56 +01:00
|
|
|
self.mainwindow.status.cmd.got_search.connect(self.searchparser.search)
|
|
|
|
self.mainwindow.status.cmd.got_search_rev.connect(
|
|
|
|
self.searchparser.search_rev)
|
2014-01-29 20:25:41 +01:00
|
|
|
self.mainwindow.status.cmd.returnPressed.connect(
|
2014-01-20 15:58:49 +01:00
|
|
|
self.mainwindow.tabs.setFocus)
|
2014-01-19 18:20:57 +01:00
|
|
|
self.commandparser.error.connect(self.mainwindow.status.disp_error)
|
2014-01-29 21:06:56 +01:00
|
|
|
self.searchparser.do_search.connect(
|
|
|
|
self.mainwindow.tabs.cur_search)
|
2014-01-20 17:20:17 +01:00
|
|
|
self.keyparser.commandparser.error.connect(
|
|
|
|
self.mainwindow.status.disp_error)
|
2014-01-20 07:01:39 +01:00
|
|
|
self.keyparser.keystring_updated.connect(
|
2014-01-20 15:58:49 +01:00
|
|
|
self.mainwindow.status.txt.set_keystring)
|
2014-01-17 20:03:21 +01:00
|
|
|
|
2014-01-19 18:20:57 +01:00
|
|
|
self.mainwindow.show()
|
2014-01-29 15:30:19 +01:00
|
|
|
self._python_hacks()
|
2014-01-30 20:41:54 +01:00
|
|
|
self._process_init_args()
|
|
|
|
|
|
|
|
def _process_init_args(self):
|
|
|
|
"""Process initial positional args.
|
|
|
|
|
|
|
|
URLs to open have no prefix, commands to execute begin with a colon.
|
2014-02-07 20:21:50 +01:00
|
|
|
|
2014-01-30 20:41:54 +01:00
|
|
|
"""
|
|
|
|
opened_urls = False
|
|
|
|
|
|
|
|
for e in self.args.command:
|
|
|
|
if e.startswith(':'):
|
|
|
|
logging.debug('Startup cmd {}'.format(e))
|
|
|
|
self.commandparser.run(e.lstrip(':'))
|
|
|
|
else:
|
|
|
|
logging.debug('Startup url {}'.format(e))
|
|
|
|
opened_urls = True
|
|
|
|
self.mainwindow.tabs.tabopen(e)
|
|
|
|
|
|
|
|
if not opened_urls:
|
|
|
|
logging.debug('Opening startpage')
|
2014-01-30 23:05:39 +01:00
|
|
|
# pylint: disable=maybe-no-member
|
|
|
|
for url in config.config.get('general', 'startpage').split(','):
|
2014-01-30 20:41:54 +01:00
|
|
|
self.mainwindow.tabs.tabopen(url)
|
2014-01-29 15:30:19 +01: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-02-07 20:21:50 +01:00
|
|
|
|
2014-01-29 15:30:19 +01:00
|
|
|
"""
|
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
|
|
|
|
|
|
|
pages = []
|
2014-01-28 14:44:12 +01:00
|
|
|
try:
|
|
|
|
for tabidx in range(self.mainwindow.tabs.count()):
|
|
|
|
try:
|
2014-01-30 20:42:47 +01:00
|
|
|
url = self.mainwindow.tabs.widget(tabidx).url().toString()
|
|
|
|
url = url.strip()
|
|
|
|
if url:
|
|
|
|
pages.append(url)
|
2014-01-28 14:44:12 +01:00
|
|
|
except Exception:
|
|
|
|
pass
|
|
|
|
except Exception:
|
|
|
|
pass
|
2014-01-30 20:42:47 +01:00
|
|
|
|
|
|
|
try:
|
|
|
|
history = self.mainwindow.status.cmd.history[-5:]
|
|
|
|
except Exception:
|
|
|
|
history = []
|
|
|
|
|
2014-02-05 11:40:30 +01:00
|
|
|
QApplication.closeAllWindows()
|
2014-01-30 20:42:47 +01:00
|
|
|
dlg = CrashDialog(pages, history, exc)
|
|
|
|
ret = dlg.exec_()
|
|
|
|
if ret == QDialog.Accepted: # restore
|
|
|
|
os.environ['PYTHONPATH'] = os.pathsep.join(sys.path)
|
|
|
|
# FIXME we might want to use argparse's features to not open pages
|
|
|
|
# again if they were opened via cmdline
|
|
|
|
argv = [sys.executable] + sys.argv + pages
|
|
|
|
logging.debug('Running {} with args {}'.format(sys.executable,
|
|
|
|
argv))
|
2014-02-05 11:40:30 +01:00
|
|
|
subprocess.Popen(argv)
|
2014-02-06 10:25:22 +01:00
|
|
|
sys.exit(1)
|
2014-01-28 14:44:12 +01:00
|
|
|
|
2014-01-29 15:30:19 +01:00
|
|
|
def _python_hacks(self):
|
|
|
|
"""Get around some PyQt-oddities by evil hacks.
|
2014-01-20 14:04:29 +01:00
|
|
|
|
2014-01-29 15:30:19 +01:00
|
|
|
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-02-07 20:21:50 +01:00
|
|
|
|
2014-01-29 15:30:19 +01:00
|
|
|
"""
|
2014-01-20 15:58:49 +01:00
|
|
|
signal(SIGINT, lambda *args: self.exit(128 + SIGINT))
|
2014-01-20 14:04:29 +01:00
|
|
|
self.timer = QTimer()
|
|
|
|
self.timer.start(500)
|
|
|
|
self.timer.timeout.connect(lambda: None)
|
|
|
|
|
2014-01-29 15:30:19 +01:00
|
|
|
def _parseopts(self):
|
|
|
|
"""Parse command line options."""
|
2014-01-20 15:58:49 +01:00
|
|
|
parser = ArgumentParser("usage: %(prog)s [options]")
|
2014-01-19 18:20:57 +01:00
|
|
|
parser.add_argument('-l', '--log', dest='loglevel',
|
2014-01-20 15:58:49 +01:00
|
|
|
help='Set loglevel', default='info')
|
2014-01-22 17:04:10 +01:00
|
|
|
parser.add_argument('-c', '--confdir', help='Set config directory '
|
|
|
|
'(empty for no config storage)')
|
2014-02-05 12:46:35 +01:00
|
|
|
parser.add_argument('-d', '--debug', help='Turn on debugging options.',
|
|
|
|
action='store_true')
|
2014-01-30 20:41:54 +01:00
|
|
|
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.')
|
2014-01-19 18:20:57 +01:00
|
|
|
self.args = parser.parse_args()
|
2013-12-15 20:33:43 +01:00
|
|
|
|
2014-01-29 15:30:19 +01:00
|
|
|
def _initlog(self):
|
|
|
|
"""Initialisation of the logging output."""
|
2014-02-05 12:46:35 +01:00
|
|
|
loglevel = 'debug' if self.args.debug else self.args.loglevel
|
2014-01-19 18:20:57 +01:00
|
|
|
numeric_level = getattr(logging, loglevel.upper(), None)
|
|
|
|
if not isinstance(numeric_level, int):
|
2014-01-20 15:58:49 +01:00
|
|
|
raise ValueError('Invalid log level: {}'.format(loglevel))
|
2014-01-19 18:20:57 +01:00
|
|
|
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-01-17 10:57:27 +01:00
|
|
|
|
2014-02-05 12:46:35 +01:00
|
|
|
def _initmisc(self):
|
|
|
|
"""Initialize misc things based on arguments."""
|
|
|
|
if self.args.debug:
|
|
|
|
os.environ['QT_FATAL_WARNINGS'] = '1'
|
|
|
|
|
2014-01-29 15:30:19 +01:00
|
|
|
def _init_cmds(self):
|
|
|
|
"""Initialisation of the qutebrowser commands.
|
|
|
|
|
|
|
|
Registers all commands, connects its signals, and sets up keyparser.
|
2014-02-07 20:21:50 +01:00
|
|
|
|
2014-01-29 15:30:19 +01:00
|
|
|
"""
|
2014-01-19 18:20:57 +01:00
|
|
|
cmdutils.register_all()
|
2014-01-20 15:58:49 +01:00
|
|
|
for cmd in cmdutils.cmd_dict.values():
|
2014-01-19 22:55:00 +01:00
|
|
|
cmd.signal.connect(self.cmd_handler)
|
2014-01-22 16:43:24 +01:00
|
|
|
try:
|
2014-01-28 12:21:00 +01:00
|
|
|
self.keyparser.from_config_sect(config.config['keybind'])
|
2014-01-22 16:43:24 +01:00
|
|
|
except KeyError:
|
|
|
|
pass
|
2014-01-19 22:55:00 +01:00
|
|
|
|
|
|
|
def cmd_handler(self, tpl):
|
2014-01-29 15:30:19 +01:00
|
|
|
"""Handle commands and delegate the specific actions.
|
|
|
|
|
|
|
|
This gets called as a slot from all commands, and then calls the
|
|
|
|
appropriate command handler.
|
2014-01-20 15:58:49 +01:00
|
|
|
|
|
|
|
tpl -- A tuple in the form (count, argv) where argv is [cmd, arg, ...]
|
|
|
|
|
|
|
|
All handlers supporting a count should have a keyword argument count.
|
2014-02-07 20:21:50 +01:00
|
|
|
|
2014-01-20 15:58:49 +01:00
|
|
|
"""
|
2014-01-19 22:55:00 +01:00
|
|
|
(count, argv) = tpl
|
|
|
|
cmd = argv[0]
|
|
|
|
args = argv[1:]
|
|
|
|
|
2014-01-19 23:03:06 +01:00
|
|
|
handlers = {
|
2014-01-28 06:52:59 +01:00
|
|
|
'open': self.mainwindow.tabs.openurl,
|
2014-02-06 13:34:49 +01:00
|
|
|
'opencur': self.mainwindow.tabs.opencur,
|
2014-01-28 06:52:59 +01:00
|
|
|
'tabopen': self.mainwindow.tabs.tabopen,
|
2014-02-06 13:34:49 +01:00
|
|
|
'tabopencur': self.mainwindow.tabs.tabopencur,
|
2014-01-28 06:52:59 +01:00
|
|
|
'quit': self.quit,
|
|
|
|
'tabclose': self.mainwindow.tabs.cur_close,
|
|
|
|
'tabprev': self.mainwindow.tabs.switch_prev,
|
|
|
|
'tabnext': self.mainwindow.tabs.switch_next,
|
|
|
|
'reload': self.mainwindow.tabs.cur_reload,
|
|
|
|
'stop': self.mainwindow.tabs.cur_stop,
|
|
|
|
'back': self.mainwindow.tabs.cur_back,
|
|
|
|
'forward': self.mainwindow.tabs.cur_forward,
|
|
|
|
'print': self.mainwindow.tabs.cur_print,
|
|
|
|
'scroll': self.mainwindow.tabs.cur_scroll,
|
2014-01-28 19:52:09 +01:00
|
|
|
'scroll_perc_x': self.mainwindow.tabs.cur_scroll_percent_x,
|
2014-01-28 06:52:59 +01:00
|
|
|
'scroll_perc_y': self.mainwindow.tabs.cur_scroll_percent_y,
|
|
|
|
'undo': self.mainwindow.tabs.undo_close,
|
|
|
|
'pyeval': self.pyeval,
|
2014-01-29 21:06:56 +01:00
|
|
|
'nextsearch': self.searchparser.nextsearch,
|
2014-01-30 00:29:46 +01:00
|
|
|
'yank': self.mainwindow.tabs.cur_yank,
|
|
|
|
'yanktitle': self.mainwindow.tabs.cur_yank_title,
|
|
|
|
'paste': self.mainwindow.tabs.paste,
|
|
|
|
'tabpaste': self.mainwindow.tabs.tabpaste,
|
2014-01-30 14:58:32 +01:00
|
|
|
'crash': self.crash,
|
2014-01-19 23:03:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
handler = handlers[cmd]
|
|
|
|
|
2014-01-30 07:08:45 +01:00
|
|
|
if count is not None and self.sender().count:
|
2014-01-29 15:30:19 +01:00
|
|
|
return handler(*args, count=count)
|
2014-01-19 23:03:06 +01:00
|
|
|
else:
|
2014-01-29 15:30:19 +01:00
|
|
|
return handler(*args)
|
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.
|
|
|
|
|
|
|
|
s -- The string to evaluate.
|
|
|
|
|
|
|
|
:pyeval command handler.
|
2014-02-07 20:21:50 +01:00
|
|
|
|
2014-01-29 15:30:19 +01:00
|
|
|
"""
|
2014-01-19 23:54:22 +01:00
|
|
|
try:
|
|
|
|
r = eval(s)
|
|
|
|
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-07 10:07:13 +01:00
|
|
|
about.pyeval_output = out
|
|
|
|
self.mainwindow.tabs.openurl('about:pyeval')
|
2014-01-30 14:58:32 +01:00
|
|
|
|
|
|
|
def crash(self):
|
|
|
|
"""Crash for debugging purposes.
|
|
|
|
|
|
|
|
:crash command handler.
|
2014-02-07 20:21:50 +01:00
|
|
|
|
2014-01-30 14:58:32 +01:00
|
|
|
"""
|
|
|
|
raise Exception
|