unpress previously pressed keys on layer change

This commit is contained in:
2022-01-30 14:27:42 +01:00
parent 09b8206742
commit df1522df6c
17 changed files with 190 additions and 417 deletions

0
boot.py Normal file → Executable file
View File

0
code.py Normal file → Executable file
View File

View File

@@ -1,34 +1,33 @@
from nmlkpy.key_codes.se import SE from nmlkpy.key_codes.se import SE
from nmlkpy.key_types.keycode import Keycode, Modifier from nmlkpy.key_types.keycode import Keycode, Modifier
from nmlkpy.key_types.layer import Hold, Toggle
from nmlkpy.layer_manager import LayerManager from nmlkpy.layer_manager import LayerManager
lm = LayerManager(5)
keymap = [[ keymap = [[
lm.hold(1), lm.toggle(2), lm.toggle(3), lm.toggle(4), Hold(1), Toggle(2), Toggle(3), Toggle(4),
lm.hold(2), Keycode(SE.B), Keycode(SE.C), Keycode(SE.D), Hold(2), Keycode(SE.B), Keycode(SE.C), Keycode(SE.D),
Keycode(SE.LEFT), Keycode(SE.DOWN), Keycode( Keycode(SE.LEFT), Keycode(SE.DOWN), Keycode(
SE.UP), Keycode(SE.RIGHT), SE.UP), Keycode(SE.RIGHT),
Keycode(SE.ENTER), Keycode(SE.J), Keycode( Keycode(SE.ENTER), Keycode(SE.J), Keycode(
SE.K), Modifier(SE.LEFT_SHIFT) SE.K), Modifier(SE.LEFT_SHIFT)
], [ ], [
lm.hold(1), lm.toggle(2), lm.toggle(3), lm.toggle(4), Hold(1), Toggle(2), Toggle(3), Toggle(4),
Keycode(SE.ONE), lm.hold(1), Keycode(SE.C), Keycode(SE.D), 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.E), Keycode(SE.F), Keycode(SE.G), Keycode(SE.H),
Keycode(SE.BACKSPACE), Keycode(SE.J), Keycode(SE.K), Keycode(SE.L) Keycode(SE.BACKSPACE), Keycode(SE.J), Keycode(SE.K), Keycode(SE.L)
], [ ], [
lm.hold(1), lm.toggle(2), lm.toggle(3), lm.toggle(4), Hold(1), Toggle(2), Toggle(3), Toggle(4),
Keycode(SE.A), lm.hold(1), lm.hold(2), Keycode(SE.D), Hold(2), Hold(1), Hold(2), Keycode(SE.D),
Keycode(SE.E), Keycode(SE.F), Keycode(SE.G), Keycode(SE.H), Keycode(SE.E), Keycode(SE.F), Keycode(SE.G), Keycode(SE.H),
Keycode(SE.I), Keycode(SE.J), Keycode(SE.K), Keycode(SE.L) Keycode(SE.I), Keycode(SE.J), Keycode(SE.K), Keycode(SE.L)
], [ ], [
lm.hold(1), lm.toggle(2), lm.toggle(3), lm.toggle(4), Hold(1), Toggle(2), Toggle(3), Toggle(4),
Keycode(SE.A), Keycode(SE.B), lm.hold(2), lm.hold(3), Keycode(SE.A), Keycode(SE.B), Hold(2), Hold(3),
Keycode(SE.E), Keycode(SE.F), Keycode(SE.G), Keycode(SE.H), Keycode(SE.E), Keycode(SE.F), Keycode(SE.G), Keycode(SE.H),
Keycode(SE.I), Keycode(SE.J), Keycode(SE.K), Keycode(SE.L) Keycode(SE.I), Keycode(SE.J), Keycode(SE.K), Keycode(SE.L)
], [ ], [
lm.hold(1), lm.toggle(2), lm.toggle(3), lm.toggle(4), Hold(1), Toggle(2), Toggle(3), Toggle(4),
Keycode(SE.A), Keycode(SE.B), Keycode(SE.C), lm.hold(3), 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.E), Keycode(SE.F), Keycode(SE.G), Keycode(SE.H),
Keycode(SE.I), Keycode(SE.J), Keycode(SE.K), Modifier(SE.LEFT_ALT) Keycode(SE.I), Keycode(SE.J), Keycode(SE.K), Modifier(SE.LEFT_ALT)
]] ]]

