Checkpoint alpha: Reflow macros and keycodes into a consistent structure. Most internal state functionality largely untouched (just moved)
This commit is contained in:
parent
af140a16a6
commit
39a6465658
@ -202,16 +202,6 @@ class Firmware:
|
|||||||
if old_timeouts_len != new_timeouts_len:
|
if old_timeouts_len != new_timeouts_len:
|
||||||
state_changed = True
|
state_changed = True
|
||||||
|
|
||||||
if self._state.macros_pending:
|
|
||||||
# Blindly assume macros are going to change state, which is almost
|
|
||||||
# always a safe assumption
|
|
||||||
state_changed = True
|
|
||||||
for macro in self._state.macros_pending:
|
|
||||||
for key in macro(self):
|
|
||||||
self._send_key(key)
|
|
||||||
|
|
||||||
self._state.resolve_macro()
|
|
||||||
|
|
||||||
if self.debug_enabled and state_changed:
|
if self.debug_enabled and state_changed:
|
||||||
print('New State: {}'.format(self._state._to_dict()))
|
print('New State: {}'.format(self._state._to_dict()))
|
||||||
|
|
||||||
|
0
kmk/handlers/__init__.py
Normal file
0
kmk/handlers/__init__.py
Normal file
108
kmk/handlers/layers.py
Normal file
108
kmk/handlers/layers.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
from kmk.kmktime import ticks_diff, ticks_ms
|
||||||
|
|
||||||
|
|
||||||
|
def df_pressed(key, state, *args, **kwargs):
|
||||||
|
"""Switches the default layer"""
|
||||||
|
state.active_layers[0] = key.meta.layer
|
||||||
|
state.reversed_active_layers = list(reversed(state.active_layers))
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def mo_pressed(key, state, *args, **kwargs):
|
||||||
|
"""Momentarily activates layer, switches off when you let go"""
|
||||||
|
state.active_layers.append(key.meta.layer)
|
||||||
|
state.reversed_active_layers = list(reversed(state.active_layers))
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def mo_released(key, state, KC, *args, **kwargs):
|
||||||
|
state.active_layers = [
|
||||||
|
layer for layer in state.active_layers
|
||||||
|
if layer != key.meta.layer
|
||||||
|
]
|
||||||
|
state.reversed_active_layers = list(reversed(state.active_layers))
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def lm_pressed(key, state, *args, **kwargs):
|
||||||
|
"""As MO(layer) but with mod active"""
|
||||||
|
state.hid_pending = True
|
||||||
|
# Sets the timer start and acts like MO otherwise
|
||||||
|
state.start_time['lm'] = ticks_ms()
|
||||||
|
state.keys_pressed.add(key.meta.kc)
|
||||||
|
return mo_pressed(key, state, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def lm_released(key, state, *args, **kwargs):
|
||||||
|
"""As MO(layer) but with mod active"""
|
||||||
|
state.hid_pending = True
|
||||||
|
state.keys_pressed.discard(key.meta.kc)
|
||||||
|
state.start_time['lm'] = None
|
||||||
|
return mo_released(key, state, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def lt_pressed(key, state, *args, **kwargs):
|
||||||
|
# Sets the timer start and acts like MO otherwise
|
||||||
|
state.start_time['lt'] = ticks_ms()
|
||||||
|
return mo_pressed(key, state, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def lt_released(key, state, *args, **kwargs):
|
||||||
|
# On keyup, check timer, and press key if needed.
|
||||||
|
if state.start_time['lt'] and (
|
||||||
|
ticks_diff(ticks_ms(), state.start_time['lt']) < state.config.tap_time
|
||||||
|
):
|
||||||
|
state.hid_pending = True
|
||||||
|
state.tap_key(key.meta.kc)
|
||||||
|
|
||||||
|
mo_released(key, state, *args, **kwargs)
|
||||||
|
state.start_time['lt'] = None
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def tg_pressed(key, state, *args, **kwargs):
|
||||||
|
"""Toggles the layer (enables it if not active, and vise versa)"""
|
||||||
|
if key.meta.layer in state.active_layers:
|
||||||
|
state.active_layers = [
|
||||||
|
layer for layer in state.active_layers
|
||||||
|
if layer != key.meta.layer
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
state.active_layers.append(key.meta.layer)
|
||||||
|
|
||||||
|
state.reversed_active_layers = list(reversed(state.active_layers))
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def to_pressed(key, state, *args, **kwargs):
|
||||||
|
"""Activates layer and deactivates all other layers"""
|
||||||
|
state.active_layers = [key.meta.kc]
|
||||||
|
state.reversed_active_layers = list(reversed(state.active_layers))
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def tt_pressed(key, state, *args, **kwargs):
|
||||||
|
"""Momentarily activates layer if held, toggles it if tapped repeatedly"""
|
||||||
|
# TODO Make this work with tap dance to function more correctly, but technically works.
|
||||||
|
if state.start_time['tt'] is None:
|
||||||
|
# Sets the timer start and acts like MO otherwise
|
||||||
|
state.start_time['tt'] = ticks_ms()
|
||||||
|
return mo_pressed(key, state, *args, **kwargs)
|
||||||
|
elif ticks_diff(ticks_ms(), state.start_time['tt']) < state.config.tap_time:
|
||||||
|
state.start_time['tt'] = None
|
||||||
|
return tg_pressed(key, state, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def tt_released(key, state, *args, **kwargs):
|
||||||
|
if (
|
||||||
|
state.start_time['tt'] is None or
|
||||||
|
ticks_diff(ticks_ms(), state.start_time['tt']) >= state.config.tap_time
|
||||||
|
):
|
||||||
|
# On first press, works like MO. On second press, does nothing unless let up within
|
||||||
|
# time window, then acts like TG.
|
||||||
|
state.start_time['tt'] = None
|
||||||
|
return mo_released(key, state, *args, **kwargs)
|
||||||
|
|
||||||
|
return state
|
40
kmk/handlers/sequences.py
Normal file
40
kmk/handlers/sequences.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
from kmk.keycodes import ALL_KEYS, KC, make_key
|
||||||
|
from kmk.types import KeySequenceMeta
|
||||||
|
|
||||||
|
|
||||||
|
def sequence_press_handler(key, state, KC, *args, **kwargs):
|
||||||
|
old_keys_pressed = state.keys_pressed
|
||||||
|
state.keys_pressed = set()
|
||||||
|
|
||||||
|
for ikey in key.meta.seq:
|
||||||
|
if not getattr(ikey, 'no_press', None):
|
||||||
|
state.process_key(ikey, True)
|
||||||
|
state.config._send_hid()
|
||||||
|
if not getattr(ikey, 'no_release', None):
|
||||||
|
state.process_key(ikey, False)
|
||||||
|
state.config._send_hid()
|
||||||
|
|
||||||
|
state.keys_pressed = old_keys_pressed
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def simple_key_sequence(seq):
|
||||||
|
return make_key(
|
||||||
|
meta=KeySequenceMeta(seq),
|
||||||
|
on_press=sequence_press_handler,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def send_string(message):
|
||||||
|
seq = []
|
||||||
|
|
||||||
|
for char in message:
|
||||||
|
kc = ALL_KEYS[char]
|
||||||
|
|
||||||
|
if char.isupper():
|
||||||
|
kc = KC.LSHIFT(kc)
|
||||||
|
|
||||||
|
seq.append(kc)
|
||||||
|
|
||||||
|
return simple_key_sequence(seq)
|
89
kmk/handlers/stock.py
Normal file
89
kmk/handlers/stock.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
from kmk.kmktime import sleep_ms
|
||||||
|
from kmk.util import reset_bootloader, reset_keyboard
|
||||||
|
|
||||||
|
|
||||||
|
def passthrough(key, state, *args, **kwargs):
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def default_pressed(key, state, KC, coord_int=None, coord_raw=None):
|
||||||
|
if coord_int is not None:
|
||||||
|
state.coord_keys_pressed[coord_int] = key
|
||||||
|
|
||||||
|
state.add_key(key)
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def default_released(key, state, KC, coord_int=None, coord_raw=None):
|
||||||
|
state.remove_key(key)
|
||||||
|
|
||||||
|
if coord_int is not None:
|
||||||
|
state.keys_pressed.discard(key.coord_keys_pressed.get(coord_int, None))
|
||||||
|
state.coord_keys_pressed[coord_int] = None
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def reset(*args, **kwargs):
|
||||||
|
reset_keyboard()
|
||||||
|
|
||||||
|
|
||||||
|
def bootloader(*args, **kwargs):
|
||||||
|
reset_bootloader()
|
||||||
|
|
||||||
|
|
||||||
|
def debug_pressed(key, state, KC, *args, **kwargs):
|
||||||
|
if state.config.debug_enabled:
|
||||||
|
print('Disabling debug mode, bye!')
|
||||||
|
else:
|
||||||
|
print('Enabling debug mode. Welcome to the jungle.')
|
||||||
|
|
||||||
|
state.config.debug_enabled = not state.config.debug_enabled
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def gesc_pressed(key, state, KC, *args, **kwargs):
|
||||||
|
GESC_TRIGGERS = {KC.LSHIFT, KC.RSHIFT, KC.LGUI, KC.RGUI}
|
||||||
|
|
||||||
|
if GESC_TRIGGERS.intersection(state.keys_pressed):
|
||||||
|
# if Shift is held, KC_GRAVE will become KC_TILDE on OS level
|
||||||
|
state.keys_pressed.add(KC.GRAVE)
|
||||||
|
return state
|
||||||
|
|
||||||
|
# else return KC_ESC
|
||||||
|
state.keys_pressed.add(KC.ESCAPE)
|
||||||
|
state.hid_pending = True
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def gesc_released(key, state, KC, *args, **kwargs):
|
||||||
|
state.keys_pressed.discard(KC.ESCAPE)
|
||||||
|
state.keys_pressed.discard(KC.GRAVE)
|
||||||
|
state.hid_pending = True
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def sleep_pressed(key, state, KC, *args, **kwargs):
|
||||||
|
sleep_ms(key.meta.ms)
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def uc_mode_pressed(key, state, *args, **kwargs):
|
||||||
|
state.config.unicode_mode = key.meta.mode
|
||||||
|
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def leader_pressed(key, state, *args, **kwargs):
|
||||||
|
return state._begin_leader_mode()
|
||||||
|
|
||||||
|
|
||||||
|
def tap_dance_pressed(key, state, *args, **kwargs):
|
||||||
|
return state._process_tap_dance(key, True)
|
||||||
|
|
||||||
|
|
||||||
|
def tap_dance_released(key, state, *args, **kwargs):
|
||||||
|
return state._process_tap_dance(key, False)
|
@ -1,19 +1,13 @@
|
|||||||
from kmk.consts import LeaderMode
|
from kmk.consts import LeaderMode
|
||||||
from kmk.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, Keycodes, RawKeycodes,
|
from kmk.keycodes import KC
|
||||||
TapDanceKeycode)
|
from kmk.kmktime import ticks_ms
|
||||||
from kmk.kmktime import sleep_ms, ticks_diff, ticks_ms
|
from kmk.types import TapDanceKeyMeta
|
||||||
from kmk.util import intify_coordinate
|
from kmk.util import intify_coordinate
|
||||||
|
|
||||||
GESC_TRIGGERS = {
|
|
||||||
Keycodes.Modifiers.KC_LSHIFT, Keycodes.Modifiers.KC_RSHIFT,
|
|
||||||
Keycodes.Modifiers.KC_LGUI, Keycodes.Modifiers.KC_RGUI,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class InternalState:
|
class InternalState:
|
||||||
keys_pressed = set()
|
keys_pressed = set()
|
||||||
coord_keys_pressed = {}
|
coord_keys_pressed = {}
|
||||||
macros_pending = []
|
|
||||||
leader_pending = None
|
leader_pending = None
|
||||||
leader_last_len = 0
|
leader_last_len = 0
|
||||||
hid_pending = False
|
hid_pending = False
|
||||||
@ -34,22 +28,6 @@ class InternalState:
|
|||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.internal_key_handlers = {
|
|
||||||
RawKeycodes.KC_DF: self._layer_df,
|
|
||||||
RawKeycodes.KC_MO: self._layer_mo,
|
|
||||||
RawKeycodes.KC_LM: self._layer_lm,
|
|
||||||
RawKeycodes.KC_LT: self._layer_lt,
|
|
||||||
RawKeycodes.KC_TG: self._layer_tg,
|
|
||||||
RawKeycodes.KC_TO: self._layer_to,
|
|
||||||
RawKeycodes.KC_TT: self._layer_tt,
|
|
||||||
Keycodes.KMK.KC_GESC.code: self._kc_gesc,
|
|
||||||
RawKeycodes.KC_UC_MODE: self._kc_uc_mode,
|
|
||||||
RawKeycodes.KC_MACRO: self._kc_macro,
|
|
||||||
Keycodes.KMK.KC_LEAD.code: self._kc_lead,
|
|
||||||
Keycodes.KMK.KC_NO.code: self._kc_no,
|
|
||||||
Keycodes.KMK.KC_DEBUG.code: self._kc_debug_mode,
|
|
||||||
RawKeycodes.KC_TAP_DANCE: self._kc_tap_dance,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'InternalState({})'.format(self._to_dict())
|
return 'InternalState({})'.format(self._to_dict())
|
||||||
@ -74,7 +52,7 @@ class InternalState:
|
|||||||
for layer in self.reversed_active_layers:
|
for layer in self.reversed_active_layers:
|
||||||
layer_key = self.config.keymap[layer][row][col]
|
layer_key = self.config.keymap[layer][row][col]
|
||||||
|
|
||||||
if not layer_key or layer_key == Keycodes.KMK.KC_TRNS:
|
if not layer_key or layer_key == KC.TRNS:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self.config.debug_enabled:
|
if self.config.debug_enabled:
|
||||||
@ -122,16 +100,16 @@ class InternalState:
|
|||||||
print('No key accessible for col, row: {}, {}'.format(row, col))
|
print('No key accessible for col, row: {}, {}'.format(row, col))
|
||||||
return self
|
return self
|
||||||
|
|
||||||
if self.tapping and not isinstance(kc_changed, TapDanceKeycode):
|
return self.process_key(kc_changed, is_pressed, int_coord, (row, col))
|
||||||
self._process_tap_dance(kc_changed, is_pressed)
|
|
||||||
|
def process_key(self, key, is_pressed, coord_int=None, coord_raw=None):
|
||||||
|
if self.tapping and not isinstance(key.meta, TapDanceKeyMeta):
|
||||||
|
self._process_tap_dance(key, is_pressed)
|
||||||
else:
|
else:
|
||||||
if is_pressed:
|
if is_pressed:
|
||||||
self.coord_keys_pressed[int_coord] = kc_changed
|
key.on_press(self, coord_int, coord_raw)
|
||||||
self.add_key(kc_changed)
|
|
||||||
else:
|
else:
|
||||||
self.remove_key(kc_changed)
|
key.on_release(self, coord_int, coord_raw)
|
||||||
self.keys_pressed.discard(self.coord_keys_pressed.get(int_coord, None))
|
|
||||||
self.coord_keys_pressed[int_coord] = None
|
|
||||||
|
|
||||||
if self.config.leader_mode % 2 == 1:
|
if self.config.leader_mode % 2 == 1:
|
||||||
self._process_leader_mode()
|
self._process_leader_mode()
|
||||||
@ -140,27 +118,11 @@ class InternalState:
|
|||||||
|
|
||||||
def remove_key(self, keycode):
|
def remove_key(self, keycode):
|
||||||
self.keys_pressed.discard(keycode)
|
self.keys_pressed.discard(keycode)
|
||||||
|
return self.process_key(keycode, False)
|
||||||
if keycode.code >= FIRST_KMK_INTERNAL_KEYCODE:
|
|
||||||
self._process_internal_key_event(keycode, False)
|
|
||||||
else:
|
|
||||||
self.hid_pending = True
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def add_key(self, keycode):
|
def add_key(self, keycode):
|
||||||
# TODO Make this itself a macro keycode with a keyup handler
|
self.keys_pressed.add(keycode)
|
||||||
# rather than handling this inline here. Gross.
|
return self.process_key(keycode, True)
|
||||||
if keycode.code == Keycodes.KMK.KC_MACRO_SLEEP_MS:
|
|
||||||
sleep_ms(keycode.ms)
|
|
||||||
else:
|
|
||||||
self.keys_pressed.add(keycode)
|
|
||||||
|
|
||||||
if keycode.code >= FIRST_KMK_INTERNAL_KEYCODE:
|
|
||||||
self._process_internal_key_event(keycode, True)
|
|
||||||
else:
|
|
||||||
self.hid_pending = True
|
|
||||||
return self
|
|
||||||
|
|
||||||
def tap_key(self, keycode):
|
def tap_key(self, keycode):
|
||||||
self.add_key(keycode)
|
self.add_key(keycode)
|
||||||
@ -175,13 +137,6 @@ class InternalState:
|
|||||||
self.hid_pending = False
|
self.hid_pending = False
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def resolve_macro(self):
|
|
||||||
if self.config.debug_enabled:
|
|
||||||
print('Macro complete!')
|
|
||||||
|
|
||||||
self.macros_pending.pop()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _process_internal_key_event(self, changed_key, is_pressed):
|
def _process_internal_key_event(self, changed_key, is_pressed):
|
||||||
# Since the key objects can be chained into new objects
|
# Since the key objects can be chained into new objects
|
||||||
# with, for example, no_press set, always check against
|
# with, for example, no_press set, always check against
|
||||||
@ -192,164 +147,9 @@ class InternalState:
|
|||||||
changed_key, is_pressed,
|
changed_key, is_pressed,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _layer_df(self, changed_key, is_pressed):
|
|
||||||
"""Switches the default layer"""
|
|
||||||
if is_pressed:
|
|
||||||
self.active_layers[0] = changed_key.layer
|
|
||||||
self.reversed_active_layers = list(reversed(self.active_layers))
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _layer_mo(self, changed_key, is_pressed):
|
|
||||||
"""Momentarily activates layer, switches off when you let go"""
|
|
||||||
if is_pressed:
|
|
||||||
self.active_layers.append(changed_key.layer)
|
|
||||||
else:
|
|
||||||
self.active_layers = [
|
|
||||||
layer for layer in self.active_layers
|
|
||||||
if layer != changed_key.layer
|
|
||||||
]
|
|
||||||
|
|
||||||
self.reversed_active_layers = list(reversed(self.active_layers))
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _layer_lm(self, changed_key, is_pressed):
|
|
||||||
"""As MO(layer) but with mod active"""
|
|
||||||
self.hid_pending = True
|
|
||||||
|
|
||||||
if is_pressed:
|
|
||||||
# Sets the timer start and acts like MO otherwise
|
|
||||||
self.start_time['lm'] = ticks_ms()
|
|
||||||
self.keys_pressed.add(changed_key.kc)
|
|
||||||
else:
|
|
||||||
self.keys_pressed.discard(changed_key.kc)
|
|
||||||
self.start_time['lm'] = None
|
|
||||||
|
|
||||||
return self._layer_mo(changed_key, is_pressed)
|
|
||||||
|
|
||||||
def _layer_lt(self, changed_key, is_pressed):
|
|
||||||
"""Momentarily activates layer if held, sends kc if tapped"""
|
|
||||||
if is_pressed:
|
|
||||||
# Sets the timer start and acts like MO otherwise
|
|
||||||
self.start_time['lt'] = ticks_ms()
|
|
||||||
self._layer_mo(changed_key, is_pressed)
|
|
||||||
else:
|
|
||||||
# On keyup, check timer, and press key if needed.
|
|
||||||
if self.start_time['lt'] and (
|
|
||||||
ticks_diff(ticks_ms(), self.start_time['lt']) < self.config.tap_time
|
|
||||||
):
|
|
||||||
self.hid_pending = True
|
|
||||||
self.tap_key(changed_key.kc)
|
|
||||||
|
|
||||||
self._layer_mo(changed_key, is_pressed)
|
|
||||||
self.start_time['lt'] = None
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _layer_tg(self, changed_key, is_pressed):
|
|
||||||
"""Toggles the layer (enables it if not active, and vise versa)"""
|
|
||||||
if is_pressed:
|
|
||||||
if changed_key.layer in self.active_layers:
|
|
||||||
self.active_layers = [
|
|
||||||
layer for layer in self.active_layers
|
|
||||||
if layer != changed_key.layer
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
self.active_layers.append(changed_key.layer)
|
|
||||||
|
|
||||||
self.reversed_active_layers = list(reversed(self.active_layers))
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _layer_to(self, changed_key, is_pressed):
|
|
||||||
"""Activates layer and deactivates all other layers"""
|
|
||||||
if is_pressed:
|
|
||||||
self.active_layers = [changed_key.layer]
|
|
||||||
self.reversed_active_layers = list(reversed(self.active_layers))
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _layer_tt(self, changed_key, is_pressed):
|
|
||||||
"""Momentarily activates layer if held, toggles it if tapped repeatedly"""
|
|
||||||
# TODO Make this work with tap dance to function more correctly, but technically works.
|
|
||||||
if is_pressed:
|
|
||||||
if self.start_time['tt'] is None:
|
|
||||||
# Sets the timer start and acts like MO otherwise
|
|
||||||
self.start_time['tt'] = ticks_ms()
|
|
||||||
return self._layer_mo(changed_key, is_pressed)
|
|
||||||
elif ticks_diff(ticks_ms(), self.start_time['tt']) < self.config.tap_time:
|
|
||||||
self.start_time['tt'] = None
|
|
||||||
return self.tg(changed_key, is_pressed)
|
|
||||||
elif (
|
|
||||||
self.start_time['tt'] is None or
|
|
||||||
ticks_diff(ticks_ms(), self.start_time['tt']) >= self.config.tap_time
|
|
||||||
):
|
|
||||||
# On first press, works like MO. On second press, does nothing unless let up within
|
|
||||||
# time window, then acts like TG.
|
|
||||||
self.start_time['tt'] = None
|
|
||||||
return self._layer_mo(changed_key, is_pressed)
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _kc_uc_mode(self, changed_key, is_pressed):
|
|
||||||
if is_pressed:
|
|
||||||
self.config.unicode_mode = changed_key.mode
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _kc_macro(self, changed_key, is_pressed):
|
|
||||||
if is_pressed:
|
|
||||||
if changed_key.keyup:
|
|
||||||
self.macros_pending.append(changed_key.keyup)
|
|
||||||
else:
|
|
||||||
if changed_key.keydown:
|
|
||||||
self.macros_pending.append(changed_key.keydown)
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _kc_lead(self, changed_key, is_pressed):
|
|
||||||
if is_pressed:
|
|
||||||
self._begin_leader_mode()
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _kc_gesc(self, changed_key, is_pressed):
|
|
||||||
self.hid_pending = True
|
|
||||||
|
|
||||||
if is_pressed:
|
|
||||||
if GESC_TRIGGERS.intersection(self.keys_pressed):
|
|
||||||
# if Shift is held, KC_GRAVE will become KC_TILDE on OS level
|
|
||||||
self.keys_pressed.add(Keycodes.Common.KC_GRAVE)
|
|
||||||
return self
|
|
||||||
|
|
||||||
# else return KC_ESC
|
|
||||||
self.keys_pressed.add(Keycodes.Common.KC_ESCAPE)
|
|
||||||
return self
|
|
||||||
|
|
||||||
self.keys_pressed.discard(Keycodes.Common.KC_ESCAPE)
|
|
||||||
self.keys_pressed.discard(Keycodes.Common.KC_GRAVE)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _kc_no(self, changed_key, is_pressed):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _kc_debug_mode(self, changed_key, is_pressed):
|
|
||||||
if is_pressed:
|
|
||||||
if self.config.debug_enabled:
|
|
||||||
print('Disabling debug mode, bye!')
|
|
||||||
else:
|
|
||||||
print('Enabling debug mode. Welcome to the jungle.')
|
|
||||||
|
|
||||||
self.config.debug_enabled = not self.config.debug_enabled
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _kc_tap_dance(self, changed_key, is_pressed):
|
|
||||||
return self._process_tap_dance(changed_key, is_pressed)
|
|
||||||
|
|
||||||
def _process_tap_dance(self, changed_key, is_pressed):
|
def _process_tap_dance(self, changed_key, is_pressed):
|
||||||
if is_pressed:
|
if is_pressed:
|
||||||
if not isinstance(changed_key, TapDanceKeycode):
|
if not isinstance(changed_key.meta, TapDanceKeyMeta):
|
||||||
# If we get here, changed_key is not a TapDanceKeycode and thus
|
# If we get here, changed_key is not a TapDanceKeycode and thus
|
||||||
# the user kept typing elsewhere (presumably). End ALL of the
|
# the user kept typing elsewhere (presumably). End ALL of the
|
||||||
# currently outstanding tap dance runs.
|
# currently outstanding tap dance runs.
|
||||||
@ -408,7 +208,7 @@ class InternalState:
|
|||||||
|
|
||||||
def _begin_leader_mode(self):
|
def _begin_leader_mode(self):
|
||||||
if self.config.leader_mode % 2 == 0:
|
if self.config.leader_mode % 2 == 0:
|
||||||
self.keys_pressed.discard(Keycodes.KMK.KC_LEAD)
|
self.keys_pressed.discard(KC.LEAD)
|
||||||
# All leader modes are one number higher when activating
|
# All leader modes are one number higher when activating
|
||||||
self.config.leader_mode += 1
|
self.config.leader_mode += 1
|
||||||
|
|
||||||
@ -421,7 +221,7 @@ class InternalState:
|
|||||||
lmh = tuple(self.leader_mode_history)
|
lmh = tuple(self.leader_mode_history)
|
||||||
|
|
||||||
if lmh in self.config.leader_dictionary:
|
if lmh in self.config.leader_dictionary:
|
||||||
self.macros_pending.append(self.config.leader_dictionary[lmh].keydown)
|
self.process_key(self.config.leader_dictionary[lmh], True)
|
||||||
|
|
||||||
return self._exit_leader_mode()
|
return self._exit_leader_mode()
|
||||||
|
|
||||||
@ -438,15 +238,15 @@ class InternalState:
|
|||||||
for key in keys_pressed:
|
for key in keys_pressed:
|
||||||
if (
|
if (
|
||||||
self.config.leader_mode == LeaderMode.ENTER_ACTIVE and
|
self.config.leader_mode == LeaderMode.ENTER_ACTIVE and
|
||||||
key == Keycodes.Common.KC_ENT
|
key == KC.ENT
|
||||||
):
|
):
|
||||||
self._handle_leader_sequence()
|
self._handle_leader_sequence()
|
||||||
break
|
break
|
||||||
elif key == Keycodes.Common.KC_ESC or key == Keycodes.KMK.KC_GESC:
|
elif key == KC.ESC or key == KC.GESC:
|
||||||
# Clean self and turn leader mode off.
|
# Clean self and turn leader mode off.
|
||||||
self._exit_leader_mode()
|
self._exit_leader_mode()
|
||||||
break
|
break
|
||||||
elif key == Keycodes.KMK.KC_LEAD:
|
elif key == KC.LEAD:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
# Add key if not needing to escape
|
# Add key if not needing to escape
|
||||||
|
1025
kmk/keycodes.py
1025
kmk/keycodes.py
File diff suppressed because it is too large
Load Diff
@ -1,76 +0,0 @@
|
|||||||
import math
|
|
||||||
|
|
||||||
from kmk.event_defs import (hid_report_event, keycode_down_event,
|
|
||||||
keycode_up_event)
|
|
||||||
from kmk.keycodes import Media
|
|
||||||
from kmk.rotary_encoder import RotaryEncoder
|
|
||||||
|
|
||||||
VAL_FALSE = False + 1
|
|
||||||
VAL_NONE = True + 2
|
|
||||||
VAL_TRUE = True + 1
|
|
||||||
VOL_UP_PRESS = keycode_down_event(Media.KC_AUDIO_VOL_UP)
|
|
||||||
VOL_UP_RELEASE = keycode_up_event(Media.KC_AUDIO_VOL_UP)
|
|
||||||
VOL_DOWN_PRESS = keycode_down_event(Media.KC_AUDIO_VOL_DOWN)
|
|
||||||
VOL_DOWN_RELEASE = keycode_up_event(Media.KC_AUDIO_VOL_DOWN)
|
|
||||||
|
|
||||||
|
|
||||||
class RotaryEncoderMacro:
|
|
||||||
def __init__(self, pos_pin, neg_pin, slop_history=1, slop_threshold=1):
|
|
||||||
self.encoder = RotaryEncoder(pos_pin, neg_pin)
|
|
||||||
self.max_history = slop_history
|
|
||||||
self.history = bytearray(slop_history)
|
|
||||||
self.history_idx = 0
|
|
||||||
self.history_threshold = math.floor(slop_threshold * slop_history)
|
|
||||||
|
|
||||||
def scan(self):
|
|
||||||
# Anti-slop logic
|
|
||||||
self.history[self.history_idx] = 0
|
|
||||||
|
|
||||||
reading = self.encoder.direction()
|
|
||||||
self.history[self.history_idx] = VAL_NONE if reading is None else reading + 1
|
|
||||||
|
|
||||||
self.history_idx += 1
|
|
||||||
|
|
||||||
if self.history_idx >= self.max_history:
|
|
||||||
self.history_idx = 0
|
|
||||||
|
|
||||||
nones = 0
|
|
||||||
trues = 0
|
|
||||||
falses = 0
|
|
||||||
|
|
||||||
for val in self.history:
|
|
||||||
if val == VAL_NONE:
|
|
||||||
nones += 1
|
|
||||||
elif val == VAL_TRUE:
|
|
||||||
trues += 1
|
|
||||||
elif val == VAL_FALSE:
|
|
||||||
falses += 1
|
|
||||||
|
|
||||||
if nones >= self.history_threshold:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if trues >= self.history_threshold:
|
|
||||||
return self.on_increase()
|
|
||||||
|
|
||||||
if falses >= self.history_threshold:
|
|
||||||
return self.on_decrease()
|
|
||||||
|
|
||||||
def on_decrease(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def on_increase(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class VolumeRotaryEncoder(RotaryEncoderMacro):
|
|
||||||
def on_decrease(self):
|
|
||||||
yield VOL_DOWN_PRESS
|
|
||||||
yield hid_report_event
|
|
||||||
yield VOL_DOWN_RELEASE
|
|
||||||
yield hid_report_event
|
|
||||||
|
|
||||||
def on_increase(self):
|
|
||||||
yield VOL_UP_PRESS
|
|
||||||
yield hid_report_event
|
|
||||||
yield VOL_UP_RELEASE
|
|
||||||
yield hid_report_event
|
|
@ -1,28 +0,0 @@
|
|||||||
from kmk.keycodes import Keycodes, Macro, char_lookup, lookup_kc_with_cache
|
|
||||||
from kmk.string import ascii_letters, digits
|
|
||||||
|
|
||||||
|
|
||||||
def simple_key_sequence(seq):
|
|
||||||
def _simple_key_sequence(state):
|
|
||||||
return seq
|
|
||||||
|
|
||||||
return Macro(keydown=_simple_key_sequence)
|
|
||||||
|
|
||||||
|
|
||||||
def send_string(message):
|
|
||||||
seq = []
|
|
||||||
|
|
||||||
for char in message:
|
|
||||||
kc = None
|
|
||||||
|
|
||||||
if char in char_lookup:
|
|
||||||
kc = char_lookup[char]
|
|
||||||
elif char in ascii_letters + digits:
|
|
||||||
kc = lookup_kc_with_cache(char)
|
|
||||||
|
|
||||||
if char.isupper():
|
|
||||||
kc = Keycodes.Modifiers.KC_LSHIFT(kc)
|
|
||||||
|
|
||||||
seq.append(kc)
|
|
||||||
|
|
||||||
return simple_key_sequence(seq)
|
|
@ -1,16 +1,15 @@
|
|||||||
from kmk.consts import UnicodeMode
|
from kmk.consts import UnicodeMode
|
||||||
from kmk.keycodes import (Common, Macro, Modifiers,
|
from kmk.keycodes import ALL_KEYS, KC, Macro
|
||||||
generate_codepoint_keysym_seq)
|
|
||||||
from kmk.macros.simple import simple_key_sequence
|
from kmk.macros.simple import simple_key_sequence
|
||||||
from kmk.types import AttrDict
|
from kmk.types import AttrDict
|
||||||
from kmk.util import get_wide_ordinal
|
from kmk.util import get_wide_ordinal
|
||||||
|
|
||||||
IBUS_KEY_COMBO = Modifiers.KC_LCTRL(Modifiers.KC_LSHIFT(Common.KC_U))
|
IBUS_KEY_COMBO = KC.LCTRL(KC.LSHIFT(KC.U))
|
||||||
RALT_KEY = Modifiers.KC_RALT
|
RALT_KEY = KC.RALT
|
||||||
U_KEY = Common.KC_U
|
U_KEY = KC.U
|
||||||
ENTER_KEY = Common.KC_ENTER
|
ENTER_KEY = KC.ENTER
|
||||||
RALT_DOWN_NO_RELEASE = Modifiers.KC_RALT(no_release=True)
|
RALT_DOWN_NO_RELEASE = KC.RALT(no_release=True)
|
||||||
RALT_UP_NO_PRESS = Modifiers.KC_RALT(no_press=True)
|
RALT_UP_NO_PRESS = KC.RALT(no_press=True)
|
||||||
|
|
||||||
|
|
||||||
def compile_unicode_string_sequences(string_table):
|
def compile_unicode_string_sequences(string_table):
|
||||||
@ -31,6 +30,26 @@ def unicode_string_sequence(unistring):
|
|||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def generate_codepoint_keysym_seq(codepoint, expected_length=4):
|
||||||
|
# To make MacOS and Windows happy, always try to send
|
||||||
|
# sequences that are of length 4 at a minimum
|
||||||
|
# On Linux systems, we can happily send longer strings.
|
||||||
|
# They will almost certainly break on MacOS and Windows,
|
||||||
|
# but this is a documentation problem more than anything.
|
||||||
|
# Not sure how to send emojis on Mac/Windows like that,
|
||||||
|
# though, since (for example) the Canadian flag is assembled
|
||||||
|
# from two five-character codepoints, 1f1e8 and 1f1e6
|
||||||
|
#
|
||||||
|
# As a bonus, this function can be pretty useful for
|
||||||
|
# leader dictionary keys as strings.
|
||||||
|
seq = [KC.N0 for _ in range(max(len(codepoint), expected_length))]
|
||||||
|
|
||||||
|
for idx, codepoint_fragment in enumerate(reversed(codepoint)):
|
||||||
|
seq[-(idx + 1)] = ALL_KEYS.get(codepoint_fragment)
|
||||||
|
|
||||||
|
return seq
|
||||||
|
|
||||||
|
|
||||||
def unicode_codepoint_sequence(codepoints):
|
def unicode_codepoint_sequence(codepoints):
|
||||||
kc_seqs = (
|
kc_seqs = (
|
||||||
generate_codepoint_keysym_seq(codepoint)
|
generate_codepoint_keysym_seq(codepoint)
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
from kmk.pins import PULL_UP
|
|
||||||
|
|
||||||
|
|
||||||
class RotaryEncoder:
|
|
||||||
# Please don't ask. I don't know. All I know is bit_value
|
|
||||||
# works as expected. Here be dragons, etc. etc.
|
|
||||||
MIN_VALUE = False + 1 << 1 | True + 1
|
|
||||||
MAX_VALUE = True + 1 << 1 | True + 1
|
|
||||||
|
|
||||||
def __init__(self, pos_pin, neg_pin):
|
|
||||||
self.pos_pin = pos_pin
|
|
||||||
self.neg_pin = neg_pin
|
|
||||||
|
|
||||||
self.pos_pin.switch_to_input(pull=PULL_UP)
|
|
||||||
self.neg_pin.switch_to_input(pull=PULL_UP)
|
|
||||||
|
|
||||||
self.prev_bit_value = self.bit_value()
|
|
||||||
|
|
||||||
def value(self):
|
|
||||||
return (self.pos_pin.value(), self.neg_pin.value())
|
|
||||||
|
|
||||||
def bit_value(self):
|
|
||||||
'''
|
|
||||||
Returns 2, 3, 5, or 6 based on the state of the rotary encoder's two
|
|
||||||
bits. This is a total hack but it does what we need pretty efficiently.
|
|
||||||
Shrug.
|
|
||||||
'''
|
|
||||||
return self.pos_pin.value() + 1 << 1 | self.neg_pin.value() + 1
|
|
||||||
|
|
||||||
def direction(self):
|
|
||||||
'''
|
|
||||||
Compares the current rotary position against the last seen position.
|
|
||||||
|
|
||||||
Returns True if we're rotating "positively", False if we're rotating "negatively",
|
|
||||||
and None if no change could safely be detected for any reason (usually this
|
|
||||||
means the encoder itself did not change)
|
|
||||||
'''
|
|
||||||
new_value = self.bit_value()
|
|
||||||
rolling_under = self.prev_bit_value == self.MIN_VALUE and new_value == self.MAX_VALUE
|
|
||||||
rolling_over = self.prev_bit_value == self.MAX_VALUE and new_value == self.MIN_VALUE
|
|
||||||
increasing = new_value > self.prev_bit_value
|
|
||||||
decreasing = new_value < self.prev_bit_value
|
|
||||||
self.prev_bit_value = new_value
|
|
||||||
|
|
||||||
if rolling_over:
|
|
||||||
return True
|
|
||||||
elif rolling_under:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if increasing:
|
|
||||||
return True
|
|
||||||
if decreasing:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Either no change, or not a type of change we can safely detect,
|
|
||||||
# so safely do nothing
|
|
||||||
return None
|
|
26
kmk/types.py
26
kmk/types.py
@ -24,3 +24,29 @@ class Anything:
|
|||||||
class Passthrough:
|
class Passthrough:
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return Anything(attr)
|
return Anything(attr)
|
||||||
|
|
||||||
|
|
||||||
|
class LayerKeyMeta:
|
||||||
|
def __init__(self, layer, kc=None):
|
||||||
|
self.layer = layer
|
||||||
|
self.kc = kc
|
||||||
|
|
||||||
|
|
||||||
|
class KeySequenceMeta:
|
||||||
|
def __init__(self, seq):
|
||||||
|
self.seq = seq
|
||||||
|
|
||||||
|
|
||||||
|
class KeySeqSleepMeta:
|
||||||
|
def __init__(self, ms):
|
||||||
|
self.ms = ms
|
||||||
|
|
||||||
|
|
||||||
|
class UnicodeModeKeyMeta:
|
||||||
|
def __init__(self, mode):
|
||||||
|
self.mode = mode
|
||||||
|
|
||||||
|
|
||||||
|
class TapDanceKeyMeta:
|
||||||
|
def __init__(self, codes):
|
||||||
|
self.codes = codes
|
||||||
|
Loading…
x
Reference in New Issue
Block a user