diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 218a1d105..b38db88fa 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -99,6 +99,8 @@ Fixed - QtWebEngine: `:follow-selected` should now work in more cases with Qt > 5.10. - QtWebEngine: Incremental search now flickers less and doesn't move to the second result when pressing Enter. +- QtWebEngine: Keys like `Ctrl-V` or `Shift-Insert` are now correctly + handled/filtered with Qt 5.10. - QtWebKit: `:view-source` now displays a valid URL. - URLs containing ampersands and other special chars are now shown correctly when filtering them in the completion. diff --git a/qutebrowser/app.py b/qutebrowser/app.py index c755c2f41..2794d36e2 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -882,6 +882,7 @@ class EventFilter(QObject): self._handlers = { QEvent.KeyPress: self._handle_key_event, QEvent.KeyRelease: self._handle_key_event, + QEvent.ShortcutOverride: self._handle_key_event, } def _handle_key_event(self, event): diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index 901e96b55..beb31a52c 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -114,7 +114,7 @@ class BaseKeyParser(QObject): return (result, None) - def handle(self, e): + def handle(self, e, *, dry_run=False): """Handle a new keypress. Separate the keypress into count/command, then check if it matches @@ -123,13 +123,16 @@ class BaseKeyParser(QObject): Args: e: the KeyPressEvent from Qt. + dry_run: Don't actually execute anything, only check whether there + would be a match. Return: A QKeySequence match. """ key = e.key() txt = str(keyutils.KeyInfo.from_event(e)) - self._debug_log("Got key: 0x{:x} / text: '{}'".format(key, txt)) + self._debug_log("Got key: 0x{:x} / text: '{}' / dry_run {}".format( + key, txt, dry_run)) if keyutils.is_modifier_key(key): self._debug_log("Ignoring, only modifier") @@ -138,33 +141,39 @@ class BaseKeyParser(QObject): if (txt.isdigit() and self._supports_count and not (not self._count and txt == '0')): assert len(txt) == 1, txt - self._count += txt + if not dry_run: + self._count += txt return QKeySequence.ExactMatch - self._sequence = self._sequence.append_event(e) - match, binding = self._match_key(self._sequence) + sequence = self._sequence.append_event(e) + match, binding = self._match_key(sequence) if match == QKeySequence.NoMatch: mappings = config.val.bindings.key_mappings - mapped = mappings.get(self._sequence, None) + mapped = mappings.get(sequence, None) if mapped is not None: self._debug_log("Mapped {} -> {}".format( - self._sequence, mapped)) + sequence, mapped)) match, binding = self._match_key(mapped) - self._sequence = mapped + sequence = mapped + + if dry_run: + return match + + self._sequence = sequence if match == QKeySequence.ExactMatch: self._debug_log("Definitive match for '{}'.".format( - self._sequence)) + sequence)) count = int(self._count) if self._count else None self.clear_keystring() self.execute(binding, count) elif match == QKeySequence.PartialMatch: self._debug_log("No match for '{}' (added {})".format( - self._sequence, txt)) - self.keystring_updated.emit(self._count + str(self._sequence)) + sequence, txt)) + self.keystring_updated.emit(self._count + str(sequence)) elif match == QKeySequence.NoMatch: self._debug_log("Giving up with '{}', no matches".format( - self._sequence)) + sequence)) self.clear_keystring() else: raise utils.Unreachable("Invalid match value {!r}".format(match)) diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 94d76832d..8d2830cc1 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -143,11 +143,12 @@ class ModeManager(QObject): def __repr__(self): return utils.get_repr(self, mode=self.mode) - def _eventFilter_keypress(self, event): + def _eventFilter_keypress(self, event, *, dry_run=False): """Handle filtering of KeyPress events. Args: event: The KeyPress to examine. + dry_run: Don't actually handle the key, only filter it. Return: True if event should be filtered, False otherwise. @@ -157,7 +158,7 @@ class ModeManager(QObject): if curmode != usertypes.KeyMode.insert: log.modes.debug("got keypress in mode {} - delegating to " "{}".format(curmode, utils.qualname(parser))) - match = parser.handle(event) + match = parser.handle(event, dry_run=dry_run) is_non_alnum = ( event.modifiers() not in [Qt.NoModifier, Qt.ShiftModifier] or @@ -173,17 +174,17 @@ class ModeManager(QObject): else: filter_this = True - if not filter_this: + if not filter_this and not dry_run: self._releaseevents_to_pass.add(KeyEvent.from_event(event)) if curmode != usertypes.KeyMode.insert: focus_widget = QApplication.instance().focusWidget() log.modes.debug("match: {}, forward_unbound_keys: {}, " - "passthrough: {}, is_non_alnum: {} --> " - "filter: {} (focused: {!r})".format( + "passthrough: {}, is_non_alnum: {}, dry_run: {} " + "--> filter: {} (focused: {!r})".format( match, forward_unbound_keys, - parser.passthrough, is_non_alnum, filter_this, - focus_widget)) + parser.passthrough, is_non_alnum, dry_run, + filter_this, focus_widget)) return filter_this def _eventFilter_keyrelease(self, event): @@ -320,6 +321,8 @@ class ModeManager(QObject): handlers = { QEvent.KeyPress: self._eventFilter_keypress, QEvent.KeyRelease: self._eventFilter_keyrelease, + QEvent.ShortcutOverride: + functools.partial(self._eventFilter_keypress, dry_run=True), } handler = handlers[event.type()] return handler(event) diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 169232e01..ce4cb71fa 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -59,11 +59,13 @@ class NormalKeyParser(keyparser.CommandKeyParser): def __repr__(self): return utils.get_repr(self) - def handle(self, e): + def handle(self, e, *, dry_run=False): """Override to abort if the key is a startchar. Args: e: the KeyPressEvent from Qt. + dry_run: Don't actually execute anything, only check whether there + would be a match. Return: A self.Match member. @@ -74,9 +76,9 @@ class NormalKeyParser(keyparser.CommandKeyParser): "currently inhibited.".format(txt)) return QKeySequence.NoMatch - match = super().handle(e) + match = super().handle(e, dry_run=dry_run) - if match == QKeySequence.PartialMatch: + if match == QKeySequence.PartialMatch and not dry_run: timeout = config.val.input.partial_timeout if timeout != 0: self._partial_timer.setInterval(timeout) @@ -198,16 +200,21 @@ class HintKeyParser(keyparser.CommandKeyParser): self._last_press = LastPress.filtertext return QKeySequence.ExactMatch - def handle(self, e): + def handle(self, e, *, dry_run=False): """Handle a new keypress and call the respective handlers. Args: e: the KeyPressEvent from Qt + dry_run: Don't actually execute anything, only check whether there + would be a match. Returns: True if the match has been handled, False otherwise. """ - match = super().handle(e) + match = super().handle(e, dry_run=dry_run) + if dry_run: + return match + if match == QKeySequence.PartialMatch: self._last_press = LastPress.keystring elif match == QKeySequence.ExactMatch: @@ -267,17 +274,19 @@ class RegisterKeyParser(keyparser.CommandKeyParser): self._mode = mode self._read_config('register') - def handle(self, e): + def handle(self, e, *, dry_run=False): """Override handle to always match the next key and use the register. Args: e: the KeyPressEvent from Qt. + dry_run: Don't actually execute anything, only check whether there + would be a match. Return: True if event has been handled, False otherwise. """ - match = super().handle(e) - if match: + match = super().handle(e, dry_run=dry_run) + if match or dry_run: return match if not keyutils.is_printable(e.key()):