0
keyboards/macropad_rev1/default/pinouts.py Normal file → Executable file
View File

0
lib/adafruit_mcp230xx/__init__.mpy Normal file → Executable file
View File

0
lib/adafruit_mcp230xx/digital_inout.mpy Normal file → Executable file
View File

0
lib/adafruit_mcp230xx/mcp23008.mpy Normal file → Executable file
View File

0
lib/adafruit_mcp230xx/mcp23016.mpy Normal file → Executable file
View File

0
lib/adafruit_mcp230xx/mcp23017.mpy Normal file → Executable file
View File

0
lib/adafruit_mcp230xx/mcp230xx.mpy Normal file → Executable file
View File

0
lib/adafruit_mcp230xx/mcp23sxx.mpy Normal file → Executable file
View File

128
nmlkpy/key_types/base.py Normal file → Executable file
View File

@@ -1,135 +1,29 @@
from micropython import const from nmlkpy.keymap_delta import KeyIdentifier, KeymapDelta, LayerDelta
from nmlkpy import keymap_delta
from nmlkpy.keymap_delta import KeymapDelta, LayerDelta
from nmlkpy.pinstate_manager import PinState from nmlkpy.pinstate_manager import PinState
class KeyDelta: class KeyChanges:
layer_delta: LayerDelta layer_delta: LayerDelta
keymap_delta: KeymapDelta keymap_delta: KeymapDelta
special_state: object
def __init__(self) -> None: def __init__(self) -> None:
self.layer_delta = LayerDelta() self.layer_delta = LayerDelta()
self.keymap_delta = KeymapDelta() self.keymap_delta = KeymapDelta()
self.special_state = None
def set_special_state(self, state: object) -> None:
class KeyEvent: self.special_state = state
"""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
class KeyBase: class KeyBase:
events: KeyEvents #identifier: KeyIdentifier
allowed_events: list[KeyEvent] # def enrich(self, identifier: KeyIdentifier):
# assert identifier is not None
# self.identifier = identifier
def __init__(self, allowed_events: list[int]): def calculate_changes(self, pin: PinState, custom_state: object) -> KeyChanges:
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):
raise NotImplementedError() raise NotImplementedError()
def self_test(self, keymap: list[list[object]]): def self_test(self, keymap: list[list[object]]):
raise NotImplementedError() raise NotImplementedError()
def soft_release(self, keyboard):
"""Release key programatically and wait until it's released physically before registrating additional key presses"""
raise NotImplementedError()

79
nmlkpy/key_types/keycode.py Normal file → Executable file
View File

@@ -1,7 +1,6 @@
from .base import KeyBase, KeyDelta, KeyEvent from .base import KeyBase, KeyChanges
from ..pin import PinEvent from ..pin import PinEvent
from ..pinstate_manager import PinState from ..pinstate_manager import PinState
from ..keymap_delta import KeymapDelta
_VALID_KEYCODES = range(0, 255) _VALID_KEYCODES = range(0, 255)
_VALID_MODIFIERS = [0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000] _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): class KeycodeBase(KeyBase):
"""Basic press and release functionality that appends a keycode to the report when active""" """Basic press and release functionality that appends a keycode to the report when active"""
keycode: int keycode: int
currently_pressed: bool
def __init__(self, keycode: int): def __init__(self, keycode: int):
allowed_events = [
KeyEvent.NO_EVENT,
KeyEvent.PRESSED_,
KeyEvent.RELEASED
]
super().__init__(allowed_events)
self.keycode = keycode self.keycode = keycode
self.currently_pressed = False
def self_test(self, keymap): def calculate_changes(self, pin: PinState, custom_state: object) -> KeyChanges:
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:
assert pin is not None assert pin is not None
changes = KeyDelta() changes = KeyChanges()
if pin.is_pressed(): if pin.is_pressed():
changes.keymap_delta.press(self.keycode) changes.keymap_delta.press(self.keycode)
else: else:

218
nmlkpy/key_types/layer.py Normal file → Executable file
View File

