353 lines
14 KiB
Python
353 lines
14 KiB
Python
from micropython import const
|
|
from pin import Pin, PinEvent
|
|
|
|
_VALID_KEYCODES = range(0, 255)
|
|
_VALID_MODIFIERS = [0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000]
|
|
|
|
|
|
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"""
|
|
SOFT_RELEASED: int = const(3)
|
|
"""The key was released programatically, and not by an actual physical release of a button."""
|
|
PRESSED_TOGGLED_ON = const(4)
|
|
"""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(5)
|
|
"""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(6)
|
|
"""Key is pressed and is emulating a toggle key in OFF state"""
|
|
|
|
|
|
class KeyEvents:
|
|
current: int
|
|
previous: int
|
|
|
|
def __init__(self):
|
|
self.current = KeyEvent.RELEASED
|
|
self.previous = KeyEvent.RELEASED
|
|
|
|
def shift(self, next):
|
|
print(
|
|
f"next: {next}, current: {self.current}, previous: {self.previous}")
|
|
self.previous = self.current
|
|
self.current = next
|
|
|
|
|
|
class KeyBase:
|
|
events: KeyEvents
|
|
allowed_events: list[KeyEvent]
|
|
key_index: int
|
|
layer_index: int
|
|
|
|
def __init__(self, allowed_events: list[int]):
|
|
self.allowed_events = allowed_events
|
|
self.events = KeyEvents()
|
|
self.key_index = None
|
|
self.layer_index = None
|
|
|
|
def enrich(self, key_index: int, layer_index: int):
|
|
self.key_index = key_index
|
|
self.layer_index = layer_index
|
|
|
|
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) -> 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
|
|
self.events.shift(next_event)
|
|
return True
|
|
|
|
def handle(self, keyboard):
|
|
raise NotImplementedError()
|
|
|
|
def self_test(self, keymap: list[list[object]]):
|
|
raise NotImplementedError()
|
|
|
|
def soft_release(self, keyboard, pin):
|
|
"""Release key programatically and wait until it's released physically before registrating additional key presses"""
|
|
raise NotImplementedError()
|
|
|
|
|
|
class KeycodeBase(KeyBase):
|
|
"""Basic press and release functionality that appends a keycode to the report when active"""
|
|
keycode: int
|
|
|
|
def __init__(self, keycode: int):
|
|
allowed_events = [KeyEvent.NO_EVENT,
|
|
KeyEvent.PRESSED_,
|
|
KeyEvent.RELEASED,
|
|
KeyEvent.SOFT_RELEASED]
|
|
super().__init__(allowed_events)
|
|
self.keycode = keycode
|
|
|
|
def self_test(self, keymap):
|
|
raise NotImplementedError
|
|
|
|
def soft_release(self, keyboard, pin):
|
|
if self.events.current != KeyEvent.PRESSED_:
|
|
return
|
|
self._set_event(KeyEvent.SOFT_RELEASED)
|
|
|
|
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, previous_key_event = self.events.current, self.events.previous
|
|
next_key_event = None
|
|
if current_key_event == KeyEvent.NO_EVENT:
|
|
if next_pin_event == PinEvent.NO_EVENT:
|
|
next_key_event = KeyEvent.NO_EVENT
|
|
if next_pin_event == PinEvent.RELEASED:
|
|
next_key_event = KeyEvent.RELEASED
|
|
if next_pin_event == PinEvent.PRESSED:
|
|
next_key_event = KeyEvent.PRESSED_
|
|
if current_key_event == KeyEvent.RELEASED:
|
|
if next_pin_event == PinEvent.NO_EVENT:
|
|
next_key_event = KeyEvent.NO_EVENT
|
|
if next_pin_event == PinEvent.RELEASED:
|
|
next_key_event = KeyEvent.NO_EVENT
|
|
if next_pin_event == PinEvent.PRESSED:
|
|
next_key_event = KeyEvent.PRESSED_
|
|
if current_key_event == KeyEvent.PRESSED_:
|
|
if next_pin_event == PinEvent.NO_EVENT:
|
|
next_key_event = KeyEvent.NO_EVENT
|
|
if next_pin_event == PinEvent.RELEASED:
|
|
next_key_event = KeyEvent.RELEASED
|
|
if next_pin_event == PinEvent.PRESSED:
|
|
next_key_event = KeyEvent.NO_EVENT
|
|
if current_key_event == KeyEvent.SOFT_RELEASED:
|
|
if next_pin_event == PinEvent.NO_EVENT:
|
|
next_key_event = KeyEvent.NO_EVENT
|
|
if next_pin_event == PinEvent.RELEASED:
|
|
next_key_event = KeyEvent.RELEASED
|
|
if next_pin_event == PinEvent.PRESSED:
|
|
next_key_event = KeyEvent.NO_EVENT
|
|
|
|
return self._set_event(next_key_event)
|
|
|
|
def _handle_event(self, keyboard) -> None:
|
|
current = self.events.current
|
|
if current == KeyEvent.NO_EVENT:
|
|
return
|
|
if current == KeyEvent.PRESSED_:
|
|
keyboard.add_keycode_to_report(self.keycode)
|
|
if current == KeyEvent.RELEASED:
|
|
keyboard.remove_keycode_from_report(self.keycode)
|
|
if current == KeyEvent.SOFT_RELEASED:
|
|
keyboard.remove_keycode_from_report(self.keycode)
|
|
|
|
def handle(self, keyboard, pin):
|
|
if not self._consume_next_event(pin):
|
|
return
|
|
self._handle_event(keyboard)
|
|
|
|
|
|
class Keycode(KeycodeBase):
|
|
def __init__(self, keycode):
|
|
super().__init__(keycode)
|
|
|
|
def self_test(self, keymap):
|
|
if not self.keycode in _VALID_KEYCODES:
|
|
raise Exception("Keycode is not valid")
|
|
|
|
|
|
class Modifier(KeycodeBase):
|
|
def __init__(self, keycode):
|
|
super().__init__(keycode)
|
|
|
|
def self_test(self, keymap):
|
|
if not self.keycode in _VALID_MODIFIERS:
|
|
raise Exception("Keycode is not valid")
|
|
|
|
|
|
class LayerKeyBase(KeyBase):
|
|
layer_to_switch_to: int
|
|
|
|
def __init__(self, layer: int, allowed_events):
|
|
super().__init__(allowed_events)
|
|
self.layer_to_switch_to = layer
|
|
|
|
|
|
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,
|
|
KeyEvent.SOFT_RELEASED]
|
|
has_received_state_from = None
|
|
super().__init__(layer, allowed_events)
|
|
|
|
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 next_pin_event == PinEvent.NO_EVENT:
|
|
return self._set_event(KeyEvent.NO_EVENT)
|
|
if current_key_event == KeyEvent.PRESSED_TOGGLED_ON:
|
|
if next_pin_event == PinEvent.PRESSED:
|
|
return self._set_event(KeyEvent.NO_EVENT)
|
|
if next_pin_event == PinEvent.RELEASED:
|
|
return self._set_event(KeyEvent.RELEASED_TOGGLED_ON)
|
|
if current_key_event == KeyEvent.RELEASED_TOGGLED_ON:
|
|
if next_pin_event == PinEvent.PRESSED:
|
|
return self._set_event(KeyEvent.PRESSED_TOGGLED_OFF)
|
|
if next_pin_event == PinEvent.RELEASED:
|
|
return self._set_event(KeyEvent.NO_EVENT)
|
|
if current_key_event == KeyEvent.PRESSED_TOGGLED_OFF:
|
|
if next_pin_event == PinEvent.PRESSED:
|
|
return self._set_event(KeyEvent.NO_EVENT)
|
|
if next_pin_event == PinEvent.RELEASED:
|
|
return self._set_event(KeyEvent.RELEASED)
|
|
if current_key_event == KeyEvent.RELEASED:
|
|
if next_pin_event == PinEvent.PRESSED:
|
|
return self._set_event(KeyEvent.PRESSED_TOGGLED_ON)
|
|
if next_pin_event == PinEvent.RELEASED:
|
|
return self._set_event(KeyEvent.NO_EVENT)
|
|
if current_key_event == KeyEvent.SOFT_RELEASED:
|
|
if next_pin_event == PinEvent.RELEASED:
|
|
return self._set_event(KeyEvent.RELEASED)
|
|
else:
|
|
return self._set_event(KeyEvent.NO_EVENT)
|
|
|
|
raise NotImplementedError()
|
|
|
|
def _release_all_other_keys(self, keyboard, pin: Pin):
|
|
for layer in keyboard.keymap:
|
|
for key in layer:
|
|
if key is self:
|
|
continue
|
|
pin_of_key = keyboard.pins[layer.index(key)]
|
|
key.soft_release(keyboard, pin_of_key)
|
|
|
|
def _handle_event(self, keyboard, pin):
|
|
current = self.events.current
|
|
if current == KeyEvent.PRESSED_TOGGLED_ON:
|
|
keyboard.layer.toggle(self.layer_to_switch_to)
|
|
self._release_all_other_keys(keyboard, pin)
|
|
self._swap_state(keyboard)
|
|
if current == KeyEvent.RELEASED_TOGGLED_ON:
|
|
pass
|
|
if current == KeyEvent.PRESSED_TOGGLED_OFF:
|
|
keyboard.layer.release_toggled()
|
|
self._release_all_other_keys(keyboard, pin)
|
|
self._swap_state_back(keyboard)
|
|
if current == KeyEvent.RELEASED:
|
|
pass
|
|
|
|
def handle(self, keyboard, pin: Pin):
|
|
if not self._consume_next_event(pin):
|
|
return
|
|
self._handle_event(keyboard, pin)
|
|
|
|
def self_test(self, keymap: list[list[object]]):
|
|
raise NotImplementedError()
|
|
|
|
def soft_release(self, keyboard, pin):
|
|
pressed = self.events.current not in [
|
|
KeyEvent.RELEASED, KeyEvent.PRESSED_TOGGLED_OFF, KeyEvent.SOFT_RELEASED]
|
|
if not pressed:
|
|
return
|
|
print(f"releasing {keyboard}, {self.events.current}")
|
|
self.has_received_state_from = None
|
|
self._set_event(KeyEvent.SOFT_RELEASED)
|
|
|
|
def _swap_state(self, keyboard) -> None:
|
|
key_to_swap_with = keyboard.get_key(
|
|
self.layer_to_switch_to, self.key_index)
|
|
other_keys_state = key_to_swap_with._handle_on_swap_state(
|
|
self.events, (self.layer_index, self.key_index))
|
|
self.events = other_keys_state
|
|
|
|
def _swap_state_back(self, keyboard) -> None:
|
|
if not self.has_received_state_from:
|
|
return
|
|
key_to_swap_with = keyboard.get_key(
|
|
self.has_received_state_from[0], self.has_received_state_from[1])
|
|
other_keys_state = key_to_swap_with._handle_on_swap_state(
|
|
self.events, None)
|
|
self.events = other_keys_state
|
|
|
|
def _handle_on_swap_state(self, received_state: KeyEvents, indexes: tuple[int, int]) -> KeyEvents:
|
|
"""Invoked on the Toggle key that is present on the layer another Toggle key is switching to
|
|
Makes sure that the state is carried over so that pressing the button once more returns to the old layer."""
|
|
self.has_received_state_from = indexes
|
|
state_to_send_back = self.events
|
|
self.events = received_state
|
|
return state_to_send_back
|
|
|
|
# todo: Release all keys not the same as the old layer
|
|
# todo: Debounce old toggle when pressing new toggle
|
|
# if type(key) is Toggle:
|
|
# if currentlyPressed and not previouslyToggled and not previouslyToggledReleased and not previouslyUntoggledPressed:
|
|
# self.toggled_layer = key.layer
|
|
# self.held_layer = None
|
|
# self.pin_states_last_cycle[pin_index] = (
|
|
# __TOGGLED_PRESSED, key)
|
|
# for i in range(len(self.pin_states_last_cycle)):
|
|
# if i == pin_index or len(self.pin_states_last_cycle[i]) == 1:
|
|
# continue
|
|
# value, last_keycode = self.pin_states_last_cycle[i]
|
|
# if type(last_keycode) is Toggle:
|
|
# self.pin_states_last_cycle[i] = (
|
|
# __UNPRESSED,)
|
|
# if type(last_keycode) is Hold:
|
|
# self.held_layer = None
|
|
# self.pin_states_last_cycle[i] = (
|
|
# __DEBOUNCE,)
|
|
# continue
|
|
# if not currentlyPressed and previouslyToggled:
|
|
# self.pin_states_last_cycle[pin_index] = (
|
|
# __TOGGLED_RELEASED, key)
|
|
# continue
|
|
# if currentlyPressed and previouslyToggledReleased:
|
|
# self.toggled_layer = 0
|
|
# self.pin_states_last_cycle[pin_index] = (
|
|
# __UNTOGGLED_PRESSED, key)
|
|
# continue
|
|
# if not currentlyPressed and previouslyUntoggledPressed:
|
|
# self.pin_states_last_cycle[pin_index] = (
|
|
# __UNPRESSED,)
|
|
# continue
|
|
#
|
|
|
|
|
|
class Hold(LayerKeyBase):
|
|
def __init__(self, layer: int):
|
|
allowed_events = [KeyEvent.SOFT_RELEASED]
|
|
super().__init__(layer, allowed_events)
|
|
|
|
def soft_release(self, keyboard, pin):
|
|
pass
|
|
|
|
# todo: Release all keys not the same as the old layer
|
|
# if type(key) is Hold:
|
|
# if not currentlyPressed and previouslyDebounced:
|
|
# self.pin_states_last_cycle[pin_index] = (
|
|
# __UNPRESSED,)
|
|
#
|
|
# if currentlyPressed and not previouslyPressed and not previouslyDebounced:
|
|
# self.held_layer = key.layer
|
|
# self.pin_states_last_cycle[pin_index] = (
|
|
# __PRESSED, key)
|
|
# continue
|
|
# if previouslyPressed and not currentlyPressed:
|
|
# self.held_layer = None
|
|
# self.pin_states_last_cycle[pin_index] = (
|
|
# __UNPRESSED,)
|
|
# continue
|
|
#
|