Minor fixes and increased debugging verbosity (which necessitated some minor tweaks to timers)

This commit is contained in:
brightonanc 2023-08-30 23:31:23 -04:00
parent 80ee62f1b9
commit 04f9073f9d
5 changed files with 83 additions and 31 deletions

View File

@ -230,6 +230,9 @@ class BaseKeyParser(QObject):
self._mode.name) self._mode.name)
log.keyboard.debug(prefix + msg) log.keyboard.debug(prefix + msg)
def get_do_log(self) -> bool:
return self._do_log
def _match_key(self, sequence: keyutils.KeySequence) -> MatchResult: def _match_key(self, sequence: keyutils.KeySequence) -> MatchResult:
"""Try to match a given keystring with any bound keychain. """Try to match a given keystring with any bound keychain.
@ -311,7 +314,7 @@ class BaseKeyParser(QObject):
self._debug_log(f"Got key: {info!r} (dry_run {dry_run})") self._debug_log(f"Got key: {info!r} (dry_run {dry_run})")
# Modifier keys should be previously handled by modeman # Modifier keys should be previously handled by modeman
if keyutils.is_modifier_key(key): if info.is_modifier_key():
self._debug_log("Ignoring, only modifier") self._debug_log("Ignoring, only modifier")
return QKeySequence.SequenceMatch.NoMatch return QKeySequence.SequenceMatch.NoMatch
@ -433,6 +436,8 @@ class BaseKeyParser(QObject):
raise utils.Unreachable("Invalid match value {!r}".format( raise utils.Unreachable("Invalid match value {!r}".format(
result.match_type)) result.match_type))
return result.match_type
@config.change_filter('bindings') @config.change_filter('bindings')
def _on_config_changed(self) -> None: def _on_config_changed(self) -> None:
self._read_config() self._read_config()

View File

@ -565,10 +565,10 @@ class QueuedKeyEventPair:
def to_events(self) -> Sequence[QKeyEvent]: def to_events(self) -> Sequence[QKeyEvent]:
"""Get a QKeyEvent from this QueuedEvent.""" """Get a QKeyEvent from this QueuedEvent."""
if self.key_info_release is None: if self.key_info_release is None:
return (self.key_info_press.to_event(QEvent.KeyPress),) return (self.key_info_press.to_event(QEvent.Type.KeyPress),)
else: else:
return (self.key_info_press.to_event(QEvent.KeyPress), return (self.key_info_press.to_event(QEvent.Type.KeyPress),
self.key_info_release.to_event(QEvent.KeyRelease)) self.key_info_release.to_event(QEvent.Type.KeyRelease))
class KeySequence: class KeySequence:

View File