@@ -1,193 +1,63 @@
from ..keymap_delta import KeymapDelta, LayerChange from ..keymap_delta import LayerChange
from ..pinstate_manager import PinState from ..pinstate_manager import PinState
from .base import KeyDelta, KeyEvent, KeyBase from .base import KeyChanges, KeyBase
from ..pin import PinEvent, Pin
class LayerKeyBase(KeyBase): class LayerKeyBase(KeyBase):
layer_to_switch_to: int layer_to_switch_to: int
def __init__(self, layer: int, allowed_events): def __init__(self, layer: int):
super().__init__(allowed_events)
self.layer_to_switch_to = layer self.layer_to_switch_to = layer
def self_test(self, keymap: list[list[KeyBase]]): def self_test(self, keymap: list[list[object]]):
# to be implemented, can not use enrichment of indexes
pass 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): class ToggleCustomState:
for layer in keyboard.keymap: _TOGGLE_ON = const(0)
for key in layer: _TOGGLE_OFF = const(1)
if key is self or isinstance(key, Toggle):
continue current_state: int
key.soft_release(keyboard)
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): class Toggle(LayerKeyBase):
def __init__(self, layer: int): def __init__(self, layer: int):
allowed_events = [ super().__init__(layer)
KeyEvent.NO_EVENT,
KeyEvent.PRESSED_TOGGLED_ON,
KeyEvent.RELEASED_TOGGLED_ON,
KeyEvent.PRESSED_TOGGLED_OFF,
KeyEvent.RELEASED
]
super().__init__(layer, allowed_events)
def _press(self, keyboard): def calculate_changes(self, pin: PinState, custom_state: object) -> KeyChanges:
keyboard.layer.toggle(self.layer_to_switch_to) changes = KeyChanges()
self._release_all_other_keys(keyboard) if pin.is_pressed():
changes.layer_delta.apply_layer(
def _release(self, keyboard): LayerChange(self.layer_to_switch_to))
keyboard.layer.release_toggled() else:
self._release_all_other_keys(keyboard) changes.layer_delta.remove_layer(
LayerChange(self.layer_to_switch_to))
def soft_release(self, keyboard): return changes
self.events.soft_release.queue()
def _handle_soft_release(self, keyboard): class Hold(LayerKeyBase):
if self.events.soft_release.is_queued(): currently_pressed: bool
# leads to recursion
self._release(keyboard) def __init__(self, layer: int):
self.events.soft_release.mark_as_handled() super().__init__(layer)
return
if self.events.soft_release.is_handled(): def calculate_changes(self, pin: PinState, custom_state: object) -> KeyChanges:
self.events.soft_release.reset() changes = KeyChanges()
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()
if pin.is_pressed(): if pin.is_pressed():
changes.layer_delta.apply_layer( changes.layer_delta.apply_layer(
LayerChange(self.layer_to_switch_to)) LayerChange(self.layer_to_switch_to))

View File

@@ -223,12 +223,11 @@ class Keyboard:
def remove_keycode_from_report(self, keycode: int): def remove_keycode_from_report(self, keycode: int):
try: try:
self.pressed_keys.remove(keycode) self.pressed_keys.remove(keycode)
except KeyError: except ValueError:
# silently pass errors when same keycode has been issued twice # silently pass errors when same keycode has been issued twice
pass pass
def _update_layer_led(self) -> None: def _update_layer_led(self, layer: int) -> None:
layer = self.layer.get()
if len(self.layer_colors) >= (layer + 1): if len(self.layer_colors) >= (layer + 1):
self.led.set(self.layer_colors[layer]) self.led.set(self.layer_colors[layer])
else: else:
@@ -260,15 +259,15 @@ class Keyboard:
print("Keyboard started") print("Keyboard started")
m = KeymapManager(self.keymap, self.pins) m = KeymapManager(self.keymap, self.pins)
while True: while True:
keymap_delta = m.step() keymap_report = m.step()
if keymap_delta.contains_keycode_changes(): if keymap_report.keymap_changes.contains_keycode_changes():
for keycode in keymap_delta.get_to_press(): for keycode in keymap_report.keymap_changes.get_to_press():
self.add_keycode_to_report(keycode) 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.remove_keycode_from_report(keycode)
self.send_nkro_report() self.send_nkro_report()
self._update_layer_led() self._update_layer_led(keymap_report.current_layer)
except Exception as e: except Exception as e:
if self.debug_repl: if self.debug_repl:
raise e raise e

