From 2d1290a12c549b730dc453772f754ebc9e556b66 Mon Sep 17 00:00:00 2001 From: Josh Klar Date: Fri, 19 Oct 2018 01:49:37 -0700 Subject: [PATCH] Add LeaderMode.TIMEOUT (QMK default Leader mode) This allows leader sequences to "time out" rather than requiring an Enter keypress to end. This also rolls back some unnecessary changes from #72 to the matrix scanner for performance reasons. In theory we can use this in the future for Tap Dance support (#40) Resolves #1 Resolves #37 --- kmk/consts.py | 4 +- kmk/firmware.py | 39 +++++------ kmk/internal_state.py | 72 ++++++++++++++------- kmk/matrix.py | 9 +-- user_keymaps/klardotsh/klarank_featherm4.py | 3 +- 5 files changed, 77 insertions(+), 50 deletions(-) diff --git a/kmk/consts.py b/kmk/consts.py index bcc5dea..d243822 100644 --- a/kmk/consts.py +++ b/kmk/consts.py @@ -150,7 +150,7 @@ class UnicodeModes: class LeaderMode: - DEFAULT = 0 - DEFAULT_ACTIVE = 1 + TIMEOUT = 0 + TIMEOUT_ACTIVE = 1 ENTER = 2 ENTER_ACTIVE = 3 diff --git a/kmk/firmware.py b/kmk/firmware.py index 32af927..3b479dc 100644 --- a/kmk/firmware.py +++ b/kmk/firmware.py @@ -55,6 +55,7 @@ class Firmware: tap_time = 300 leader_mode = LeaderMode.ENTER leader_dictionary = {} + leader_timeout = 1000 hid_helper = USB_HID @@ -94,28 +95,30 @@ class Firmware: print("Firin' lazers. Keyboard is booted.") while True: - for update in self.matrix.scan_for_changes(): - if update is not None: - self._state.matrix_changed( - update[0], - update[1], - update[2], - ) + update = self.matrix.scan_for_changes() + if update is not None: + self._state.matrix_changed( + update[0], + update[1], + update[2], + ) - if self._state.hid_pending: - self._send_hid() + if self._state.hid_pending: + self._send_hid() - for key in self._state.pending_keys: - self._send_key(key) - self._state.pending_key_handled() + if self.debug_enabled: + print('New State: {}'.format(self._state._to_dict())) - if self._state.macro_pending: - for key in self._state.macro_pending(self): - self._send_key(key) + self._state.process_timeouts() - self._state.resolve_macro() + for key in self._state.pending_keys: + self._send_key(key) + self._state.pending_key_handled() - if self.debug_enabled: - print('New State: {}'.format(self._state._to_dict())) + if self._state.macro_pending: + for key in self._state.macro_pending(self): + self._send_key(key) + + self._state.resolve_macro() gc.collect() diff --git a/kmk/internal_state.py b/kmk/internal_state.py index 677583a..555fe39 100644 --- a/kmk/internal_state.py +++ b/kmk/internal_state.py @@ -1,3 +1,4 @@ +from kmk.consts import LeaderMode from kmk.keycodes import FIRST_KMK_INTERNAL_KEYCODE, Keycodes, RawKeycodes from kmk.kmktime import sleep_ms, ticks_diff, ticks_ms from kmk.util import intify_coordinate @@ -26,12 +27,10 @@ class InternalState: 'lm': None, 'leader': None, } + timeouts = {} def __init__(self, config): self.config = config - - self.leader_mode = config.leader_mode - self.internal_key_handlers = { RawKeycodes.KC_DF: self._layer_df, RawKeycodes.KC_MO: self._layer_mo, @@ -56,6 +55,7 @@ class InternalState: 'keys_pressed': self.keys_pressed, 'active_layers': self.active_layers, 'leader_mode_history': self.leader_mode_history, + 'leader_mode': self.config.leader_mode, 'start_time': self.start_time, } @@ -75,6 +75,21 @@ class InternalState: return layer_key + def set_timeout(self, after_ticks, callback): + timeout_key = ticks_ms() + after_ticks + self.timeouts[timeout_key] = callback + return self + + def process_timeouts(self): + current_time = ticks_ms() + + for k, v in self.timeouts.items(): + if k <= current_time: + v() + del self.timeouts[k] + + return self + def matrix_changed(self, row, col, is_pressed): if self.config.debug_enabled: print('Matrix changed (col, row, pressed?): {}, {}, {}'.format( @@ -88,24 +103,24 @@ class InternalState: print('No key accessible for col, row: {}, {}'.format(row, col)) return self + if is_pressed: + self.keys_pressed.add(kc_changed) + self.coord_keys_pressed[int_coord] = kc_changed + else: + self.keys_pressed.discard(kc_changed) + self.keys_pressed.discard(self.coord_keys_pressed[int_coord]) + self.coord_keys_pressed[int_coord] = None + if kc_changed.code >= FIRST_KMK_INTERNAL_KEYCODE: self._process_internal_key_event( kc_changed, is_pressed, ) else: - if is_pressed: - self.keys_pressed.add(kc_changed) - self.coord_keys_pressed[int_coord] = kc_changed - else: - self.keys_pressed.discard(kc_changed) - self.keys_pressed.discard(self.coord_keys_pressed[int_coord]) - self.coord_keys_pressed[int_coord] = None - self.hid_pending = True - if self.leader_mode % 2 == 1: - self._process_leader_mode() + if self.config.leader_mode % 2 == 1: + self._process_leader_mode() return self @@ -304,13 +319,24 @@ class InternalState: return self def _begin_leader_mode(self): - if self.leader_mode % 2 == 0: + if self.config.leader_mode % 2 == 0: self.keys_pressed.discard(Keycodes.KMK.KC_LEAD) # All leader modes are one number higher when activating - self.leader_mode += 1 + self.config.leader_mode += 1 + + if self.config.leader_mode == LeaderMode.TIMEOUT_ACTIVE: + self.set_timeout(self.config.leader_timeout, self._handle_leader_sequence) return self + def _handle_leader_sequence(self): + lmh = tuple(self.leader_mode_history) + + if lmh in self.config.leader_dictionary: + self.macro_pending = self.config.leader_dictionary[lmh].keydown + + return self._exit_leader_mode() + def _process_leader_mode(self): keys_pressed = self.keys_pressed @@ -322,15 +348,11 @@ class InternalState: self.leader_last_len = len(self.keys_pressed) for key in keys_pressed: - if key == Keycodes.Common.KC_ENT: - # Process the action and remove the extra KC.ENT that was added to get here - - lmh = tuple(self.leader_mode_history) - - if lmh in self.config.leader_dictionary: - self.macro_pending = self.config.leader_dictionary[lmh].keydown - - self._exit_leader_mode() + if ( + self.config.leader_mode == LeaderMode.ENTER_ACTIVE and + key == Keycodes.Common.KC_ENT + ): + self._handle_leader_sequence() break elif key == Keycodes.Common.KC_ESC or key == Keycodes.KMK.KC_GESC: # Clean self and turn leader mode off. @@ -348,7 +370,7 @@ class InternalState: def _exit_leader_mode(self): self.leader_mode_history.clear() - self.leader_mode -= 1 + self.config.leader_mode -= 1 self.leader_last_len = 0 self.keys_pressed.clear() return self diff --git a/kmk/matrix.py b/kmk/matrix.py index 0b428e6..e1be0d5 100644 --- a/kmk/matrix.py +++ b/kmk/matrix.py @@ -98,12 +98,13 @@ class MatrixScanner: self.report[2] = new_val self.state[ba_idx] = new_val any_changed = True - - yield self.report + break ba_idx += 1 opin.value(False) + if any_changed: + break - if not any_changed: - yield None + if any_changed: + return self.report diff --git a/user_keymaps/klardotsh/klarank_featherm4.py b/user_keymaps/klardotsh/klarank_featherm4.py index bb465e2..2cb1843 100644 --- a/user_keymaps/klardotsh/klarank_featherm4.py +++ b/user_keymaps/klardotsh/klarank_featherm4.py @@ -1,5 +1,5 @@ from kmk.boards.klarank import Firmware -from kmk.consts import UnicodeModes +from kmk.consts import LeaderMode, UnicodeModes from kmk.keycodes import KC from kmk.keycodes import generate_leader_dictionary_seq as glds from kmk.macros.simple import send_string @@ -45,6 +45,7 @@ emoticons = cuss({ WPM = send_string("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Bibendum arcu vitae elementum curabitur vitae nunc sed. Facilisis sed odio morbi quis.") +keyboard.leader_mode = LeaderMode.TIMEOUT keyboard.leader_dictionary = { glds('hello'): send_string('hello world from kmk macros'), glds('wpm'): WPM,