@ -4,7 +4,7 @@
"""Mode manager (per window) which handles the current keyboard mode.""" """Mode manager (per window) which handles the current keyboard mode."""
from PyQt5.QtWidgets import QApplication from qutebrowser.qt.widgets import QApplication
import functools import functools
import dataclasses import dataclasses
@ -278,7 +278,8 @@ class ModeManager(QObject):
""" """
curmode = self.mode curmode = self.mode
parser = self.parsers[curmode] parser = self.parsers[curmode]
if curmode != usertypes.KeyMode.insert: do_log = parser.get_do_log()
if do_log:
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)))
@ -286,11 +287,14 @@ class ModeManager(QObject):
if parser.allow_forward and (not dry_run) and (not had_empty_queue): if parser.allow_forward and (not dry_run) and (not had_empty_queue):
# Immediately record the event so that parser.handle may forward if # Immediately record the event so that parser.handle may forward if
# appropriate from its logic. # appropriate from its logic.
if do_log:
log.modes.debug("Enqueuing key event due to non-empty queue: "
"{}".format(event))
self._partial_match_events.append( self._partial_match_events.append(
keyutils.QueuedKeyEventPair.from_event_press(event)) keyutils.QueuedKeyEventPair.from_event_press(event))
if keyutils.is_modifier_key(Qt.Key(event.key())): if keyutils.KeyInfo.from_event(event).is_modifier_key():
if curmode != usertypes.KeyMode.insert: if do_log:
log.modes.debug("Ignoring, only modifier") log.modes.debug("Ignoring, only modifier")
if not dry_run: if not dry_run:
# Since this is a NoMatch without a call to parser.handle, we # Since this is a NoMatch without a call to parser.handle, we
@ -304,30 +308,44 @@ class ModeManager(QObject):
if match == QKeySequence.SequenceMatch.ExactMatch: if match == QKeySequence.SequenceMatch.ExactMatch:
filter_this = True filter_this = True
if not dry_run: if not dry_run:
if do_log:
log.modes.debug("Stopping partial timer.")
self._stop_partial_timer() self._stop_partial_timer()
if do_log:
log.modes.debug("Clearing partial match events.")
self.clear_partial_match_events() self.clear_partial_match_events()
elif match == QKeySequence.SequenceMatch.PartialMatch: elif match == QKeySequence.SequenceMatch.PartialMatch:
filter_this = True filter_this = True
if parser.allow_forward and (not dry_run): if parser.allow_forward and (not dry_run):
if had_empty_queue: if had_empty_queue:
# Begin recording partial match events # Begin recording partial match events
if do_log:
log.modes.debug("Enqueuing key event as first entry "
"in an empty queue: {}".format(event))
self._partial_match_events.append( self._partial_match_events.append(
keyutils.QueuedKeyEventPair.from_event_press(event)) keyutils.QueuedKeyEventPair.from_event_press(event))
if do_log:
log.modes.debug("Starting partial timer.")
self._start_partial_timer() self._start_partial_timer()
elif not had_empty_queue: elif not had_empty_queue:
# Since partial events were recorded, this event must be filtered. # Since partial events were recorded, this event must be filtered.
# Since a NoMatch was found, this event has already been forwarded # Since a NoMatch was found, this event has already been forwarded
filter_this = True filter_this = True
if not dry_run: if not dry_run:
if do_log:
log.modes.debug("Stopping partial timer.")
self._stop_partial_timer() self._stop_partial_timer()
else: else:
key_info = keyutils.KeyInfo.from_event(event) key_info = keyutils.KeyInfo.from_event(event)
filter_this = self._should_filter_event(key_info, parser) filter_this = self._should_filter_event(key_info, parser)
if not filter_this and not dry_run: if not filter_this and not dry_run:
if do_log:
log.modes.debug("Adding release event for pass: "
"{}".format(event))
self._releaseevents_to_pass.add(keyutils.KeyEvent.from_event(event)) self._releaseevents_to_pass.add(keyutils.KeyEvent.from_event(event))
if curmode != usertypes.KeyMode.insert: if do_log:
if machinery.IS_QT5: # FIXME:v4 needed for Qt 5 typing if machinery.IS_QT5: # FIXME:v4 needed for Qt 5 typing
ignored_modifiers = [ ignored_modifiers = [
cast(Qt.KeyboardModifiers, Qt.KeyboardModifier.NoModifier), cast(Qt.KeyboardModifiers, Qt.KeyboardModifier.NoModifier),
@ -365,7 +383,11 @@ class ModeManager(QObject):
""" """
# handle like matching KeyPress # handle like matching KeyPress
keyevent = keyutils.KeyEvent.from_event(event) keyevent = keyutils.KeyEvent.from_event(event)
do_log = (self.mode in self.parsers) and \
self.parsers[self.mode].get_do_log()
if keyevent in self._releaseevents_to_pass: if keyevent in self._releaseevents_to_pass:
if do_log:
log.modes.debug("Passing release event: {}".format(keyevent))
self._releaseevents_to_pass.remove(keyevent) self._releaseevents_to_pass.remove(keyevent)
filter_this = False filter_this = False
else: else:
@ -375,7 +397,7 @@ class ModeManager(QObject):
if match_event.add_event_release(event): if match_event.add_event_release(event):
break break
filter_this = True filter_this = True
if self.mode != usertypes.KeyMode.insert: if do_log:
log.modes.debug("filter: {}".format(filter_this)) log.modes.debug("filter: {}".format(filter_this))
return filter_this return filter_this
@ -413,6 +435,7 @@ class ModeManager(QObject):
raise ValueError("Can't forward partial key: No keyparser for " raise ValueError("Can't forward partial key: No keyparser for "
"mode {}".format(mode)) "mode {}".format(mode))
parser = self.parsers[mode] parser = self.parsers[mode]
do_log = parser.get_do_log()
if not self._partial_match_events: if not self._partial_match_events:
if parser.allow_forward: if parser.allow_forward:
log.modes.warning("Attempting to forward for mode {} " log.modes.warning("Attempting to forward for mode {} "
@ -423,7 +446,7 @@ class ModeManager(QObject):
match_event = self._partial_match_events.pop(0) match_event = self._partial_match_events.pop(0)
if parser.allow_forward and (not if parser.allow_forward and (not
self._should_filter_event(match_event.key_info_press, parser)): self._should_filter_event(match_event.key_info_press, parser)):
if mode != usertypes.KeyMode.insert: if do_log:
log.modes.debug("Forwarding partial match event in mode " log.modes.debug("Forwarding partial match event in mode "
"{}.".format(mode)) "{}.".format(mode))
text_actual = str(match_event.key_info_press) text_actual = str(match_event.key_info_press)
@ -447,22 +470,33 @@ class ModeManager(QObject):
window=self._win_id) window=self._win_id)
send_event = functools.partial(QApplication.sendEvent, widget) send_event = functools.partial(QApplication.sendEvent, widget)
for event_ in match_event.to_events(): for event_ in match_event.to_events():
if do_log:
log.modes.debug("Sending event {}".format(event_))
send_event(event_) send_event(event_)
if not match_event.is_released(): if not match_event.is_released():
if do_log:
log.modes.debug("Adding release event for pass: "
"{}".format(match_event.key_event))
self._releaseevents_to_pass.add(match_event.key_event) self._releaseevents_to_pass.add(match_event.key_event)
@pyqtSlot(usertypes.KeyMode) @pyqtSlot(usertypes.KeyMode)
def forward_all_partial_match_events(self, mode: usertypes.KeyMode, *, def forward_all_partial_match_events(self, mode: usertypes.KeyMode, *,
stop_timer: bool = False) -> None: stop_timer: bool = False,
is_timeout: bool = False) -> None:
"""Forward all partial match events for a given mode. """Forward all partial match events for a given mode.
Args: Args:
mode: The mode from which the forwarded match is. mode: The mode from which the forwarded match is.
stop_timer: If true, stop the partial timer (and any nested timers) stop_timer: If true, stop the partial timer (and any nested timers)
as well. Default is False. as well. Default is False.
is_timeout: True if this invocation is the result of a timeout.
""" """
log.modes.debug("Forwarding all partial matches.") do_log = (mode in self.parsers) and self.parsers[mode].get_do_log()
if do_log:
log.modes.debug(f"Forwarding all partial matches ({is_timeout=}).")
if stop_timer: if stop_timer:
if do_log:
log.modes.debug("Stopping partial timer.")
self._stop_partial_timer() self._stop_partial_timer()
if mode in self.parsers: if mode in self.parsers:
parser = self.parsers[mode] parser = self.parsers[mode]
@ -494,7 +528,8 @@ class ModeManager(QObject):
except TypeError: except TypeError:
pass pass
self._partial_timer.timeout.connect(functools.partial( self._partial_timer.timeout.connect(functools.partial(
self.forward_all_partial_match_events, self.mode)) self.forward_all_partial_match_events, self.mode,
is_timeout=True))
self._partial_timer.start() self._partial_timer.start()
def _stop_partial_timer(self) -> None: def _stop_partial_timer(self) -> None:

View File

@ -10,6 +10,7 @@ Module attributes:
import traceback import traceback
import enum import enum
import functools
from typing import TYPE_CHECKING, List from typing import TYPE_CHECKING, List
from collections.abc import Sequence from collections.abc import Sequence
@ -154,8 +155,8 @@ class HintKeyParser(basekeyparser.BaseKeyParser):
self.clear_partial_match_events) self.clear_partial_match_events)
self._partial_timer = usertypes.Timer(self, 'partial-match') self._partial_timer = usertypes.Timer(self, 'partial-match')
self._partial_timer.setSingleShot(True) self._partial_timer.setSingleShot(True)
self._partial_timer.timeout.connect( self._partial_timer.timeout.connect(functools.partial(
self.forward_all_partial_match_events) self.forward_all_partial_match_events, is_timeout=True))
def _handle_filter_key(self, e: QKeyEvent) -> QKeySequence.SequenceMatch: def _handle_filter_key(self, e: QKeyEvent) -> QKeySequence.SequenceMatch:
"""Handle keys for string filtering.""" """Handle keys for string filtering."""
@ -206,11 +207,14 @@ class HintKeyParser(basekeyparser.BaseKeyParser):
if not had_empty_queue: if not had_empty_queue:
# Immediately record the event so that parser.handle may forward if # Immediately record the event so that parser.handle may forward if
# appropriate from its logic. # appropriate from its logic.
self._debug_log("Enqueuing key event due to non-empty queue: "
"{}".format(e))
self._partial_match_events.append( self._partial_match_events.append(
keyutils.QueuedKeyEventPair.from_event_press(e)) keyutils.QueuedKeyEventPair.from_event_press(e))
result = self._command_parser.handle(e) result = self._command_parser.handle(e)
if result == QKeySequence.SequenceMatch.ExactMatch: if result == QKeySequence.SequenceMatch.ExactMatch:
self._debug_log("Stopping partial timer.")
self._stop_partial_timer() self._stop_partial_timer()
self.clear_partial_match_events() self.clear_partial_match_events()
log.keyboard.debug("Handling key via command parser") log.keyboard.debug("Handling key via command parser")
@ -220,11 +224,15 @@ class HintKeyParser(basekeyparser.BaseKeyParser):
log.keyboard.debug("Handling key via command parser") log.keyboard.debug("Handling key via command parser")
if had_empty_queue: if had_empty_queue:
# Begin recording partial match events # Begin recording partial match events
self._debug_log("Enqueuing key event as first entry in an "
"empty queue: {}".format(e))
self._partial_match_events.append( self._partial_match_events.append(
keyutils.QueuedKeyEventPair.from_event_press(e)) keyutils.QueuedKeyEventPair.from_event_press(e))
self._debug_log("Staring partial timer.")
self._start_partial_timer() self._start_partial_timer()
return result return result
elif not had_empty_queue: elif not had_empty_queue:
self._debug_log("Stopping partial timer.")
self._stop_partial_timer() self._stop_partial_timer()
# It's unclear exactly what the return here should be. The safest # It's unclear exactly what the return here should be. The safest
# bet seems to be PartialMatch as it won't clear the unused # bet seems to be PartialMatch as it won't clear the unused
@ -275,14 +283,18 @@ class HintKeyParser(basekeyparser.BaseKeyParser):
@pyqtSlot() @pyqtSlot()
def forward_all_partial_match_events(self, *, def forward_all_partial_match_events(self, *,
stop_timer: bool = False) -> None: stop_timer: bool = False,
is_timeout: bool = False) -> None:
"""Forward all partial match events. """Forward all partial match events.
Args: Args:
stop_timer: If true, stop the partial timer as well. Default is False. stop_timer: If true, stop the partial timer as well. Default is
False.
is_timeout: True if this invocation is the result of a timeout.
""" """
self._debug_log("Forwarding all partial matches.") self._debug_log(f"Forwarding all partial matches ({is_timeout=}).")
if stop_timer: if stop_timer:
self._debug_log("Stopping partial timer.")
self._stop_partial_timer() self._stop_partial_timer()
if self._partial_match_events: if self._partial_match_events:
while self._partial_match_events: while self._partial_match_events:

