From df1522df6c5e997825583d68688bac91f70b668d Mon Sep 17 00:00:00 2001 From: Zackarias Montell Date: Sun, 30 Jan 2022 14:27:42 +0100 Subject: [PATCH] unpress previously pressed keys on layer change --- boot.py | 0 code.py | 0 keyboards/macropad_rev1/default/keymap.py | 23 ++- keyboards/macropad_rev1/default/pinouts.py | 0 lib/adafruit_mcp230xx/__init__.mpy | Bin lib/adafruit_mcp230xx/digital_inout.mpy | Bin lib/adafruit_mcp230xx/mcp23008.mpy | Bin lib/adafruit_mcp230xx/mcp23016.mpy | Bin lib/adafruit_mcp230xx/mcp23017.mpy | Bin lib/adafruit_mcp230xx/mcp230xx.mpy | Bin lib/adafruit_mcp230xx/mcp23sxx.mpy | Bin nmlkpy/key_types/base.py | 128 ++---------- nmlkpy/key_types/keycode.py | 79 +------- nmlkpy/key_types/layer.py | 218 +++++---------------- nmlkpy/keyboard.py | 15 +- nmlkpy/keymap_delta.py | 45 ++++- nmlkpy/keymap_manager.py | 99 +++++++--- 17 files changed, 190 insertions(+), 417 deletions(-) mode change 100644 => 100755 boot.py mode change 100644 => 100755 code.py mode change 100644 => 100755 keyboards/macropad_rev1/default/pinouts.py mode change 100644 => 100755 lib/adafruit_mcp230xx/__init__.mpy mode change 100644 => 100755 lib/adafruit_mcp230xx/digital_inout.mpy mode change 100644 => 100755 lib/adafruit_mcp230xx/mcp23008.mpy mode change 100644 => 100755 lib/adafruit_mcp230xx/mcp23016.mpy mode change 100644 => 100755 lib/adafruit_mcp230xx/mcp23017.mpy mode change 100644 => 100755 lib/adafruit_mcp230xx/mcp230xx.mpy mode change 100644 => 100755 lib/adafruit_mcp230xx/mcp23sxx.mpy mode change 100644 => 100755 nmlkpy/key_types/base.py mode change 100644 => 100755 nmlkpy/key_types/keycode.py mode change 100644 => 100755 nmlkpy/key_types/layer.py mode change 100644 => 100755 nmlkpy/keymap_delta.py mode change 100644 => 100755 nmlkpy/keymap_manager.py diff --git a/boot.py b/boot.py old mode 100644 new mode 100755 diff --git a/code.py b/code.py old mode 100644 new mode 100755 diff --git a/keyboards/macropad_rev1/default/keymap.py b/keyboards/macropad_rev1/default/keymap.py index 31a9dd4..220cb65 100644 --- a/keyboards/macropad_rev1/default/keymap.py +++ b/keyboards/macropad_rev1/default/keymap.py @@ -1,34 +1,33 @@ from nmlkpy.key_codes.se import SE from nmlkpy.key_types.keycode import Keycode, Modifier +from nmlkpy.key_types.layer import Hold, Toggle from nmlkpy.layer_manager import LayerManager -lm = LayerManager(5) - keymap = [[ - lm.hold(1), lm.toggle(2), lm.toggle(3), lm.toggle(4), - lm.hold(2), Keycode(SE.B), Keycode(SE.C), Keycode(SE.D), + Hold(1), Toggle(2), Toggle(3), Toggle(4), + Hold(2), Keycode(SE.B), Keycode(SE.C), Keycode(SE.D), Keycode(SE.LEFT), Keycode(SE.DOWN), Keycode( SE.UP), Keycode(SE.RIGHT), Keycode(SE.ENTER), Keycode(SE.J), Keycode( SE.K), Modifier(SE.LEFT_SHIFT) ], [ - lm.hold(1), lm.toggle(2), lm.toggle(3), lm.toggle(4), - Keycode(SE.ONE), lm.hold(1), Keycode(SE.C), Keycode(SE.D), + Hold(1), Toggle(2), Toggle(3), Toggle(4), + Keycode(SE.ONE), Hold(1), Keycode(SE.C), Keycode(SE.D), Keycode(SE.E), Keycode(SE.F), Keycode(SE.G), Keycode(SE.H), Keycode(SE.BACKSPACE), Keycode(SE.J), Keycode(SE.K), Keycode(SE.L) ], [ - lm.hold(1), lm.toggle(2), lm.toggle(3), lm.toggle(4), - Keycode(SE.A), lm.hold(1), lm.hold(2), Keycode(SE.D), + Hold(1), Toggle(2), Toggle(3), Toggle(4), + Hold(2), Hold(1), Hold(2), Keycode(SE.D), Keycode(SE.E), Keycode(SE.F), Keycode(SE.G), Keycode(SE.H), Keycode(SE.I), Keycode(SE.J), Keycode(SE.K), Keycode(SE.L) ], [ - lm.hold(1), lm.toggle(2), lm.toggle(3), lm.toggle(4), - Keycode(SE.A), Keycode(SE.B), lm.hold(2), lm.hold(3), + Hold(1), Toggle(2), Toggle(3), Toggle(4), + Keycode(SE.A), Keycode(SE.B), Hold(2), Hold(3), Keycode(SE.E), Keycode(SE.F), Keycode(SE.G), Keycode(SE.H), Keycode(SE.I), Keycode(SE.J), Keycode(SE.K), Keycode(SE.L) ], [ - lm.hold(1), lm.toggle(2), lm.toggle(3), lm.toggle(4), - Keycode(SE.A), Keycode(SE.B), Keycode(SE.C), lm.hold(3), + Hold(1), Toggle(2), Toggle(3), Toggle(4), + Keycode(SE.A), Keycode(SE.B), Keycode(SE.C), Hold(3), Keycode(SE.E), Keycode(SE.F), Keycode(SE.G), Keycode(SE.H), Keycode(SE.I), Keycode(SE.J), Keycode(SE.K), Modifier(SE.LEFT_ALT) ]] diff --git a/keyboards/macropad_rev1/default/pinouts.py b/keyboards/macropad_rev1/default/pinouts.py old mode 100644 new mode 100755 diff --git a/lib/adafruit_mcp230xx/__init__.mpy b/lib/adafruit_mcp230xx/__init__.mpy old mode 100644 new mode 100755 diff --git a/lib/adafruit_mcp230xx/digital_inout.mpy b/lib/adafruit_mcp230xx/digital_inout.mpy old mode 100644 new mode 100755 diff --git a/lib/adafruit_mcp230xx/mcp23008.mpy b/lib/adafruit_mcp230xx/mcp23008.mpy old mode 100644 new mode 100755 diff --git a/lib/adafruit_mcp230xx/mcp23016.mpy b/lib/adafruit_mcp230xx/mcp23016.mpy old mode 100644 new mode 100755 diff --git a/lib/adafruit_mcp230xx/mcp23017.mpy b/lib/adafruit_mcp230xx/mcp23017.mpy old mode 100644 new mode 100755 diff --git a/lib/adafruit_mcp230xx/mcp230xx.mpy b/lib/adafruit_mcp230xx/mcp230xx.mpy old mode 100644 new mode 100755 diff --git a/lib/adafruit_mcp230xx/mcp23sxx.mpy b/lib/adafruit_mcp230xx/mcp23sxx.mpy old mode 100644 new mode 100755 diff --git a/nmlkpy/key_types/base.py b/nmlkpy/key_types/base.py old mode 100644 new mode 100755 index 1624720..61c64e7 --- a/nmlkpy/key_types/base.py +++ b/nmlkpy/key_types/base.py @@ -1,135 +1,29 @@ -from micropython import const -from nmlkpy import keymap_delta -from nmlkpy.keymap_delta import KeymapDelta, LayerDelta +from nmlkpy.keymap_delta import KeyIdentifier, KeymapDelta, LayerDelta from nmlkpy.pinstate_manager import PinState -class KeyDelta: +class KeyChanges: layer_delta: LayerDelta keymap_delta: KeymapDelta + special_state: object def __init__(self) -> None: self.layer_delta = LayerDelta() self.keymap_delta = KeymapDelta() + self.special_state = None - -class KeyEvent: - """Interpretations of KeyStates as single events""" - NO_EVENT: int = const(0) - """No new event has occurred""" - PRESSED_: int = const(1) - """The key was pressed""" - RELEASED: int = const(2) - """The key was released""" - PRESSED_TOGGLED_ON = const(3) - """Key is pressed and is emulating a toggle key in ON state that is not released until the physical key is released, then pressed again""" - RELEASED_TOGGLED_ON = const(4) - """Key is unpressed and is emulating a toggle key in ON state that is not released unit the physical key is pressed again""" - PRESSED_TOGGLED_OFF = const(5) - """Key is pressed and is emulating a toggle key in OFF state""" - - -def _event_to_string(event: int) -> str: - if event == KeyEvent.NO_EVENT: - return "NO EVENT" - if event == KeyEvent.PRESSED_: - return "PRESSED" - if event == KeyEvent.RELEASED: - return "RELEASED" - if event == KeyEvent.PRESSED_TOGGLED_ON: - return "PRESSED TOGGLED ON" - if event == KeyEvent.RELEASED_TOGGLED_ON: - return "RELEASED TOGGLED ON" - if event == KeyEvent.PRESSED_TOGGLED_OFF: - return "PRESSED TOGGLED OFF" - raise NotImplementedError() - - -class SoftRelease: - _NOT_QUEUED = 0 - _QUEUED = 1 - _HANDLED = 2 - _state: int - - def __init__(self): - self._state = self._NOT_QUEUED - - def needs_attention(self) -> bool: - return self._state != self._NOT_QUEUED - - def is_queued(self) -> bool: - return self._state == self._QUEUED - - def is_handled(self) -> bool: - return self._state == self._HANDLED - - def reset(self): - self._state = self._NOT_QUEUED - - def queue(self): - self._state = self._QUEUED - - def mark_as_handled(self): - self._state = self._HANDLED - - def __str__(self): - if self._state == self._NOT_QUEUED: - return "Not queued" - if self._state == self._QUEUED: - return "Queued" - if self._state == self._HANDLED: - return "Handled" - raise NotImplementedError() - - -class KeyEvents: - current: int - previous: int - soft_release: SoftRelease - - def __init__(self): - self.current = KeyEvent.RELEASED - self.previous = KeyEvent.RELEASED - self.soft_release = SoftRelease() - - def shift(self, next): - self.previous = self.current - self.current = next + def set_special_state(self, state: object) -> None: + self.special_state = state class KeyBase: - events: KeyEvents - allowed_events: list[KeyEvent] + #identifier: KeyIdentifier + # def enrich(self, identifier: KeyIdentifier): + # assert identifier is not None + # self.identifier = identifier - def __init__(self, allowed_events: list[int]): - self.allowed_events = allowed_events - self.events = KeyEvents() - - def get_delta(self, pin: PinState) -> KeyDelta: - raise NotImplementedError() - - def _validate_next_event(self, next_event: int): - if next_event is None or next_event not in self.allowed_events: - raise Exception( - f"KeyEvent with value of {next_event} is not a valid event for this type of key") - - def _set_event(self, next_event: int, index: int = 99) -> bool: - """Sets previous state if new state is valid - Returns: A boolean that determines if the state was updated or not""" - self._validate_next_event(next_event) - if next_event is KeyEvent.NO_EVENT: - return False - print( - f"[{index}] next: {_event_to_string(next_event)}, current: {_event_to_string(self.events.current)}, previous: {_event_to_string(self.events.previous)}, soft_release: {self.events.soft_release}") - self.events.shift(next_event) - return True - - def handle(self, keyboard, is_active: bool = False): + def calculate_changes(self, pin: PinState, custom_state: object) -> KeyChanges: raise NotImplementedError() def self_test(self, keymap: list[list[object]]): raise NotImplementedError() - - def soft_release(self, keyboard): - """Release key programatically and wait until it's released physically before registrating additional key presses""" - raise NotImplementedError() diff --git a/nmlkpy/key_types/keycode.py b/nmlkpy/key_types/keycode.py old mode 100644 new mode 100755 index f0a2f90..adf1b99 --- a/nmlkpy/key_types/keycode.py +++ b/nmlkpy/key_types/keycode.py @@ -1,7 +1,6 @@ -from .base import KeyBase, KeyDelta, KeyEvent +from .base import KeyBase, KeyChanges from ..pin import PinEvent from ..pinstate_manager import PinState -from ..keymap_delta import KeymapDelta _VALID_KEYCODES = range(0, 255) _VALID_MODIFIERS = [0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000] @@ -10,85 +9,13 @@ _VALID_MODIFIERS = [0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000] class KeycodeBase(KeyBase): """Basic press and release functionality that appends a keycode to the report when active""" keycode: int - currently_pressed: bool def __init__(self, keycode: int): - allowed_events = [ - KeyEvent.NO_EVENT, - KeyEvent.PRESSED_, - KeyEvent.RELEASED - ] - super().__init__(allowed_events) self.keycode = keycode - self.currently_pressed = False - def self_test(self, keymap): - raise NotImplementedError - - def soft_release(self, keyboard): - if self.currently_pressed: - self.events.soft_release.queue() - - def _press(self, keyboard): - self.currently_pressed = True - keyboard.add_keycode_to_report(self.keycode) - - def _release(self, keyboard): - self.currently_pressed = False - keyboard.remove_keycode_from_report(self.keycode) - - def _handle_soft_release(self, keyboard): - if self.events.soft_release.is_queued(): - if self.currently_pressed: - self._release(keyboard) - self._set_event(KeyEvent.RELEASED) - self.events.soft_release.reset() - return - - raise NotImplementedError() - - def _consume_next_event(self, pin) -> bool: - """Returns: a boolean representing if the event property was updated with a new event or not""" - next_pin_event = pin.read_event() - current_key_event = self.events.current - - if current_key_event == KeyEvent.RELEASED: - if next_pin_event == PinEvent.PRESSED: - return self._set_event(KeyEvent.PRESSED_) - return self._set_event(KeyEvent.NO_EVENT) - if current_key_event == KeyEvent.PRESSED_: - if next_pin_event == PinEvent.RELEASED: - return self._set_event(KeyEvent.RELEASED) - return self._set_event(KeyEvent.NO_EVENT) - - raise NotImplementedError() - - def _handle_event(self, keyboard) -> None: - current = self.events.current - if self.events.soft_release.is_queued(): - pass - if self.events.soft_release.is_handled(): - pass - if current == KeyEvent.PRESSED_: - self._press(keyboard) - return - if current == KeyEvent.RELEASED: - self._release(keyboard) - return - - raise NotImplementedError() - - def handle(self, keyboard, pin, is_active: bool = False): - new_event_available = self._consume_next_event(pin) - if self.events.soft_release.needs_attention(): - self._handle_soft_release(keyboard) - return - if new_event_available and is_active: - self._handle_event(keyboard) - - def get_delta(self, pin: PinState) -> KeyDelta: + def calculate_changes(self, pin: PinState, custom_state: object) -> KeyChanges: assert pin is not None - changes = KeyDelta() + changes = KeyChanges() if pin.is_pressed(): changes.keymap_delta.press(self.keycode) else: diff --git a/nmlkpy/key_types/layer.py b/nmlkpy/key_types/layer.py old mode 100644 new mode 100755 index bb3ff7e..d55237e --- a/nmlkpy/key_types/layer.py +++ b/nmlkpy/key_types/layer.py @@ -1,193 +1,63 @@ -from ..keymap_delta import KeymapDelta, LayerChange +from ..keymap_delta import LayerChange from ..pinstate_manager import PinState -from .base import KeyDelta, KeyEvent, KeyBase -from ..pin import PinEvent, Pin +from .base import KeyChanges, KeyBase class LayerKeyBase(KeyBase): layer_to_switch_to: int - def __init__(self, layer: int, allowed_events): - super().__init__(allowed_events) + def __init__(self, layer: int): self.layer_to_switch_to = layer - def self_test(self, keymap: list[list[KeyBase]]): - # to be implemented, can not use enrichment of indexes + def self_test(self, keymap: list[list[object]]): pass - def _release_all_other_keys(self, keyboard): - for layer in keyboard.keymap: - for key in layer: - if key is self: - continue - key.soft_release(keyboard) - def _release_all_other_non_toggle_keys(self, keyboard): - for layer in keyboard.keymap: - for key in layer: - if key is self or isinstance(key, Toggle): - continue - key.soft_release(keyboard) +class ToggleCustomState: + _TOGGLE_ON = const(0) + _TOGGLE_OFF = const(1) + + current_state: int + + def __init__(self) -> None: + self.toggle_off + + def toggle_on(self): + self.current_state = ToggleCustomState._TOGGLE_ON + + def toggle_off(self): + self.current_state = ToggleCustomState._TOGGLE_OFF + + def is_on(self) -> int: + return self.current_state == ToggleCustomState._TOGGLE_ON + + def is_toggle_state(obj: object) -> bool: + return isinstance(obj, ToggleCustomState) class Toggle(LayerKeyBase): def __init__(self, layer: int): - allowed_events = [ - KeyEvent.NO_EVENT, - KeyEvent.PRESSED_TOGGLED_ON, - KeyEvent.RELEASED_TOGGLED_ON, - KeyEvent.PRESSED_TOGGLED_OFF, - KeyEvent.RELEASED - ] - super().__init__(layer, allowed_events) + super().__init__(layer) - def _press(self, keyboard): - keyboard.layer.toggle(self.layer_to_switch_to) - self._release_all_other_keys(keyboard) - - def _release(self, keyboard): - keyboard.layer.release_toggled() - self._release_all_other_keys(keyboard) - - def soft_release(self, keyboard): - self.events.soft_release.queue() - - def _handle_soft_release(self, keyboard): - if self.events.soft_release.is_queued(): - # leads to recursion - self._release(keyboard) - self.events.soft_release.mark_as_handled() - return - if self.events.soft_release.is_handled(): - self.events.soft_release.reset() - return - raise NotImplementedError() - - def _consume_next_event(self, pin: Pin) -> bool: - """Returns: a boolean representing if the event property was updated with a new event or not""" - next_pin_event = pin.read_event() - current_key_event = self.events.current - - if current_key_event == KeyEvent.RELEASED: - if next_pin_event == PinEvent.PRESSED: - return self._set_event(KeyEvent.PRESSED_TOGGLED_ON) - return self._set_event(KeyEvent.NO_EVENT) - if current_key_event == KeyEvent.PRESSED_TOGGLED_ON: - if next_pin_event == PinEvent.RELEASED: - return self._set_event(KeyEvent.RELEASED_TOGGLED_ON) - return self._set_event(KeyEvent.NO_EVENT) - if current_key_event == KeyEvent.RELEASED_TOGGLED_ON: - if next_pin_event == PinEvent.PRESSED: - return self._set_event(KeyEvent.PRESSED_TOGGLED_OFF) - return self._set_event(KeyEvent.NO_EVENT) - if current_key_event == KeyEvent.PRESSED_TOGGLED_OFF: - if next_pin_event == PinEvent.RELEASED: - return self._set_event(KeyEvent.RELEASED) - return self._set_event(KeyEvent.NO_EVENT) - - raise NotImplementedError() - - def _handle_event(self, keyboard, pin): - current = self.events.current - if self.events.soft_release.is_queued(): - pass - if self.events.soft_release.is_handled(): - pass - - if current == KeyEvent.PRESSED_TOGGLED_ON: - keyboard.layer.toggle(self.layer_to_switch_to) - self._release_all_other_keys(keyboard) - return - if current == KeyEvent.RELEASED_TOGGLED_ON: - return - if current == KeyEvent.PRESSED_TOGGLED_OFF: - keyboard.layer.release_toggled() - self._release_all_other_keys(keyboard) - return - if current == KeyEvent.RELEASED: - return - - raise NotImplementedError() - - def handle(self, keyboard, pin: Pin, is_active: bool = False): - if self.events.soft_release.needs_attention(): - self._handle_soft_release(keyboard) - return - if self._consume_next_event(pin): - self._handle_event(keyboard, pin) - - -class Hold(LayerKeyBase): - currently_pressed: bool - - def __init__(self, layer: int): - allowed_events = [ - KeyEvent.NO_EVENT, - KeyEvent.PRESSED_, - KeyEvent.RELEASED - ] - super().__init__(layer, allowed_events) - self.currently_pressed = False - - def soft_release(self, keyboard): - if self.currently_pressed: - self.events.soft_release.queue() - - def _handle_soft_release(self, keyboard): - if self.events.soft_release.is_queued(): - if self.currently_pressed: - self._release(keyboard) - self._set_event(KeyEvent.RELEASED) - self.events.soft_release.reset() - return - raise NotImplementedError() - - def handle(self, keyboard, pin, is_active: bool = False): - new_event_available = self._consume_next_event(pin) - if self.events.soft_release.needs_attention(): - self._handle_soft_release(keyboard) - return - if new_event_available and is_active: - self._handle_event(keyboard) - - def _consume_next_event(self, pin: Pin) -> bool: - next_pin_event = pin.read_event() - - if self.events.current == KeyEvent.RELEASED: - if next_pin_event == PinEvent.PRESSED: - return self._set_event(KeyEvent.PRESSED_) - return self._set_event(KeyEvent.NO_EVENT) - if self.events.current == KeyEvent.PRESSED_: - if next_pin_event == PinEvent.RELEASED: - return self._set_event(KeyEvent.RELEASED) - return self._set_event(KeyEvent.NO_EVENT) - - raise NotImplementedError() - - def _handle_event(self, keyboard): - current = self.events.current - - if current == KeyEvent.PRESSED_: - self._press(keyboard) - return - if current == KeyEvent.RELEASED: - self._release(keyboard) - return - - raise NotImplementedError() - - def _press(self, keyboard): - self.currently_pressed = True - keyboard.layer.hold(self.layer_to_switch_to) - self._release_all_other_non_toggle_keys(keyboard) - - def _release(self, keyboard): - self.currently_pressed = False - keyboard.layer.release_held() - self._release_all_other_non_toggle_keys(keyboard) - - def get_delta(self, pin: PinState) -> KeyDelta: - changes = KeyDelta() + def calculate_changes(self, pin: PinState, custom_state: object) -> KeyChanges: + changes = KeyChanges() + if pin.is_pressed(): + changes.layer_delta.apply_layer( + LayerChange(self.layer_to_switch_to)) + else: + changes.layer_delta.remove_layer( + LayerChange(self.layer_to_switch_to)) + return changes + + +class Hold(LayerKeyBase): + currently_pressed: bool + + def __init__(self, layer: int): + super().__init__(layer) + + def calculate_changes(self, pin: PinState, custom_state: object) -> KeyChanges: + changes = KeyChanges() if pin.is_pressed(): changes.layer_delta.apply_layer( LayerChange(self.layer_to_switch_to)) diff --git a/nmlkpy/keyboard.py b/nmlkpy/keyboard.py index d37a66c..5060b1f 100644 --- a/nmlkpy/keyboard.py +++ b/nmlkpy/keyboard.py @@ -223,12 +223,11 @@ class Keyboard: def remove_keycode_from_report(self, keycode: int): try: self.pressed_keys.remove(keycode) - except KeyError: + except ValueError: # silently pass errors when same keycode has been issued twice pass - def _update_layer_led(self) -> None: - layer = self.layer.get() + def _update_layer_led(self, layer: int) -> None: if len(self.layer_colors) >= (layer + 1): self.led.set(self.layer_colors[layer]) else: @@ -260,15 +259,15 @@ class Keyboard: print("Keyboard started") m = KeymapManager(self.keymap, self.pins) while True: - keymap_delta = m.step() - if keymap_delta.contains_keycode_changes(): - for keycode in keymap_delta.get_to_press(): + keymap_report = m.step() + if keymap_report.keymap_changes.contains_keycode_changes(): + for keycode in keymap_report.keymap_changes.get_to_press(): self.add_keycode_to_report(keycode) - for keycode in keymap_delta.get_to_unpress(): + for keycode in keymap_report.keymap_changes.get_to_unpress(): self.remove_keycode_from_report(keycode) self.send_nkro_report() - self._update_layer_led() + self._update_layer_led(keymap_report.current_layer) except Exception as e: if self.debug_repl: raise e diff --git a/nmlkpy/keymap_delta.py b/nmlkpy/keymap_delta.py old mode 100644 new mode 100755 index d8b0d30..2d5b622 --- a/nmlkpy/keymap_delta.py +++ b/nmlkpy/keymap_delta.py @@ -23,6 +23,9 @@ class LayerChange: def get_layer(self) -> int: return self._to_layer + def is_toggle_layer_change(self) -> bool: + return self._is_toggle + class LayerDelta: _layers_to_apply: list[LayerChange] @@ -35,6 +38,24 @@ class LayerDelta: def contains_layer_change(self) -> bool: return len(self._layers_to_apply) + len(self._layers_to_remove) > 0 + def contains_layers_to_apply(self) -> bool: + return len(self.get_layers_to_apply()) > 0 + + def contains_layers_to_remove(self) -> bool: + return len(self.get_layers_to_remove()) > 0 + + def get_hold_layers_to_apply(self) -> list[LayerChange]: + return filter(lambda delta: not delta.is_toggle_layer_change(), self._layers_to_apply) + + def get_hold_layers_to_remove(self) -> list[LayerChange]: + return filter(lambda delta: not delta.is_toggle_layer_change(), self._layers_to_remove) + + def get_toggle_layers_to_apply(self) -> list[LayerChange]: + return filter(lambda delta: delta.is_toggle_layer_change(), self._layers_to_apply) + + def get_toggle_layers_to_remove(self) -> list[LayerChange]: + return filter(lambda delta: delta.is_toggle_layer_change(), self._layers_to_remove) + def get_layers_to_apply(self) -> list[LayerChange]: return self._layers_to_apply @@ -57,27 +78,35 @@ class LayerDelta: class KeymapDelta: - _keycodes_to_press: list[int] - _keycodes_to_unpress: list[int] + _keycodes_to_press: set[int] + _keycodes_to_unpress: set[int] def __init__(self) -> None: - self._keycodes_to_press = [] - self._keycodes_to_unpress = [] + self._keycodes_to_press = set() + self._keycodes_to_unpress = set() - def get_to_press(self) -> list[int]: + def get_to_press(self) -> set[int]: return self._keycodes_to_press - def get_to_unpress(self) -> list[int]: + def get_to_unpress(self) -> set[int]: return self._keycodes_to_unpress def contains_keycode_changes(self) -> bool: return len(self._keycodes_to_unpress) + len(self._keycodes_to_press) > 0 def press(self, keycode: int) -> None: - self._keycodes_to_press.append(keycode) + self._keycodes_to_press.add(keycode) + try: + self._keycodes_to_unpress.remove(keycode) + except KeyError: + pass def unpress(self, keycode: int) -> None: - self._keycodes_to_unpress.append(keycode) + self._keycodes_to_unpress.add(keycode) + try: + self._keycodes_to_press.remove(keycode) + except KeyError: + pass def merge_deltas(self, other_delta) -> None: for keycode in other_delta.get_to_press(): diff --git a/nmlkpy/keymap_manager.py b/nmlkpy/keymap_manager.py old mode 100644 new mode 100755 index b36ed33..c586fac --- a/nmlkpy/keymap_manager.py +++ b/nmlkpy/keymap_manager.py @@ -1,12 +1,8 @@ -from nmlkpy.key_types.layer import Hold -from .keymap_delta import KeymapDelta, LayerChange, LayerDelta -from .pinstate_manager import PinState, PinManager -from .key_types.base import KeyBase -from .key_types.keycode import KeycodeBase -from .pin import Pin -from micropython import const -from nmlkpy import keymap_delta +from .keymap_delta import KeymapDelta, LayerDelta +from .pinstate_manager import PinManager +from .key_types.base import KeyBase +from .pin import Pin # Needs to be built in a way where every "key type" only has to know how to modify it's own state @@ -35,51 +31,110 @@ from nmlkpy import keymap_delta # Pins # Can be asked for their current event # Pins state is a class that holds current and last, can give you the delta to figure out what to change -_LAYERS_NONE = 0 +_LAYERS_HOLD_NONE = None +_LAYERS_TOGGLE_NONE = 0 class LayerManager: number_of_layers: int = None - current_layer: int = None + current_hold_layer: int = None + current_toggle_layer: int = None def __init__(self, number_of_layers: int) -> None: assert number_of_layers is not None assert number_of_layers > 0 self.number_of_layers = number_of_layers - self.current_layer = _LAYERS_NONE + self.current_hold_layer = _LAYERS_HOLD_NONE + self.current_toggle_layer = _LAYERS_TOGGLE_NONE def get_current_layer(self) -> int: - return self.current_layer + return self.current_hold_layer if self.current_hold_layer is not _LAYERS_HOLD_NONE else self.current_toggle_layer - def handle_layer_changes(self, layer): - pass + def step(self, changes: LayerDelta): + hold_layer = self.current_hold_layer + for layer in changes.get_hold_layers_to_remove(): + if layer.get_layer() == hold_layer: + hold_layer = _LAYERS_HOLD_NONE + for layer in changes.get_hold_layers_to_apply(): + hold_layer = layer.get_layer() + self.current_hold_layer = hold_layer + + toggle_layer = self.current_toggle_layer + for layer in changes.get_toggle_layers_to_remove(): + if layer.get_layer() == toggle_layer: + toggle_layer = _LAYERS_TOGGLE_NONE + for layer in changes.get_toggle_layers_to_apply(): + toggle_layer = layer.get_layer() + self.current_toggle_layer = toggle_layer + + +class KeymapReport: + keymap_changes: KeymapDelta = None + current_layer: int = None + + def __init__(self, keymap_changes: KeymapDelta, current_layer: int) -> None: + assert keymap_changes is not None + assert current_layer is not None + self.keymap_changes = keymap_changes + self.current_layer = current_layer class KeymapManager: keymap: list[list[KeyBase]] = None pin_manager: PinManager = None layer_manager: LayerManager = None + keys_state: list[object] = None + keycodes_currently_pressed: set = None def __init__(self, keymap: list[list[KeyBase]], pins: list[Pin]): self.keymap = keymap self.pin_manager = PinManager(pins) self.layer_manager = LayerManager(len(keymap)) + self.keys_state = [None for key in keymap[0]] + self.keycodes_currently_pressed = set() - def step(self) -> KeymapDelta: + def add_to_pressed_keycodes(self, keycode: int): + self.keycodes_currently_pressed.add(keycode) + + def remove_from_pressed_keycodes(self, keycode: int): + try: + self.keycodes_currently_pressed.remove(keycode) + except KeyError: + pass + + def createReport(self, keymap_changes: KeymapDelta, layer_changes: LayerDelta) -> KeymapReport: + old_layer = self.layer_manager.get_current_layer() + self.layer_manager.step(layer_changes) + new_layer = self.layer_manager.get_current_layer() + layer_has_changed = new_layer != old_layer + if layer_has_changed: + delta = KeymapDelta() + for keycode in self.keycodes_currently_pressed: + delta.unpress(keycode) + self.keycodes_currently_pressed.clear() + keymap_changes.merge_deltas(delta) + [self.add_to_pressed_keycodes(keycode) + for keycode in keymap_changes.get_to_press()] + [self.remove_from_pressed_keycodes( + keycode) for keycode in keymap_changes.get_to_unpress()] + return KeymapReport(keymap_changes, new_layer) + + def step(self) -> KeymapReport: self.pin_manager.step() pin_changes = self.pin_manager.get_state_delta() keymap_changes = KeymapDelta() layer_changes = LayerDelta() - layer_index = self.layer_manager.get_current_layer() + current_layer_index = self.layer_manager.get_current_layer() for pin_index in range(len(pin_changes)): pin_state = pin_changes[pin_index] if pin_state is None: continue - key = self.keymap[layer_index][pin_index] - if isinstance(key, KeycodeBase) or isinstance(key, Hold): - key_changes = key.get_delta(pin_state) - keymap_changes.merge_deltas(key_changes.keymap_delta) - layer_changes.merge_deltas(key_changes.layer_delta) + key = self.keymap[current_layer_index][pin_index] + custom_state = self.keys_state[pin_index] + key_changes = key.calculate_changes(pin_state, custom_state) + keymap_changes.merge_deltas(key_changes.keymap_delta) + layer_changes.merge_deltas(key_changes.layer_delta) + self.keys_state[pin_index] = key_changes.special_state - return keymap_changes + return self.createReport(keymap_changes, layer_changes)