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
This commit is contained in:
@@ -150,7 +150,7 @@ class UnicodeModes:
|
||||
|
||||
|
||||
class LeaderMode:
|
||||
DEFAULT = 0
|
||||
DEFAULT_ACTIVE = 1
|
||||
TIMEOUT = 0
|
||||
TIMEOUT_ACTIVE = 1
|
||||
ENTER = 2
|
||||
ENTER_ACTIVE = 3
|
||||
|
@@ -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()
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user