View File

@ -192,38 +192,38 @@ def test_release_forwarding(modeman_with_basekeyparser):
info_b = keyutils.KeyInfo(Qt.Key.Key_B, Qt.KeyboardModifier.NoModifier) info_b = keyutils.KeyInfo(Qt.Key.Key_B, Qt.KeyboardModifier.NoModifier)
info_c = keyutils.KeyInfo(Qt.Key.Key_C, Qt.KeyboardModifier.NoModifier) info_c = keyutils.KeyInfo(Qt.Key.Key_C, Qt.KeyboardModifier.NoModifier)
res = mwb.handle_event(info_b.to_event(QEvent.KeyPress)) res = mwb.handle_event(info_b.to_event(QEvent.Type.KeyPress))
assert (True, 1, 0) == helper_data(res, mwb) assert (True, 1, 0) == helper_data(res, mwb)
assert not mwb._partial_match_events[0].is_released() assert not mwb._partial_match_events[0].is_released()
res = mwb.handle_event(info_c.to_event(QEvent.KeyPress)) res = mwb.handle_event(info_c.to_event(QEvent.Type.KeyPress))
assert (True, 0, 2) == helper_data(res, mwb) assert (True, 0, 2) == helper_data(res, mwb)
res = mwb.handle_event(info_b.to_event(QEvent.KeyRelease)) res = mwb.handle_event(info_b.to_event(QEvent.Type.KeyRelease))
assert (False, 0, 1) == helper_data(res, mwb) assert (False, 0, 1) == helper_data(res, mwb)
res = mwb.handle_event(info_c.to_event(QEvent.KeyRelease)) res = mwb.handle_event(info_c.to_event(QEvent.Type.KeyRelease))
assert (False, 0, 0) == helper_data(res, mwb) assert (False, 0, 0) == helper_data(res, mwb)
info_y = keyutils.KeyInfo(Qt.Key.Key_Y, Qt.KeyboardModifier.NoModifier) info_y = keyutils.KeyInfo(Qt.Key.Key_Y, Qt.KeyboardModifier.NoModifier)
res = mwb.handle_event(info_b.to_event(QEvent.KeyPress)) res = mwb.handle_event(info_b.to_event(QEvent.Type.KeyPress))
assert (True, 1, 0) == helper_data(res, mwb) assert (True, 1, 0) == helper_data(res, mwb)
assert not mwb._partial_match_events[0].is_released() assert not mwb._partial_match_events[0].is_released()
res = mwb.handle_event(info_y.to_event(QEvent.KeyPress)) res = mwb.handle_event(info_y.to_event(QEvent.Type.KeyPress))
assert (True, 2, 0) == helper_data(res, mwb) assert (True, 2, 0) == helper_data(res, mwb)
assert not mwb._partial_match_events[0].is_released() assert not mwb._partial_match_events[0].is_released()
assert not mwb._partial_match_events[1].is_released() assert not mwb._partial_match_events[1].is_released()
res = mwb.handle_event(info_y.to_event(QEvent.KeyRelease)) res = mwb.handle_event(info_y.to_event(QEvent.Type.KeyRelease))
assert (True, 2, 0) == helper_data(res, mwb) assert (True, 2, 0) == helper_data(res, mwb)
assert not mwb._partial_match_events[0].is_released() assert not mwb._partial_match_events[0].is_released()
assert mwb._partial_match_events[1].is_released() assert mwb._partial_match_events[1].is_released()
res = mwb.handle_event(info_c.to_event(QEvent.KeyPress)) res = mwb.handle_event(info_c.to_event(QEvent.Type.KeyPress))
assert (True, 0, 2) == helper_data(res, mwb) assert (True, 0, 2) == helper_data(res, mwb)
res = mwb.handle_event(info_c.to_event(QEvent.KeyRelease)) res = mwb.handle_event(info_c.to_event(QEvent.Type.KeyRelease))
assert (False, 0, 1) == helper_data(res, mwb) assert (False, 0, 1) == helper_data(res, mwb)
res = mwb.handle_event(info_b.to_event(QEvent.KeyRelease)) res = mwb.handle_event(info_b.to_event(QEvent.Type.KeyRelease))
assert (False, 0, 0) == helper_data(res, mwb) assert (False, 0, 0) == helper_data(res, mwb)
res = mwb.handle_event(info_b.to_event(QEvent.KeyPress)) res = mwb.handle_event(info_b.to_event(QEvent.Type.KeyPress))
assert (True, 1, 0) == helper_data(res, mwb) assert (True, 1, 0) == helper_data(res, mwb)
assert not mwb._partial_match_events[0].is_released() assert not mwb._partial_match_events[0].is_released()
res = mwb.handle_event(info_c.to_event(QEvent.KeyRelease)) res = mwb.handle_event(info_c.to_event(QEvent.Type.KeyRelease))
assert (True, 1, 0) == helper_data(res, mwb) assert (True, 1, 0) == helper_data(res, mwb)