Initial attempt to merge internal_state with kmk_keyboard. Seems to work on Plank so far
This commit is contained in:
42
kmk/extensions/__init__.py
Normal file
42
kmk/extensions/__init__.py
Normal file
@@ -0,0 +1,42 @@
|
||||
class InvalidExtensionEnvironment(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Extension:
|
||||
_enabled = True
|
||||
|
||||
def enable(self, keyboard):
|
||||
self._enabled = True
|
||||
|
||||
self.on_runtime_enable(self, keyboard)
|
||||
|
||||
def disable(self, keyboard):
|
||||
self._enabled = False
|
||||
|
||||
self.on_runtime_disable(self, keyboard)
|
||||
|
||||
# The below methods should be implemented by subclasses
|
||||
|
||||
def on_runtime_enable(self, keyboard):
|
||||
pass
|
||||
|
||||
def on_runtime_disable(self, keyboard):
|
||||
pass
|
||||
|
||||
def during_bootup(self, keyboard):
|
||||
pass
|
||||
|
||||
def before_matrix_scan(self, keyboard):
|
||||
'''
|
||||
Return value will be injected as an extra matrix update
|
||||
'''
|
||||
pass
|
||||
|
||||
def after_matrix_scan(self, keyboard, matrix_update):
|
||||
pass
|
||||
|
||||
def before_hid_send(self, keyboard):
|
||||
pass
|
||||
|
||||
def after_hid_send(self, keyboard):
|
||||
pass
|
111
kmk/extensions/leader.py
Normal file
111
kmk/extensions/leader.py
Normal file
@@ -0,0 +1,111 @@
|
||||
import gc
|
||||
|
||||
from kmk.extensions import Extension, InvalidExtensionEnvironment
|
||||
from kmk.handlers.stock import passthrough as handler_passthrough
|
||||
from kmk.keys import KC, make_key
|
||||
|
||||
|
||||
class LeaderMode:
|
||||
TIMEOUT = 0
|
||||
TIMEOUT_ACTIVE = 1
|
||||
ENTER = 2
|
||||
ENTER_ACTIVE = 3
|
||||
|
||||
|
||||
class Leader(Extension):
|
||||
def __init__(self, mode=LeaderMode.TIMEOUT, timeout=1000, sequences=None):
|
||||
if sequences is None:
|
||||
raise InvalidExtensionEnvironment(
|
||||
'sequences must be a dictionary, not None'
|
||||
)
|
||||
|
||||
self._mode = mode
|
||||
self._timeout = timeout
|
||||
self._sequences = self._compile_sequences(sequences)
|
||||
|
||||
self._leader_pending = None
|
||||
self._assembly_last_len = 0
|
||||
self._sequence_assembly = []
|
||||
|
||||
make_key(
|
||||
names=('LEADER', 'LEAD'),
|
||||
on_press=self._key_leader_pressed,
|
||||
on_release=handler_passthrough,
|
||||
)
|
||||
|
||||
gc.collect()
|
||||
|
||||
def after_matrix_scan(self, keyboard_state, *args):
|
||||
if self._mode % 2 == 1:
|
||||
keys_pressed = keyboard_state._keys_pressed
|
||||
|
||||
if self._assembly_last_len and self._sequence_assembly:
|
||||
history_set = set(self._sequence_assembly)
|
||||
|
||||
keys_pressed = keys_pressed - history_set
|
||||
|
||||
self._assembly_last_len = len(keyboard_state._keys_pressed)
|
||||
|
||||
for key in keys_pressed:
|
||||
if self._mode == LeaderMode.ENTER_ACTIVE and key == KC.ENT:
|
||||
self._handle_leader_sequence(keyboard_state)
|
||||
break
|
||||
elif key == KC.ESC or key == KC.GESC:
|
||||
# Clean self and turn leader mode off.
|
||||
self._exit_leader_mode(keyboard_state)
|
||||
break
|
||||
elif key == KC.LEAD:
|
||||
break
|
||||
else:
|
||||
# Add key if not needing to escape
|
||||
# This needs replaced later with a proper debounce
|
||||
self._sequence_assembly.append(key)
|
||||
|
||||
keyboard_state._hid_pending = False
|
||||
|
||||
def _compile_sequences(self, sequences):
|
||||
gc.collect()
|
||||
|
||||
for k, v in sequences.items():
|
||||
if not isinstance(k, tuple):
|
||||
new_key = tuple(KC[c] for c in k)
|
||||
sequences[new_key] = v
|
||||
|
||||
for k, v in sequences.items():
|
||||
if not isinstance(k, tuple):
|
||||
del sequences[k]
|
||||
|
||||
gc.collect()
|
||||
|
||||
return sequences
|
||||
|
||||
def _handle_leader_sequence(self, keyboard_state):
|
||||
lmh = tuple(self._sequence_assembly)
|
||||
# Will get caught in infinite processing loops if we don't
|
||||
# exit leader mode before processing the target key
|
||||
self._exit_leader_mode(keyboard_state)
|
||||
|
||||
if lmh in self._sequences:
|
||||
# Stack depth exceeded if try to use add_key here with a unicode sequence
|
||||
keyboard_state._process_key(self._sequences[lmh], True)
|
||||
|
||||
keyboard_state._set_timeout(
|
||||
False, lambda: keyboard_state._remove_key(self._sequences[lmh])
|
||||
)
|
||||
|
||||
def _exit_leader_mode(self, keyboard_state):
|
||||
self._sequence_assembly.clear()
|
||||
self._mode -= 1
|
||||
self._assembly_last_len = 0
|
||||
keyboard_state._keys_pressed.clear()
|
||||
|
||||
def _key_leader_pressed(self, key, keyboard_state, *args, **kwargs):
|
||||
if self._mode % 2 == 0:
|
||||
keyboard_state._keys_pressed.discard(key)
|
||||
# All leader modes are one number higher when activating
|
||||
self._mode += 1
|
||||
|
||||
if self._mode == LeaderMode.TIMEOUT_ACTIVE:
|
||||
keyboard_state._set_timeout(
|
||||
self._timeout, lambda: self._handle_leader_sequence(keyboard_state)
|
||||
)
|
103
kmk/extensions/split.py
Normal file
103
kmk/extensions/split.py
Normal file
@@ -0,0 +1,103 @@
|
||||
import busio
|
||||
import gc
|
||||
|
||||
from kmk.extensions import Extension
|
||||
from kmk.kmktime import sleep_ms
|
||||
from kmk.matrix import intify_coordinate
|
||||
|
||||
|
||||
class SplitType:
|
||||
UART = 1
|
||||
I2C = 2 # unused
|
||||
ONEWIRE = 3 # unused
|
||||
BLE = 4 # unused
|
||||
|
||||
|
||||
class Split(Extension):
|
||||
def __init__(
|
||||
self,
|
||||
extra_data_pin=None,
|
||||
offsets=(),
|
||||
flip=False,
|
||||
side=None,
|
||||
stype=None,
|
||||
master_left=True,
|
||||
uart_flip=True,
|
||||
uart_pin=None,
|
||||
uart_timeout=20,
|
||||
):
|
||||
self.extra_data_pin = extra_data_pin
|
||||
self.split_offsets = offsets
|
||||
self.split_flip = flip
|
||||
self.split_side = side
|
||||
self.split_type = stype
|
||||
self.split_master_left = master_left
|
||||
self._uart = None
|
||||
self.uart_flip = uart_flip
|
||||
self.uart_pin = uart_pin
|
||||
self.uart_timeout = uart_timeout
|
||||
|
||||
def during_bootup(self, keyboard):
|
||||
if self.split_type is not None:
|
||||
try:
|
||||
# Working around https://github.com/adafruit/circuitpython/issues/1769
|
||||
keyboard._hid_helper_inst.create_report([]).send()
|
||||
self._is_master = True
|
||||
|
||||
# Sleep 2s so master portion doesn't "appear" to boot quicker than
|
||||
# dependent portions (which will take ~2s to time out on the HID send)
|
||||
sleep_ms(2000)
|
||||
except OSError:
|
||||
self._is_master = False
|
||||
|
||||
if self.split_flip and not self._is_master:
|
||||
keyboard.col_pins = list(reversed(self.col_pins))
|
||||
if self.split_side == 'Left':
|
||||
self.split_master_left = self._is_master
|
||||
elif self.split_side == 'Right':
|
||||
self.split_master_left = not self._is_master
|
||||
else:
|
||||
self._is_master = True
|
||||
|
||||
if self.uart_pin is not None:
|
||||
if self._is_master:
|
||||
self._uart = busio.UART(
|
||||
tx=None, rx=self.uart_pin, timeout=self.uart_timeout
|
||||
)
|
||||
else:
|
||||
self._uart = busio.UART(
|
||||
tx=self.uart_pin, rx=None, timeout=self.uart_timeout
|
||||
)
|
||||
|
||||
# Attempt to sanely guess a coord_mapping if one is not provided.
|
||||
if not keyboard.coord_mapping:
|
||||
keyboard.coord_mapping = []
|
||||
|
||||
rows_to_calc = len(keyboard.row_pins)
|
||||
cols_to_calc = len(keyboard.col_pins)
|
||||
|
||||
if self.split_offsets:
|
||||
rows_to_calc *= 2
|
||||
cols_to_calc *= 2
|
||||
|
||||
for ridx in range(rows_to_calc):
|
||||
for cidx in range(cols_to_calc):
|
||||
keyboard.coord_mapping.append(intify_coordinate(ridx, cidx))
|
||||
|
||||
gc.collect()
|
||||
|
||||
def before_matrix_scan(self, keyboard_state):
|
||||
if self.split_type is not None and self._is_master:
|
||||
return self._receive_from_slave()
|
||||
|
||||
def after_matrix_scan(self, keyboard_state, matrix_update):
|
||||
if matrix_update is not None and not self._is_master:
|
||||
self._send_to_master(matrix_update)
|
||||
|
||||
def _send_to_master(self, update):
|
||||
if self.split_master_left:
|
||||
update[1] += self.split_offsets[update[0]]
|
||||
else:
|
||||
update[1] -= self.split_offsets[update[0]]
|
||||
if self._uart is not None:
|
||||
self._uart.write(update)
|
Reference in New Issue
Block a user