45
nmlkpy/keymap_delta.py Normal file → Executable file
View File

@@ -23,6 +23,9 @@ class LayerChange:
def get_layer(self) -> int: def get_layer(self) -> int:
return self._to_layer return self._to_layer
def is_toggle_layer_change(self) -> bool:
return self._is_toggle
class LayerDelta: class LayerDelta:
_layers_to_apply: list[LayerChange] _layers_to_apply: list[LayerChange]
@@ -35,6 +38,24 @@ class LayerDelta:
def contains_layer_change(self) -> bool: def contains_layer_change(self) -> bool:
return len(self._layers_to_apply) + len(self._layers_to_remove) > 0 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]: def get_layers_to_apply(self) -> list[LayerChange]:
return self._layers_to_apply return self._layers_to_apply
@@ -57,27 +78,35 @@ class LayerDelta:
class KeymapDelta: class KeymapDelta:
_keycodes_to_press: list[int] _keycodes_to_press: set[int]
_keycodes_to_unpress: list[int] _keycodes_to_unpress: set[int]
def __init__(self) -> None: def __init__(self) -> None:
self._keycodes_to_press = [] self._keycodes_to_press = set()
self._keycodes_to_unpress = [] self._keycodes_to_unpress = set()
def get_to_press(self) -> list[int]: def get_to_press(self) -> set[int]:
return self._keycodes_to_press return self._keycodes_to_press
def get_to_unpress(self) -> list[int]: def get_to_unpress(self) -> set[int]:
return self._keycodes_to_unpress return self._keycodes_to_unpress
def contains_keycode_changes(self) -> bool: def contains_keycode_changes(self) -> bool:
return len(self._keycodes_to_unpress) + len(self._keycodes_to_press) > 0 return len(self._keycodes_to_unpress) + len(self._keycodes_to_press) > 0
def press(self, keycode: int) -> None: 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: 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: def merge_deltas(self, other_delta) -> None:
for keycode in other_delta.get_to_press(): for keycode in other_delta.get_to_press():

99
nmlkpy/keymap_manager.py Normal file → Executable file
View File

@@ -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 # 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 # Pins
# Can be asked for their current event # 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 # 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: class LayerManager:
number_of_layers: int = None 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: def __init__(self, number_of_layers: int) -> None:
assert number_of_layers is not None assert number_of_layers is not None
assert number_of_layers > 0 assert number_of_layers > 0
self.number_of_layers = number_of_layers 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: 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): def step(self, changes: LayerDelta):
pass 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: class KeymapManager:
keymap: list[list[KeyBase]] = None keymap: list[list[KeyBase]] = None
pin_manager: PinManager = None pin_manager: PinManager = None
layer_manager: LayerManager = 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]): def __init__(self, keymap: list[list[KeyBase]], pins: list[Pin]):
self.keymap = keymap self.keymap = keymap
self.pin_manager = PinManager(pins) self.pin_manager = PinManager(pins)
self.layer_manager = LayerManager(len(keymap)) 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() self.pin_manager.step()
pin_changes = self.pin_manager.get_state_delta() pin_changes = self.pin_manager.get_state_delta()
keymap_changes = KeymapDelta() keymap_changes = KeymapDelta()
layer_changes = LayerDelta() 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)): for pin_index in range(len(pin_changes)):
pin_state = pin_changes[pin_index] pin_state = pin_changes[pin_index]
if pin_state is None: if pin_state is None:
continue continue
key = self.keymap[layer_index][pin_index] key = self.keymap[current_layer_index][pin_index]
if isinstance(key, KeycodeBase) or isinstance(key, Hold): custom_state = self.keys_state[pin_index]
key_changes = key.get_delta(pin_state) key_changes = key.calculate_changes(pin_state, custom_state)
keymap_changes.merge_deltas(key_changes.keymap_delta) keymap_changes.merge_deltas(key_changes.keymap_delta)
layer_changes.merge_deltas(key_changes.layer_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)