Filter out ShortcutOverride events properly

Fixes #3419
This commit is contained in:
Florian Bruhin 2018-03-05 06:32:54 +01:00
parent 4ef5db1bc4
commit e01db79ce9
5 changed files with 51 additions and 27 deletions

View File

@ -99,6 +99,8 @@ Fixed
- QtWebEngine: `:follow-selected` should now work in more cases with Qt > 5.10. - 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 - QtWebEngine: Incremental search now flickers less and doesn't move to the
second result when pressing Enter. 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. - QtWebKit: `:view-source` now displays a valid URL.
- URLs containing ampersands and other special chars are now shown - URLs containing ampersands and other special chars are now shown
correctly when filtering them in the completion. correctly when filtering them in the completion.

View File

@ -882,6 +882,7 @@ class EventFilter(QObject):
self._handlers = { self._handlers = {
QEvent.KeyPress: self._handle_key_event, QEvent.KeyPress: self._handle_key_event,
QEvent.KeyRelease: self._handle_key_event, QEvent.KeyRelease: self._handle_key_event,
QEvent.ShortcutOverride: self._handle_key_event,
} }
def _handle_key_event(self, event): def _handle_key_event(self, event):

View File

@ -114,7 +114,7 @@ class BaseKeyParser(QObject):
return (result, None) return (result, None)
def handle(self, e): def handle(self, e, *, dry_run=False):
"""Handle a new keypress. """Handle a new keypress.
Separate the keypress into count/command, then check if it matches Separate the keypress into count/command, then check if it matches
@ -123,13 +123,16 @@ class BaseKeyParser(QObject):
Args: Args:
e: the KeyPressEvent from Qt. e: the KeyPressEvent from Qt.
dry_run: Don't actually execute anything, only check whether there
would be a match.
Return: Return:
A QKeySequence match. A QKeySequence match.
""" """
key = e.key() key = e.key()
txt = str(keyutils.KeyInfo.from_event(e)) 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): if keyutils.is_modifier_key(key):
self._debug_log("Ignoring, only modifier") self._debug_log("Ignoring, only modifier")
@ -138,33 +141,39 @@ class BaseKeyParser(QObject):
if (txt.isdigit() and self._supports_count and not if (txt.isdigit() and self._supports_count and not
(not self._count and txt == '0')): (not self._count and txt == '0')):
assert len(txt) == 1, txt assert len(txt) == 1, txt
if not dry_run:
self._count += txt self._count += txt
return QKeySequence.ExactMatch return QKeySequence.ExactMatch
self._sequence = self._sequence.append_event(e) sequence = self._sequence.append_event(e)
match, binding = self._match_key(self._sequence) match, binding = self._match_key(sequence)
if match == QKeySequence.NoMatch: if match == QKeySequence.NoMatch:
mappings = config.val.bindings.key_mappings mappings = config.val.bindings.key_mappings
mapped = mappings.get(self._sequence, None) mapped = mappings.get(sequence, None)
if mapped is not None: if mapped is not None:
self._debug_log("Mapped {} -> {}".format( self._debug_log("Mapped {} -> {}".format(
self._sequence, mapped)) sequence, mapped))
match, binding = self._match_key(mapped) match, binding = self._match_key(mapped)
self._sequence = mapped sequence = mapped
if dry_run:
return match
self._sequence = sequence
if match == QKeySequence.ExactMatch: if match == QKeySequence.ExactMatch:
self._debug_log("Definitive match for '{}'.".format( self._debug_log("Definitive match for '{}'.".format(
self._sequence)) sequence))
count = int(self._count) if self._count else None count = int(self._count) if self._count else None
self.clear_keystring() self.clear_keystring()
self.execute(binding, count) self.execute(binding, count)
elif match == QKeySequence.PartialMatch: elif match == QKeySequence.PartialMatch:
self._debug_log("No match for '{}' (added {})".format( self._debug_log("No match for '{}' (added {})".format(
self._sequence, txt)) sequence, txt))
self.keystring_updated.emit(self._count + str(self._sequence)) self.keystring_updated.emit(self._count + str(sequence))
elif match == QKeySequence.NoMatch: elif match == QKeySequence.NoMatch:
self._debug_log("Giving up with '{}', no matches".format( self._debug_log("Giving up with '{}', no matches".format(
self._sequence)) sequence))
self.clear_keystring() self.clear_keystring()
else: else:
raise utils.Unreachable("Invalid match value {!r}".format(match)) raise utils.Unreachable("Invalid match value {!r}".format(match))

View File

