Bring back keyutils.is_modifier() and modifier handling

Turns out when we press yY, we get three events:

Qt.Key_Y, Qt.NoModifier
Qt.Key_Shift, Qt.ShiftModifier
Qt.Key_Y, Qt.ShiftModifier

If we don't ignore the second one, our keychain will be interrupted by the Shift
keypress.
This commit is contained in:
Florian Bruhin 2018-03-02 14:16:40 +01:00
parent e306e2dadb
commit b3834835ed
5 changed files with 46 additions and 9 deletions

View File

@ -128,6 +128,10 @@ class BaseKeyParser(QObject):
txt = str(keyutils.KeyInfo.from_event(e))
self._debug_log("Got key: 0x{:x} / text: '{}'".format(key, txt))
if keyutils.is_modifier_key(key):
self._debug_log("Ignoring, only modifier")
return QKeySequence.NoMatch
if (txt.isdigit() and self._supports_count and not
(not self._count and txt == '0')):
assert len(txt) == 1, txt

View File

@ -29,10 +29,29 @@ from PyQt5.QtGui import QKeySequence, QKeyEvent
from qutebrowser.utils import utils
# Map Qt::Key values to their Qt::KeyboardModifier value.
_MODIFIER_MAP = {
Qt.Key_Shift: Qt.ShiftModifier,
Qt.Key_Control: Qt.ControlModifier,
Qt.Key_Alt: Qt.AltModifier,
Qt.Key_Meta: Qt.MetaModifier,
Qt.Key_Mode_switch: Qt.GroupSwitchModifier,
}
def is_printable(key):
return key <= 0xff
def is_modifier_key(key):
"""Test whether the given key is a modifier.
This only considers keys which are part of Qt::KeyboardModifiers, i.e. which
would interrupt a key chain like "yY" when handled.
"""
return key in _MODIFIER_MAP
def _key_to_string(key):
"""Convert a Qt::Key member to a meaningful name.
@ -195,18 +214,11 @@ class KeyInfo:
A name of the key (combination) as a string.
"""
key_string = _key_to_string(self.key)
modifier_map = {
Qt.Key_Shift: Qt.ShiftModifier,
Qt.Key_Control: Qt.ControlModifier,
Qt.Key_Alt: Qt.AltModifier,
Qt.Key_Meta: Qt.MetaModifier,
Qt.Key_Mode_switch: Qt.GroupSwitchModifier,
}
modifiers = int(self.modifiers)
if self.key in modifier_map:
if self.key in _MODIFIER_MAP:
# Don't return e.g. <Shift+Shift>
modifiers &= ~modifier_map[self.key]
modifiers &= ~_MODIFIER_MAP[self.key]
elif is_printable(self.key):
# "normal" binding
# FIXME Add a test to make sure Tab doesn't become TAB

View File

@ -27,6 +27,7 @@ BINDINGS = {'prompt': {'<Ctrl-a>': 'message-info ctrla',
'ba': 'message-info ba',
'ax': 'message-info ax',
'ccc': 'message-info ccc',
'yY': 'yank -s',
'0': 'message-info 0'},
'command': {'foo': 'message-info bar',
'<Ctrl+X>': 'message-info ctrlx'},

View File

@ -241,6 +241,17 @@ class TestKeyChain:
handle_text((Qt.Key_B, 'b'))
assert not keyparser.execute.called
def test_binding_with_shift(self, keyparser, fake_keyevent_factory):
"""Simulate a binding which involves shift."""
keyparser.handle(
fake_keyevent_factory(Qt.Key_Y, text='y'))
keyparser.handle(
fake_keyevent_factory(Qt.Key_Shift, Qt.ShiftModifier, text=''))
keyparser.handle(
fake_keyevent_factory(Qt.Key_Y, Qt.ShiftModifier, text='Y'))
keyparser.execute.assert_called_once_with('yank -s', None)
class TestCount:

View File

@ -200,3 +200,12 @@ def test_normalize_keystr(orig, normalized):
])
def test_is_printable(key, printable):
assert keyutils.is_printable(key) == printable
@pytest.mark.parametrize('key, ismodifier', [
(Qt.Key_Control, True),
(Qt.Key_X, False),
(Qt.Key_Super_L, False), # Modifier but not in _MODIFIER_MAP
])
def test_is_modifier_key(key, ismodifier):
assert keyutils.is_modifier_key(key) == ismodifier