Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b4d6a8f89 | ||
|
|
baeb374589 | ||
|
|
26cfac60d4 | ||
|
|
f9d5af3546 | ||
|
|
bd7cefddd5 | ||
|
|
e925e8a059 | ||
|
|
17cdf2eab6 | ||
|
|
38f7f63689 | ||
|
|
3b0e7a07f3 | ||
|
|
5f3922d1d6 | ||
|
|
6cd159246c | ||
|
|
552198ac31 | ||
|
|
1222394efd | ||
|
|
a7ff4075de | ||
|
|
a876f4a199 | ||
|
|
661945c61b | ||
|
|
496e5a38fa | ||
|
|
a43aa52c81 | ||
|
|
4abbe50289 | ||
|
|
363d4f4654 | ||
|
|
34e184ebf5 | ||
|
|
17840d06da | ||
|
|
e9e2adb9a7 | ||
|
|
113675a0b5 | ||
|
|
c4d8a767f9 | ||
|
|
de3867fe95 | ||
|
|
6ac3940264 | ||
|
|
5e9eafd5a3 | ||
|
|
5d4b9e815c | ||
|
|
3eba7fc314 | ||
|
|
11d7486f97 | ||
|
|
8e7a1d3d97 | ||
|
|
98704c0471 | ||
|
|
c2bf595b79 | ||
|
|
f73f3a2001 | ||
|
|
afde5bbc79 | ||
|
|
66a76a4504 | ||
|
|
feb73f06c5 | ||
|
|
e32fbe9013 | ||
|
|
776a16bf65 | ||
|
|
225c860452 | ||
|
|
f982402526 | ||
|
|
6a6e7ecb38 | ||
|
|
c94ed93f13 | ||
|
|
95d1721f01 | ||
|
|
410be07f54 | ||
|
|
01de52c23a | ||
|
|
a84807ed05 | ||
|
|
2795ae9478 |
@@ -12,9 +12,6 @@ matrix:
|
||||
- os: linux
|
||||
env: DOCKER=archlinux
|
||||
services: docker
|
||||
- os: linux
|
||||
env: DOCKER=ubuntu-wily
|
||||
services: docker
|
||||
- os: linux
|
||||
env: DOCKER=ubuntu-xenial
|
||||
services: docker
|
||||
@@ -63,8 +60,8 @@ notifications:
|
||||
- https://buildtimetrend.herokuapp.com/travis
|
||||
irc:
|
||||
channels:
|
||||
- "chat.freenode.net#qutebrowser"
|
||||
on_success: change
|
||||
- "chat.freenode.net#qutebrowser-dev"
|
||||
on_success: always
|
||||
on_failure: always
|
||||
skip_join: true
|
||||
template:
|
||||
|
||||
@@ -14,6 +14,59 @@ This project adheres to http://semver.org/[Semantic Versioning].
|
||||
// `Fixed` for any bug fixes.
|
||||
// `Security` to invite users to upgrade in case of vulnerabilities.
|
||||
|
||||
v0.8.3
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fixed crash when doing `:<space><enter>`, another corner-case introduced in v0.8.0
|
||||
- Fixed `:open-editor` (`<Ctrl-e>`) on Windows
|
||||
- Fixed crash when setting `general -> auto-save-interval` to a too big value.
|
||||
- Fixed crash when using hints on Void Linux.
|
||||
- Fixed compatibility with Python 3.5.2+ on Debian unstable
|
||||
- Compatibility with pdfjs v1.6.210
|
||||
- `:bind` can now be used to bind to an alias (binding by editing `keys.conf`
|
||||
already worked before)
|
||||
- The command completion now updates correctly when changing aliases
|
||||
- The tabbar now displays correctly with the Adwaita Qt theme
|
||||
- The default `sk` keybinding now sets the commandline to `:bind` correctly
|
||||
- Fixed crash when closing a window without focusing it
|
||||
|
||||
v0.8.2
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fixed `general -> private-browsing` not being set correctly until a restart
|
||||
(which caused e.g. local storage to be enabled).
|
||||
- When hinting input fields (`:t`), also consider input elements without a type.
|
||||
- Fixed crash when opening an invalid URL with a percent-encoded and a real @ in it
|
||||
- Fixed default `;o` and `;O` bindings
|
||||
- Fixed local storage not working (and possible other bugs) when using a
|
||||
relative path with `--basedir`.
|
||||
- Fixed crash when deleting a quickmark with Ctrl-D
|
||||
- Fixed HTML5 video playback on Windows
|
||||
- Fixed crash when using `:prompt-open-download` with a file with chars not
|
||||
encodable with the OS' filesystem encoding (e.g. with `LC_ALL=C`)
|
||||
- Fixed `:prompt-open-download` with a too long filename (> 255 bytes)
|
||||
- Fixed crash when cancelling a download after doing `:prompt-open-download`
|
||||
- Fixed crash when writing a download to disk fails with
|
||||
`:prompt-open-download`.
|
||||
- Fixed `:restart` deleting the basedir when it was given with `--basedir`.
|
||||
|
||||
v0.8.1
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fix crash when pressing enter without a command
|
||||
- Adjust error message to point out QtWebEngine is unsupported with the OS
|
||||
X .app currently.
|
||||
- Hide Harfbuzz warning with the OS X .app
|
||||
|
||||
v0.8.0
|
||||
------
|
||||
|
||||
@@ -33,7 +86,7 @@ Added
|
||||
`$QUTE_DOWNLOAD_DIR` available for userscripts.
|
||||
- New option `ui` -> `status-position` to configure the position of the
|
||||
status bar (top/bottom).
|
||||
- New `--pdf <filename>` argument for `:print` which can be used to generate a
|
||||
- New `--pdf <filename>` argument for `:print` WHICH can be used to generate a
|
||||
PDF without a dialog.
|
||||
|
||||
Changed
|
||||
@@ -108,7 +161,6 @@ Changed
|
||||
- `:navigate` now clears the URL fragment
|
||||
- `:completion-item-del` (`Ctrl-D`) can now be used in `:buffer` completion to
|
||||
close a tab
|
||||
- Counts can now be used with special keybindings (e.g. with modifiers)
|
||||
- Various SSL ciphers are now disabled by default. With recent Qt/OpenSSL
|
||||
versions those already all are disabled, but with older versions they might
|
||||
not be.
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
FROM ubuntu:wily
|
||||
MAINTAINER Florian Bruhin <me@the-compiler.org>
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get -y update && \
|
||||
apt-get -y dist-upgrade && \
|
||||
apt-get -y install --no-install-recommends \
|
||||
python3-pyqt5 \
|
||||
python3-pyqt5.qtwebkit \
|
||||
python-tox \
|
||||
python3-sip \
|
||||
xvfb \
|
||||
git \
|
||||
python3-setuptools \
|
||||
wget \
|
||||
herbstluftwm \
|
||||
language-pack-en \
|
||||
libjs-pdf \
|
||||
dbus
|
||||
|
||||
RUN dbus-uuidgen --ensure
|
||||
|
||||
RUN useradd user && mkdir /home/user && chown -R user:users /home/user
|
||||
USER user
|
||||
WORKDIR /home/user
|
||||
|
||||
ENV DISPLAY=:0
|
||||
ENV LC_ALL=en_US.UTF-8
|
||||
ENV LANG=en_US.UTF-8
|
||||
|
||||
CMD Xvfb -screen 0 800x600x24 :0 & \
|
||||
sleep 2 && \
|
||||
herbstluftwm & \
|
||||
git clone /outside qutebrowser.git && \
|
||||
cd qutebrowser.git && \
|
||||
tox -e py34
|
||||
@@ -28,7 +28,7 @@ __copyright__ = "Copyright 2014-2016 Florian Bruhin (The Compiler)"
|
||||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version_info__ = (0, 8, 0)
|
||||
__version_info__ = (0, 8, 4)
|
||||
__version__ = '.'.join(str(e) for e in __version_info__)
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit."
|
||||
|
||||
|
||||
@@ -551,8 +551,9 @@ class Quitter:
|
||||
argdict['session'] = session
|
||||
argdict['override_restore'] = False
|
||||
# Ensure :restart works with --temp-basedir
|
||||
argdict['temp_basedir'] = False
|
||||
argdict['temp_basedir_restarted'] = True
|
||||
if self._args.temp_basedir:
|
||||
argdict['temp_basedir'] = False
|
||||
argdict['temp_basedir_restarted'] = True
|
||||
|
||||
# Dump the data
|
||||
data = json.dumps(argdict)
|
||||
|
||||
@@ -377,6 +377,8 @@ class CommandDispatcher:
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def tab_detach(self):
|
||||
"""Detach the current tab to its own window."""
|
||||
if self._count() < 2:
|
||||
raise cmdexc.CommandError("Cannot detach one tab.")
|
||||
url = self._current_url()
|
||||
self._open(url, window=True)
|
||||
cur_widget = self._current_widget()
|
||||
|
||||
@@ -63,8 +63,10 @@ def _generate_pdfjs_script(url):
|
||||
url: The url of the pdf page as QUrl.
|
||||
"""
|
||||
return (
|
||||
'PDFJS.verbosity = PDFJS.VERBOSITY_LEVELS.info;\n'
|
||||
'PDFView.open("{url}");\n'
|
||||
'document.addEventListener("DOMContentLoaded", function() {{\n'
|
||||
' PDFJS.verbosity = PDFJS.VERBOSITY_LEVELS.info;\n'
|
||||
' (window.PDFView || window.PDFViewerApplication).open("{url}");\n'
|
||||
'}});\n'
|
||||
).format(url=webelem.javascript_escape(url.toString(QUrl.FullyEncoded)))
|
||||
|
||||
|
||||
|
||||
@@ -211,7 +211,11 @@ class WebEngineScroller(browsertab.AbstractScroller):
|
||||
"""Update the scroll position attributes when it changed."""
|
||||
def update_scroll_pos(jsret):
|
||||
"""Callback after getting scroll position via JS."""
|
||||
assert isinstance(jsret, dict)
|
||||
if jsret is None:
|
||||
# This can happen when the callback would get called after
|
||||
# shutting down a tab
|
||||
return
|
||||
assert isinstance(jsret, dict), jsret
|
||||
self._pos_perc = (jsret['perc']['x'], jsret['perc']['y'])
|
||||
self._pos_px = QPoint(jsret['px']['x'], jsret['px']['y'])
|
||||
self.perc_changed.emit(*self._pos_perc)
|
||||
|
||||
@@ -947,13 +947,28 @@ class DownloadManager(QAbstractListModel):
|
||||
download.set_filename(target.filename)
|
||||
elif isinstance(target, usertypes.OpenFileDownloadTarget):
|
||||
tmp_manager = objreg.get('temporary-downloads')
|
||||
fobj = tmp_manager.get_tmpfile(suggested_filename)
|
||||
download.finished.connect(download.open_file)
|
||||
try:
|
||||
fobj = tmp_manager.get_tmpfile(suggested_filename)
|
||||
except OSError as exc:
|
||||
msg = "Download error: {}".format(exc)
|
||||
message.error(self._win_id, msg)
|
||||
download.cancel()
|
||||
return
|
||||
download.finished.connect(
|
||||
functools.partial(self._open_download, download))
|
||||
download.autoclose = True
|
||||
download.set_fileobj(fobj)
|
||||
else:
|
||||
log.downloads.error("Unknown download target: {}".format(target))
|
||||
|
||||
def _open_download(self, download):
|
||||
"""Open the given download but only if it was successful."""
|
||||
if download.successful:
|
||||
download.open_file()
|
||||
else:
|
||||
log.downloads.debug("{} finished but not successful, not opening!"
|
||||
.format(download))
|
||||
|
||||
def raise_no_download(self, count):
|
||||
"""Raise an exception that the download doesn't exist.
|
||||
|
||||
@@ -1316,6 +1331,11 @@ class TempDownloadManager(QObject):
|
||||
A tempfile.NamedTemporaryFile that should be used to save the file.
|
||||
"""
|
||||
tmpdir = self._get_tmpdir()
|
||||
encoding = sys.getfilesystemencoding()
|
||||
suggested_name = utils.force_encoding(suggested_name, encoding)
|
||||
# Make sure that the filename is not too long
|
||||
if len(suggested_name) > 50:
|
||||
suggested_name = suggested_name[:25] + '...' + suggested_name[-25:]
|
||||
fobj = tempfile.NamedTemporaryFile(dir=tmpdir.name, delete=False,
|
||||
suffix=suggested_name)
|
||||
self.files.append(fobj)
|
||||
|
||||
@@ -295,6 +295,10 @@ class WebHistory(QObject):
|
||||
"""
|
||||
if config.get('general', 'private-browsing'):
|
||||
return
|
||||
if not url.isValid():
|
||||
log.misc.warning("Ignoring invalid URL being added to history")
|
||||
return
|
||||
|
||||
if atime is None:
|
||||
atime = time.time()
|
||||
entry = Entry(atime, url, title, redirect=redirect)
|
||||
|
||||
@@ -52,7 +52,8 @@ SELECTORS = {
|
||||
Group.focus: '*:focus',
|
||||
Group.inputs: ('input[type=text], input[type=email], input[type=url], '
|
||||
'input[type=tel], input[type=number], '
|
||||
'input[type=password], input[type=search], textarea'),
|
||||
'input[type=password], input[type=search], '
|
||||
'input:not([type]), textarea'),
|
||||
}
|
||||
|
||||
|
||||
@@ -398,26 +399,28 @@ def rect_on_view(elem, *, elem_geometry=None, adjust_zoom=True, no_js=False):
|
||||
text = utils.compact_text(elem.toOuterXml(), 500)
|
||||
log.hints.vdebug("Client rectangles of element '{}': {}".format(text,
|
||||
rects))
|
||||
for i in range(int(rects.get("length", 0))):
|
||||
rect = rects[str(i)]
|
||||
width = rect.get("width", 0)
|
||||
height = rect.get("height", 0)
|
||||
if width > 1 and height > 1:
|
||||
# fix coordinates according to zoom level
|
||||
zoom = elem.webFrame().zoomFactor()
|
||||
if not config.get('ui', 'zoom-text-only') and adjust_zoom:
|
||||
rect["left"] *= zoom
|
||||
rect["top"] *= zoom
|
||||
width *= zoom
|
||||
height *= zoom
|
||||
rect = QRect(rect["left"], rect["top"], width, height)
|
||||
frame = elem.webFrame()
|
||||
while frame is not None:
|
||||
# Translate to parent frames' position
|
||||
# (scroll position is taken care of inside getClientRects)
|
||||
rect.translate(frame.geometry().topLeft())
|
||||
frame = frame.parentFrame()
|
||||
return rect
|
||||
if rects is not None: # pragma: no branch
|
||||
for i in range(int(rects.get("length", 0))):
|
||||
rect = rects[str(i)]
|
||||
width = rect.get("width", 0)
|
||||
height = rect.get("height", 0)
|
||||
if width > 1 and height > 1:
|
||||
# fix coordinates according to zoom level
|
||||
zoom = elem.webFrame().zoomFactor()
|
||||
if not config.get('ui', 'zoom-text-only') and adjust_zoom:
|
||||
rect["left"] *= zoom
|
||||
rect["top"] *= zoom
|
||||
width *= zoom
|
||||
height *= zoom
|
||||
rect = QRect(rect["left"], rect["top"], width, height)
|
||||
frame = elem.webFrame()
|
||||
while frame is not None:
|
||||
# Translate to parent frames' position
|
||||
# (scroll position is taken care of inside
|
||||
# getClientRects)
|
||||
rect.translate(frame.geometry().topLeft())
|
||||
frame = frame.parentFrame()
|
||||
return rect
|
||||
|
||||
# No suitable rects found via JS, try via the QWebElement API
|
||||
if elem_geometry is None:
|
||||
|
||||
@@ -419,9 +419,16 @@ class Command:
|
||||
if isinstance(typ, tuple):
|
||||
raise TypeError("{}: Legacy tuple type annotation!".format(
|
||||
self.name))
|
||||
elif issubclass(typ, typing.Union):
|
||||
elif type(typ) is type(typing.Union): # flake8: disable=E721
|
||||
# this is... slightly evil, I know
|
||||
types = list(typ.__union_params__)
|
||||
# We also can't use isinstance here because typing.Union doesn't
|
||||
# support that.
|
||||
# pylint: disable=no-member,useless-suppression
|
||||
try:
|
||||
types = list(typ.__union_params__)
|
||||
except AttributeError:
|
||||
types = list(typ.__args__)
|
||||
# pylint: enable=no-member,useless-suppression
|
||||
if param.default is not inspect.Parameter.empty:
|
||||
types.append(type(param.default))
|
||||
choices = self.get_arg_info(param).choices
|
||||
|
||||
@@ -119,6 +119,9 @@ class CommandRunner(QObject):
|
||||
Yields:
|
||||
ParseResult tuples.
|
||||
"""
|
||||
if not text.strip():
|
||||
raise cmdexc.NoSuchCommandError("No command given")
|
||||
|
||||
if aliases:
|
||||
text = self._get_alias(text, text)
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ import functools
|
||||
from qutebrowser.completion.models import (miscmodels, urlmodel, configmodel,
|
||||
base)
|
||||
from qutebrowser.utils import objreg, usertypes, log, debug
|
||||
from qutebrowser.config import configdata
|
||||
from qutebrowser.config import configdata, config
|
||||
|
||||
|
||||
_instances = {}
|
||||
@@ -164,6 +164,12 @@ def update(completions):
|
||||
did_run.append(func)
|
||||
|
||||
|
||||
@config.change_filter('aliases', function=True)
|
||||
def _update_aliases():
|
||||
"""Update completions that include command aliases."""
|
||||
update([usertypes.Completion.command])
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize completions. Note this only connects signals."""
|
||||
quickmark_manager = objreg.get('quickmark-manager')
|
||||
@@ -185,3 +191,5 @@ def init():
|
||||
keyconf = objreg.get('key-config')
|
||||
keyconf.changed.connect(
|
||||
functools.partial(update, [usertypes.Completion.command]))
|
||||
|
||||
objreg.get('config').changed.connect(_update_aliases)
|
||||
|
||||
@@ -192,4 +192,4 @@ class UrlCompletionModel(base.BaseCompletionModel):
|
||||
sibling = index.sibling(index.row(), self.TEXT_COLUMN)
|
||||
qtutils.ensure_valid(sibling)
|
||||
name = sibling.data()
|
||||
quickmark_manager.quickmark_del(name)
|
||||
quickmark_manager.delete(name)
|
||||
|
||||
@@ -1458,8 +1458,8 @@ KEY_DATA = collections.OrderedDict([
|
||||
('hint all hover', [';h']),
|
||||
('hint images', [';i']),
|
||||
('hint images tab', [';I']),
|
||||
('hint links fill ":open {hint-url}"', [';o']),
|
||||
('hint links fill ":open -t {hint-url}"', [';O']),
|
||||
('hint links fill :open {hint-url}', [';o']),
|
||||
('hint links fill :open -t {hint-url}', [';O']),
|
||||
('hint links yank', [';y']),
|
||||
('hint links yank-primary', [';Y']),
|
||||
('hint --rapid links tab-bg', [';r']),
|
||||
@@ -1504,7 +1504,7 @@ KEY_DATA = collections.OrderedDict([
|
||||
('save', ['sf']),
|
||||
('set-cmd-text -s :set', ['ss']),
|
||||
('set-cmd-text -s :set -t', ['sl']),
|
||||
('set-cmd-text -s :set keybind', ['sk']),
|
||||
('set-cmd-text -s :bind', ['sk']),
|
||||
('zoom-out', ['-']),
|
||||
('zoom-in', ['+']),
|
||||
('zoom', ['=']),
|
||||
@@ -1649,4 +1649,6 @@ CHANGED_KEY_COMMANDS = [
|
||||
(re.compile(r'^leave-mode$'), r'clear-keychain ;; leave-mode'),
|
||||
|
||||
(re.compile(r'^download-remove --all$'), r'download-clear'),
|
||||
|
||||
(re.compile(r'^hint links fill "([^"]*)"$'), r'hint links fill \1'),
|
||||
]
|
||||
|
||||
@@ -335,6 +335,7 @@ class KeyConfigParser(QObject):
|
||||
|
||||
def _validate_command(self, line):
|
||||
"""Check if a given command is valid."""
|
||||
from qutebrowser.config import config
|
||||
if line == self.UNBOUND_COMMAND:
|
||||
return
|
||||
commands = line.split(';;')
|
||||
@@ -352,7 +353,8 @@ class KeyConfigParser(QObject):
|
||||
line))
|
||||
commands = [c.split(maxsplit=1)[0].strip() for c in commands]
|
||||
for cmd in commands:
|
||||
if cmd not in cmdutils.cmd_dict:
|
||||
aliases = config.section('aliases')
|
||||
if cmd not in cmdutils.cmd_dict and cmd not in aliases:
|
||||
raise KeyConfigError("Invalid command '{}'!".format(cmd))
|
||||
|
||||
def _read_command(self, line):
|
||||
@@ -361,12 +363,12 @@ class KeyConfigParser(QObject):
|
||||
raise KeyConfigError("Got command '{}' without getting a "
|
||||
"section!".format(line))
|
||||
else:
|
||||
self._validate_command(line)
|
||||
for rgx, repl in configdata.CHANGED_KEY_COMMANDS:
|
||||
if rgx.match(line):
|
||||
line = rgx.sub(repl, line)
|
||||
self._mark_config_dirty()
|
||||
break
|
||||
self._validate_command(line)
|
||||
self._cur_command = line
|
||||
|
||||
def _read_keybinding(self, line):
|
||||
|
||||
@@ -433,10 +433,10 @@ def update_settings(section, option):
|
||||
QWebSettings.setIconDatabasePath('')
|
||||
else:
|
||||
QWebSettings.setIconDatabasePath(cache_path)
|
||||
else:
|
||||
try:
|
||||
mapping = MAPPINGS[section][option]
|
||||
except KeyError:
|
||||
return
|
||||
value = config.get(section, option)
|
||||
mapping.set(value)
|
||||
|
||||
try:
|
||||
mapping = MAPPINGS[section][option]
|
||||
except KeyError:
|
||||
return
|
||||
value = config.get(section, option)
|
||||
mapping.set(value)
|
||||
|
||||
@@ -50,6 +50,7 @@ class TabWidget(QTabWidget):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
bar = TabBar(win_id)
|
||||
self.setStyle(TabBarStyle(self.style()))
|
||||
self.setTabBar(bar)
|
||||
bar.tabCloseRequested.connect(self.tabCloseRequested)
|
||||
bar.tabMoved.connect(functools.partial(
|
||||
@@ -652,6 +653,12 @@ class TabBarStyle(QCommonStyle):
|
||||
if sr == QStyle.SE_TabBarTabText:
|
||||
layouts = self._tab_layout(opt)
|
||||
return layouts.text
|
||||
elif sr == QStyle.SE_TabWidgetTabBar:
|
||||
# Need to use super() because we also use super() to render
|
||||
# element in drawControl(); otherwise, we may get bit by
|
||||
# style differences...
|
||||
rct = super().subElementRect(sr, opt, widget)
|
||||
return rct
|
||||
else:
|
||||
return self._style.subElementRect(sr, opt, widget)
|
||||
|
||||
|
||||
@@ -59,9 +59,13 @@ def _missing_str(name, *, windows=None, pip=None, webengine=False):
|
||||
'distributions packages, or install it via pip.'.format(name)]
|
||||
blocks.append('<br />'.join(lines))
|
||||
if webengine:
|
||||
lines = ['Note QtWebEngine is not available for some distributions '
|
||||
'(like Debian/Ubuntu), so you need to start without '
|
||||
'--backend webengine there.']
|
||||
lines = [
|
||||
'Note QtWebEngine is not available for some distributions '
|
||||
'(like Debian/Ubuntu), so you need to start without '
|
||||
'--backend webengine there.',
|
||||
'QtWebEngine is currently unsupported with the OS X .app, see '
|
||||
'https://github.com/The-Compiler/qutebrowser/issues/1692',
|
||||
]
|
||||
else:
|
||||
lines = ['<b>If you installed a qutebrowser package for your '
|
||||
'distribution, please report this as a bug.</b>']
|
||||
@@ -166,8 +170,11 @@ def fix_harfbuzz(args):
|
||||
from qutebrowser.utils import log
|
||||
from PyQt5.QtCore import qVersion
|
||||
if 'PyQt5.QtWidgets' in sys.modules:
|
||||
log.init.warning("Harfbuzz fix attempted but QtWidgets is already "
|
||||
"imported!")
|
||||
msg = "Harfbuzz fix attempted but QtWidgets is already imported!"
|
||||
if getattr(sys, 'frozen', False):
|
||||
log.init.debug(msg)
|
||||
else:
|
||||
log.init.warning(msg)
|
||||
if sys.platform.startswith('linux') and args.harfbuzz == 'auto':
|
||||
if qVersion() == '5.3.0':
|
||||
log.init.debug("Using new harfbuzz engine (auto)")
|
||||
|
||||
@@ -35,8 +35,8 @@ class ExternalEditor(QObject):
|
||||
|
||||
Attributes:
|
||||
_text: The current text before the editor is opened.
|
||||
_oshandle: The OS level handle to the tmpfile.
|
||||
_filehandle: The file handle to the tmpfile.
|
||||
_file: The file handle as tempfile.NamedTemporaryFile. Note that this
|
||||
handle will be closed after the initial file has been created.
|
||||
_proc: The GUIProcess of the editor.
|
||||
_win_id: The window ID the ExternalEditor is associated with.
|
||||
"""
|
||||
@@ -46,20 +46,18 @@ class ExternalEditor(QObject):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._text = None
|
||||
self._oshandle = None
|
||||
self._filename = None
|
||||
self._file = None
|
||||
self._proc = None
|
||||
self._win_id = win_id
|
||||
|
||||
def _cleanup(self):
|
||||
"""Clean up temporary files after the editor closed."""
|
||||
if self._oshandle is None or self._filename is None:
|
||||
if self._file is None:
|
||||
# Could not create initial file.
|
||||
return
|
||||
try:
|
||||
os.close(self._oshandle)
|
||||
if self._proc.exit_status() != QProcess.CrashExit:
|
||||
os.remove(self._filename)
|
||||
os.remove(self._file.name)
|
||||
except OSError as e:
|
||||
# NOTE: Do not replace this with "raise CommandError" as it's
|
||||
# executed async.
|
||||
@@ -82,7 +80,7 @@ class ExternalEditor(QObject):
|
||||
return
|
||||
encoding = config.get('general', 'editor-encoding')
|
||||
try:
|
||||
with open(self._filename, 'r', encoding=encoding) as f:
|
||||
with open(self._file.name, 'r', encoding=encoding) as f:
|
||||
text = f.read()
|
||||
except OSError as e:
|
||||
# NOTE: Do not replace this with "raise CommandError" as it's
|
||||
@@ -108,13 +106,18 @@ class ExternalEditor(QObject):
|
||||
if self._text is not None:
|
||||
raise ValueError("Already editing a file!")
|
||||
self._text = text
|
||||
encoding = config.get('general', 'editor-encoding')
|
||||
try:
|
||||
self._oshandle, self._filename = tempfile.mkstemp(
|
||||
text=True, prefix='qutebrowser-editor-')
|
||||
if text:
|
||||
encoding = config.get('general', 'editor-encoding')
|
||||
with open(self._filename, 'w', encoding=encoding) as f:
|
||||
f.write(text)
|
||||
# Close while the external process is running, as otherwise systems
|
||||
# with exclusive write access (e.g. Windows) may fail to update
|
||||
# the file from the external editor, see
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/1767
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode='w', prefix='qutebrowser-editor-', encoding=encoding,
|
||||
delete=False) as fobj:
|
||||
if text:
|
||||
fobj.write(text)
|
||||
self._file = fobj
|
||||
except OSError as e:
|
||||
message.error(self._win_id, "Failed to create initial file: "
|
||||
"{}".format(e))
|
||||
@@ -125,6 +128,6 @@ class ExternalEditor(QObject):
|
||||
self._proc.error.connect(self.on_proc_error)
|
||||
editor = config.get('general', 'editor')
|
||||
executable = editor[0]
|
||||
args = [arg.replace('{}', self._filename) for arg in editor[1:]]
|
||||
args = [arg.replace('{}', self._file.name) for arg in editor[1:]]
|
||||
log.procs.debug("Calling \"{}\" with args {}".format(executable, args))
|
||||
self._proc.start(executable, args)
|
||||
|
||||
@@ -168,7 +168,7 @@ def _from_args(typ, args):
|
||||
suffix = basedir_suffix[typ]
|
||||
except KeyError: # pragma: no cover
|
||||
return (False, None)
|
||||
return (True, os.path.join(basedir, suffix))
|
||||
return (True, os.path.abspath(os.path.join(basedir, suffix)))
|
||||
|
||||
try:
|
||||
argname = typ_to_argparse_arg[typ]
|
||||
|
||||
@@ -24,7 +24,7 @@ SOURCE_DIR ?= .
|
||||
SOURCE_FILES ?= dist/qutebrowser.app COPYING
|
||||
|
||||
TEMPLATE_DMG ?= template.dmg
|
||||
TEMPLATE_SIZE ?= 120m
|
||||
TEMPLATE_SIZE ?= 300m
|
||||
|
||||
################################################################################
|
||||
# DMG building. No editing should be needed beyond this point.
|
||||
@@ -37,17 +37,12 @@ WC_DIR=wc
|
||||
.PHONY: all
|
||||
all: $(MASTER_DMG)
|
||||
|
||||
$(TEMPLATE_DMG): $(TEMPLATE_DMG).bz2
|
||||
bunzip2 -k $<
|
||||
|
||||
$(TEMPLATE_DMG).bz2:
|
||||
$(TEMPLATE_DMG):
|
||||
@echo
|
||||
@echo --------------------- Generating empty template --------------------
|
||||
mkdir template
|
||||
hdiutil create -fs HFSX -layout SPUD -size $(TEMPLATE_SIZE) "$(TEMPLATE_DMG)" -srcfolder template -format UDRW -volname "$(NAME)" -quiet
|
||||
rmdir template
|
||||
bzip2 "$(TEMPLATE_DMG)"
|
||||
@echo
|
||||
|
||||
$(WC_DMG): $(TEMPLATE_DMG)
|
||||
cp $< $@
|
||||
|
||||
@@ -28,6 +28,7 @@ import shutil
|
||||
import subprocess
|
||||
import argparse
|
||||
import tarfile
|
||||
import tempfile
|
||||
import collections
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir,
|
||||
@@ -50,7 +51,7 @@ def call_script(name, *args, python=sys.executable):
|
||||
subprocess.check_call([python, path] + list(args))
|
||||
|
||||
|
||||
def call_tox(toxenv, *args, python=sys.executable):
|
||||
def call_tox(toxenv, *args, python=os.path.dirname(sys.executable)):
|
||||
"""Call tox.
|
||||
|
||||
Args:
|
||||
@@ -94,7 +95,7 @@ def build_osx():
|
||||
utils.print_title("Updating 3rdparty content")
|
||||
update_3rdparty.update_pdfjs()
|
||||
utils.print_title("Building .app via pyinstaller")
|
||||
call_tox('pyinstaller')
|
||||
call_tox('pyinstaller', '-r')
|
||||
utils.print_title("Building .dmg")
|
||||
subprocess.check_call(['make', '-f', 'scripts/dev/Makefile-dmg'])
|
||||
utils.print_title("Cleaning up...")
|
||||
@@ -103,6 +104,22 @@ def build_osx():
|
||||
for d in ['dist', 'build']:
|
||||
shutil.rmtree(d)
|
||||
|
||||
dmg_name = 'qutebrowser-{}.dmg'.format(qutebrowser.__version__)
|
||||
os.rename('qutebrowser.dmg', dmg_name)
|
||||
|
||||
utils.print_title("Running smoke test")
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
subprocess.check_call(['hdiutil', 'attach', dmg_name,
|
||||
'-mountpoint', tmpdir])
|
||||
try:
|
||||
binary = os.path.join(tmpdir, 'qutebrowser.app', 'Contents',
|
||||
'MacOS', 'qutebrowser')
|
||||
smoke_test(binary)
|
||||
finally:
|
||||
subprocess.check_call(['hdiutil', 'detach', tmpdir])
|
||||
|
||||
return [(dmg_name, 'application/x-apple-diskimage', 'OS X .dmg')]
|
||||
|
||||
|
||||
def build_windows():
|
||||
"""Build windows executables/setups."""
|
||||
@@ -116,6 +133,10 @@ def build_windows():
|
||||
python_x86 = r'C:\Python{}_x32'.format(ver)
|
||||
python_x64 = r'C:\Python{}'.format(ver)
|
||||
|
||||
artifacts = []
|
||||
|
||||
utils.print_title("Rebuilding tox environment")
|
||||
call_tox('cxfreeze-windows', '-r', '--notest')
|
||||
utils.print_title("Running 32bit freeze.py build_exe")
|
||||
call_tox('cxfreeze-windows', 'build_exe', python=python_x86)
|
||||
utils.print_title("Running 32bit freeze.py bdist_msi")
|
||||
@@ -125,15 +146,21 @@ def build_windows():
|
||||
utils.print_title("Running 64bit freeze.py bdist_msi")
|
||||
call_tox('cxfreeze-windows', 'bdist_msi', python=python_x64)
|
||||
|
||||
name_32 = 'qutebrowser-{}-win32.msi'.format(qutebrowser.__version__)
|
||||
name_64 = 'qutebrowser-{}-amd64.msi'.format(qutebrowser.__version__)
|
||||
|
||||
artifacts += [
|
||||
(os.path.join('dist', name_32), 'application/x-msi',
|
||||
'Windows 32bit installer'),
|
||||
(os.path.join('dist', name_64), 'application/x-msi',
|
||||
'Windows 64bit installer'),
|
||||
]
|
||||
|
||||
utils.print_title("Running 32bit smoke test")
|
||||
smoke_test('build/exe.win32-{}/qutebrowser.exe'.format(dotver))
|
||||
utils.print_title("Running 64bit smoke test")
|
||||
smoke_test('build/exe.win-amd64-{}/qutebrowser.exe'.format(dotver))
|
||||
|
||||
destdir = os.path.join('dist', 'zip')
|
||||
_maybe_remove(destdir)
|
||||
os.makedirs(destdir)
|
||||
|
||||
basedirname = 'qutebrowser-{}'.format(qutebrowser.__version__)
|
||||
builddir = os.path.join('build', basedirname)
|
||||
_maybe_remove(builddir)
|
||||
@@ -143,26 +170,24 @@ def build_windows():
|
||||
qutebrowser.__version__)
|
||||
origin = os.path.join('build', 'exe.win32-{}'.format(dotver))
|
||||
os.rename(origin, builddir)
|
||||
shutil.make_archive(os.path.join(destdir, name), 'zip', 'build',
|
||||
basedirname)
|
||||
shutil.make_archive(name, 'zip', 'build', basedirname)
|
||||
shutil.rmtree(builddir)
|
||||
artifacts.append(('{}.zip'.format(name),
|
||||
'application/zip',
|
||||
'Windows 32bit standalone'))
|
||||
|
||||
utils.print_title("Zipping 64bit standalone...")
|
||||
name = 'qutebrowser-{}-windows-standalone-amd64'.format(
|
||||
qutebrowser.__version__)
|
||||
origin = os.path.join('build', 'exe.win-amd64-{}'.format(dotver))
|
||||
os.rename(origin, builddir)
|
||||
shutil.make_archive(os.path.join(destdir, name), 'zip', 'build',
|
||||
basedirname)
|
||||
shutil.make_archive(name, 'zip', 'build', basedirname)
|
||||
shutil.rmtree(builddir)
|
||||
artifacts.append(('{}.zip'.format(name),
|
||||
'application/zip',
|
||||
'Windows 64bit standalone'))
|
||||
|
||||
utils.print_title("Creating final zip...")
|
||||
shutil.move(os.path.join('dist', 'qutebrowser-{}-amd64.msi'.format(
|
||||
qutebrowser.__version__)), os.path.join('dist', 'zip'))
|
||||
shutil.move(os.path.join('dist', 'qutebrowser-{}-win32.msi'.format(
|
||||
qutebrowser.__version__)), os.path.join('dist', 'zip'))
|
||||
shutil.make_archive('qutebrowser-{}-windows'.format(
|
||||
qutebrowser.__version__), 'zip', destdir)
|
||||
return artifacts
|
||||
|
||||
|
||||
def build_sdist():
|
||||
@@ -196,6 +221,51 @@ def build_sdist():
|
||||
utils.print_subtitle(ext)
|
||||
print('\n'.join(files))
|
||||
|
||||
filename = 'qutebrowser-{}.tar.gz'.format(qutebrowser.__version__)
|
||||
artifacts = [
|
||||
(os.path.join('dist', filename), 'application/gzip', 'Source release'),
|
||||
(os.path.join('dist', filename + '.asc'), 'application/pgp-signature',
|
||||
'Source release - PGP signature'),
|
||||
]
|
||||
|
||||
return artifacts
|
||||
|
||||
|
||||
def github_upload(artifacts, tag):
|
||||
"""Upload the given artifacts to GitHub.
|
||||
|
||||
Args:
|
||||
artifacts: A list of (filename, mimetype, description) tuples
|
||||
tag: The name of the release tag
|
||||
"""
|
||||
import github3
|
||||
utils.print_title("Uploading to github...")
|
||||
|
||||
token_file = os.path.join(os.path.expanduser('~'), '.gh_token')
|
||||
with open(token_file, encoding='ascii') as f:
|
||||
token = f.read().strip()
|
||||
gh = github3.login(token=token)
|
||||
repo = gh.repository('The-Compiler', 'qutebrowser')
|
||||
|
||||
release = None # to satisfy pylint
|
||||
for release in repo.iter_releases():
|
||||
if release.tag_name == tag:
|
||||
break
|
||||
else:
|
||||
raise Exception("No release found for {!r}!".format(tag))
|
||||
|
||||
for filename, mimetype, description in artifacts:
|
||||
with open(filename, 'rb') as f:
|
||||
basename = os.path.basename(filename)
|
||||
asset = release.upload_asset(mimetype, basename, f)
|
||||
asset.edit(basename, description)
|
||||
|
||||
|
||||
def pypi_upload(artifacts):
|
||||
"""Upload the given artifacts to PyPI using twine."""
|
||||
filenames = [a[0] for a in artifacts]
|
||||
subprocess.check_call(['twine', 'upload'] + filenames)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
@@ -203,9 +273,13 @@ def main():
|
||||
"asciidoc.py. If not given, it's searched in PATH.",
|
||||
nargs=2, required=False,
|
||||
metavar=('PYTHON', 'ASCIIDOC'))
|
||||
parser.add_argument('--upload', help="Tag to upload the release for",
|
||||
nargs=1, required=False, metavar='TAG')
|
||||
args = parser.parse_args()
|
||||
utils.change_cwd()
|
||||
|
||||
upload_to_pypi = False
|
||||
|
||||
if os.name == 'nt':
|
||||
if sys.maxsize > 2**32:
|
||||
# WORKAROUND
|
||||
@@ -216,12 +290,20 @@ def main():
|
||||
print("https://github.com/pypa/virtualenv/issues/774")
|
||||
sys.exit(1)
|
||||
run_asciidoc2html(args)
|
||||
build_windows()
|
||||
artifacts = build_windows()
|
||||
elif sys.platform == 'darwin':
|
||||
run_asciidoc2html(args)
|
||||
build_osx()
|
||||
artifacts = build_osx()
|
||||
else:
|
||||
build_sdist()
|
||||
artifacts = build_sdist()
|
||||
upload_to_pypi = True
|
||||
|
||||
if args.upload is not None:
|
||||
utils.print_title("Press enter to release...")
|
||||
input()
|
||||
github_upload(artifacts, args.upload[0])
|
||||
if upload_to_pypi:
|
||||
pypi_upload(artifacts)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -49,6 +49,16 @@ def get_egl_path():
|
||||
r'PyQt5\libEGL.dll')
|
||||
|
||||
|
||||
def get_plugin_folders():
|
||||
"""Get the plugin folders to copy to the output."""
|
||||
if not sys.platform.startswith('win'):
|
||||
return []
|
||||
plugin_dir = os.path.join(distutils.sysconfig.get_python_lib(),
|
||||
'PyQt5', 'plugins')
|
||||
folders = ['audio', 'iconengines', 'mediaservice', 'printsupport']
|
||||
return [os.path.join(plugin_dir, folder) for folder in folders]
|
||||
|
||||
|
||||
def get_build_exe_options(skip_html=False):
|
||||
"""Get the options passed as build_exe_options to cx_Freeze.
|
||||
|
||||
@@ -81,6 +91,8 @@ def get_build_exe_options(skip_html=False):
|
||||
if egl_path is not None:
|
||||
include_files.append((egl_path, 'libEGL.dll'))
|
||||
|
||||
include_files += get_plugin_folders()
|
||||
|
||||
return {
|
||||
'include_files': include_files,
|
||||
'include_msvcr': True,
|
||||
@@ -88,7 +100,8 @@ def get_build_exe_options(skip_html=False):
|
||||
'excludes': ['tkinter'],
|
||||
'packages': ['pygments', 'pkg_resources._vendor.packaging',
|
||||
'pkg_resources._vendor.pyparsing',
|
||||
'pkg_resources._vendor.six'],
|
||||
'pkg_resources._vendor.six',
|
||||
'pkg_resources._vendor.appdirs'],
|
||||
}
|
||||
|
||||
|
||||
|
||||
11
tests/end2end/data/hints/input.html
Normal file
11
tests/end2end/data/hints/input.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Simple input</title>
|
||||
</head>
|
||||
<body>
|
||||
<form><input></input></form>
|
||||
</body>
|
||||
</html>
|
||||
19
tests/end2end/data/javascript/localstorage.html
Normal file
19
tests/end2end/data/javascript/localstorage.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript">
|
||||
function check_storage() {
|
||||
var elem = document.getElementById("status");
|
||||
try {
|
||||
localStorage.qute_test = "foo";
|
||||
elem.innerHTML = "working";
|
||||
} catch (e) {
|
||||
elem.innerHTML = "not working";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="check_storage()">
|
||||
<p>Local storage status: <span id="status">checking...</span></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -119,6 +119,15 @@ Feature: Using hints
|
||||
And I run :follow-hint a
|
||||
Then the error "Invalid link clicked - *" should be shown
|
||||
|
||||
Scenario: Hinting inputs without type
|
||||
When I open data/hints/input.html
|
||||
And I run :hint inputs
|
||||
And I run :follow-hint a
|
||||
And I wait for "Entering mode KeyMode.insert (reason: click)" in the log
|
||||
And I run :leave-mode
|
||||
# The actual check is already done above
|
||||
Then no crash should happen
|
||||
|
||||
### iframes
|
||||
|
||||
Scenario: Using :follow-hint inside an iframe
|
||||
|
||||
@@ -51,3 +51,12 @@ Feature: Page history
|
||||
When I open data/title.html
|
||||
And I run :history-clear
|
||||
Then the history file should be empty
|
||||
|
||||
## Bugs
|
||||
|
||||
Scenario: Opening a valid URL which turns out invalid
|
||||
When I set general -> auto-search to true
|
||||
And I run :open http://foo%40bar@baz
|
||||
Then "QFSFileEngine::open: No file name specified" should be logged
|
||||
And "Error while loading : Host not found" should be logged
|
||||
And "Ignoring invalid URL being added to history" should be logged
|
||||
|
||||
@@ -61,6 +61,18 @@ Feature: Keyboard input
|
||||
And I run :bind <ctrl-test23>
|
||||
Then the message "<ctrl-test23> is bound to 'message-info bar' in normal mode" should be shown
|
||||
|
||||
Scenario: Binding to an alias
|
||||
When I run :set aliases 'mib' 'message-info baz'
|
||||
And I run :bind test25 mib
|
||||
And I press the keys "test25"
|
||||
Then the message "baz" should be shown
|
||||
|
||||
Scenario: Printing a bound alias
|
||||
When I run :set aliases 'mib' 'message-info baz'
|
||||
And I run :bind <test26> mib
|
||||
And I run :bind <test26>
|
||||
Then the message "<test26> is bound to 'mib' in normal mode" should be shown
|
||||
|
||||
# :unbind
|
||||
|
||||
Scenario: Binding and unbinding a keychain
|
||||
|
||||
@@ -471,6 +471,13 @@ Feature: Various utility commands.
|
||||
And I open cookies in a new tab
|
||||
Then the cookie qute-test should be set to 42
|
||||
|
||||
## https://github.com/The-Compiler/qutebrowser/issues/1742
|
||||
|
||||
Scenario: Private browsing is activated in QtWebKit without restart
|
||||
When I set general -> private-browsing to true
|
||||
And I open data/javascript/localstorage.html
|
||||
Then the page should contain the plaintext "Local storage status: not working"
|
||||
|
||||
Scenario: :repeat-command
|
||||
Given I open data/scroll.html
|
||||
And I run :tab-only
|
||||
|
||||
@@ -562,6 +562,12 @@ Feature: Tab management
|
||||
- history:
|
||||
- url: http://localhost:*/data/numbers/2.txt
|
||||
|
||||
Scenario: Detach tab from window with only one tab
|
||||
Given I have a fresh instance
|
||||
When I open data/hello.txt
|
||||
And I run :tab-detach
|
||||
Then the error "Cannot detach one tab." should be shown
|
||||
|
||||
# :undo
|
||||
|
||||
Scenario: Undo without any closed tabs
|
||||
|
||||
@@ -162,7 +162,7 @@ class SelectionAndFilterTests:
|
||||
('<textarea />', [webelem.Group.all, webelem.Group.inputs]),
|
||||
('<select />', [webelem.Group.all]),
|
||||
|
||||
('<input />', [webelem.Group.all]),
|
||||
('<input />', [webelem.Group.all, webelem.Group.inputs]),
|
||||
('<input type="hidden" />', []),
|
||||
('<input type="text" />', [webelem.Group.inputs, webelem.Group.all]),
|
||||
('<input type="email" />', [webelem.Group.inputs, webelem.Group.all]),
|
||||
|
||||
@@ -53,6 +53,17 @@ class TestCommandRunner:
|
||||
with pytest.raises(cmdexc.NoSuchCommandError):
|
||||
list(cr.parse_all("alias_name"))
|
||||
|
||||
@pytest.mark.parametrize('command', ['', ' '])
|
||||
def test_parse_empty_with_alias(self, command):
|
||||
"""An empty command should not crash.
|
||||
|
||||
See https://github.com/The-Compiler/qutebrowser/issues/1690
|
||||
and https://github.com/The-Compiler/qutebrowser/issues/1773
|
||||
"""
|
||||
cr = runners.CommandRunner(0)
|
||||
with pytest.raises(cmdexc.NoSuchCommandError):
|
||||
list(cr.parse_all(command))
|
||||
|
||||
def test_parse_with_count(self):
|
||||
"""Test parsing of commands with a count."""
|
||||
cr = runners.CommandRunner(0)
|
||||
|
||||
@@ -214,7 +214,7 @@ class TestKeyConfigParser:
|
||||
|
||||
"""Test config.parsers.keyconf.KeyConfigParser."""
|
||||
|
||||
def test_cmd_binding(self, cmdline_test):
|
||||
def test_cmd_binding(self, cmdline_test, config_stub):
|
||||
"""Test various command bindings.
|
||||
|
||||
See https://github.com/The-Compiler/qutebrowser/issues/615
|
||||
@@ -222,6 +222,7 @@ class TestKeyConfigParser:
|
||||
Args:
|
||||
cmdline_test: A pytest fixture which provides testcases.
|
||||
"""
|
||||
config_stub.data = {'aliases': []}
|
||||
kcp = keyconf.KeyConfigParser(None, None)
|
||||
kcp._cur_section = 'normal'
|
||||
if cmdline_test.valid:
|
||||
@@ -281,6 +282,11 @@ class TestKeyConfigParser:
|
||||
('leave-mode ;; foo', None),
|
||||
|
||||
('download-remove --all', 'download-clear'),
|
||||
|
||||
('hint links fill ":open {hint-url}"',
|
||||
'hint links fill :open {hint-url}'),
|
||||
('hint links fill ":open -t {hint-url}"',
|
||||
'hint links fill :open -t {hint-url}'),
|
||||
]
|
||||
)
|
||||
def test_migrations(self, old, new_expected):
|
||||
@@ -329,7 +335,7 @@ class TestDefaultConfig:
|
||||
If it did change, place a new qutebrowser-vx.y.z.conf in old_configs
|
||||
and then increment the version.
|
||||
"""
|
||||
assert qutebrowser.__version__ == '0.8.0'
|
||||
assert qutebrowser.__version__ == '0.8.4'
|
||||
|
||||
@pytest.mark.parametrize('filename',
|
||||
os.listdir(os.path.join(os.path.dirname(__file__), 'old_configs')),
|
||||
|
||||
@@ -69,14 +69,14 @@ class TestArg:
|
||||
config_stub.data['general']['editor'] = ['bin', 'foo', '{}', 'bar']
|
||||
editor.edit("")
|
||||
editor._proc._proc.start.assert_called_with(
|
||||
"bin", ["foo", editor._filename, "bar"])
|
||||
"bin", ["foo", editor._file.name, "bar"])
|
||||
|
||||
def test_placeholder_inline(self, config_stub, editor):
|
||||
"""Test starting editor with placeholder arg inside of another arg."""
|
||||
config_stub.data['general']['editor'] = ['bin', 'foo{}', 'bar']
|
||||
editor.edit("")
|
||||
editor._proc._proc.start.assert_called_with(
|
||||
"bin", ["foo" + editor._filename, "bar"])
|
||||
"bin", ["foo" + editor._file.name, "bar"])
|
||||
|
||||
|
||||
class TestFileHandling:
|
||||
@@ -86,7 +86,7 @@ class TestFileHandling:
|
||||
def test_ok(self, editor):
|
||||
"""Test file handling when closing with an exit status == 0."""
|
||||
editor.edit("")
|
||||
filename = editor._filename
|
||||
filename = editor._file.name
|
||||
assert os.path.exists(filename)
|
||||
assert os.path.basename(filename).startswith('qutebrowser-editor-')
|
||||
editor._proc.finished.emit(0, QProcess.NormalExit)
|
||||
@@ -95,7 +95,7 @@ class TestFileHandling:
|
||||
def test_error(self, editor):
|
||||
"""Test file handling when closing with an exit status != 0."""
|
||||
editor.edit("")
|
||||
filename = editor._filename
|
||||
filename = editor._file.name
|
||||
assert os.path.exists(filename)
|
||||
|
||||
editor._proc._proc.exitStatus = mock.Mock(
|
||||
@@ -109,7 +109,7 @@ class TestFileHandling:
|
||||
def test_crash(self, editor):
|
||||
"""Test file handling when closing with a crash."""
|
||||
editor.edit("")
|
||||
filename = editor._filename
|
||||
filename = editor._file.name
|
||||
assert os.path.exists(filename)
|
||||
|
||||
editor._proc._proc.exitStatus = mock.Mock(
|
||||
@@ -125,7 +125,7 @@ class TestFileHandling:
|
||||
def test_unreadable(self, message_mock, editor):
|
||||
"""Test file handling when closing with an unreadable file."""
|
||||
editor.edit("")
|
||||
filename = editor._filename
|
||||
filename = editor._file.name
|
||||
assert os.path.exists(filename)
|
||||
os.chmod(filename, 0o077)
|
||||
editor._proc.finished.emit(0, QProcess.NormalExit)
|
||||
@@ -160,10 +160,10 @@ def test_modify(editor, initial_text, edited_text):
|
||||
"""Test if inputs get modified correctly."""
|
||||
editor.edit(initial_text)
|
||||
|
||||
with open(editor._filename, 'r', encoding='utf-8') as f:
|
||||
with open(editor._file.name, 'r', encoding='utf-8') as f:
|
||||
assert f.read() == initial_text
|
||||
|
||||
with open(editor._filename, 'w', encoding='utf-8') as f:
|
||||
with open(editor._file.name, 'w', encoding='utf-8') as f:
|
||||
f.write(edited_text)
|
||||
|
||||
editor._proc.finished.emit(0, QProcess.NormalExit)
|
||||
|
||||
@@ -532,32 +532,33 @@ class TestSendToRunningInstance:
|
||||
@pytest.mark.linux(reason="Causes random trouble on Windows and OS X")
|
||||
def test_normal(self, qtbot, tmpdir, ipc_server, mocker, has_cwd):
|
||||
ipc_server.listen()
|
||||
raw_spy = QSignalSpy(ipc_server.got_raw)
|
||||
|
||||
with qtbot.assertNotEmitted(ipc_server.got_invalid_data):
|
||||
with qtbot.waitSignal(ipc_server.got_args,
|
||||
timeout=5000) as blocker:
|
||||
with tmpdir.as_cwd():
|
||||
if not has_cwd:
|
||||
m = mocker.patch('qutebrowser.misc.ipc.os')
|
||||
m.getcwd.side_effect = OSError
|
||||
sent = ipc.send_to_running_instance('qute-test', ['foo'],
|
||||
None)
|
||||
with qtbot.waitSignal(ipc_server.got_raw,
|
||||
timeout=5000) as raw_blocker:
|
||||
with tmpdir.as_cwd():
|
||||
if not has_cwd:
|
||||
m = mocker.patch('qutebrowser.misc.ipc.os')
|
||||
m.getcwd.side_effect = OSError
|
||||
sent = ipc.send_to_running_instance(
|
||||
'qute-test', ['foo'], None)
|
||||
|
||||
assert sent
|
||||
assert sent
|
||||
|
||||
expected_cwd = str(tmpdir) if has_cwd else ''
|
||||
|
||||
assert blocker.args == [['foo'], '', expected_cwd]
|
||||
|
||||
assert len(raw_spy) == 1
|
||||
assert len(raw_spy[0]) == 1
|
||||
raw_expected = {'args': ['foo'], 'target_arg': None,
|
||||
'version': qutebrowser.__version__,
|
||||
'protocol_version': ipc.PROTOCOL_VERSION}
|
||||
if has_cwd:
|
||||
raw_expected['cwd'] = str(tmpdir)
|
||||
parsed = json.loads(raw_spy[0][0].decode('utf-8'))
|
||||
|
||||
assert len(raw_blocker.args) == 1
|
||||
parsed = json.loads(raw_blocker.args[0].decode('utf-8'))
|
||||
assert parsed == raw_expected
|
||||
|
||||
def test_socket_error(self):
|
||||
|
||||
@@ -225,6 +225,15 @@ class TestArguments:
|
||||
func = getattr(standarddir, typ)
|
||||
assert func() == expected
|
||||
|
||||
def test_basedir_relative(self, tmpdir):
|
||||
"""Test --basedir with a relative path."""
|
||||
basedir = (tmpdir / 'basedir')
|
||||
basedir.ensure(dir=True)
|
||||
with tmpdir.as_cwd():
|
||||
args = types.SimpleNamespace(basedir='basedir')
|
||||
standarddir.init(args)
|
||||
assert standarddir.config() == str(basedir / 'config')
|
||||
|
||||
|
||||
class TestInitCacheDirTag:
|
||||
|
||||
@@ -311,6 +320,7 @@ class TestCreatingDir:
|
||||
m.sep = os.sep
|
||||
m.path.join = os.path.join
|
||||
m.path.exists.return_value = False
|
||||
m.path.abspath = lambda x: x
|
||||
|
||||
args = types.SimpleNamespace(basedir=str(tmpdir))
|
||||
standarddir.init(args)
|
||||
|
||||
Reference in New Issue
Block a user