@ -143,11 +143,12 @@ class ModeManager(QObject):
def __repr__(self): def __repr__(self):
return utils.get_repr(self, mode=self.mode) 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. """Handle filtering of KeyPress events.
Args: Args:
event: The KeyPress to examine. event: The KeyPress to examine.
dry_run: Don't actually handle the key, only filter it.
Return: Return:
True if event should be filtered, False otherwise. True if event should be filtered, False otherwise.
@ -157,7 +158,7 @@ class ModeManager(QObject):
if curmode != usertypes.KeyMode.insert: if curmode != usertypes.KeyMode.insert:
log.modes.debug("got keypress in mode {} - delegating to " log.modes.debug("got keypress in mode {} - delegating to "
"{}".format(curmode, utils.qualname(parser))) "{}".format(curmode, utils.qualname(parser)))
match = parser.handle(event) match = parser.handle(event, dry_run=dry_run)
is_non_alnum = ( is_non_alnum = (
event.modifiers() not in [Qt.NoModifier, Qt.ShiftModifier] or event.modifiers() not in [Qt.NoModifier, Qt.ShiftModifier] or
@ -173,17 +174,17 @@ class ModeManager(QObject):
else: else:
filter_this = True filter_this = True
if not filter_this: if not filter_this and not dry_run:
self._releaseevents_to_pass.add(KeyEvent.from_event(event)) self._releaseevents_to_pass.add(KeyEvent.from_event(event))
if curmode != usertypes.KeyMode.insert: if curmode != usertypes.KeyMode.insert:
focus_widget = QApplication.instance().focusWidget() focus_widget = QApplication.instance().focusWidget()
log.modes.debug("match: {}, forward_unbound_keys: {}, " log.modes.debug("match: {}, forward_unbound_keys: {}, "
"passthrough: {}, is_non_alnum: {} --> " "passthrough: {}, is_non_alnum: {}, dry_run: {} "
"filter: {} (focused: {!r})".format( "--> filter: {} (focused: {!r})".format(
match, forward_unbound_keys, match, forward_unbound_keys,
parser.passthrough, is_non_alnum, filter_this, parser.passthrough, is_non_alnum, dry_run,
focus_widget)) filter_this, focus_widget))
return filter_this return filter_this
def _eventFilter_keyrelease(self, event): def _eventFilter_keyrelease(self, event):
@ -320,6 +321,8 @@ class ModeManager(QObject):
handlers = { handlers = {
QEvent.KeyPress: self._eventFilter_keypress, QEvent.KeyPress: self._eventFilter_keypress,
QEvent.KeyRelease: self._eventFilter_keyrelease, QEvent.KeyRelease: self._eventFilter_keyrelease,
QEvent.ShortcutOverride:
functools.partial(self._eventFilter_keypress, dry_run=True),
} }
handler = handlers[event.type()] handler = handlers[event.type()]
return handler(event) return handler(event)

View File

@ -59,11 +59,13 @@ class NormalKeyParser(keyparser.CommandKeyParser):
def __repr__(self): def __repr__(self):
return utils.get_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. """Override to abort if the key is a startchar.
Args: Args:
e: the KeyPressEvent from Qt. e: the KeyPressEvent from Qt.
dry_run: Don't actually execute anything, only check whether there
would be a match.
Return: Return:
A self.Match member. A self.Match member.
@ -74,9 +76,9 @@ class NormalKeyParser(keyparser.CommandKeyParser):
"currently inhibited.".format(txt)) "currently inhibited.".format(txt))
return QKeySequence.NoMatch 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 timeout = config.val.input.partial_timeout
if timeout != 0: if timeout != 0:
self._partial_timer.setInterval(timeout) self._partial_timer.setInterval(timeout)
@ -198,16 +200,21 @@ class HintKeyParser(keyparser.CommandKeyParser):
self._last_press = LastPress.filtertext self._last_press = LastPress.filtertext
return QKeySequence.ExactMatch return QKeySequence.ExactMatch
def handle(self, e): def handle(self, e, *, dry_run=False):
"""Handle a new keypress and call the respective handlers. """Handle a new keypress and call the respective handlers.
Args: Args:
e: the KeyPressEvent from Qt e: the KeyPressEvent from Qt
dry_run: Don't actually execute anything, only check whether there
would be a match.
Returns: Returns:
True if the match has been handled, False otherwise. 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: if match == QKeySequence.PartialMatch:
self._last_press = LastPress.keystring self._last_press = LastPress.keystring
elif match == QKeySequence.ExactMatch: elif match == QKeySequence.ExactMatch:
@ -267,17 +274,19 @@ class RegisterKeyParser(keyparser.CommandKeyParser):
self._mode = mode self._mode = mode
self._read_config('register') 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. """Override handle to always match the next key and use the register.
Args: Args:
e: the KeyPressEvent from Qt. e: the KeyPressEvent from Qt.
dry_run: Don't actually execute anything, only check whether there
would be a match.
Return: Return:
True if event has been handled, False otherwise. True if event has been handled, False otherwise.
""" """
match = super().handle(e) match = super().handle(e, dry_run=dry_run)
if match: if match or dry_run:
return match return match
if not keyutils.is_printable(e.key()): if not keyutils.is_printable(e.key()):