From 69f729dbe5156330c4a866a78be6003ecb12c4d5 Mon Sep 17 00:00:00 2001 From: Austin Anderson Date: Sat, 9 May 2015 18:07:40 -0400 Subject: [PATCH 001/214] Added foreground color settings for statusbar messages. --- qutebrowser/config/configdata.py | 16 ++++++++++++++++ qutebrowser/mainwindow/statusbar/bar.py | 21 +++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index e0e613892..d5ec8e9bd 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -804,18 +804,34 @@ def data(readonly=False): SettingValue(typ.QssColor(), 'red'), "Background color of the statusbar if there was an error."), + ('statusbar.fg.error', + SettingValue(typ.QssColor(), 'white'), + "Foreground color of the statusbar if there was an error."), + ('statusbar.bg.warning', SettingValue(typ.QssColor(), 'darkorange'), "Background color of the statusbar if there is a warning."), + ('statusbar.fg.warning', + SettingValue(typ.QssColor(), 'white'), + "Foreground color of the statusbar if there is a warning."), + ('statusbar.bg.prompt', SettingValue(typ.QssColor(), 'darkblue'), "Background color of the statusbar if there is a prompt."), + ('statusbar.fg.prompt', + SettingValue(typ.QssColor(), 'white'), + "Foreground color of the statusbar if there is a prompt."), + ('statusbar.bg.insert', SettingValue(typ.QssColor(), 'darkgreen'), "Background color of the statusbar in insert mode."), + ('statusbar.fg.insert', + SettingValue(typ.QssColor(), 'white'), + "Foreground color of the statusbar in insert mode."), + ('statusbar.progress.bg', SettingValue(typ.QssColor(), 'white'), "Background color of the progress bar."), diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index a1c8aabd0..2a034223b 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -97,26 +97,47 @@ class StatusBar(QWidget): {{ color['statusbar.bg'] }} } + QWidget#StatusBar QLabel { + {{ color['statusbar.fg'] }} + } + QWidget#StatusBar[insert_active="true"] { {{ color['statusbar.bg.insert'] }} } + QWidget#StatusBar[insert_active="true"] QLabel { + {{ color['statusbar.fg.insert'] }} + } + QWidget#StatusBar[prompt_active="true"] { {{ color['statusbar.bg.prompt'] }} } + QWidget#StatusBar[prompt_active="true"] QLabel { + {{ color['statusbar.fg.prompt'] }} + } + QWidget#StatusBar[severity="error"] { {{ color['statusbar.bg.error'] }} } + QWidget#StatusBar[severity="error"] QLabel { + {{ color['statusbar.fg.error'] }} + } + QWidget#StatusBar[severity="warning"] { {{ color['statusbar.bg.warning'] }} } + QWidget#StatusBar[severity="warning"] QLabel { + {{ color['statusbar.fg.warning'] }} + } + QLabel, QLineEdit { {{ color['statusbar.fg'] }} {{ font['statusbar'] }} } + """ def __init__(self, win_id, parent=None): From 244d2753df3fd63af468e49d31d591e4a6a16342 Mon Sep 17 00:00:00 2001 From: Austin Anderson Date: Sun, 10 May 2015 15:33:58 -0400 Subject: [PATCH 002/214] Reordered fg/bg statusbar color options Options are now all fg, bg for each variant. --- qutebrowser/config/configdata.py | 56 ++++++++++++++++---------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index d5ec8e9bd..98787c284 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -792,46 +792,46 @@ def data(readonly=False): SettingValue(typ.QssColor(), '#ff4444'), "Foreground color of the matched text in the completion."), - ('statusbar.bg', - SettingValue(typ.QssColor(), 'black'), - "Foreground color of the statusbar."), - ('statusbar.fg', SettingValue(typ.QssColor(), 'white'), "Foreground color of the statusbar."), - ('statusbar.bg.error', - SettingValue(typ.QssColor(), 'red'), - "Background color of the statusbar if there was an error."), + ('statusbar.bg', + SettingValue(typ.QssColor(), 'black'), + "Foreground color of the statusbar."), ('statusbar.fg.error', SettingValue(typ.QssColor(), 'white'), "Foreground color of the statusbar if there was an error."), - ('statusbar.bg.warning', - SettingValue(typ.QssColor(), 'darkorange'), - "Background color of the statusbar if there is a warning."), + ('statusbar.bg.error', + SettingValue(typ.QssColor(), 'red'), + "Background color of the statusbar if there was an error."), ('statusbar.fg.warning', SettingValue(typ.QssColor(), 'white'), "Foreground color of the statusbar if there is a warning."), - ('statusbar.bg.prompt', - SettingValue(typ.QssColor(), 'darkblue'), - "Background color of the statusbar if there is a prompt."), + ('statusbar.bg.warning', + SettingValue(typ.QssColor(), 'darkorange'), + "Background color of the statusbar if there is a warning."), ('statusbar.fg.prompt', SettingValue(typ.QssColor(), 'white'), "Foreground color of the statusbar if there is a prompt."), - ('statusbar.bg.insert', - SettingValue(typ.QssColor(), 'darkgreen'), - "Background color of the statusbar in insert mode."), + ('statusbar.bg.prompt', + SettingValue(typ.QssColor(), 'darkblue'), + "Background color of the statusbar if there is a prompt."), ('statusbar.fg.insert', SettingValue(typ.QssColor(), 'white'), "Foreground color of the statusbar in insert mode."), + ('statusbar.bg.insert', + SettingValue(typ.QssColor(), 'darkgreen'), + "Background color of the statusbar in insert mode."), + ('statusbar.progress.bg', SettingValue(typ.QssColor(), 'white'), "Background color of the progress bar."), @@ -863,22 +863,22 @@ def data(readonly=False): SettingValue(typ.QtColor(), 'white'), "Foreground color of unselected odd tabs."), - ('tabs.fg.even', - SettingValue(typ.QtColor(), 'white'), - "Foreground color of unselected even tabs."), - - ('tabs.fg.selected', - SettingValue(typ.QtColor(), 'white'), - "Foreground color of selected tabs."), - ('tabs.bg.odd', SettingValue(typ.QtColor(), 'grey'), "Background color of unselected odd tabs."), + ('tabs.fg.even', + SettingValue(typ.QtColor(), 'white'), + "Foreground color of unselected even tabs."), + ('tabs.bg.even', SettingValue(typ.QtColor(), 'darkgrey'), "Background color of unselected even tabs."), + ('tabs.fg.selected', + SettingValue(typ.QtColor(), 'white'), + "Foreground color of selected tabs."), + ('tabs.bg.selected', SettingValue(typ.QtColor(), 'black'), "Background color of selected tabs."), @@ -907,10 +907,6 @@ def data(readonly=False): SettingValue(typ.CssColor(), 'black'), "Font color for hints."), - ('hints.fg.match', - SettingValue(typ.CssColor(), 'green'), - "Font color for the matched part of hints."), - ('hints.bg', SettingValue( typ.CssColor(), '-webkit-gradient(linear, left top, ' @@ -918,6 +914,10 @@ def data(readonly=False): 'color-stop(100%,#FFC542))'), "Background color for hints."), + ('hints.fg.match', + SettingValue(typ.CssColor(), 'green'), + "Font color for the matched part of hints."), + ('downloads.fg', SettingValue(typ.QtColor(), '#ffffff'), "Foreground color for downloads."), From 1a2a57d59eccee3de70899fdfac6db4c90dd1c61 Mon Sep 17 00:00:00 2001 From: Austin Anderson Date: Mon, 11 May 2015 22:27:21 -0400 Subject: [PATCH 003/214] Added command mode color configuration options. Including necessary tracker variable _command_active. --- qutebrowser/config/configdata.py | 8 ++++ qutebrowser/mainwindow/statusbar/bar.py | 49 +++++++++++++++++++++---- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 98787c284..9327dd377 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -832,6 +832,14 @@ def data(readonly=False): SettingValue(typ.QssColor(), 'darkgreen'), "Background color of the statusbar in insert mode."), + ('statusbar.fg.command', + SettingValue(typ.QssColor(), '${statusbar.fg}'), + "Foreground color of the statusbar in command mode."), + + ('statusbar.bg.command', + SettingValue(typ.QssColor(), '${statusbar.bg}'), + "Background color of the statusbar in command mode."), + ('statusbar.progress.bg', SettingValue(typ.QssColor(), 'white'), "Background color of the progress bar."), diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index 2a034223b..98d2d05e0 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -77,6 +77,10 @@ class StatusBar(QWidget): For some reason we need to have this as class attribute so pyqtProperty works correctly. + _command_active: If we're currently in command mode. + + For some reason we need to have this as class attribute + so pyqtProperty works correctly. Signals: resized: Emitted when the statusbar has resized, so the completion widget can adjust its size to it. @@ -91,6 +95,7 @@ class StatusBar(QWidget): _severity = None _prompt_active = False _insert_active = False + _command_active = False STYLESHEET = """ QWidget#StatusBar { @@ -101,12 +106,8 @@ class StatusBar(QWidget): {{ color['statusbar.fg'] }} } - QWidget#StatusBar[insert_active="true"] { - {{ color['statusbar.bg.insert'] }} - } - - QWidget#StatusBar[insert_active="true"] QLabel { - {{ color['statusbar.fg.insert'] }} + QWidget#StatusBar QLineEdit { + {{ color['statusbar.fg.command'] }} } QWidget#StatusBar[prompt_active="true"] { @@ -117,6 +118,22 @@ class StatusBar(QWidget): {{ color['statusbar.fg.prompt'] }} } + QWidget#StatusBar[insert_active="true"] { + {{ color['statusbar.bg.insert'] }} + } + + QWidget#StatusBar[insert_active="true"] QLabel { + {{ color['statusbar.fg.insert'] }} + } + + QWidget#StatusBar[command_active="true"] QLabel { + {{ color['statusbar.fg.command'] }} + } + + QWidget#StatusBar[command_active="true"] { + {{ color['statusbar.bg.command'] }} + } + QWidget#StatusBar[severity="error"] { {{ color['statusbar.bg.error'] }} } @@ -134,7 +151,6 @@ class StatusBar(QWidget): } QLabel, QLineEdit { - {{ color['statusbar.fg'] }} {{ font['statusbar'] }} } @@ -269,6 +285,21 @@ class StatusBar(QWidget): self._prompt_active = val self.setStyleSheet(style.get_stylesheet(self.STYLESHEET)) + @pyqtProperty(bool) + def command_active(self): + """Getter for self.command_active, so it can be used as Qt property.""" + return self._command_active + + def _set_command_active(self, val): + """Setter for self._command_active. + + Re-set the stylesheet after setting the value, so everything gets + updated by Qt properly. + """ + log.statusbar.debug("Setting command_active to {}".format(val)) + self._command_active = val + self.setStyleSheet(style.get_stylesheet(self.STYLESHEET)) + @pyqtProperty(bool) def insert_active(self): """Getter for self.insert_active, so it can be used as Qt property.""" @@ -461,6 +492,8 @@ class StatusBar(QWidget): self._set_mode_text(mode.name) if mode == usertypes.KeyMode.insert: self._set_insert_active(True) + if mode == usertypes.KeyMode.command: + self._set_command_active(True) @pyqtSlot(usertypes.KeyMode, usertypes.KeyMode) def on_mode_left(self, old_mode, new_mode): @@ -474,6 +507,8 @@ class StatusBar(QWidget): self.txt.set_text(self.txt.Text.normal, '') if old_mode == usertypes.KeyMode.insert: self._set_insert_active(False) + if old_mode == usertypes.KeyMode.command: + self._set_command_active(False) @config.change_filter('ui', 'message-timeout') def set_pop_timer_interval(self): From 14c1332017f23a01f167b910b140d7291d2aa6dc Mon Sep 17 00:00:00 2001 From: Austin Anderson Date: Mon, 11 May 2015 22:28:12 -0400 Subject: [PATCH 004/214] Reordered statusbar stylesheet to match configuration ordering. --- qutebrowser/mainwindow/statusbar/bar.py | 32 ++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index 98d2d05e0..53c991c86 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -110,6 +110,22 @@ class StatusBar(QWidget): {{ color['statusbar.fg.command'] }} } + QWidget#StatusBar[severity="error"] { + {{ color['statusbar.bg.error'] }} + } + + QWidget#StatusBar[severity="error"] QLabel { + {{ color['statusbar.fg.error'] }} + } + + QWidget#StatusBar[severity="warning"] { + {{ color['statusbar.bg.warning'] }} + } + + QWidget#StatusBar[severity="warning"] QLabel { + {{ color['statusbar.fg.warning'] }} + } + QWidget#StatusBar[prompt_active="true"] { {{ color['statusbar.bg.prompt'] }} } @@ -134,22 +150,6 @@ class StatusBar(QWidget): {{ color['statusbar.bg.command'] }} } - QWidget#StatusBar[severity="error"] { - {{ color['statusbar.bg.error'] }} - } - - QWidget#StatusBar[severity="error"] QLabel { - {{ color['statusbar.fg.error'] }} - } - - QWidget#StatusBar[severity="warning"] { - {{ color['statusbar.bg.warning'] }} - } - - QWidget#StatusBar[severity="warning"] QLabel { - {{ color['statusbar.fg.warning'] }} - } - QLabel, QLineEdit { {{ font['statusbar'] }} } From 0d66647918c4d27001752ba8ba08dd1bcd954f0d Mon Sep 17 00:00:00 2001 From: Austin Anderson Date: Mon, 11 May 2015 22:35:44 -0400 Subject: [PATCH 005/214] Set extra foreground colors to match the default by default. --- qutebrowser/config/configdata.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 9327dd377..b4bfbd900 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -801,7 +801,7 @@ def data(readonly=False): "Foreground color of the statusbar."), ('statusbar.fg.error', - SettingValue(typ.QssColor(), 'white'), + SettingValue(typ.QssColor(), '${statusbar.fg}'), "Foreground color of the statusbar if there was an error."), ('statusbar.bg.error', @@ -809,7 +809,7 @@ def data(readonly=False): "Background color of the statusbar if there was an error."), ('statusbar.fg.warning', - SettingValue(typ.QssColor(), 'white'), + SettingValue(typ.QssColor(), '${statusbar.fg}'), "Foreground color of the statusbar if there is a warning."), ('statusbar.bg.warning', @@ -817,7 +817,7 @@ def data(readonly=False): "Background color of the statusbar if there is a warning."), ('statusbar.fg.prompt', - SettingValue(typ.QssColor(), 'white'), + SettingValue(typ.QssColor(), '${statusbar.fg}'), "Foreground color of the statusbar if there is a prompt."), ('statusbar.bg.prompt', @@ -825,7 +825,7 @@ def data(readonly=False): "Background color of the statusbar if there is a prompt."), ('statusbar.fg.insert', - SettingValue(typ.QssColor(), 'white'), + SettingValue(typ.QssColor(), '${statusbar.fg}'), "Foreground color of the statusbar in insert mode."), ('statusbar.bg.insert', From 229733f1b03079dd2c1ad6c28a7d4fe518ba97e7 Mon Sep 17 00:00:00 2001 From: Austin Anderson Date: Mon, 11 May 2015 22:46:26 -0400 Subject: [PATCH 006/214] Properly distinguish between statusbar modes when styling line input. --- qutebrowser/mainwindow/statusbar/bar.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index 53c991c86..aca145f6a 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -107,7 +107,7 @@ class StatusBar(QWidget): } QWidget#StatusBar QLineEdit { - {{ color['statusbar.fg.command'] }} + {{ color['statusbar.fg'] }} } QWidget#StatusBar[severity="error"] { @@ -134,6 +134,10 @@ class StatusBar(QWidget): {{ color['statusbar.fg.prompt'] }} } + QWidget#StatusBar[prompt_active="true"] QLineEdit { + {{ color['statusbar.fg.prompt'] }} + } + QWidget#StatusBar[insert_active="true"] { {{ color['statusbar.bg.insert'] }} } @@ -142,12 +146,16 @@ class StatusBar(QWidget): {{ color['statusbar.fg.insert'] }} } + QWidget#StatusBar[command_active="true"] { + {{ color['statusbar.bg.command'] }} + } + QWidget#StatusBar[command_active="true"] QLabel { {{ color['statusbar.fg.command'] }} } - QWidget#StatusBar[command_active="true"] { - {{ color['statusbar.bg.command'] }} + QWidget#StatusBar[command_active="true"] QLineEdit { + {{ color['statusbar.fg.command'] }} } QLabel, QLineEdit { From 58f031630c6b04c0178f268bf5ad3803455d0c31 Mon Sep 17 00:00:00 2001 From: Lamar Pavel Date: Fri, 22 May 2015 14:44:04 +0200 Subject: [PATCH 007/214] user-stylesheet can be read from relative paths This ist just a first draft to approach issue622 (https://github.com/The-Compiler/qutebrowser/issues/622) and my very first babysteps with python. With this change it is possible to set a user-stylesheet with a relative path, eg.: :set ui user-stylesheet mystyle.css where mystyle.css is in the ~/.config/qutebrowser/. --- .gitignore | 2 ++ qutebrowser/config/configtypes.py | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/.gitignore b/.gitignore index f3ff3652a..9552dc3c4 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ __pycache__ /htmlcov /.tox /testresults.html +tags +*.swp diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index b9c760116..354c09598 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -34,6 +34,7 @@ from PyQt5.QtWidgets import QTabWidget, QTabBar from qutebrowser.commands import cmdutils from qutebrowser.config import configexc +from qutebrowser.utils import standarddir SYSTEM_PROXY = object() # Return value for Proxy type @@ -792,6 +793,9 @@ class RegexList(List): raise configexc.ValidationError(value, "items may not be empty!") +# TODO(lamar) Issue622, relative paths for some config files and directories +# should be implemented here in the base class for files and below in the base +# class for directories. class File(BaseType): """A file on the local filesystem.""" @@ -806,6 +810,10 @@ class File(BaseType): raise configexc.ValidationError(value, "may not be empty!") value = os.path.expanduser(value) try: + if not os.path.isabs(value): + relpath = os.path.join(standarddir.config(), value) + if os.path.isfile(relpath): + value = relpath if not os.path.isfile(value): raise configexc.ValidationError(value, "must be a valid file!") if not os.path.isabs(value): @@ -1160,6 +1168,10 @@ class UserStyleSheet(File): value = os.path.expandvars(value) value = os.path.expanduser(value) try: + if not os.path.isabs(value): + relpath = os.path.join(standarddir.config(), value) + if os.path.isfile(relpath): + value = relpath if not os.path.isabs(value): # probably a CSS, so we don't handle it as filename. # FIXME We just try if it is encodable, maybe we should From 29b25206f6341f8747447e451ddda78e9bf925fd Mon Sep 17 00:00:00 2001 From: Lamar Pavel Date: Fri, 22 May 2015 17:21:00 +0200 Subject: [PATCH 008/214] Fix UserStyleSheet, roll back File The former version of UserStyleSheet never actually loaded the css file, this is now fixed. The changes to class File were rolled back as its functions are overloaded by UserStyleSheet; a general solution in classes File and Directory can be implemented when the changes in UserStyleSheet meet the expectation. --- qutebrowser/config/configtypes.py | 54 ++++++++++++++----------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 354c09598..60bd5ecce 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -793,15 +793,17 @@ class RegexList(List): raise configexc.ValidationError(value, "items may not be empty!") -# TODO(lamar) Issue622, relative paths for some config files and directories -# should be implemented here in the base class for files and below in the base -# class for directories. class File(BaseType): """A file on the local filesystem.""" typestr = 'file' + def transform(self, value): + if not value: + return None + return os.path.expanduser(value) + def validate(self, value): if not value: if self._none_ok: @@ -810,10 +812,6 @@ class File(BaseType): raise configexc.ValidationError(value, "may not be empty!") value = os.path.expanduser(value) try: - if not os.path.isabs(value): - relpath = os.path.join(standarddir.config(), value) - if os.path.isfile(relpath): - value = relpath if not os.path.isfile(value): raise configexc.ValidationError(value, "must be a valid file!") if not os.path.isabs(value): @@ -822,11 +820,6 @@ class File(BaseType): except UnicodeEncodeError as e: raise configexc.ValidationError(value, e) - def transform(self, value): - if not value: - return None - return os.path.expanduser(value) - class Directory(BaseType): @@ -1159,19 +1152,33 @@ class UserStyleSheet(File): def __init__(self): super().__init__(none_ok=True) + def relapath(self, path): + if not os.path.isabs(path): + abspath = os.path.join(standarddir.config(), path) + if os.path.isfile(abspath): + return abspath + return path + + def transform(self, value): + path = os.path.expandvars(value) + path = os.path.expanduser(path) + path = self.relapath(path) + if not value: + return None + elif os.path.isabs(path): + return QUrl.fromLocalFile(path) + else: + data = base64.b64encode(value.encode('utf-8')).decode('ascii') + return QUrl("data:text/css;charset=utf-8;base64,{}".format(data)) + def validate(self, value): if not value: if self._none_ok: return else: raise configexc.ValidationError(value, "may not be empty!") - value = os.path.expandvars(value) - value = os.path.expanduser(value) + value = self.relapath(value) try: - if not os.path.isabs(value): - relpath = os.path.join(standarddir.config(), value) - if os.path.isfile(relpath): - value = relpath if not os.path.isabs(value): # probably a CSS, so we don't handle it as filename. # FIXME We just try if it is encodable, maybe we should @@ -1187,17 +1194,6 @@ class UserStyleSheet(File): except UnicodeEncodeError as e: raise configexc.ValidationError(value, e) - def transform(self, value): - path = os.path.expandvars(value) - path = os.path.expanduser(path) - if not value: - return None - elif os.path.isabs(path): - return QUrl.fromLocalFile(path) - else: - data = base64.b64encode(value.encode('utf-8')).decode('ascii') - return QUrl("data:text/css;charset=utf-8;base64,{}".format(data)) - class AutoSearch(BaseType): From 14ba20670b8923a480c460e41562561556d114d0 Mon Sep 17 00:00:00 2001 From: Lamar Pavel Date: Fri, 22 May 2015 17:31:37 +0200 Subject: [PATCH 009/214] Fix potential bug with missing path-expansion The last commit removed two lines in function validate of class UserStyleSheet that were expanding the path. As it turns out those two lines are needed by validate as well as transform, so I outsourced them to the function they both call at that point. --- qutebrowser/config/configtypes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 60bd5ecce..a1cf77561 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1153,6 +1153,8 @@ class UserStyleSheet(File): super().__init__(none_ok=True) def relapath(self, path): + path = os.path.expandvars(path) + path = os.path.expanduser(path) if not os.path.isabs(path): abspath = os.path.join(standarddir.config(), path) if os.path.isfile(abspath): @@ -1160,9 +1162,7 @@ class UserStyleSheet(File): return path def transform(self, value): - path = os.path.expandvars(value) - path = os.path.expanduser(path) - path = self.relapath(path) + path = self.relapath(value) if not value: return None elif os.path.isabs(path): From 61f32b3e9b693895f2fad4f44122bcb6bb161dba Mon Sep 17 00:00:00 2001 From: Lamar Pavel Date: Fri, 22 May 2015 18:40:56 +0200 Subject: [PATCH 010/214] Revert some changes, trying to get rid of the tox failures --- qutebrowser/config/configtypes.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index a1cf77561..a9d73d454 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1152,20 +1152,16 @@ class UserStyleSheet(File): def __init__(self): super().__init__(none_ok=True) - def relapath(self, path): - path = os.path.expandvars(path) + def transform(self, value): + if not value: + return None + path = os.path.expandvars(value) path = os.path.expanduser(path) if not os.path.isabs(path): abspath = os.path.join(standarddir.config(), path) if os.path.isfile(abspath): - return abspath - return path - - def transform(self, value): - path = self.relapath(value) - if not value: - return None - elif os.path.isabs(path): + path = abspath + if os.path.isabs(path): return QUrl.fromLocalFile(path) else: data = base64.b64encode(value.encode('utf-8')).decode('ascii') @@ -1177,7 +1173,12 @@ class UserStyleSheet(File): return else: raise configexc.ValidationError(value, "may not be empty!") - value = self.relapath(value) + value = os.path.expandvars(value) + value = os.path.expanduser(value) + if not os.path.isabs(value): + abspath = os.path.join(standarddir.config(), value) + if os.path.isfile(abspath): + value = abspath try: if not os.path.isabs(value): # probably a CSS, so we don't handle it as filename. From 93b92f4aab777ff14d74980e4d871ad5097ddb54 Mon Sep 17 00:00:00 2001 From: Lamar Pavel Date: Sat, 23 May 2015 16:09:44 +0200 Subject: [PATCH 011/214] Fix tox failure regarding exceptions in transform Function transform is not supposed to raise exceptions, so I wrapped the call to os.path.join in an if-clause to test if standarddir.config returns a valid value. --- qutebrowser/config/configtypes.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index a9d73d454..2941f6c72 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1158,9 +1158,10 @@ class UserStyleSheet(File): path = os.path.expandvars(value) path = os.path.expanduser(path) if not os.path.isabs(path): - abspath = os.path.join(standarddir.config(), path) - if os.path.isfile(abspath): - path = abspath + if standarddir.config(): + abspath = os.path.join(standarddir.config(), path) + if os.path.isfile(abspath): + path = abspath if os.path.isabs(path): return QUrl.fromLocalFile(path) else: From ad7920dda1bb110cda9983fb9ab6b3861caddf45 Mon Sep 17 00:00:00 2001 From: Lamar Pavel Date: Sat, 23 May 2015 16:49:40 +0200 Subject: [PATCH 012/214] Fix bug; all tox tests succeed My logic in the validate function of class UserStyleSheet was faulty and caused the check for encoding to be skipped. This is now fixed and all tests run successfully. --- qutebrowser/config/configtypes.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 2941f6c72..fb51a4c2e 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1176,12 +1176,11 @@ class UserStyleSheet(File): raise configexc.ValidationError(value, "may not be empty!") value = os.path.expandvars(value) value = os.path.expanduser(value) - if not os.path.isabs(value): - abspath = os.path.join(standarddir.config(), value) - if os.path.isfile(abspath): - value = abspath try: if not os.path.isabs(value): + abspath = os.path.join(standarddir.config(), value) + if os.path.isfile(abspath): + return # probably a CSS, so we don't handle it as filename. # FIXME We just try if it is encodable, maybe we should # validate CSS? From 05530944944b95e324303d6e0ae02ec5e98b09d5 Mon Sep 17 00:00:00 2001 From: Austin Anderson Date: Mon, 25 May 2015 19:20:33 -0400 Subject: [PATCH 013/214] Added explanation of *.system values to settings page. --- qutebrowser/config/configdata.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index b4bfbd900..ad321b73d 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -103,6 +103,10 @@ SECTION_DESC = { " * A gradient as explained in http://qt-project.org/doc/qt-4.8/" "stylesheet-reference.html#list-of-property-types[the Qt " "documentation] under ``Gradient''.\n\n" + "A *.system value determines the color system to use for color " + "interpolation between similarly-named *.start and *.stop entries, " + "regardless of how they are defined in the options. " + "Valid values are 'rgb', 'hsv', and 'hsl'.\n\n" "The `hints.*` values are a special case as they're real CSS " "colors, not Qt-CSS colors. There, for a gradient, you need to use " "`-webkit-gradient`, see https://www.webkit.org/blog/175/introducing-" From a8d2dbfdfb44d41f7f9c17c38c5251d26379f129 Mon Sep 17 00:00:00 2001 From: Austin Anderson Date: Mon, 25 May 2015 20:47:16 -0400 Subject: [PATCH 014/214] Added downloads bar fg customization, and refactored the download's color-picking. --- qutebrowser/browser/downloads.py | 20 ++++++++++++-------- qutebrowser/config/configdata.py | 20 ++++++++++++++++---- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 6d12761f6..e30147e10 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -356,12 +356,16 @@ class DownloadItem(QObject): if reply.error() != QNetworkReply.NoError: QTimer.singleShot(0, lambda: self.error.emit(reply.errorString())) - def bg_color(self): - """Background color to be shown.""" - start = config.get('colors', 'downloads.bg.start') - stop = config.get('colors', 'downloads.bg.stop') - system = config.get('colors', 'downloads.bg.system') - error = config.get('colors', 'downloads.bg.error') + def get_status_color(self, position): + """Choose an appropriate color for presenting the download's status. + + Args: + position: The color type requested, can be 'fg' or 'bg'. + """ + start = config.get('colors', 'downloads.{}.start'.format(position)) + stop = config.get('colors', 'downloads.{}.stop'.format(position)) + system = config.get('colors', 'downloads.{}.system'.format(position)) + error = config.get('colors', 'downloads.{}.error'.format(position)) if self.error_msg is not None: assert not self.successful return error @@ -1020,9 +1024,9 @@ class DownloadManager(QAbstractListModel): if role == Qt.DisplayRole: data = str(item) elif role == Qt.ForegroundRole: - data = config.get('colors', 'downloads.fg') + data = item.get_status_color('fg') elif role == Qt.BackgroundRole: - data = item.bg_color() + data = item.get_status_color('bg') elif role == ModelRole.item: data = item elif role == Qt.ToolTipRole: diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index ad321b73d..db16a2bbf 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -930,26 +930,38 @@ def data(readonly=False): SettingValue(typ.CssColor(), 'green'), "Font color for the matched part of hints."), - ('downloads.fg', - SettingValue(typ.QtColor(), '#ffffff'), - "Foreground color for downloads."), - ('downloads.bg.bar', SettingValue(typ.QssColor(), 'black'), "Background color for the download bar."), + ('downloads.fg.start', + SettingValue(typ.QtColor(), '#0000aa'), + "Color gradient start for downloads."), + ('downloads.bg.start', SettingValue(typ.QtColor(), '#0000aa'), "Color gradient start for downloads."), + ('downloads.fg.stop', + SettingValue(typ.QtColor(), '#00aa00'), + "Color gradient end for downloads."), + ('downloads.bg.stop', SettingValue(typ.QtColor(), '#00aa00'), "Color gradient end for downloads."), + ('downloads.fg.system', + SettingValue(typ.ColorSystem(), 'rgb'), + "Color gradient interpolation system for downloads."), + ('downloads.bg.system', SettingValue(typ.ColorSystem(), 'rgb'), "Color gradient interpolation system for downloads."), + ('downloads.fg.error', + SettingValue(typ.QtColor(), 'red'), + "Foreground color for downloads with errors."), + ('downloads.bg.error', SettingValue(typ.QtColor(), 'red'), "Background color for downloads with errors."), From c54c637ccc65f4f114f3c25e960deac47b69ad6d Mon Sep 17 00:00:00 2001 From: Lamar Pavel Date: Tue, 26 May 2015 12:38:04 +0200 Subject: [PATCH 015/214] Class File not transforms relative paths The code from function transform in class UserStyleSheet is now migrated to class File. --- qutebrowser/config/configtypes.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index fb51a4c2e..d0d822a6f 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -802,7 +802,13 @@ class File(BaseType): def transform(self, value): if not value: return None - return os.path.expanduser(value) + value = os.path.expanduser(value) + if not os.path.isabs(value): + if standarddir.config(): + abspath = os.path.join(standarddir.config(), value) + if os.path.isfile(abspath): + return abspath + return value def validate(self, value): if not value: @@ -1155,13 +1161,8 @@ class UserStyleSheet(File): def transform(self, value): if not value: return None - path = os.path.expandvars(value) - path = os.path.expanduser(path) - if not os.path.isabs(path): - if standarddir.config(): - abspath = os.path.join(standarddir.config(), path) - if os.path.isfile(abspath): - path = abspath + path = super().transform(value) + path = os.path.expandvars(path) if os.path.isabs(path): return QUrl.fromLocalFile(path) else: From f1129460d871d4bca46036085f8d869ff913ad49 Mon Sep 17 00:00:00 2001 From: Lamar Pavel Date: Tue, 26 May 2015 13:54:27 +0200 Subject: [PATCH 016/214] Class File now validates relative paths The code from function validate in class UserStyleSheet has been migrated to class File. One test had to be modified due to different expected behaviour. --- qutebrowser/config/configtypes.py | 34 ++++++++++++++++--------------- tests/config/test_configtypes.py | 3 +-- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index d0d822a6f..4b0b5b70f 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -818,6 +818,10 @@ class File(BaseType): raise configexc.ValidationError(value, "may not be empty!") value = os.path.expanduser(value) try: + if not os.path.isabs(value): + abspath = os.path.join(standarddir.config(), value) + if os.path.isfile(abspath): + return if not os.path.isfile(value): raise configexc.ValidationError(value, "must be a valid file!") if not os.path.isabs(value): @@ -1178,23 +1182,21 @@ class UserStyleSheet(File): value = os.path.expandvars(value) value = os.path.expanduser(value) try: - if not os.path.isabs(value): - abspath = os.path.join(standarddir.config(), value) - if os.path.isfile(abspath): + super().validate(value) + except configexc.ValidationError: + try: + if not os.path.isabs(value): + # probably a CSS, so we don't handle it as filename. + # FIXME We just try if it is encodable, maybe we should + # validate CSS? + # https://github.com/The-Compiler/qutebrowser/issues/115 + try: + value.encode('utf-8') + except UnicodeEncodeError as e: + raise configexc.ValidationError(value, str(e)) return - # probably a CSS, so we don't handle it as filename. - # FIXME We just try if it is encodable, maybe we should - # validate CSS? - # https://github.com/The-Compiler/qutebrowser/issues/115 - try: - value.encode('utf-8') - except UnicodeEncodeError as e: - raise configexc.ValidationError(value, str(e)) - return - elif not os.path.isfile(value): - raise configexc.ValidationError(value, "must be a valid file!") - except UnicodeEncodeError as e: - raise configexc.ValidationError(value, e) + except UnicodeEncodeError as e: + raise configexc.ValidationError(value, e) class AutoSearch(BaseType): diff --git a/tests/config/test_configtypes.py b/tests/config/test_configtypes.py index 871b6cf4d..c515c6cef 100644 --- a/tests/config/test_configtypes.py +++ b/tests/config/test_configtypes.py @@ -1378,8 +1378,7 @@ class TestFile: os_path.expanduser.side_effect = lambda x: x os_path.isfile.return_value = True os_path.isabs.return_value = False - with pytest.raises(configexc.ValidationError): - self.t.validate('foobar') + self.t.validate('foobar') def test_validate_expanduser(self, os_path): """Test if validate expands the user correctly.""" From 4e61a6123e3bf074c122bba2de9311d14d2290b9 Mon Sep 17 00:00:00 2001 From: Lamar Pavel Date: Wed, 27 May 2015 12:06:51 +0200 Subject: [PATCH 017/214] Probably shouldn't include changes to the gitignore in a PR --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 9552dc3c4..f3ff3652a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,5 +22,3 @@ __pycache__ /htmlcov /.tox /testresults.html -tags -*.swp From cfae36c5c82d7ec95b67f7c8f0a1db76fea1e8f1 Mon Sep 17 00:00:00 2001 From: Lamar Pavel Date: Wed, 27 May 2015 14:05:29 +0200 Subject: [PATCH 018/214] Adjust name and doc of modified test --- tests/config/test_configtypes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/config/test_configtypes.py b/tests/config/test_configtypes.py index c515c6cef..d8af6995d 100644 --- a/tests/config/test_configtypes.py +++ b/tests/config/test_configtypes.py @@ -1373,8 +1373,8 @@ class TestFile: os_path.isabs.return_value = True self.t.validate('foobar') - def test_validate_exists_not_abs(self, os_path): - """Test validate with a file which does exist but is not absolute.""" + def test_validate_exists_rel(self, os_path): + """Test validate with a relative path to an existing file.""" os_path.expanduser.side_effect = lambda x: x os_path.isfile.return_value = True os_path.isabs.return_value = False From e12dce9d55eb3d1d3aeb56691e1b10fa28c5d506 Mon Sep 17 00:00:00 2001 From: Lamar Pavel Date: Wed, 27 May 2015 14:40:07 +0200 Subject: [PATCH 019/214] Include expandvars in File.transform, adjust test --- qutebrowser/config/configtypes.py | 8 +++----- tests/config/test_configtypes.py | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 4b0b5b70f..626ec43c2 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -803,6 +803,7 @@ class File(BaseType): if not value: return None value = os.path.expanduser(value) + value = os.path.expandvars(value) if not os.path.isabs(value): if standarddir.config(): abspath = os.path.join(standarddir.config(), value) @@ -818,10 +819,8 @@ class File(BaseType): raise configexc.ValidationError(value, "may not be empty!") value = os.path.expanduser(value) try: - if not os.path.isabs(value): - abspath = os.path.join(standarddir.config(), value) - if os.path.isfile(abspath): - return + if os.path.isfile(os.path.join(standarddir.config(), value)): + return if not os.path.isfile(value): raise configexc.ValidationError(value, "must be a valid file!") if not os.path.isabs(value): @@ -1166,7 +1165,6 @@ class UserStyleSheet(File): if not value: return None path = super().transform(value) - path = os.path.expandvars(path) if os.path.isabs(path): return QUrl.fromLocalFile(path) else: diff --git a/tests/config/test_configtypes.py b/tests/config/test_configtypes.py index d8af6995d..c7df07f14 100644 --- a/tests/config/test_configtypes.py +++ b/tests/config/test_configtypes.py @@ -1398,6 +1398,7 @@ class TestFile: def test_transform(self, os_path): """Test transform.""" os_path.expanduser.side_effect = lambda x: x.replace('~', '/home/foo') + os_path.expandvars.side_effect = lambda x: x assert self.t.transform('~/foobar') == '/home/foo/foobar' os_path.expanduser.assert_called_once_with('~/foobar') From 4851a3d4424e61b01b3035ca69b12cbacde1c808 Mon Sep 17 00:00:00 2001 From: Lamar Pavel Date: Wed, 27 May 2015 15:39:58 +0200 Subject: [PATCH 020/214] Replace isabs with exists in transform In UserStyleSheet.transform os.path.isabs was replaced with os.path.exists, a more fitting condition. Accordingly two test cases needed to include mocks for os.path.exists and QUrl.fromLocalFile. --- qutebrowser/config/configtypes.py | 2 +- tests/config/test_configtypes.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 626ec43c2..c3815cb6d 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1165,7 +1165,7 @@ class UserStyleSheet(File): if not value: return None path = super().transform(value) - if os.path.isabs(path): + if os.path.exists(path): return QUrl.fromLocalFile(path) else: data = base64.b64encode(value.encode('utf-8')).decode('ascii') diff --git a/tests/config/test_configtypes.py b/tests/config/test_configtypes.py index c7df07f14..345a79d3b 100644 --- a/tests/config/test_configtypes.py +++ b/tests/config/test_configtypes.py @@ -1930,13 +1930,21 @@ class TestUserStyleSheet: """Test transform with an empty value.""" assert self.t.transform('') is None - def test_transform_file(self): + def test_transform_file(self, os_path, mocker): """Test transform with a filename.""" + qurl = mocker.patch('qutebrowser.config.configtypes.QUrl', + autospec=True) + qurl.fromLocalFile.return_value = QUrl("file:///foo/bar") + os_path.exists.return_value = True path = os.path.join(os.path.sep, 'foo', 'bar') assert self.t.transform(path) == QUrl("file:///foo/bar") - def test_transform_file_expandvars(self, monkeypatch): + def test_transform_file_expandvars(self, os_path, monkeypatch, mocker): """Test transform with a filename (expandvars).""" + qurl = mocker.patch('qutebrowser.config.configtypes.QUrl', + autospec=True) + qurl.fromLocalFile.return_value = QUrl("file:///foo/bar") + os_path.exists.return_value = True monkeypatch.setenv('FOO', 'foo') path = os.path.join(os.path.sep, '$FOO', 'bar') assert self.t.transform(path) == QUrl("file:///foo/bar") From b5eea81e2ed389487a8cf5cb15043ca6a8094efb Mon Sep 17 00:00:00 2001 From: Lamar Pavel Date: Thu, 28 May 2015 12:14:12 +0200 Subject: [PATCH 021/214] Fix File.validate and corresponding tests There were no tests regarding the return value of standarddir.config() and thus it wasn't caught that it returned None in some cases. This is now fixed by checking the return of standdarddir.config before calling it and modifying the corresponding test_validate_exists_rel as well as adding a new test_validate_rel_config_none. --- qutebrowser/config/configtypes.py | 12 +++++++----- tests/config/test_configtypes.py | 16 ++++++++++++++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index c3815cb6d..edc96d89d 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -819,13 +819,15 @@ class File(BaseType): raise configexc.ValidationError(value, "may not be empty!") value = os.path.expanduser(value) try: - if os.path.isfile(os.path.join(standarddir.config(), value)): - return + if not os.path.isabs(value): + if standarddir.config(): + if os.path.isfile( + os.path.join(standarddir.config(), value)): + return + raise configexc.ValidationError(value, + "must be an absolute path!") if not os.path.isfile(value): raise configexc.ValidationError(value, "must be a valid file!") - if not os.path.isabs(value): - raise configexc.ValidationError( - value, "must be an absolute path!") except UnicodeEncodeError as e: raise configexc.ValidationError(value, e) diff --git a/tests/config/test_configtypes.py b/tests/config/test_configtypes.py index 345a79d3b..1d52afdc8 100644 --- a/tests/config/test_configtypes.py +++ b/tests/config/test_configtypes.py @@ -30,7 +30,7 @@ from PyQt5.QtGui import QColor, QFont from PyQt5.QtNetwork import QNetworkProxy from qutebrowser.config import configtypes, configexc -from qutebrowser.utils import debug, utils +from qutebrowser.utils import debug, utils, standarddir class Font(QFont): @@ -1373,12 +1373,24 @@ class TestFile: os_path.isabs.return_value = True self.t.validate('foobar') - def test_validate_exists_rel(self, os_path): + def test_validate_exists_rel(self, os_path, monkeypatch): """Test validate with a relative path to an existing file.""" + monkeypatch.setattr('qutebrowser.config.configtypes.standarddir.config', + lambda: '/home/foo/.config/') os_path.expanduser.side_effect = lambda x: x os_path.isfile.return_value = True os_path.isabs.return_value = False self.t.validate('foobar') + os_path.join.assert_called_once_with('/home/foo/.config/', + 'foobar') + + def test_validate_rel_config_none(self, os_path, monkeypatch): + """Test with a relative path and standarddir.config returning None.""" + monkeypatch.setattr('qutebrowser.config.configtypes.standarddir.config', + lambda: None) + os_path.isabs.return_value = False + with pytest.raises(configexc.ValidationError): + self.t.validate('foobar') def test_validate_expanduser(self, os_path): """Test if validate expands the user correctly.""" From f5d299d8c7170061406fa89020183a2762a2e1ef Mon Sep 17 00:00:00 2001 From: Lamar Pavel Date: Thu, 28 May 2015 13:05:12 +0200 Subject: [PATCH 022/214] Fix intents --- qutebrowser/config/configtypes.py | 10 +++++----- tests/config/test_configtypes.py | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index edc96d89d..489dc456d 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -820,12 +820,12 @@ class File(BaseType): value = os.path.expanduser(value) try: if not os.path.isabs(value): - if standarddir.config(): - if os.path.isfile( - os.path.join(standarddir.config(), value)): - return + cfgdir = standarddir.config() + if cfgdir: + if os.path.isfile(os.path.join(cfgdir, value)): + return raise configexc.ValidationError(value, - "must be an absolute path!") + "must be an absolute path!") if not os.path.isfile(value): raise configexc.ValidationError(value, "must be a valid file!") except UnicodeEncodeError as e: diff --git a/tests/config/test_configtypes.py b/tests/config/test_configtypes.py index 1d52afdc8..317acbe5e 100644 --- a/tests/config/test_configtypes.py +++ b/tests/config/test_configtypes.py @@ -30,7 +30,7 @@ from PyQt5.QtGui import QColor, QFont from PyQt5.QtNetwork import QNetworkProxy from qutebrowser.config import configtypes, configexc -from qutebrowser.utils import debug, utils, standarddir +from qutebrowser.utils import debug, utils class Font(QFont): @@ -1375,19 +1375,19 @@ class TestFile: def test_validate_exists_rel(self, os_path, monkeypatch): """Test validate with a relative path to an existing file.""" - monkeypatch.setattr('qutebrowser.config.configtypes.standarddir.config', - lambda: '/home/foo/.config/') + monkeypatch.setattr( + 'qutebrowser.config.configtypes.standarddir.config', + lambda: '/home/foo/.config/') os_path.expanduser.side_effect = lambda x: x os_path.isfile.return_value = True os_path.isabs.return_value = False self.t.validate('foobar') - os_path.join.assert_called_once_with('/home/foo/.config/', - 'foobar') + os_path.join.assert_called_once_with('/home/foo/.config/', 'foobar') def test_validate_rel_config_none(self, os_path, monkeypatch): """Test with a relative path and standarddir.config returning None.""" - monkeypatch.setattr('qutebrowser.config.configtypes.standarddir.config', - lambda: None) + monkeypatch.setattr( + 'qutebrowser.config.configtypes.standarddir.config', lambda: None) os_path.isabs.return_value = False with pytest.raises(configexc.ValidationError): self.t.validate('foobar') From 63c9e6a444f1a45a8970e3bfebc2c131bc1c9827 Mon Sep 17 00:00:00 2001 From: Lamar Pavel Date: Thu, 28 May 2015 13:20:00 +0200 Subject: [PATCH 023/214] Another indentation-related fix --- qutebrowser/config/configtypes.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 489dc456d..94bc278bf 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -821,9 +821,8 @@ class File(BaseType): try: if not os.path.isabs(value): cfgdir = standarddir.config() - if cfgdir: - if os.path.isfile(os.path.join(cfgdir, value)): - return + if cfgdir and os.path.isfile(os.path.join(cfgdir, value)): + return raise configexc.ValidationError(value, "must be an absolute path!") if not os.path.isfile(value): From 27fdf4903a5bffec6af39032c07bd8f22f255ca6 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Fri, 29 May 2015 18:36:39 +0200 Subject: [PATCH 024/214] Implement :jseval (Issue #334) TODO: - Tests - Doesn't show errors --- qutebrowser/misc/utilcmds.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index ea6fdf1cc..1c7e40d99 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -111,6 +111,19 @@ def message_warning(win_id, text): message.warning(win_id, text) +@cmdutils.register(maxsplit=0, no_cmd_split=True) +def jseval(s): + """Evaluate a JavaScript string. + + Args: + s: The string to evaluate. + """ + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window='last-focused') + message_info(tabbed_browser.widget(0) + .page().mainFrame().evaluateJavaScript(s)) + + @cmdutils.register(debug=True) def debug_crash(typ: {'type': ('exception', 'segfault')}='exception'): """Crash for debugging purposes. From 7b5d2ace2485b91707ce03b79e76be6580c51d41 Mon Sep 17 00:00:00 2001 From: Austin Anderson Date: Sat, 30 May 2015 15:21:34 -0400 Subject: [PATCH 025/214] Added assertion for parameterized download color picker. --- qutebrowser/browser/downloads.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index e30147e10..f10cd7aad 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -362,6 +362,7 @@ class DownloadItem(QObject): Args: position: The color type requested, can be 'fg' or 'bg'. """ + assert position in ("fg", "bg") start = config.get('colors', 'downloads.{}.start'.format(position)) stop = config.get('colors', 'downloads.{}.stop'.format(position)) system = config.get('colors', 'downloads.{}.system'.format(position)) From fed2cdad4ef138f44171a699d39a3eb7211b87ba Mon Sep 17 00:00:00 2001 From: Austin Anderson Date: Sat, 30 May 2015 15:22:00 -0400 Subject: [PATCH 026/214] Cleaned up download configuration options. --- qutebrowser/config/configdata.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index db16a2bbf..67f847865 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -935,31 +935,32 @@ def data(readonly=False): "Background color for the download bar."), ('downloads.fg.start', - SettingValue(typ.QtColor(), '#0000aa'), - "Color gradient start for downloads."), + SettingValue(typ.QtColor(), 'white'), + "Color gradient start for download foreground text."), ('downloads.bg.start', SettingValue(typ.QtColor(), '#0000aa'), - "Color gradient start for downloads."), + "Color gradient start for download background."), ('downloads.fg.stop', - SettingValue(typ.QtColor(), '#00aa00'), - "Color gradient end for downloads."), + SettingValue(typ.QtColor(), '${downloads.fg.start}'), + "Color gradient end for download foreground text."), ('downloads.bg.stop', SettingValue(typ.QtColor(), '#00aa00'), - "Color gradient end for downloads."), + "Color gradient stop for download background."), ('downloads.fg.system', SettingValue(typ.ColorSystem(), 'rgb'), - "Color gradient interpolation system for downloads."), + "Color gradient interpolation system for download foreground" + "text."), ('downloads.bg.system', SettingValue(typ.ColorSystem(), 'rgb'), - "Color gradient interpolation system for downloads."), + "Color gradient interpolation system for download background."), ('downloads.fg.error', - SettingValue(typ.QtColor(), 'red'), + SettingValue(typ.QtColor(), 'white'), "Foreground color for downloads with errors."), ('downloads.bg.error', From 5c599879f819e600d11bcf197f0a902a812fdde6 Mon Sep 17 00:00:00 2001 From: Austin Anderson Date: Sat, 30 May 2015 16:03:36 -0400 Subject: [PATCH 027/214] Fixed a line-length error. --- qutebrowser/mainwindow/statusbar/bar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index 1e4ae3b84..9b1219b38 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -80,8 +80,8 @@ class StatusBar(QWidget): _command_active: If we're currently in command mode. - For some reason we need to have this as class attribute - so pyqtProperty works correctly. + For some reason we need to have this as class + attribute so pyqtProperty works correctly. _caret_mode: The current caret mode (off/on/selection). From 4d141f489f18d98e00df249a89356c87fef69361 Mon Sep 17 00:00:00 2001 From: Austin Anderson Date: Wed, 3 Jun 2015 08:42:13 -0400 Subject: [PATCH 028/214] Added pylint workaround directive to quash rebellion. --- qutebrowser/browser/downloads.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 3a7c63fc0..22e8858af 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -362,6 +362,8 @@ class DownloadItem(QObject): Args: position: The color type requested, can be 'fg' or 'bg'. """ + # pylint: disable=bad-config-call + # WORKAROUND for https://bitbucket.org/logilab/astroid/issue/104/ assert position in ("fg", "bg") start = config.get('colors', 'downloads.{}.start'.format(position)) stop = config.get('colors', 'downloads.{}.stop'.format(position)) From 85eea17b183751a89ddc8bb4bb82a3a0ccdf5ba5 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Wed, 3 Jun 2015 22:31:15 +0200 Subject: [PATCH 029/214] Try to get the error ... not sure about this ... source is undefined when you type stuff in the console, I *think* this is the only scenario? But maybe not? --- qutebrowser/browser/commands.py | 20 ++++++++++++++++++++ qutebrowser/browser/webpage.py | 11 +++++++++++ qutebrowser/misc/utilcmds.py | 13 ------------- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 0c01260d7..bc2159843 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1510,3 +1510,23 @@ class CommandDispatcher: view = self._current_widget() for _ in range(count): view.triggerPageAction(member) + + @cmdutils.register(instance='command-dispatcher', scope='window', + maxsplit=0, no_cmd_split=True) + def jseval(self, js): + """Evaluate a JavaScript string. + + Args: + s: The string to evaluate. + """ + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window='last-focused') + out = tabbed_browser.widget(0).page().mainFrame().evaluateJavaScript( + 'window.__qute_jseval__ = true;\n' + js) + + if out is not None: + message.info(self._win_id, out) + elif tabbed_browser.widget(0).page().jseval_error: + message.error(self._win_id, + tabbed_browser.widget(0).page().jseval_error) + tabbed_browser.widget(0).page().jseval_error = None diff --git a/qutebrowser/browser/webpage.py b/qutebrowser/browser/webpage.py index 005a6e300..9e49032df 100644 --- a/qutebrowser/browser/webpage.py +++ b/qutebrowser/browser/webpage.py @@ -63,6 +63,7 @@ class BrowserPage(QWebPage): def __init__(self, win_id, tab_id, parent=None): super().__init__(parent) self._win_id = win_id + self.jseval_error = None self._is_shutting_down = False self._extension_handlers = { QWebPage.ErrorPageExtension: self._handle_errorpage, @@ -497,6 +498,16 @@ class BrowserPage(QWebPage): def javaScriptConsoleMessage(self, msg, line, source): """Override javaScriptConsoleMessage to use debug log.""" + + jseval = self.mainFrame().evaluateJavaScript('window.__qute_jseval__') + if jseval: + self.mainFrame().evaluateJavaScript('window.__qute_jseval__ = undefined;') + if source == 'undefined' and jseval: + self.mainFrame().evaluateJavaScript('window.__qute_jseval__ = false;') + print('jseval errror ->', jseval) + self.jseval_error = 'Error on line {}: {}'.format(line, msg) + print('other js error ->', msg, line, source) + if config.get('general', 'log-javascript-console'): log.js.debug("[{}:{}] {}".format(source, line, msg)) diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index 1c7e40d99..ea6fdf1cc 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -111,19 +111,6 @@ def message_warning(win_id, text): message.warning(win_id, text) -@cmdutils.register(maxsplit=0, no_cmd_split=True) -def jseval(s): - """Evaluate a JavaScript string. - - Args: - s: The string to evaluate. - """ - tabbed_browser = objreg.get('tabbed-browser', scope='window', - window='last-focused') - message_info(tabbed_browser.widget(0) - .page().mainFrame().evaluateJavaScript(s)) - - @cmdutils.register(debug=True) def debug_crash(typ: {'type': ('exception', 'segfault')}='exception'): """Crash for debugging purposes. From 472071c0476f3d42d5767a6823194857aa7f240b Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Wed, 3 Jun 2015 23:59:24 +0200 Subject: [PATCH 030/214] Add setting: 'content.third-party-cookie-policy', fixes #607 This sets the third-party cookie policy. - I created a new ThirdPartyCookiePolicy() class, since this setting seems to be unique in the way it is set... - I set the default to 'never', which is the most secure/private setting, but *may* break *some* features of a (very) limited number of sites; these are usually "non-critical" features. For example, on Stack Exchange sites you're logged in all 200+ sites if you sign in on one of them, this features required 3rd party cookies. You can still sign in with out, but you have to do so 200+ times (this is actually the only example I've ever noticed). AFAIK all "major" browsers accept 3rd-party cookies by default, except for Safari. Firefox also made this change, but reversed it (see: https://brendaneich.com/2013/05/c-is-for-cookie/), but they don't offer any good arguments to *not* have it IMHO, at least not that I could find. In any case, in my humble opinion "secure and private by default" is the best way to ship. But you're of course free to change it if you disagree ;-) --- doc/help/settings.asciidoc | 13 +++++++++++++ qutebrowser/config/configdata.py | 4 ++++ qutebrowser/config/configtypes.py | 10 ++++++++++ qutebrowser/config/websettings.py | 21 +++++++++++++++++++++ 4 files changed, 48 insertions(+) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index fc4802232..717a46d13 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -151,6 +151,7 @@ |<>|Whether locally loaded documents are allowed to access other local urls. |<>|Whether to accept cookies. |<>|Whether to store cookies. +|<>|Accept cookies from domains other than the main website |<>|List of URLs of lists which contain hosts to block. |<>|Whether host blocking is enabled. |============== @@ -1336,6 +1337,18 @@ Valid values: Default: +pass:[true]+ +[[content-third-party-cookie-policy]] +=== third-party-cookie-policy +Accept cookies from domains other than the main website + +Valid values: + + * +always+: Always accept. + * +never+: Never accept. + * +existing+: Only accept if we already have acookie stored for the domain + +Default: +pass:[never]+ + [[content-host-block-lists]] === host-block-lists List of URLs of lists which contain hosts to block. diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index a3bbe2d48..9028d2c78 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -681,6 +681,10 @@ def data(readonly=False): SettingValue(typ.Bool(), 'true'), "Whether to store cookies."), + ('third-party-cookie-policy', + SettingValue(typ.ThirdPartyCookiePolicy(), 'never'), + "Accept cookies from domains other than the main website"), + ('host-block-lists', SettingValue( typ.UrlList(none_ok=True), diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 55d782202..b4d7319c6 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1329,6 +1329,16 @@ class AcceptCookies(BaseType): ('never', "Don't accept cookies at all.")) +class ThirdPartyCookiePolicy(BaseType): + + """Accept cookies from domains other than the main website.""" + + valid_values = ValidValues(('always', "Always accept."), + ('never', "Never accept."), + ('existing', "Only accept if we already have a" + "cookie stored for the domain.")) + + class ConfirmQuit(List): """Whether to display a confirmation when the window is closed.""" diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index 117abbb26..edea4edeb 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -238,6 +238,25 @@ class GlobalSetter(Setter): self._setter(*args) +class ThirdPartyCookies(Base): + + """The ThirdPartyCookiePolicy setting is different from other settings.""" + + mapping = ( + ('always', QWebSettings.AlwaysAllowThirdPartyCookies), + ('never', QWebSettings.AlwaysBlockThirdPartyCookies), + ('existing', QWebSettings.AllowThirdPartyWithExistingCookies), + ) + + def get(self, qws=None): + policy = QWebSettings.globalSettings().thirdPartyCookiePolicy() + return tuple(filter(lambda i: i[1] == policy, self.mapping))[0][0] + + def _set(self, value, qws=None): + x = filter(lambda i: i[0] == value, self.mapping) + QWebSettings.globalSettings().setThirdPartyCookiePolicy(tuple(x)[0][1]) + + MAPPINGS = { 'content': { 'allow-images': @@ -264,6 +283,8 @@ MAPPINGS = { Attribute(QWebSettings.LocalContentCanAccessRemoteUrls), 'local-content-can-access-file-urls': Attribute(QWebSettings.LocalContentCanAccessFileUrls), + 'third-party-cookie-policy': + ThirdPartyCookies(), }, 'network': { 'dns-prefetch': From 0132bea42bb023139b773c331115ac00ad93f081 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Thu, 4 Jun 2015 12:20:43 +0200 Subject: [PATCH 031/214] Add --domain to yank to yank only the domain ... As I want to copy only the domain fairly frequently. I also changed the message in the statusline to show the actual text being copied, which I find helpful. But if you disagree, then just undo it (it's not that important or anything). --- doc/help/commands.asciidoc | 3 ++- qutebrowser/browser/commands.py | 13 ++++++++++--- qutebrowser/config/configdata.py | 2 ++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 6cc2ee9db..e3dd81e62 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -642,13 +642,14 @@ Save open pages and quit. [[yank]] === yank -Syntax: +:yank [*--title*] [*--sel*]+ +Syntax: +:yank [*--title*] [*--sel*] [*--domain*]+ Yank the current URL/title to the clipboard or primary selection. ==== optional arguments * +*-t*+, +*--title*+: Yank the title instead of the URL. * +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard. +* +*-d*+, +*--domain*+: Yank only the scheme & domain. [[zoom]] === zoom diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 6585abed6..9cb2a9991 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -696,19 +696,26 @@ class CommandDispatcher: frame.scroll(dx, dy) @cmdutils.register(instance='command-dispatcher', scope='window') - def yank(self, title=False, sel=False): + def yank(self, title=False, sel=False, domain=False): """Yank the current URL/title to the clipboard or primary selection. Args: sel: Use the primary selection instead of the clipboard. title: Yank the title instead of the URL. + domain: Yank only the scheme & domain. """ clipboard = QApplication.clipboard() if title: s = self._tabbed_browser.page_title(self._current_index()) + what = 'title' + elif domain: + s = '{}://{}'.format(self._current_url().scheme(), + self._current_url().host()) + what = 'domain' else: s = self._current_url().toString( QUrl.FullyEncoded | QUrl.RemovePassword) + what = 'URL' if sel and clipboard.supportsSelection(): mode = QClipboard.Selection target = "primary selection" @@ -717,8 +724,8 @@ class CommandDispatcher: target = "clipboard" log.misc.debug("Yanking to {}: '{}'".format(target, s)) clipboard.setText(s, mode) - what = 'Title' if title else 'URL' - message.info(self._win_id, "{} yanked to {}".format(what, target)) + message.info(self._win_id, "Yanked {} to {}: {}".format( + what, target, s)) @cmdutils.register(instance='command-dispatcher', scope='window', count='count') diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index a3bbe2d48..ad269d212 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1204,6 +1204,8 @@ KEY_DATA = collections.OrderedDict([ ('yank -s', ['yY']), ('yank -t', ['yt']), ('yank -ts', ['yT']), + ('yank -d', ['yd']), + ('yank -ds', ['yD']), ('paste', ['pp']), ('paste -s', ['pP']), ('paste -t', ['Pp']), From d60d4d756ce4e5cb45908d9940372e771d0b37dd Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Thu, 4 Jun 2015 13:20:39 +0200 Subject: [PATCH 032/214] Also yank port number --- doc/help/commands.asciidoc | 2 +- qutebrowser/browser/commands.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index e3dd81e62..104478c10 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -649,7 +649,7 @@ Yank the current URL/title to the clipboard or primary selection. ==== optional arguments * +*-t*+, +*--title*+: Yank the title instead of the URL. * +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard. -* +*-d*+, +*--domain*+: Yank only the scheme & domain. +* +*-d*+, +*--domain*+: Yank only the scheme, domain, and port number. [[zoom]] === zoom diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 9cb2a9991..9bfe81a10 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -702,15 +702,17 @@ class CommandDispatcher: Args: sel: Use the primary selection instead of the clipboard. title: Yank the title instead of the URL. - domain: Yank only the scheme & domain. + domain: Yank only the scheme, domain, and port number. """ clipboard = QApplication.clipboard() if title: s = self._tabbed_browser.page_title(self._current_index()) what = 'title' elif domain: - s = '{}://{}'.format(self._current_url().scheme(), - self._current_url().host()) + port = self._current_url().port() + s = '{}://{}{}'.format(self._current_url().scheme(), + self._current_url().host(), + ':' + str(port) if port > -1 else '') what = 'domain' else: s = self._current_url().toString( From 9ec6e6da80eeacd6c127768bd3a41a965d7ab21c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 4 Jun 2015 15:13:20 +0200 Subject: [PATCH 033/214] Fix exit status codes to be 0-based. --- CHANGELOG.asciidoc | 1 + qutebrowser/utils/usertypes.py | 2 +- tests/utils/usertypes/test_enum.py | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 92129b3e1..6bd22ced2 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -81,6 +81,7 @@ Fixed - Fixed handling of keybindings containing Ctrl/Meta on OS X. - Fixed crash when downloading an URL without filename (e.g. magnet links) via "Save as...". - Fixed exception when starting qutebrowser with `:set` as argument. +- Fixed exit status codes (successful exit was 1 instead of 0). https://github.com/The-Compiler/qutebrowser/releases/tag/v0.2.1[v0.2.1] ----------------------------------------------------------------------- diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index 5d19ad515..c82a54596 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -242,7 +242,7 @@ Completion = enum('Completion', ['command', 'section', 'option', 'value', # Exit statuses for errors. Needs to be an int for sys.exit. Exit = enum('Exit', ['ok', 'reserved', 'exception', 'err_ipc', 'err_init', - 'err_config', 'err_key_config'], is_int=True) + 'err_config', 'err_key_config'], is_int=True, start=0) class Question(QObject): diff --git a/tests/utils/usertypes/test_enum.py b/tests/utils/usertypes/test_enum.py index e1443b7be..7298b2861 100644 --- a/tests/utils/usertypes/test_enum.py +++ b/tests/utils/usertypes/test_enum.py @@ -54,3 +54,9 @@ def test_start(): e = usertypes.enum('Enum', ['three', 'four'], start=3) assert e.three.value == 3 assert e.four.value == 4 + + +def test_exit(): + """Make sure the exit status enum is correct.""" + assert usertypes.Exit.ok == 0 + assert usertypes.Exit.reserved == 1 From f41acc8fb5c00e9ba5c14dc3a3ec9fc335528c7e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 4 Jun 2015 15:14:56 +0200 Subject: [PATCH 034/214] Remove changelog entry as it's a recent regression --- CHANGELOG.asciidoc | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 6bd22ced2..92129b3e1 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -81,7 +81,6 @@ Fixed - Fixed handling of keybindings containing Ctrl/Meta on OS X. - Fixed crash when downloading an URL without filename (e.g. magnet links) via "Save as...". - Fixed exception when starting qutebrowser with `:set` as argument. -- Fixed exit status codes (successful exit was 1 instead of 0). https://github.com/The-Compiler/qutebrowser/releases/tag/v0.2.1[v0.2.1] ----------------------------------------------------------------------- From 4a909aa028bdc79424b48b24d4a908f8186058f1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 4 Jun 2015 15:25:36 +0200 Subject: [PATCH 035/214] Use pylint's built-in checker to check for CRLF. --- .pylintrc | 1 - scripts/pylint_checkers/crlf.py | 45 --------------------------------- tox.ini | 4 +-- 3 files changed, 2 insertions(+), 48 deletions(-) delete mode 100644 scripts/pylint_checkers/crlf.py diff --git a/.pylintrc b/.pylintrc index 2cc56909d..a4abb32a0 100644 --- a/.pylintrc +++ b/.pylintrc @@ -4,7 +4,6 @@ ignore=resources.py extension-pkg-whitelist=PyQt5,sip load-plugins=pylint_checkers.config, - pylint_checkers.crlf, pylint_checkers.modeline, pylint_checkers.openencoding, pylint_checkers.settrace diff --git a/scripts/pylint_checkers/crlf.py b/scripts/pylint_checkers/crlf.py deleted file mode 100644 index a77f8b9e0..000000000 --- a/scripts/pylint_checkers/crlf.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2014-2015 Florian Bruhin (The Compiler) -# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: - -# 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 . - -"""Checker for CRLF in files.""" - -from pylint import interfaces, checkers - - -class CrlfChecker(checkers.BaseChecker): - - """Check for CRLF in files.""" - - __implements__ = interfaces.IRawChecker - - name = 'crlf' - msgs = {'W9001': ('Uses CRLFs', 'crlf', None)} - options = () - priority = -1 - - def process_module(self, node): - """Process the module.""" - for (lineno, line) in enumerate(node.file_stream): - if b'\r\n' in line: - self.add_message('crlf', line=lineno) - return - - -def register(linter): - """Register the checker.""" - linter.register_checker(CrlfChecker(linter)) diff --git a/tox.ini b/tox.ini index 0b14e4e74..cff331d21 100644 --- a/tox.ini +++ b/tox.ini @@ -61,8 +61,8 @@ deps = six==1.9.0 commands = {[testenv:mkvenv]commands} - {envdir}/bin/pylint scripts qutebrowser --rcfile=.pylintrc --output-format=colorized --reports=no - {envpython} scripts/run_pylint_on_tests.py --rcfile=.pylintrc --output-format=colorized --reports=no + {envdir}/bin/pylint scripts qutebrowser --rcfile=.pylintrc --output-format=colorized --reports=no --expected-line-ending-format=LF + {envpython} scripts/run_pylint_on_tests.py --rcfile=.pylintrc --output-format=colorized --reports=no --expected-line-ending-format=LF [testenv:pep257] skip_install = true From 05fe68ccab1a6f64819c903a6edb424be758e3fc Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 06:39:37 +0200 Subject: [PATCH 036/214] Regenerate authors --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index d42a895ef..3a701fd76 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -138,9 +138,9 @@ Contributors, sorted by the number of commits in descending order: * Raphael Pierzina * Joel Torstensson * Claude +* Martin Tournoij * Artur Shaik * Antoni Boucher -* Martin Tournoij * ZDarian * Peter Vilim * John ShaggyTwoDope Jenkins From 622938e3d3e39fb5222d6c3271b654165f404130 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 07:16:33 +0200 Subject: [PATCH 037/214] Fix completion performance with shrink=True. Before, the completion was shrinked every time any item was removed/added to the completion (rowsRemoved/rowsInserted signals), which was >3000 times when completing history. Also, the signals got connected multiple times if setting the same model, which made the situation worse. Fixes #734. --- CHANGELOG.asciidoc | 1 + qutebrowser/completion/completer.py | 2 +- qutebrowser/completion/completionwidget.py | 13 +++++++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 92129b3e1..c2daf1efe 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -81,6 +81,7 @@ Fixed - Fixed handling of keybindings containing Ctrl/Meta on OS X. - Fixed crash when downloading an URL without filename (e.g. magnet links) via "Save as...". - Fixed exception when starting qutebrowser with `:set` as argument. +- Fixed horrible completion performance when the `shrink` option was set. https://github.com/The-Compiler/qutebrowser/releases/tag/v0.2.1[v0.2.1] ----------------------------------------------------------------------- diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index 2fd1858ca..197c62ce1 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -272,7 +272,7 @@ class Completer(QObject): pattern = parts[self._cursor_part].strip() except IndexError: pattern = '' - self._model().set_pattern(pattern) + completion.set_pattern(pattern) log.completion.debug( "New completion for {}: {}, with pattern '{}'".format( diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index aa2fd31da..0bd6b04c9 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -201,8 +201,17 @@ class CompletionView(QTreeView): for i in range(model.rowCount()): self.expand(model.index(i, 0)) self._resize_columns() - model.rowsRemoved.connect(self.maybe_resize_completion) - model.rowsInserted.connect(self.maybe_resize_completion) + self.maybe_resize_completion() + + def set_pattern(self, pattern): + """Set the completion pattern for the current model. + + Called from on_update_completion(). + + Args: + pattern: The filter pattern to set (what the user entered). + """ + self.model().set_pattern(pattern) self.maybe_resize_completion() @pyqtSlot() From 7102459c8148f1f356b47231638d48c4d90161e8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 11:15:18 +0200 Subject: [PATCH 038/214] Rename _get_modeman() to instance(). --- qutebrowser/keyinput/modeman.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index fc70ac76b..e64522204 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -97,25 +97,25 @@ def init(win_id, parent): return modeman -def _get_modeman(win_id): +def instance(win_id): """Get a modemanager object.""" return objreg.get('mode-manager', scope='window', window=win_id) def enter(win_id, mode, reason=None, only_if_normal=False): """Enter the mode 'mode'.""" - _get_modeman(win_id).enter(mode, reason, only_if_normal) + instance(win_id).enter(mode, reason, only_if_normal) def leave(win_id, mode, reason=None): """Leave the mode 'mode'.""" - _get_modeman(win_id).leave(mode, reason) + instance(win_id).leave(mode, reason) def maybe_leave(win_id, mode, reason=None): """Convenience method to leave 'mode' without exceptions.""" try: - _get_modeman(win_id).leave(mode, reason) + instance(win_id).leave(mode, reason) except NotInModeError as e: # This is rather likely to happen, so we only log to debug log. log.modes.debug("{} (leave reason: {})".format(e, reason)) From 728f06e797d9c9cf002979b2b365748913bb3c32 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 11:15:22 +0200 Subject: [PATCH 039/214] Close context menu if another mode was entered. Fixes #735. --- qutebrowser/browser/webview.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/browser/webview.py b/qutebrowser/browser/webview.py index 584c7f268..ec82d2567 100644 --- a/qutebrowser/browser/webview.py +++ b/qutebrowser/browser/webview.py @@ -620,6 +620,7 @@ class WebView(QWebView): """Save a reference to the context menu so we can close it.""" menu = self.page().createStandardContextMenu() self.shutting_down.connect(menu.close) + modeman.instance(self.win_id).entered.connect(menu.close) menu.exec_(e.globalPos()) def wheelEvent(self, e): From 57ddd8e95e5d8ff8ad0473e84e58094045e57d6a Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Fri, 5 Jun 2015 14:24:43 +0200 Subject: [PATCH 040/214] Always handle the key, even if it's bound. This fixes #716, which sufficiently annoyed me to make this quick fix. It's not a great fix, but it's not worse than what we had already, and the current behaviour is very surprising IMHO. --- qutebrowser/keyinput/basekeyparser.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index b52a39824..ce9719f31 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -162,12 +162,6 @@ class BaseKeyParser(QObject): key = e.key() self._debug_log("Got key: 0x{:x} / text: '{}'".format(key, txt)) - if key == Qt.Key_Escape: - self._debug_log("Escape pressed, discarding '{}'.".format( - self._keystring)) - self._keystring = '' - return self.Match.none - if len(txt) == 1: category = unicodedata.category(txt) is_control_char = (category == 'Cc') @@ -303,6 +297,15 @@ class BaseKeyParser(QObject): True if the event was handled, False otherwise. """ handled = self._handle_special_key(e) + + # Special case for . See: + # https://github.com/The-Compiler/qutebrowser/issues/716 + if e.key() == Qt.Key_Escape: + self._debug_log("Escape pressed, discarding '{}'.".format( + self._keystring)) + self._keystring = '' + self.keystring_updated.emit(self._keystring) + if handled or not self._supports_chains: return handled match = self._handle_single_key(e) From fa65f345ac36bbb3e075d682d6c072af9a8bc347 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Fri, 5 Jun 2015 15:10:41 +0200 Subject: [PATCH 041/214] Perhaps fix it more properly after all :-) --- doc/help/commands.asciidoc | 5 +++++ qutebrowser/config/configdata.py | 2 +- qutebrowser/keyinput/basekeyparser.py | 14 ++++++-------- qutebrowser/keyinput/modeman.py | 5 +++++ 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 104478c10..c14f23b81 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -685,6 +685,7 @@ How many steps to zoom out. [options="header",width="75%",cols="25%,75%"] |============== |Command|Description +|<>|Clear the currently entered key chain. |<>|Execute the command currently in the commandline. |<>|Go forward in the commandline history. |<>|Go back in the commandline history. @@ -739,6 +740,10 @@ How many steps to zoom out. |<>|Toggle caret selection mode. |<>|Yank the selected text to the clipboard or primary selection. |============== +[[clear-keychain]] +=== clear-keychain +Clear the currently entered key chain. + [[command-accept]] === command-accept Execute the command currently in the commandline. diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index ad269d212..aa7a9c8c8 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1143,7 +1143,7 @@ KEY_DATA = collections.OrderedDict([ ])), ('normal', collections.OrderedDict([ - ('search', ['']), + ('search ;; clear-keychain', ['']), ('set-cmd-text -s :open', ['o']), ('set-cmd-text :open {url}', ['go']), ('set-cmd-text -s :open -t', ['O']), diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index ce9719f31..487575127 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -298,14 +298,6 @@ class BaseKeyParser(QObject): """ handled = self._handle_special_key(e) - # Special case for . See: - # https://github.com/The-Compiler/qutebrowser/issues/716 - if e.key() == Qt.Key_Escape: - self._debug_log("Escape pressed, discarding '{}'.".format( - self._keystring)) - self._keystring = '' - self.keystring_updated.emit(self._keystring) - if handled or not self._supports_chains: return handled match = self._handle_single_key(e) @@ -362,3 +354,9 @@ class BaseKeyParser(QObject): "defined!") if mode == self._modename: self.read_config() + + def clear_keystring(self): + """Clear the currently entered key sequence.""" + self._debug_log("discarding keystring '{}'.".format(self._keystring)) + self._keystring = '' + self.keystring_updated.emit(self._keystring) diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index e64522204..686741044 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -332,3 +332,8 @@ class ModeManager(QObject): return self._eventFilter_keypress(event) else: return self._eventFilter_keyrelease(event) + + @cmdutils.register(instance='mode-manager', scope='window', hide=True) + def clear_keychain(self): + """Clear the currently entered key chain.""" + self._handlers[self.mode].__self__.clear_keystring() From b55e22b5c335104b80266bc761461bd0285aa201 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 15:29:09 +0200 Subject: [PATCH 042/214] Refactor key mode/parser handling in modeman. --- qutebrowser/keyinput/basekeyparser.py | 3 ++ qutebrowser/keyinput/keyparser.py | 1 + qutebrowser/keyinput/modeman.py | 53 +++++++++---------------- qutebrowser/keyinput/modeparsers.py | 2 + qutebrowser/mainwindow/statusbar/bar.py | 14 +++---- 5 files changed, 32 insertions(+), 41 deletions(-) diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index b52a39824..1c2c9ab86 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -49,6 +49,8 @@ class BaseKeyParser(QObject): special: execute() was called via a special key binding do_log: Whether to log keypresses or not. + passthrough: Whether unbound keys should be passed through with this + handler. Attributes: bindings: Bound key bindings @@ -69,6 +71,7 @@ class BaseKeyParser(QObject): keystring_updated = pyqtSignal(str) do_log = True + passthrough = False Match = usertypes.enum('Match', ['partial', 'definitive', 'ambiguous', 'other', 'none']) diff --git a/qutebrowser/keyinput/keyparser.py b/qutebrowser/keyinput/keyparser.py index bef364e66..46f179fdb 100644 --- a/qutebrowser/keyinput/keyparser.py +++ b/qutebrowser/keyinput/keyparser.py @@ -55,6 +55,7 @@ class PassthroughKeyParser(CommandKeyParser): """ do_log = False + passthrough = True def __init__(self, win_id, mode, parent=None, warn=True): """Constructor. diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index e64522204..357a5ffc9 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -84,16 +84,8 @@ def init(win_id, parent): modeman.destroyed.connect( functools.partial(objreg.delete, 'keyparsers', scope='window', window=win_id)) - modeman.register(KM.normal, keyparsers[KM.normal].handle) - modeman.register(KM.hint, keyparsers[KM.hint].handle) - modeman.register(KM.insert, keyparsers[KM.insert].handle, passthrough=True) - modeman.register(KM.passthrough, keyparsers[KM.passthrough].handle, - passthrough=True) - modeman.register(KM.command, keyparsers[KM.command].handle, - passthrough=True) - modeman.register(KM.prompt, keyparsers[KM.prompt].handle, passthrough=True) - modeman.register(KM.yesno, keyparsers[KM.yesno].handle) - modeman.register(KM.caret, keyparsers[KM.caret].handle, passthrough=True) + for mode, parser in keyparsers.items(): + modeman.register(mode, parser) return modeman @@ -126,10 +118,9 @@ class ModeManager(QObject): """Manager for keyboard modes. Attributes: - passthrough: A list of modes in which to pass through events. mode: The mode we're currently in. _win_id: The window ID of this ModeManager - _handlers: A dictionary of modes and their handlers. + _parsers: A dictionary of modes and their keyparsers. _forward_unbound_keys: If we should forward unbound keys. _releaseevents_to_pass: A set of KeyEvents where the keyPressEvent was passed through, so the release event should as @@ -151,8 +142,7 @@ class ModeManager(QObject): def __init__(self, win_id, parent=None): super().__init__(parent) self._win_id = win_id - self._handlers = {} - self.passthrough = [] + self._parsers = {} self.mode = usertypes.KeyMode.normal self._releaseevents_to_pass = set() self._forward_unbound_keys = config.get( @@ -160,8 +150,7 @@ class ModeManager(QObject): objreg.get('config').changed.connect(self.set_forward_unbound_keys) def __repr__(self): - return utils.get_repr(self, mode=self.mode, - passthrough=self.passthrough) + return utils.get_repr(self, mode=self.mode) def _eventFilter_keypress(self, event): """Handle filtering of KeyPress events. @@ -173,11 +162,11 @@ class ModeManager(QObject): True if event should be filtered, False otherwise. """ curmode = self.mode - handler = self._handlers[curmode] + parser = self._parsers[curmode] if curmode != usertypes.KeyMode.insert: - log.modes.debug("got keypress in mode {} - calling handler " - "{}".format(curmode, utils.qualname(handler))) - handled = handler(event) if handler is not None else False + log.modes.debug("got keypress in mode {} - delegating to " + "{}".format(curmode, utils.qualname(parser))) + handled = parser.handle(event) is_non_alnum = bool(event.modifiers()) or not event.text().strip() focus_widget = QApplication.instance().focusWidget() @@ -187,7 +176,7 @@ class ModeManager(QObject): filter_this = True elif is_tab and not isinstance(focus_widget, QWebView): filter_this = True - elif (curmode in self.passthrough or + elif (parser.passthrough or self._forward_unbound_keys == 'all' or (self._forward_unbound_keys == 'auto' and is_non_alnum)): filter_this = False @@ -202,8 +191,8 @@ class ModeManager(QObject): "passthrough: {}, is_non_alnum: {}, is_tab {} --> " "filter: {} (focused: {!r})".format( handled, self._forward_unbound_keys, - curmode in self.passthrough, is_non_alnum, - is_tab, filter_this, focus_widget)) + parser.passthrough, is_non_alnum, is_tab, + filter_this, focus_widget)) return filter_this def _eventFilter_keyrelease(self, event): @@ -226,20 +215,16 @@ class ModeManager(QObject): log.modes.debug("filter: {}".format(filter_this)) return filter_this - def register(self, mode, handler, passthrough=False): + def register(self, mode, parser): """Register a new mode. Args: mode: The name of the mode. - handler: Handler for keyPressEvents. - passthrough: Whether to pass key bindings in this mode through to - the widgets. + parser: The KeyParser which should be used. """ - if not isinstance(mode, usertypes.KeyMode): - raise TypeError("Mode {} is no KeyMode member!".format(mode)) - self._handlers[mode] = handler - if passthrough: - self.passthrough.append(mode) + assert isinstance(mode, usertypes.KeyMode) + assert parser is not None + self._parsers[mode] = parser def enter(self, mode, reason=None, only_if_normal=False): """Enter a new mode. @@ -253,8 +238,8 @@ class ModeManager(QObject): raise TypeError("Mode {} is no KeyMode member!".format(mode)) log.modes.debug("Entering mode {}{}".format( mode, '' if reason is None else ' (reason: {})'.format(reason))) - if mode not in self._handlers: - raise ValueError("No handler for mode {}".format(mode)) + if mode not in self._parsers: + raise ValueError("No keyparser for mode {}".format(mode)) prompt_modes = (usertypes.KeyMode.prompt, usertypes.KeyMode.yesno) if self.mode == mode or (self.mode in prompt_modes and mode in prompt_modes): diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 8d47de0c1..d16734ed0 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -224,6 +224,8 @@ class CaretKeyParser(keyparser.CommandKeyParser): """KeyParser for caret mode.""" + passthrough = True + def __init__(self, win_id, parent=None): super().__init__(win_id, parent, supports_count=True, supports_chains=True) diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index 5f633eaaa..bc828a261 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -469,9 +469,9 @@ class StatusBar(QWidget): @pyqtSlot(usertypes.KeyMode) def on_mode_entered(self, mode): """Mark certain modes in the commandline.""" - mode_manager = objreg.get('mode-manager', scope='window', - window=self._win_id) - if mode in mode_manager.passthrough: + keyparsers = objreg.get('keyparsers', scope='window', + window=self._win_id) + if keyparsers[mode].passthrough: self._set_mode_text(mode.name) if mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret): self.set_mode_active(mode, True) @@ -479,10 +479,10 @@ class StatusBar(QWidget): @pyqtSlot(usertypes.KeyMode, usertypes.KeyMode) def on_mode_left(self, old_mode, new_mode): """Clear marked mode.""" - mode_manager = objreg.get('mode-manager', scope='window', - window=self._win_id) - if old_mode in mode_manager.passthrough: - if new_mode in mode_manager.passthrough: + keyparsers = objreg.get('keyparsers', scope='window', + window=self._win_id) + if keyparsers[old_mode].passthrough: + if keyparsers[new_mode].passthrough: self._set_mode_text(new_mode.name) else: self.txt.set_text(self.txt.Text.normal, '') From fc4c7bd2e449fe2fca2abbde9f49e96e458cdc24 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Fri, 5 Jun 2015 15:57:43 +0200 Subject: [PATCH 043/214] Merge the cookies-accept and third-party-cookie-policy settings --- doc/help/settings.asciidoc | 23 ++++++----------------- qutebrowser/config/configdata.py | 8 ++------ qutebrowser/config/configtypes.py | 19 +++++++------------ qutebrowser/config/websettings.py | 24 ++++++++++++------------ 4 files changed, 27 insertions(+), 47 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 717a46d13..da631e52f 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -149,9 +149,8 @@ |<>|Whether all javascript alerts should be ignored. |<>|Whether locally loaded documents are allowed to access remote urls. |<>|Whether locally loaded documents are allowed to access other local urls. -|<>|Whether to accept cookies. +|<>|Control which cookies to accept. |<>|Whether to store cookies. -|<>|Accept cookies from domains other than the main website |<>|List of URLs of lists which contain hosts to block. |<>|Whether host blocking is enabled. |============== @@ -1317,14 +1316,16 @@ Default: +pass:[true]+ [[content-cookies-accept]] === cookies-accept -Whether to accept cookies. +Control which cookies to accept. Valid values: - * +default+: Default QtWebKit behavior. + * +all+: Accept all cookies. + * +no-3rdparty+: Accept cookies from the same origin only. + * +no-unknown-3rdparty+: Accept cookies from the same origin only, unless a cookie is already set for the domain. * +never+: Don't accept cookies at all. -Default: +pass:[default]+ +Default: +pass:[no-3rdparty]+ [[content-cookies-store]] === cookies-store @@ -1337,18 +1338,6 @@ Valid values: Default: +pass:[true]+ -[[content-third-party-cookie-policy]] -=== third-party-cookie-policy -Accept cookies from domains other than the main website - -Valid values: - - * +always+: Always accept. - * +never+: Never accept. - * +existing+: Only accept if we already have acookie stored for the domain - -Default: +pass:[never]+ - [[content-host-block-lists]] === host-block-lists List of URLs of lists which contain hosts to block. diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 9028d2c78..7a8d3ea83 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -674,17 +674,13 @@ def data(readonly=False): "local urls."), ('cookies-accept', - SettingValue(typ.AcceptCookies(), 'default'), - "Whether to accept cookies."), + SettingValue(typ.AcceptCookies(), 'no-3rdparty'), + "Control which cookies to accept."), ('cookies-store', SettingValue(typ.Bool(), 'true'), "Whether to store cookies."), - ('third-party-cookie-policy', - SettingValue(typ.ThirdPartyCookiePolicy(), 'never'), - "Accept cookies from domains other than the main website"), - ('host-block-lists', SettingValue( typ.UrlList(none_ok=True), diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index b4d7319c6..6aaef90db 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1323,22 +1323,17 @@ class LastClose(BaseType): class AcceptCookies(BaseType): - """Whether to accept a cookie.""" + """Control which cookies to accept.""" - valid_values = ValidValues(('default', "Default QtWebKit behavior."), + valid_values = ValidValues(('all', "Accept all cookies."), + ('no-3rdparty', "Accept cookies from the same" + " origin only."), + ('no-unknown-3rdparty', "Accept cookies from " + "the same origin only, unless a cookie is " + "already set for the domain."), ('never', "Don't accept cookies at all.")) -class ThirdPartyCookiePolicy(BaseType): - - """Accept cookies from domains other than the main website.""" - - valid_values = ValidValues(('always', "Always accept."), - ('never', "Never accept."), - ('existing', "Only accept if we already have a" - "cookie stored for the domain.")) - - class ConfirmQuit(List): """Whether to display a confirmation when the window is closed.""" diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index edea4edeb..c3216aae2 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -238,23 +238,23 @@ class GlobalSetter(Setter): self._setter(*args) -class ThirdPartyCookies(Base): +class CookiePolicy(Base): """The ThirdPartyCookiePolicy setting is different from other settings.""" - mapping = ( - ('always', QWebSettings.AlwaysAllowThirdPartyCookies), - ('never', QWebSettings.AlwaysBlockThirdPartyCookies), - ('existing', QWebSettings.AllowThirdPartyWithExistingCookies), - ) + MAPPING = { + 'all': QWebSettings.AlwaysAllowThirdPartyCookies, + 'no-3rdparty': QWebSettings.AlwaysBlockThirdPartyCookies, + 'never': QWebSettings.AlwaysBlockThirdPartyCookies, + 'no-unknown-3rdparty': QWebSettings.AllowThirdPartyWithExistingCookies, + } def get(self, qws=None): - policy = QWebSettings.globalSettings().thirdPartyCookiePolicy() - return tuple(filter(lambda i: i[1] == policy, self.mapping))[0][0] + return config.get('content', 'cookies-accept') def _set(self, value, qws=None): - x = filter(lambda i: i[0] == value, self.mapping) - QWebSettings.globalSettings().setThirdPartyCookiePolicy(tuple(x)[0][1]) + QWebSettings.globalSettings().setThirdPartyCookiePolicy( + self.MAPPING[value]) MAPPINGS = { @@ -283,8 +283,8 @@ MAPPINGS = { Attribute(QWebSettings.LocalContentCanAccessRemoteUrls), 'local-content-can-access-file-urls': Attribute(QWebSettings.LocalContentCanAccessFileUrls), - 'third-party-cookie-policy': - ThirdPartyCookies(), + 'cookies-accept': + CookiePolicy(), }, 'network': { 'dns-prefetch': From dfada850e0ebcbc1f9a847bbf964deed27ef17d4 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Fri, 5 Jun 2015 16:52:33 +0200 Subject: [PATCH 044/214] Update code after refactor, and add migration --- qutebrowser/config/configdata.py | 2 ++ qutebrowser/keyinput/modeman.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index aa7a9c8c8..f9b0470c7 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1354,4 +1354,6 @@ CHANGED_KEY_COMMANDS = [ (re.compile(r'^scroll 0 -50$'), r'scroll up'), (re.compile(r'^scroll 50 0$'), r'scroll right'), (re.compile(r'^scroll ([-\d]+ [-\d]+)$'), r'scroll-px \1'), + + (re.compile(r'^search$'), r'search ;; clear-keychain'), ] diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 5b03bd9e0..6906a8720 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -321,4 +321,4 @@ class ModeManager(QObject): @cmdutils.register(instance='mode-manager', scope='window', hide=True) def clear_keychain(self): """Clear the currently entered key chain.""" - self._handlers[self.mode].__self__.clear_keystring() + self._parsers[self.mode].clear_keystring() From e38169433ebe69d082fade285789c790a1645525 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 17:26:49 +0200 Subject: [PATCH 045/214] tox: Update pytest-flakes to 1.0.0. Upstream changelog: - Fix issue #6 - support PEP263 for source file encoding. - Clarified license to be MIT like pytest-pep8 from which this is derived. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index cff331d21..93f00d40a 100644 --- a/tox.ini +++ b/tox.ini @@ -82,7 +82,7 @@ deps = py==1.4.27 pytest==2.7.1 pyflakes==0.9.0 - pytest-flakes==0.2 + pytest-flakes==1.0.0 commands = {[testenv:mkvenv]commands} {envpython} -m py.test -q --flakes -m flakes From 6ec8bbaca55f812bd1a32d78bea99f63272e155a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 17:27:59 +0200 Subject: [PATCH 046/214] tox: Update pytest-mock to 0.6.0. Upstream changelog: - Two new auxiliary methods, spy and stub. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 93f00d40a..0c4393277 100644 --- a/tox.ini +++ b/tox.ini @@ -24,7 +24,7 @@ deps = pytest==2.7.1 pytest-capturelog==0.7 pytest-qt==1.3.0 - pytest-mock==0.5 + pytest-mock==0.6.0 pytest-html==1.3.1 # We don't use {[testenv:mkvenv]commands} here because that seems to be broken # on Ubuntu Trusty. From d3f7d9319a346993a8401d81c7f2b69c55b8fa99 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 17:29:00 +0200 Subject: [PATCH 047/214] tox: Update py to 1.4.28. Upstream changelog: - fix issue64 -- dirpath regression when "abs=True" is passed. Thanks Gilles Dartiguelongue. --- tox.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 0c4393277..b24842ff4 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,7 @@ setenv = QT_QPA_PLATFORM_PLUGIN_PATH={envdir}/Lib/site-packages/PyQt5/plugins/pl passenv = DISPLAY XAUTHORITY HOME deps = -r{toxinidir}/requirements.txt - py==1.4.27 + py==1.4.28 pytest==2.7.1 pytest-capturelog==0.7 pytest-qt==1.3.0 @@ -79,7 +79,7 @@ commands = {envpython} -m pep257 scripts tests qutebrowser --ignore=D102,D103,D2 setenv = LANG=en_US.UTF-8 deps = -r{toxinidir}/requirements.txt - py==1.4.27 + py==1.4.28 pytest==2.7.1 pyflakes==0.9.0 pytest-flakes==1.0.0 @@ -90,7 +90,7 @@ commands = [testenv:pep8] deps = -r{toxinidir}/requirements.txt - py==1.4.27 + py==1.4.28 pytest==2.7.1 pep8==1.6.2 pytest-pep8==1.0.6 @@ -101,7 +101,7 @@ commands = [testenv:mccabe] deps = -r{toxinidir}/requirements.txt - py==1.4.27 + py==1.4.28 pytest==2.7.1 mccabe==0.3 pytest-mccabe==0.1 From 80010996618372aa69764cd670a79f6f13848cdb Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 17:45:32 +0200 Subject: [PATCH 048/214] Adjust tests. --- tests/config/test_config.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/config/test_config.py b/tests/config/test_config.py index d5fab2ed1..3fff0ea66 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -197,8 +197,10 @@ class TestKeyConfigParser: ('download-page', 'download'), ('cancel-download', 'download-cancel'), - ('search ""', 'search'), - ("search ''", 'search'), + ('search ""', 'search ;; clear-keychain'), + ("search ''", 'search ;; clear-keychain'), + ("search", 'search ;; clear-keychain'), + ("search ;; foobar", None), ('search "foo"', None), ('set-cmd-text "foo bar"', 'set-cmd-text foo bar'), From 5fb23f1373dd00db62224127ed0d22ab15c5944b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 17:45:38 +0200 Subject: [PATCH 049/214] Also migrate older search calls. --- qutebrowser/config/configdata.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index f9b0470c7..d2c360682 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1340,8 +1340,8 @@ CHANGED_KEY_COMMANDS = [ (re.compile(r'^download-page$'), r'download'), (re.compile(r'^cancel-download$'), r'download-cancel'), - (re.compile(r'^search ""$'), r'search'), - (re.compile(r"^search ''$"), r'search'), + (re.compile(r"""^search (''|"")$"""), r'search ;; clear-keychain'), + (re.compile(r'^search$'), r'search ;; clear-keychain'), (re.compile(r"""^set-cmd-text ['"](.*) ['"]$"""), r'set-cmd-text -s \1'), (re.compile(r"""^set-cmd-text ['"](.*)['"]$"""), r'set-cmd-text \1'), @@ -1354,6 +1354,4 @@ CHANGED_KEY_COMMANDS = [ (re.compile(r'^scroll 0 -50$'), r'scroll up'), (re.compile(r'^scroll 50 0$'), r'scroll right'), (re.compile(r'^scroll ([-\d]+ [-\d]+)$'), r'scroll-px \1'), - - (re.compile(r'^search$'), r'search ;; clear-keychain'), ] From d3e85ad9824d06bf27d90b6b35c6a54b02ca6c56 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 17:50:00 +0200 Subject: [PATCH 050/214] Update docs. --- doc/help/commands.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index c14f23b81..5a6ce06bf 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -685,7 +685,7 @@ How many steps to zoom out. [options="header",width="75%",cols="25%,75%"] |============== |Command|Description -|<>|Clear the currently entered key chain. +|<>|Clear the currently entered key chain. |<>|Execute the command currently in the commandline. |<>|Go forward in the commandline history. |<>|Go back in the commandline history. From 015de0e6dbd0171acbc881427adf03695480254f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 17:51:33 +0200 Subject: [PATCH 051/214] misc_checks: Check spelling case-insensitively. --- scripts/misc_checks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/misc_checks.py b/scripts/misc_checks.py index 4e2c185f5..36bc28079 100644 --- a/scripts/misc_checks.py +++ b/scripts/misc_checks.py @@ -87,7 +87,7 @@ def check_spelling(target): continue for line in f: for w in words: - if re.search(w, line) and fn not in seen[w]: + if re.search(w, line, re.I) and fn not in seen[w]: print("Found '{}' in {}!".format(w, fn)) seen[w].append(fn) ok = False From 2459f14f6fe431f6b8bc0d8aa8ebcc4d6c7b16ad Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 17:53:16 +0200 Subject: [PATCH 052/214] Update changelog. --- CHANGELOG.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index c2daf1efe..92cc4cd24 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -38,6 +38,7 @@ Added - New (hidden) command `:follow-selected` (bound to `Enter`/`Ctrl-Enter` by default) to follow the link which is currently selected (e.g. after searching via `/`). - New setting `ui -> modal-js-dialog` to use the standard modal dialogs for javascript questions instead of using the statusbar. - New setting `colors -> webpage.bg` to set the background color to use for websites which don't set one. +- New (hidden) command `:clear-keychain` to clear a partially entered keychain (bound to `` by default, in addition to clearing search). Changed ~~~~~~~ From 94178c558abf0d5da0bdd6a5ec81348022d72a23 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Fri, 5 Jun 2015 20:07:47 +0200 Subject: [PATCH 053/214] Well, getting the error doesn't work... --- doc/help/commands.asciidoc | 14 ++++++++++++++ qutebrowser/browser/commands.py | 28 ++++++++++++++++++---------- qutebrowser/browser/webpage.py | 11 ----------- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index c33bccaaf..287620867 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -20,6 +20,7 @@ |<>|Start hinting. |<>|Open main startpage in current tab. |<>|Toggle the web inspector. +|<>|Evaluate a JavaScript string. |<>|Execute a command after some time. |<>|Open typical prev/next links or navigate using the URL path. |<>|Open a URL in the current/[count]th tab. @@ -241,6 +242,19 @@ Open main startpage in current tab. === inspector Toggle the web inspector. +[[jseval]] +=== jseval +Syntax: +:jseval 'js_code'+ + +Evaluate a JavaScript string. + +==== positional arguments +* +'js_code'+: The string to evaluate. + +==== note +* This command does not split arguments after the last argument and handles quotes literally. +* With this command, +;;+ is interpreted literally instead of splitting off a second command. + [[later]] === later Syntax: +:later 'ms' 'command'+ diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index bc2159843..aa8430127 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1513,20 +1513,28 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0, no_cmd_split=True) - def jseval(self, js): + def jseval(self, js_code): """Evaluate a JavaScript string. Args: - s: The string to evaluate. + js_code: The string to evaluate. """ tabbed_browser = objreg.get('tabbed-browser', scope='window', window='last-focused') - out = tabbed_browser.widget(0).page().mainFrame().evaluateJavaScript( - 'window.__qute_jseval__ = true;\n' + js) + frame = tabbed_browser.widget(0).page().mainFrame() + out = frame.evaluateJavaScript(js_code) - if out is not None: - message.info(self._win_id, out) - elif tabbed_browser.widget(0).page().jseval_error: - message.error(self._win_id, - tabbed_browser.widget(0).page().jseval_error) - tabbed_browser.widget(0).page().jseval_error = None + if out is None: + # Getting the actual error (if any) seems to be difficult. The + # error does end up in BrowserPage.javaScriptConsoleMessage(), but + # distinguishing between :jseval errors and errors from the webpage + # is not trivial... + message.info(self._win_id, 'No output or error') + else: + # The output can be a string, number, dict, array, etc. But *don't* + # output too much data, as this will make qutebrowser hang + out = str(out) + if len(out) > 5000: + message.info(self._win_id, out[:5000] + ' [...trimmed...]') + else: + message.info(self._win_id, out) diff --git a/qutebrowser/browser/webpage.py b/qutebrowser/browser/webpage.py index 9e49032df..005a6e300 100644 --- a/qutebrowser/browser/webpage.py +++ b/qutebrowser/browser/webpage.py @@ -63,7 +63,6 @@ class BrowserPage(QWebPage): def __init__(self, win_id, tab_id, parent=None): super().__init__(parent) self._win_id = win_id - self.jseval_error = None self._is_shutting_down = False self._extension_handlers = { QWebPage.ErrorPageExtension: self._handle_errorpage, @@ -498,16 +497,6 @@ class BrowserPage(QWebPage): def javaScriptConsoleMessage(self, msg, line, source): """Override javaScriptConsoleMessage to use debug log.""" - - jseval = self.mainFrame().evaluateJavaScript('window.__qute_jseval__') - if jseval: - self.mainFrame().evaluateJavaScript('window.__qute_jseval__ = undefined;') - if source == 'undefined' and jseval: - self.mainFrame().evaluateJavaScript('window.__qute_jseval__ = false;') - print('jseval errror ->', jseval) - self.jseval_error = 'Error on line {}: {}'.format(line, msg) - print('other js error ->', msg, line, source) - if config.get('general', 'log-javascript-console'): log.js.debug("[{}:{}] {}".format(source, line, msg)) From b0880df695867bc60a8ac9edd79bd8fee671a393 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Fri, 5 Jun 2015 23:24:47 +0200 Subject: [PATCH 054/214] Execute in the current tab, and not the first one --- qutebrowser/browser/commands.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index aa8430127..774cbaaa9 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1519,10 +1519,8 @@ class CommandDispatcher: Args: js_code: The string to evaluate. """ - tabbed_browser = objreg.get('tabbed-browser', scope='window', - window='last-focused') - frame = tabbed_browser.widget(0).page().mainFrame() - out = frame.evaluateJavaScript(js_code) + out = self._current_widget().page().mainFrame().evaluateJavaScript( + js_code) if out is None: # Getting the actual error (if any) seems to be difficult. The From de0686c50ab0a337ba94315a0c3a54ab97499e86 Mon Sep 17 00:00:00 2001 From: Lamar Pavel Date: Sat, 6 Jun 2015 14:04:45 +0200 Subject: [PATCH 055/214] Error messages and explicit test for None Error messages for validate() are more specific. Return of standarddir.conf() is explicitly tested for None to avoid ambiguity with other falsey values. --- qutebrowser/config/configtypes.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 267ae8789..ef23cbbbe 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -805,7 +805,7 @@ class File(BaseType): value = os.path.expanduser(value) value = os.path.expandvars(value) if not os.path.isabs(value): - if standarddir.config(): + if standarddir.config() is not None: abspath = os.path.join(standarddir.config(), value) if os.path.isfile(abspath): return abspath @@ -821,12 +821,16 @@ class File(BaseType): try: if not os.path.isabs(value): cfgdir = standarddir.config() - if cfgdir and os.path.isfile(os.path.join(cfgdir, value)): + if cfgdir is not None and os.path.isfile( + os.path.join(cfgdir, value)): return - raise configexc.ValidationError(value, - "must be an absolute path!") - if not os.path.isfile(value): - raise configexc.ValidationError(value, "must be a valid file!") + raise configexc.ValidationError( + value, "must be an absolute path when not using a config " + "directory!") + elif not os.path.isfile(value): + raise configexc.ValidationError( + value, "must be a valid path relative to the config " + "directory!") except UnicodeEncodeError as e: raise configexc.ValidationError(value, e) @@ -1195,7 +1199,7 @@ class UserStyleSheet(File): raise configexc.ValidationError(value, str(e)) return except UnicodeEncodeError as e: - raise configexc.ValidationError(value, e) + raise configexc.ValidationError(value, str(e)) class AutoSearch(BaseType): From 5bacbc9d3862c7b8225dd9ad3416a35b5fb6a090 Mon Sep 17 00:00:00 2001 From: Lamar Pavel Date: Sat, 6 Jun 2015 14:07:57 +0200 Subject: [PATCH 056/214] Remove obsolete try-except block --- qutebrowser/config/configtypes.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index ef23cbbbe..3b623e3ab 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1193,11 +1193,7 @@ class UserStyleSheet(File): # FIXME We just try if it is encodable, maybe we should # validate CSS? # https://github.com/The-Compiler/qutebrowser/issues/115 - try: - value.encode('utf-8') - except UnicodeEncodeError as e: - raise configexc.ValidationError(value, str(e)) - return + value.encode('utf-8') except UnicodeEncodeError as e: raise configexc.ValidationError(value, str(e)) From fd75f77108330beea10d78c3907fa59fa9fcbe8f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 18:18:29 +0200 Subject: [PATCH 057/214] Fix spell checker to check all files. --- scripts/misc_checks.py | 35 ++++++++++++++--------------------- tox.ini | 4 ++-- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/scripts/misc_checks.py b/scripts/misc_checks.py index 36bc28079..c6033cf3b 100644 --- a/scripts/misc_checks.py +++ b/scripts/misc_checks.py @@ -35,9 +35,13 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) from scripts import utils -def _py_files(target): +def _py_files(): """Iterate over all python files and yield filenames.""" - for (dirpath, _dirnames, filenames) in os.walk(target): + for (dirpath, _dirnames, filenames) in os.walk('.'): + parts = dirpath.split(os.sep) + if len(parts) >= 2 and parts[1].startswith('.'): + # ignore hidden dirs + continue for name in (e for e in filenames if e.endswith('.py')): yield os.path.join(dirpath, name) @@ -64,7 +68,7 @@ def check_git(): return status -def check_spelling(target): +def check_spelling(): """Check commonly misspelled words.""" # Words which I often misspell words = {'behaviour', 'quitted', 'likelyhood', 'sucessfully', @@ -81,9 +85,9 @@ def check_spelling(target): seen = collections.defaultdict(list) try: ok = True - for fn in _py_files(target): + for fn in _py_files(): with tokenize.open(fn) as f: - if fn == os.path.join('scripts', 'misc_checks.py'): + if fn == os.path.join('.', 'scripts', 'misc_checks.py'): continue for line in f: for w in words: @@ -98,11 +102,11 @@ def check_spelling(target): return None -def check_vcs_conflict(target): +def check_vcs_conflict(): """Check VCS conflict markers.""" try: ok = True - for fn in _py_files(target): + for fn in _py_files(): with tokenize.open(fn) as f: for line in f: if any(line.startswith(c * 7) for c in '<>=|'): @@ -120,25 +124,14 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument('checker', choices=('git', 'vcs', 'spelling'), help="Which checker to run.") - parser.add_argument('target', help="What to check", nargs='*') args = parser.parse_args() if args.checker == 'git': ok = check_git() - return 0 if ok else 1 elif args.checker == 'vcs': - is_ok = True - for target in args.target: - ok = check_vcs_conflict(target) - if not ok: - is_ok = False - return 0 if is_ok else 1 + ok = check_vcs_conflict() elif args.checker == 'spelling': - is_ok = True - for target in args.target: - ok = check_spelling(target) - if not ok: - is_ok = False - return 0 if is_ok else 1 + ok = check_spelling() + return 0 if ok else 1 if __name__ == '__main__': diff --git a/tox.ini b/tox.ini index b24842ff4..7717dd8ec 100644 --- a/tox.ini +++ b/tox.ini @@ -46,8 +46,8 @@ commands = [testenv:misc] commands = {envpython} scripts/misc_checks.py git - {envpython} scripts/misc_checks.py vcs qutebrowser scripts tests - {envpython} scripts/misc_checks.py spelling qutebrowser scripts tests + {envpython} scripts/misc_checks.py vcs + {envpython} scripts/misc_checks.py spelling [testenv:pylint] skip_install = true From def41e70bf69af1bc8abf92f632f0c6e6b99c18a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 01:11:16 +0200 Subject: [PATCH 058/214] Fix some spelling mistakes. --- qutebrowser/browser/webelem.py | 2 +- qutebrowser/browser/webpage.py | 4 ++-- qutebrowser/browser/webview.py | 2 +- qutebrowser/config/configdata.py | 2 +- qutebrowser/config/configtypes.py | 2 +- qutebrowser/keyinput/basekeyparser.py | 2 +- qutebrowser/mainwindow/tabbedbrowser.py | 2 +- qutebrowser/utils/log.py | 2 +- qutebrowser/utils/usertypes.py | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 59fea9897..263289e6b 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -312,7 +312,7 @@ def javascript_escape(text): def get_child_frames(startframe): """Get all children recursively of a given QWebFrame. - Loosly based on http://blog.nextgenetics.net/?e=64 + Loosely based on http://blog.nextgenetics.net/?e=64 Args: startframe: The QWebFrame to start with. diff --git a/qutebrowser/browser/webpage.py b/qutebrowser/browser/webpage.py index 8e430efcb..15659f56f 100644 --- a/qutebrowser/browser/webpage.py +++ b/qutebrowser/browser/webpage.py @@ -109,7 +109,7 @@ class BrowserPage(QWebPage): def _handle_errorpage(self, info, errpage): """Display an error page if needed. - Loosly based on Helpviewer/HelpBrowserWV.py from eric5 + Loosely based on Helpviewer/HelpBrowserWV.py from eric5 (line 260 @ 5d937eb378dd) Args: @@ -178,7 +178,7 @@ class BrowserPage(QWebPage): def _handle_multiple_files(self, info, files): """Handle uploading of multiple files. - Loosly based on Helpviewer/HelpBrowserWV.py from eric5. + Loosely based on Helpviewer/HelpBrowserWV.py from eric5. Args: info: The ChooseMultipleFilesExtensionOption instance. diff --git a/qutebrowser/browser/webview.py b/qutebrowser/browser/webview.py index ec82d2567..5a4fc3b69 100644 --- a/qutebrowser/browser/webview.py +++ b/qutebrowser/browser/webview.py @@ -163,7 +163,7 @@ class WebView(QWebView): return utils.get_repr(self, tab_id=self.tab_id, url=url) def __del__(self): - # Explicitely releasing the page here seems to prevent some segfaults + # Explicitly releasing the page here seems to prevent some segfaults # when quitting. # Copied from: # https://code.google.com/p/webscraping/source/browse/webkit.py#325 diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index d2c360682..9cf7115f7 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -464,7 +464,7 @@ def data(readonly=False): ('last-close', SettingValue(typ.LastClose(), 'ignore'), - "Behaviour when the last tab is closed."), + "Behavior when the last tab is closed."), ('hide-auto', SettingValue(typ.Bool(), 'false'), diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 55d782202..748748d42 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1312,7 +1312,7 @@ class SelectOnRemove(BaseType): class LastClose(BaseType): - """Behaviour when the last tab is closed.""" + """Behavior when the last tab is closed.""" valid_values = ValidValues(('ignore', "Don't do anything."), ('blank', "Load a blank page."), diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index d19e7ad9a..11b965c27 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -195,7 +195,7 @@ class BaseKeyParser(QObject): self._keystring = '' self.execute(binding, self.Type.chain, count) elif match == self.Match.ambiguous: - self._debug_log("Ambigious match for '{}'.".format( + self._debug_log("Ambiguous match for '{}'.".format( self._keystring)) self._handle_ambiguous_match(binding, count) elif match == self.Match.partial: diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index d54b22d0c..7b4d84b7b 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -332,7 +332,7 @@ class TabbedBrowser(tabwidget.TabWidget): the default settings we handle it like Chromium does: - Tabs from clicked links etc. are to the right of the current. - - Explicitely opened tabs are at the very right. + - Explicitly opened tabs are at the very right. Return: The opened WebView instance. diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index d156c6be1..1f1071673 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -377,7 +377,7 @@ class RAMHandler(logging.Handler): """Logging handler which keeps the messages in a deque in RAM. - Loosly based on logging.BufferingHandler which is unsuitable because it + Loosely based on logging.BufferingHandler which is unsuitable because it uses a simple list rather than a deque. Attributes: diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index c82a54596..13a2e1cfb 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -73,7 +73,7 @@ class NeighborList(collections.abc.Sequence): Args: items: The list of items to iterate in. _default: The initially selected value. - _mode: Behaviour when the first/last item is reached. + _mode: Behavior when the first/last item is reached. Modes.block: Stay on the selected item Modes.wrap: Wrap around to the other end Modes.exception: Raise an IndexError. From 5a73ad0c190e67daebd24128fdf3c775f05fb8e6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 01:11:25 +0200 Subject: [PATCH 059/214] Improve spell-checker case-sensitivity. This only checks case-insensitively for the first char, so things like "QMouseEvent" don't trigger the check. --- scripts/misc_checks.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/scripts/misc_checks.py b/scripts/misc_checks.py index c6033cf3b..dac0fe017 100644 --- a/scripts/misc_checks.py +++ b/scripts/misc_checks.py @@ -71,16 +71,17 @@ def check_git(): def check_spelling(): """Check commonly misspelled words.""" # Words which I often misspell - words = {'behaviour', 'quitted', 'likelyhood', 'sucessfully', - 'occur[^r .]', 'seperator', 'explicitely', 'resetted', - 'auxillary', 'accidentaly', 'ambigious', 'loosly', - 'initialis', 'convienence', 'similiar', 'uncommited', - 'reproducable'} + words = {'[Bb]ehaviour', '[Qq]uitted', 'Ll]ikelyhood', '[Ss]ucessfully', + '[Oo]ccur[^r .]', '[Ss]eperator', '[Ee]xplicitely', '[Rr]esetted', + '[Aa]uxillary', '[Aa]ccidentaly', '[Aa]mbigious', '[Ll]oosly', + '[Ii]nitialis', '[Cc]onvienence', '[Ss]imiliar', '[Uu]ncommited', + '[Rr]eproducable'} # Words which look better when splitted, but might need some fine tuning. - words |= {'keystrings', 'webelements', 'mouseevent', 'keysequence', - 'normalmode', 'eventloops', 'sizehint', 'statemachine', - 'metaobject', 'logrecord', 'filetype'} + words |= {'[Kk]eystrings', '[Ww]ebelements', '[Mm]ouseevent', + '[Kk]eysequence', '[Nn]ormalmode', '[Ee]ventloops', + '[Ss]izehint', '[Ss]tatemachine', '[Mm]etaobject', + '[Ll]ogrecord', '[Ff]iletype'} seen = collections.defaultdict(list) try: @@ -91,8 +92,8 @@ def check_spelling(): continue for line in f: for w in words: - if re.search(w, line, re.I) and fn not in seen[w]: - print("Found '{}' in {}!".format(w, fn)) + if re.search(w, line) and fn not in seen[w]: + print('Found "{}" in {}!'.format(w, fn)) seen[w].append(fn) ok = False print() From a0e5a3e8eebbe33e80238fb1eaad73afeb1e1d45 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 01:13:19 +0200 Subject: [PATCH 060/214] tox: Update pytest-qt to 1.4.0. Upstream changelog: - Messages sent by qDebug, qWarning, qCritical are captured and displayed when tests fail, similar to pytest-catchlog. Also, tests can be configured to automatically fail if an unexpected message is generated. (See docs). - New method waitSignals: will block untill all signals given are triggered, see docs (thanks @The-Compiler for idea and complete PR). - New parameter raising to waitSignals and waitSignals: when True (defaults to False) will raise a qtbot.SignalTimeoutError exception when timeout is reached, see docs (thanks again to @The-Compiler for idea and complete PR). - pytest-qt now requires pytest version >= 2.7. Internal changes to improve memory management - QApplication.exit() is no longer called at the end of the test session and the QApplication instance is not garbage collected anymore; - QtBot no longer receives a QApplication as a parameter in the constructor, always referencing QApplication.instance() now; this avoids keeping an extra reference in the qtbot instances. - deleteLater is called on widgets added in QtBot.addWidget at the end of each test; - QApplication.processEvents() is called at the end of each test to make sure widgets are cleaned up; --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 7717dd8ec..9b1e0821a 100644 --- a/tox.ini +++ b/tox.ini @@ -23,7 +23,7 @@ deps = py==1.4.28 pytest==2.7.1 pytest-capturelog==0.7 - pytest-qt==1.3.0 + pytest-qt==1.4.0 pytest-mock==0.6.0 pytest-html==1.3.1 # We don't use {[testenv:mkvenv]commands} here because that seems to be broken From 5310c60d586be3e8abc4b39dcbd05952c816333d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 01:24:24 +0200 Subject: [PATCH 061/214] Remove unused import. --- qutebrowser/keyinput/basekeyparser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index 11b965c27..56b9cfaac 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -23,7 +23,7 @@ import re import functools import unicodedata -from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QObject +from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject from qutebrowser.config import config from qutebrowser.utils import usertypes, log, utils, objreg From 2117b2afc6ad369bdfee2eaa26d5e5dde6270414 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 01:25:10 +0200 Subject: [PATCH 062/214] Revert "Skip test which might be responsible for segfaults." This reverts commit 592ace18d4aca6928fef2c269e46ee15fd492882. --- tests/mainwindow/statusbar/test_progress.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/mainwindow/statusbar/test_progress.py b/tests/mainwindow/statusbar/test_progress.py index a0d066808..07e93e0e5 100644 --- a/tests/mainwindow/statusbar/test_progress.py +++ b/tests/mainwindow/statusbar/test_progress.py @@ -44,9 +44,6 @@ def progress_widget(qtbot, monkeypatch, config_stub): return widget -@pytest.mark.xfail( - reason='Blacklisted because it could cause random segfaults - see ' - 'https://github.com/hackebrot/qutebrowser/issues/22', run=False) def test_load_started(progress_widget): """Ensure the Progress widget reacts properly when the page starts loading. From aa4cb2927d054b11821715f5575be55d3d5aab15 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 02:29:20 +0200 Subject: [PATCH 063/214] Fix TestHideQtWarning tests for pytest 1.4.0. pytest captures the Qt logging messages, so we can't use qWarning to test. --- tests/utils/test_log.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/utils/test_log.py b/tests/utils/test_log.py index a09a354ee..eadaa7470 100644 --- a/tests/utils/test_log.py +++ b/tests/utils/test_log.py @@ -27,7 +27,6 @@ import itertools import sys import pytest -from PyQt5.QtCore import qWarning from qutebrowser.utils import log @@ -230,33 +229,37 @@ class TestHideQtWarning: """Tests for hide_qt_warning/QtWarningFilter.""" - def test_unfiltered(self, caplog): + @pytest.fixture() + def logger(self): + return logging.getLogger('qt-tests') + + def test_unfiltered(self, logger, caplog): """Test a message which is not filtered.""" with log.hide_qt_warning("World", logger='qt-tests'): with caplog.atLevel(logging.WARNING, logger='qt-tests'): - qWarning("Hello World") + logger.warning("Hello World") assert len(caplog.records()) == 1 record = caplog.records()[0] assert record.levelname == 'WARNING' assert record.message == "Hello World" - def test_filtered_exact(self, caplog): + def test_filtered_exact(self, logger, caplog): """Test a message which is filtered (exact match).""" with log.hide_qt_warning("Hello", logger='qt-tests'): with caplog.atLevel(logging.WARNING, logger='qt-tests'): - qWarning("Hello") + logger.warning("Hello") assert not caplog.records() - def test_filtered_start(self, caplog): + def test_filtered_start(self, logger, caplog): """Test a message which is filtered (match at line start).""" with log.hide_qt_warning("Hello", logger='qt-tests'): with caplog.atLevel(logging.WARNING, logger='qt-tests'): - qWarning("Hello World") + logger.warning("Hello World") assert not caplog.records() - def test_filtered_whitespace(self, caplog): + def test_filtered_whitespace(self, logger, caplog): """Test a message which is filtered (match with whitespace).""" with log.hide_qt_warning("Hello", logger='qt-tests'): with caplog.atLevel(logging.WARNING, logger='qt-tests'): - qWarning(" Hello World ") + logger.warning(" Hello World ") assert not caplog.records() From e86a79740a88230826f742af9ce01a292b379302 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 02:30:36 +0200 Subject: [PATCH 064/214] Use raising=True for QtBot.waitSignal. --- tests/javascript/conftest.py | 4 ++-- tests/utils/test_log.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/javascript/conftest.py b/tests/javascript/conftest.py index d97b38625..442ad73cb 100644 --- a/tests/javascript/conftest.py +++ b/tests/javascript/conftest.py @@ -80,7 +80,7 @@ class JSTester: def scroll_anchor(self, name): """Scroll the main frame to the given anchor.""" page = self.webview.page() - with self._qtbot.waitSignal(page.scrollRequested): + with self._qtbot.waitSignal(page.scrollRequested, raising=True): page.mainFrame().scrollToAnchor(name) def load(self, path, **kwargs): @@ -92,7 +92,7 @@ class JSTester: **kwargs: Passed to jinja's template.render(). """ template = self._jinja_env.get_template(path) - with self._qtbot.waitSignal(self.webview.loadFinished): + with self._qtbot.waitSignal(self.webview.loadFinished, raising=True): self.webview.setHtml(template.render(**kwargs)) def run_file(self, filename): diff --git a/tests/utils/test_log.py b/tests/utils/test_log.py index eadaa7470..03575ea5b 100644 --- a/tests/utils/test_log.py +++ b/tests/utils/test_log.py @@ -213,7 +213,7 @@ class TestInitLog: @pytest.fixture def args(self): - """Fixture providing an argparse namespace.""" + """Fixture providing an argparse namespace for init_log.""" return argparse.Namespace(debug=True, loglevel=logging.DEBUG, color=True, loglines=10, logfilter="") From d887623377c098063d3feaf162bc41cac1642301 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 02:34:19 +0200 Subject: [PATCH 065/214] Make tests fail on unexpected Qt messages. --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 9b1e0821a..870ee72fd 100644 --- a/tox.ini +++ b/tox.ini @@ -165,3 +165,5 @@ pep8ignore = W503 # line break before binary operator resources.py ALL mccabe-complexity = 12 +qt_log_level_fail = WARNING +qt_log_ignore = ^SpellCheck: .* From e98a05e53ddbadef07647589f13ea481c9a6c7e3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 10:38:58 +0200 Subject: [PATCH 066/214] Fix scroll_anchor in javascript tests. It seems scrollRequested doesn't actually get emitted. --- tests/javascript/conftest.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/javascript/conftest.py b/tests/javascript/conftest.py index 442ad73cb..85b56a577 100644 --- a/tests/javascript/conftest.py +++ b/tests/javascript/conftest.py @@ -80,8 +80,10 @@ class JSTester: def scroll_anchor(self, name): """Scroll the main frame to the given anchor.""" page = self.webview.page() - with self._qtbot.waitSignal(page.scrollRequested, raising=True): - page.mainFrame().scrollToAnchor(name) + old_pos = page.mainFrame().scrollPosition() + page.mainFrame().scrollToAnchor(name) + new_pos = page.mainFrame().scrollPosition() + assert old_pos != new_pos def load(self, path, **kwargs): """Load and display the given test data. From a82b0d007dc9bf62eced30b17f3efcaccd2899b6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 10:46:47 +0200 Subject: [PATCH 067/214] Enforce a Qt with SSL support. --- qutebrowser/browser/network/networkmanager.py | 155 ++++++++---------- qutebrowser/misc/earlyinit.py | 14 ++ qutebrowser/utils/version.py | 12 +- 3 files changed, 84 insertions(+), 97 deletions(-) diff --git a/qutebrowser/browser/network/networkmanager.py b/qutebrowser/browser/network/networkmanager.py index a3fc76baf..aaf5f951b 100644 --- a/qutebrowser/browser/network/networkmanager.py +++ b/qutebrowser/browser/network/networkmanager.py @@ -23,14 +23,8 @@ import collections from PyQt5.QtCore import (pyqtSlot, pyqtSignal, PYQT_VERSION, QCoreApplication, QUrl) -from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QSslError - -try: - from PyQt5.QtNetwork import QSslSocket -except ImportError: - SSL_AVAILABLE = False -else: - SSL_AVAILABLE = QSslSocket.supportsSsl() +from PyQt5.QtNetwork import (QNetworkAccessManager, QNetworkReply, QSslError, + QSslSocket) from qutebrowser.config import config from qutebrowser.utils import (message, log, usertypes, utils, objreg, qtutils, @@ -46,13 +40,12 @@ _proxy_auth_cache = {} def init(): """Disable insecure SSL ciphers on old Qt versions.""" - if SSL_AVAILABLE: - if not qtutils.version_check('5.3.0'): - # Disable weak SSL ciphers. - # See https://codereview.qt-project.org/#/c/75943/ - good_ciphers = [c for c in QSslSocket.supportedCiphers() - if c.usedBits() >= 128] - QSslSocket.setDefaultCiphers(good_ciphers) + if not qtutils.version_check('5.3.0'): + # Disable weak SSL ciphers. + # See https://codereview.qt-project.org/#/c/75943/ + good_ciphers = [c for c in QSslSocket.supportedCiphers() + if c.usedBits() >= 128] + QSslSocket.setDefaultCiphers(good_ciphers) class SslError(QSslError): @@ -107,10 +100,9 @@ class NetworkManager(QNetworkAccessManager): } self._set_cookiejar() self._set_cache() - if SSL_AVAILABLE: - self.sslErrors.connect(self.on_ssl_errors) - self._rejected_ssl_errors = collections.defaultdict(list) - self._accepted_ssl_errors = collections.defaultdict(list) + self.sslErrors.connect(self.on_ssl_errors) + self._rejected_ssl_errors = collections.defaultdict(list) + self._accepted_ssl_errors = collections.defaultdict(list) self.authenticationRequired.connect(self.on_authentication_required) self.proxyAuthenticationRequired.connect( self.on_proxy_authentication_required) @@ -181,76 +173,67 @@ class NetworkManager(QNetworkAccessManager): request.deleteLater() self.shutting_down.emit() - if SSL_AVAILABLE: # pragma: no mccabe - @pyqtSlot('QNetworkReply*', 'QList') - def on_ssl_errors(self, reply, errors): - """Decide if SSL errors should be ignored or not. + @pyqtSlot('QNetworkReply*', 'QList') + def on_ssl_errors(self, reply, errors): # pragma: no mccabe + """Decide if SSL errors should be ignored or not. - This slot is called on SSL/TLS errors by the self.sslErrors signal. + This slot is called on SSL/TLS errors by the self.sslErrors signal. - Args: - reply: The QNetworkReply that is encountering the errors. - errors: A list of errors. - """ - errors = [SslError(e) for e in errors] - ssl_strict = config.get('network', 'ssl-strict') - if ssl_strict == 'ask': - try: - host_tpl = urlutils.host_tuple(reply.url()) - except ValueError: - host_tpl = None - is_accepted = False - is_rejected = False - else: - is_accepted = set(errors).issubset( - self._accepted_ssl_errors[host_tpl]) - is_rejected = set(errors).issubset( - self._rejected_ssl_errors[host_tpl]) - if is_accepted: - reply.ignoreSslErrors() - elif is_rejected: - pass - else: - err_string = '\n'.join('- ' + err.errorString() for err in - errors) - answer = self._ask('SSL errors - continue?\n{}'.format( - err_string), mode=usertypes.PromptMode.yesno, - owner=reply) - if answer: - reply.ignoreSslErrors() - d = self._accepted_ssl_errors - else: - d = self._rejected_ssl_errors - if host_tpl is not None: - d[host_tpl] += errors - elif ssl_strict: + Args: + reply: The QNetworkReply that is encountering the errors. + errors: A list of errors. + """ + errors = [SslError(e) for e in errors] + ssl_strict = config.get('network', 'ssl-strict') + if ssl_strict == 'ask': + try: + host_tpl = urlutils.host_tuple(reply.url()) + except ValueError: + host_tpl = None + is_accepted = False + is_rejected = False + else: + is_accepted = set(errors).issubset( + self._accepted_ssl_errors[host_tpl]) + is_rejected = set(errors).issubset( + self._rejected_ssl_errors[host_tpl]) + if is_accepted: + reply.ignoreSslErrors() + elif is_rejected: pass else: - for err in errors: - # FIXME we might want to use warn here (non-fatal error) - # https://github.com/The-Compiler/qutebrowser/issues/114 - message.error(self._win_id, - 'SSL error: {}'.format(err.errorString())) - reply.ignoreSslErrors() + err_string = '\n'.join('- ' + err.errorString() for err in + errors) + answer = self._ask('SSL errors - continue?\n{}'.format( + err_string), mode=usertypes.PromptMode.yesno, + owner=reply) + if answer: + reply.ignoreSslErrors() + d = self._accepted_ssl_errors + else: + d = self._rejected_ssl_errors + if host_tpl is not None: + d[host_tpl] += errors + elif ssl_strict: + pass + else: + for err in errors: + # FIXME we might want to use warn here (non-fatal error) + # https://github.com/The-Compiler/qutebrowser/issues/114 + message.error(self._win_id, + 'SSL error: {}'.format(err.errorString())) + reply.ignoreSslErrors() - @pyqtSlot(QUrl) - def clear_rejected_ssl_errors(self, url): - """Clear the rejected SSL errors on a reload. + @pyqtSlot(QUrl) + def clear_rejected_ssl_errors(self, url): + """Clear the rejected SSL errors on a reload. - Args: - url: The URL to remove. - """ - try: - del self._rejected_ssl_errors[url] - except KeyError: - pass - else: - @pyqtSlot(QUrl) - def clear_rejected_ssl_errors(self, _url): - """Clear the rejected SSL errors on a reload. - - Does nothing because SSL is unavailable. - """ + Args: + url: The URL to remove. + """ + try: + del self._rejected_ssl_errors[url] + except KeyError: pass @pyqtSlot('QNetworkReply', 'QAuthenticator') @@ -334,11 +317,7 @@ class NetworkManager(QNetworkAccessManager): A QNetworkReply. """ scheme = req.url().scheme() - if scheme == 'https' and not SSL_AVAILABLE: - return networkreply.ErrorNetworkReply( - req, "SSL is not supported by the installed Qt library!", - QNetworkReply.ProtocolUnknownError, self) - elif scheme in self._scheme_handlers: + if scheme in self._scheme_handlers: return self._scheme_handlers[scheme].createRequest( op, req, outgoing_data) diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index 3bc214389..43806df58 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -213,6 +213,19 @@ def check_qt_version(): _die(text) +def check_ssl_support(): + """Check if SSL support is available.""" + try: + from PyQt5.QtNetwork import QSslSocket + except ImportError: + ok = False + else: + ok = QSslSocket.supportsSsl() + if not ok: + text = "Fatal error: Your Qt is built without SSL support." + _die(text) + + def check_libraries(): """Check if all needed Python libraries are installed.""" modules = { @@ -288,6 +301,7 @@ def earlyinit(args): # Now we can be sure QtCore is available, so we can print dialogs on # errors, so people only using the GUI notice them as well. check_qt_version() + check_ssl_support() remove_inputhook() check_libraries() init_log(args) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 19dae311a..add7e4c84 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -29,10 +29,7 @@ import collections from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, qVersion from PyQt5.QtWebKit import qWebKitVersion -try: - from PyQt5.QtNetwork import QSslSocket -except ImportError: - QSslSocket = None +from PyQt5.QtNetwork import QSslSocket import qutebrowser from qutebrowser.utils import log, utils @@ -199,16 +196,13 @@ def version(): 'Qt: {}, runtime: {}'.format(QT_VERSION_STR, qVersion()), 'PyQt: {}'.format(PYQT_VERSION_STR), ] + lines += _module_versions() - if QSslSocket is not None and QSslSocket.supportsSsl(): - ssl_version = QSslSocket.sslLibraryVersionString() - else: - ssl_version = 'unavailable' lines += [ 'Webkit: {}'.format(qWebKitVersion()), 'Harfbuzz: {}'.format(os.environ.get('QT_HARFBUZZ', 'system')), - 'SSL: {}'.format(ssl_version), + 'SSL: {}'.format(QSslSocket.sslLibraryVersionString()), '', 'Frozen: {}'.format(hasattr(sys, 'frozen')), 'Platform: {}, {}'.format(platform.platform(), From 37750b9e30ced50d27c7f775b7c9ce22d687f6b7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 10:47:53 +0200 Subject: [PATCH 068/214] Regenerate docs. --- doc/help/settings.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index fc4802232..1a99fc9ad 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -99,7 +99,7 @@ |<>|Which tab to select when the focused tab is removed. |<>|How new tabs are positioned. |<>|How new tabs opened explicitly are positioned. -|<>|Behaviour when the last tab is closed. +|<>|Behavior when the last tab is closed. |<>|Hide the tab bar if only one tab is open. |<>|Always hide the tab bar. |<>|Whether to wrap when changing tabs. @@ -911,7 +911,7 @@ Default: +pass:[last]+ [[tabs-last-close]] === last-close -Behaviour when the last tab is closed. +Behavior when the last tab is closed. Valid values: From 83f7cf84a9af99bfad52bc4e07aff157fae70804 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 11:14:14 +0200 Subject: [PATCH 069/214] tests: Set progress widget geometry. This hopefully fixes this warning on Windows: QWindowsWindow::setGeometryDp: Unable to set geometry 113x16+192+124 on QWidgetWindow/'ProgressClassWindow'. Resulting geometry: 124x16+192+124 (frame: 8, 31, 8, 8, custom margin: 0, 0, 0, 0, minimum size: 0x0, maximum size: 16777215x16777215). --- tests/mainwindow/statusbar/test_progress.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/mainwindow/statusbar/test_progress.py b/tests/mainwindow/statusbar/test_progress.py index 07e93e0e5..b8b03cd29 100644 --- a/tests/mainwindow/statusbar/test_progress.py +++ b/tests/mainwindow/statusbar/test_progress.py @@ -39,6 +39,7 @@ def progress_widget(qtbot, monkeypatch, config_stub): 'qutebrowser.mainwindow.statusbar.progress.style.config', config_stub) widget = Progress() qtbot.add_widget(widget) + widget.setGeometry(200, 200, 200, 200) assert not widget.isVisible() assert not widget.isTextVisible() return widget From 2fa6c952c26dfc387940f124aa5abd6de8c8b5e2 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Sun, 7 Jun 2015 16:37:25 +0200 Subject: [PATCH 070/214] Use less CPU when downloading files When downloading a bunch (7 or 8) of files I noticed qutebrowser was using a lot of CPU (>60%). I did some looking, and in the `downloadProgress` callback qutebrower emits the updated signal which causes everything to be updated. We don't really need this, since _update_speed() calls it every 500ms anyway. I tested by downloading 3 copies of the 1GB file [on this page]( http://www.thinkbroadband.com/download.html ) qutebrowser consistently pulls about 25% CPU on my system. When removing this call, the system pulls about 17% CPU. Not a great amount, but still significant enough to warrant a pull request ;-) Some other notes: - wget uses about 1.5%-2% for each process when downloading. - When not doing any UI updates & speed calculations qutebrowser uses about 15%. - Doing some quick profiling and strategic commenting seems to indicate there isn't any other low hanging fruit to be improved on here. --- qutebrowser/browser/downloads.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 790459f6a..aa29fce9c 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -148,7 +148,7 @@ class DownloadItemStats(QObject): @pyqtSlot(int, int) def on_download_progress(self, bytes_done, bytes_total): - """Upload local variables when the download progress changed. + """Update local variables when the download progress changed. Args: bytes_done: How many bytes are downloaded. @@ -158,7 +158,6 @@ class DownloadItemStats(QObject): bytes_total = None self.done = bytes_done self.total = bytes_total - self.updated.emit() class DownloadItem(QObject): From 6b94dc5279791788d32f427b98b4e149cc14e566 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 17:20:52 +0200 Subject: [PATCH 071/214] Add continue to default next-regexes. --- qutebrowser/config/configdata.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 9cf7115f7..e1d24a610 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -740,7 +740,8 @@ def data(readonly=False): ('next-regexes', SettingValue(typ.RegexList(flags=re.IGNORECASE), - r'\bnext\b,\bmore\b,\bnewer\b,\b[>→≫]\b,\b(>>|»)\b'), + r'\bnext\b,\bmore\b,\bnewer\b,\b[>→≫]\b,\b(>>|»)\b,' + r'\bcontinue\b'), "A comma-separated list of regexes to use for 'next' links."), ('prev-regexes', From 525d3ee4c99fd72b42af876828d7f392eeb0683f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 17:38:27 +0200 Subject: [PATCH 072/214] Regenerate docs --- doc/help/settings.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 1a99fc9ad..af2453378 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -1434,7 +1434,7 @@ Default: +pass:[true]+ === next-regexes A comma-separated list of regexes to use for 'next' links. -Default: +pass:[\bnext\b,\bmore\b,\bnewer\b,\b[>→≫]\b,\b(>>|»)\b]+ +Default: +pass:[\bnext\b,\bmore\b,\bnewer\b,\b[>→≫]\b,\b(>>|»)\b,\bcontinue\b]+ [[hints-prev-regexes]] === prev-regexes From da2ff6f3cb168cb4fe00161fbc3b8e8f587701be Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 21:27:50 +0200 Subject: [PATCH 073/214] Update recommended Qt version in README. --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index 3a701fd76..4ff63f5a7 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -89,7 +89,7 @@ Requirements The following software and libraries are required to run qutebrowser: * http://www.python.org/[Python] 3.4 -* http://qt-project.org/[Qt] 5.2.0 or newer (5.4.1 recommended) +* http://qt-project.org/[Qt] 5.2.0 or newer (5.4.2 recommended) * QtWebKit * http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2.0 or newer (5.4.1 recommended) for Python 3 From f85ca19cef3aa2d7eac3cecb73f806a0d75c740d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 21:33:35 +0200 Subject: [PATCH 074/214] Use