Initial attempt to merge internal_state with kmk_keyboard. Seems to work on Plank so far

This commit is contained in:
Josh Klar 2019-07-28 17:09:58 -07:00
parent ea327f8f76
commit 9821f7bcc3
No known key found for this signature in database
GPG Key ID: A4A0C7B4E8EEE222
15 changed files with 879 additions and 812 deletions

View File

@ -9,10 +9,3 @@ class UnicodeMode:
LINUX = IBUS = 1 LINUX = IBUS = 1
MACOS = OSX = RALT = 2 MACOS = OSX = RALT = 2
WINC = 3 WINC = 3
class LeaderMode:
TIMEOUT = 0
TIMEOUT_ACTIVE = 1
ENTER = 2
ENTER_ACTIVE = 3

View 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
View 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
View 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)

View File

@ -5,7 +5,7 @@ def df_pressed(key, state, *args, **kwargs):
''' '''
Switches the default layer Switches the default layer
''' '''
state.active_layers[-1] = key.meta.layer state._active_layers[-1] = key.meta.layer
return state return state
@ -13,7 +13,7 @@ def mo_pressed(key, state, *args, **kwargs):
''' '''
Momentarily activates layer, switches off when you let go Momentarily activates layer, switches off when you let go
''' '''
state.active_layers.insert(0, key.meta.layer) state._active_layers.insert(0, key.meta.layer)
return state return state
@ -27,8 +27,8 @@ def mo_released(key, state, KC, *args, **kwargs):
# triggered by MO() and then defaulting to the MO()'s layer # triggered by MO() and then defaulting to the MO()'s layer
# would result in no layers active # would result in no layers active
try: try:
del_idx = state.active_layers.index(key.meta.layer) del_idx = state._active_layers.index(key.meta.layer)
del state.active_layers[del_idx] del state._active_layers[del_idx]
except ValueError: except ValueError:
pass pass
@ -39,10 +39,10 @@ def lm_pressed(key, state, *args, **kwargs):
''' '''
As MO(layer) but with mod active As MO(layer) but with mod active
''' '''
state.hid_pending = True state._hid_pending = True
# Sets the timer start and acts like MO otherwise # Sets the timer start and acts like MO otherwise
state.start_time['lm'] = ticks_ms() state._start_time['lm'] = ticks_ms()
state.keys_pressed.add(key.meta.kc) state._keys_pressed.add(key.meta.kc)
return mo_pressed(key, state, *args, **kwargs) return mo_pressed(key, state, *args, **kwargs)
@ -50,28 +50,28 @@ def lm_released(key, state, *args, **kwargs):
''' '''
As MO(layer) but with mod active As MO(layer) but with mod active
''' '''
state.hid_pending = True state._hid_pending = True
state.keys_pressed.discard(key.meta.kc) state._keys_pressed.discard(key.meta.kc)
state.start_time['lm'] = None state._start_time['lm'] = None
return mo_released(key, state, *args, **kwargs) return mo_released(key, state, *args, **kwargs)
def lt_pressed(key, state, *args, **kwargs): def lt_pressed(key, state, *args, **kwargs):
# Sets the timer start and acts like MO otherwise # Sets the timer start and acts like MO otherwise
state.start_time['lt'] = ticks_ms() state._start_time['lt'] = ticks_ms()
return mo_pressed(key, state, *args, **kwargs) return mo_pressed(key, state, *args, **kwargs)
def lt_released(key, state, *args, **kwargs): def lt_released(key, state, *args, **kwargs):
# On keyup, check timer, and press key if needed. # On keyup, check timer, and press key if needed.
if state.start_time['lt'] and ( if state._start_time['lt'] and (
ticks_diff(ticks_ms(), state.start_time['lt']) < state.config.tap_time ticks_diff(ticks_ms(), state._start_time['lt']) < state.tap_time
): ):
state.hid_pending = True state._hid_pending = True
state.tap_key(key.meta.kc) state._tap_key(key.meta.kc)
mo_released(key, state, *args, **kwargs) mo_released(key, state, *args, **kwargs)
state.start_time['lt'] = None state._start_time['lt'] = None
return state return state
@ -81,10 +81,10 @@ def tg_pressed(key, state, *args, **kwargs):
''' '''
# See mo_released for implementation details around this # See mo_released for implementation details around this
try: try:
del_idx = state.active_layers.index(key.meta.layer) del_idx = state._active_layers.index(key.meta.layer)
del state.active_layers[del_idx] del state._active_layers[del_idx]
except ValueError: except ValueError:
state.active_layers.insert(0, key.meta.layer) state._active_layers.insert(0, key.meta.layer)
return state return state
@ -93,8 +93,8 @@ def to_pressed(key, state, *args, **kwargs):
''' '''
Activates layer and deactivates all other layers Activates layer and deactivates all other layers
''' '''
state.active_layers.clear() state._active_layers.clear()
state.active_layers.insert(0, key.meta.layer) state._active_layers.insert(0, key.meta.layer)
return state return state
@ -104,23 +104,21 @@ def tt_pressed(key, state, *args, **kwargs):
Momentarily activates layer if held, toggles it if tapped repeatedly Momentarily activates layer if held, toggles it if tapped repeatedly
''' '''
# TODO Make this work with tap dance to function more correctly, but technically works. # TODO Make this work with tap dance to function more correctly, but technically works.
if state.start_time['tt'] is None: if state._start_time['tt'] is None:
# Sets the timer start and acts like MO otherwise # Sets the timer start and acts like MO otherwise
state.start_time['tt'] = ticks_ms() state._start_time['tt'] = ticks_ms()
return mo_pressed(key, state, *args, **kwargs) return mo_pressed(key, state, *args, **kwargs)
elif ticks_diff(ticks_ms(), state.start_time['tt']) < state.config.tap_time: elif ticks_diff(ticks_ms(), state._start_time['tt']) < state.tap_time:
state.start_time['tt'] = None state._start_time['tt'] = None
return tg_pressed(key, state, *args, **kwargs) return tg_pressed(key, state, *args, **kwargs)
def tt_released(key, state, *args, **kwargs): def tt_released(key, state, *args, **kwargs):
tap_timed_out = ( tap_timed_out = ticks_diff(ticks_ms(), state._start_time['tt']) >= state.tap_time
ticks_diff(ticks_ms(), state.start_time['tt']) >= state.config.tap_time if state._start_time['tt'] is None or tap_timed_out:
)
if state.start_time['tt'] is None or tap_timed_out:
# On first press, works like MO. On second press, does nothing unless let up within # On first press, works like MO. On second press, does nothing unless let up within
# time window, then acts like TG. # time window, then acts like TG.
state.start_time['tt'] = None state._start_time['tt'] = None
return mo_released(key, state, *args, **kwargs) return mo_released(key, state, *args, **kwargs)
return state return state

View File

@ -3,21 +3,21 @@ from kmk.kmktime import ticks_diff, ticks_ms
def mt_pressed(key, state, *args, **kwargs): def mt_pressed(key, state, *args, **kwargs):
# Sets the timer start and acts like a modifier otherwise # Sets the timer start and acts like a modifier otherwise
state.keys_pressed.add(key.meta.mods) state._keys_pressed.add(key.meta.mods)
state.start_time['mod_tap'] = ticks_ms() state._start_time['mod_tap'] = ticks_ms()
return state return state
def mt_released(key, state, *args, **kwargs): def mt_released(key, state, *args, **kwargs):
# On keyup, check timer, and press key if needed. # On keyup, check timer, and press key if needed.
state.keys_pressed.discard(key.meta.mods) state._keys_pressed.discard(key.meta.mods)
timer_name = 'mod_tap' timer_name = 'mod_tap'
if state.start_time[timer_name] and ( if state._start_time[timer_name] and (
ticks_diff(ticks_ms(), state.start_time[timer_name]) < state.config.tap_time ticks_diff(ticks_ms(), state._start_time[timer_name]) < state.tap_time
): ):
state.hid_pending = True state._hid_pending = True
state.tap_key(key.meta.kc) state._tap_key(key.meta.kc)
state.start_time[timer_name] = None state._start_time[timer_name] = None
return state return state

View File

@ -12,18 +12,18 @@ def get_wide_ordinal(char):
def sequence_press_handler(key, state, KC, *args, **kwargs): def sequence_press_handler(key, state, KC, *args, **kwargs):
old_keys_pressed = state.keys_pressed old_keys_pressed = state._keys_pressed
state.keys_pressed = set() state._keys_pressed = set()
for ikey in key.meta.seq: for ikey in key.meta.seq:
if not getattr(ikey, 'no_press', None): if not getattr(ikey, 'no_press', None):
state.process_key(ikey, True) state._process_key(ikey, True)
state.config._send_hid() state._send_hid()
if not getattr(ikey, 'no_release', None): if not getattr(ikey, 'no_release', None):
state.process_key(ikey, False) state._process_key(ikey, False)
state.config._send_hid() state._send_hid()
state.keys_pressed = old_keys_pressed state._keys_pressed = old_keys_pressed
return state return state
@ -103,16 +103,16 @@ def unicode_codepoint_sequence(codepoints):
kc_macros = [simple_key_sequence(kc_seq) for kc_seq in kc_seqs] kc_macros = [simple_key_sequence(kc_seq) for kc_seq in kc_seqs]
def _unicode_sequence(key, state, *args, **kwargs): def _unicode_sequence(key, state, *args, **kwargs):
if state.config.unicode_mode == UnicodeMode.IBUS: if state.unicode_mode == UnicodeMode.IBUS:
state.process_key( state._process_key(
simple_key_sequence(_ibus_unicode_sequence(kc_macros, state)), True simple_key_sequence(_ibus_unicode_sequence(kc_macros, state)), True
) )
elif state.config.unicode_mode == UnicodeMode.RALT: elif state.unicode_mode == UnicodeMode.RALT:
state.process_key( state._process_key(
simple_key_sequence(_ralt_unicode_sequence(kc_macros, state)), True simple_key_sequence(_ralt_unicode_sequence(kc_macros, state)), True
) )
elif state.config.unicode_mode == UnicodeMode.WINC: elif state.unicode_mode == UnicodeMode.WINC:
state.process_key( state._process_key(
simple_key_sequence(_winc_unicode_sequence(kc_macros, state)), True simple_key_sequence(_winc_unicode_sequence(kc_macros, state)), True
) )

View File

@ -6,23 +6,23 @@ def passthrough(key, state, *args, **kwargs):
def default_pressed(key, state, KC, coord_int=None, coord_raw=None): def default_pressed(key, state, KC, coord_int=None, coord_raw=None):
state.hid_pending = True state._hid_pending = True
if coord_int is not None: if coord_int is not None:
state.coord_keys_pressed[coord_int] = key state._coord_keys_pressed[coord_int] = key
state.keys_pressed.add(key) state._keys_pressed.add(key)
return state return state
def default_released(key, state, KC, coord_int=None, coord_raw=None): def default_released(key, state, KC, coord_int=None, coord_raw=None):
state.hid_pending = True state._hid_pending = True
state.keys_pressed.discard(key) state._keys_pressed.discard(key)
if coord_int is not None: if coord_int is not None:
state.keys_pressed.discard(state.coord_keys_pressed.get(coord_int, None)) state._keys_pressed.discard(state._coord_keys_pressed.get(coord_int, None))
state.coord_keys_pressed[coord_int] = None state._coord_keys_pressed[coord_int] = None
return state return state
@ -53,12 +53,12 @@ def bootloader(*args, **kwargs):
def debug_pressed(key, state, KC, *args, **kwargs): def debug_pressed(key, state, KC, *args, **kwargs):
if state.config.debug_enabled: if state.debug_enabled:
print('DebugDisable()') print('DebugDisable()')
else: else:
print('DebugEnable()') print('DebugEnable()')
state.config.debug_enabled = not state.config.debug_enabled state.debug_enabled = not state.debug_enabled
return state return state
@ -66,48 +66,48 @@ def debug_pressed(key, state, KC, *args, **kwargs):
def gesc_pressed(key, state, KC, *args, **kwargs): def gesc_pressed(key, state, KC, *args, **kwargs):
GESC_TRIGGERS = {KC.LSHIFT, KC.RSHIFT, KC.LGUI, KC.RGUI} GESC_TRIGGERS = {KC.LSHIFT, KC.RSHIFT, KC.LGUI, KC.RGUI}
if GESC_TRIGGERS.intersection(state.keys_pressed): if GESC_TRIGGERS.intersection(state._keys_pressed):
# First, release GUI if already pressed # First, release GUI if already pressed
state.config._send_hid() state._send_hid()
# if Shift is held, KC_GRAVE will become KC_TILDE on OS level # if Shift is held, KC_GRAVE will become KC_TILDE on OS level
state.keys_pressed.add(KC.GRAVE) state._keys_pressed.add(KC.GRAVE)
state.hid_pending = True state._hid_pending = True
return state return state
# else return KC_ESC # else return KC_ESC
state.keys_pressed.add(KC.ESCAPE) state._keys_pressed.add(KC.ESCAPE)
state.hid_pending = True state._hid_pending = True
return state return state
def gesc_released(key, state, KC, *args, **kwargs): def gesc_released(key, state, KC, *args, **kwargs):
state.keys_pressed.discard(KC.ESCAPE) state._keys_pressed.discard(KC.ESCAPE)
state.keys_pressed.discard(KC.GRAVE) state._keys_pressed.discard(KC.GRAVE)
state.hid_pending = True state._hid_pending = True
return state return state
def bkdl_pressed(key, state, KC, *args, **kwargs): def bkdl_pressed(key, state, KC, *args, **kwargs):
BKDL_TRIGGERS = {KC.LGUI, KC.RGUI} BKDL_TRIGGERS = {KC.LGUI, KC.RGUI}
if BKDL_TRIGGERS.intersection(state.keys_pressed): if BKDL_TRIGGERS.intersection(state._keys_pressed):
state.config._send_hid() state._send_hid()
state.keys_pressed.add(KC.DEL) state._keys_pressed.add(KC.DEL)
state.hid_pending = True state._hid_pending = True
return state return state
# else return KC_ESC # else return KC_ESC
state.keys_pressed.add(KC.BKSP) state._keys_pressed.add(KC.BKSP)
state.hid_pending = True state._hid_pending = True
return state return state
def bkdl_released(key, state, KC, *args, **kwargs): def bkdl_released(key, state, KC, *args, **kwargs):
state.keys_pressed.discard(KC.BKSP) state._keys_pressed.discard(KC.BKSP)
state.keys_pressed.discard(KC.DEL) state._keys_pressed.discard(KC.DEL)
state.hid_pending = True state._hid_pending = True
return state return state
@ -117,15 +117,11 @@ def sleep_pressed(key, state, KC, *args, **kwargs):
def uc_mode_pressed(key, state, *args, **kwargs): def uc_mode_pressed(key, state, *args, **kwargs):
state.config.unicode_mode = key.meta.mode state.unicode_mode = key.meta.mode
return state return state
def leader_pressed(key, state, *args, **kwargs):
return state._begin_leader_mode()
def td_pressed(key, state, *args, **kwargs): def td_pressed(key, state, *args, **kwargs):
return state._process_tap_dance(key, True) return state._process_tap_dance(key, True)
@ -135,137 +131,83 @@ def td_released(key, state, *args, **kwargs):
def rgb_tog(key, state, *args, **kwargs): def rgb_tog(key, state, *args, **kwargs):
if state.config.pixels.animation_mode == 'static_standby': if state.pixels.animation_mode == 'static_standby':
state.config.pixels.animation_mode = 'static' state.pixels.animation_mode = 'static'
state.config.pixels.enabled = not state.config.pixels.enabled state.pixels.enabled = not state.pixels.enabled
return state return state
def rgb_hui(key, state, *args, **kwargs): def rgb_hui(key, state, *args, **kwargs):
state.config.pixels.increase_hue() state.pixels.increase_hue()
return state return state
def rgb_hud(key, state, *args, **kwargs): def rgb_hud(key, state, *args, **kwargs):
state.config.pixels.decrease_hue() state.pixels.decrease_hue()
return state return state
def rgb_sai(key, state, *args, **kwargs): def rgb_sai(key, state, *args, **kwargs):
state.config.pixels.increase_sat() state.pixels.increase_sat()
return state return state
def rgb_sad(key, state, *args, **kwargs): def rgb_sad(key, state, *args, **kwargs):
state.config.pixels.decrease_sat() state.pixels.decrease_sat()
return state return state
def rgb_vai(key, state, *args, **kwargs): def rgb_vai(key, state, *args, **kwargs):
state.config.pixels.increase_val() state.pixels.increase_val()
return state return state
def rgb_vad(key, state, *args, **kwargs): def rgb_vad(key, state, *args, **kwargs):
state.config.pixels.decrease_val() state.pixels.decrease_val()
return state return state
def rgb_ani(key, state, *args, **kwargs): def rgb_ani(key, state, *args, **kwargs):
state.config.pixels.increase_ani() state.pixels.increase_ani()
return state return state
def rgb_and(key, state, *args, **kwargs): def rgb_and(key, state, *args, **kwargs):
state.config.pixels.decrease_ani() state.pixels.decrease_ani()
return state return state
def rgb_mode_static(key, state, *args, **kwargs): def rgb_mode_static(key, state, *args, **kwargs):
state.config.pixels.effect_init = True state.pixels.effect_init = True
state.config.pixels.animation_mode = 'static' state.pixels.animation_mode = 'static'
return state return state
def rgb_mode_breathe(key, state, *args, **kwargs): def rgb_mode_breathe(key, state, *args, **kwargs):
state.config.pixels.effect_init = True state.pixels.effect_init = True
state.config.pixels.animation_mode = 'breathing' state.pixels.animation_mode = 'breathing'
return state return state
def rgb_mode_breathe_rainbow(key, state, *args, **kwargs): def rgb_mode_breathe_rainbow(key, state, *args, **kwargs):
state.config.pixels.effect_init = True state.pixels.effect_init = True
state.config.pixels.animation_mode = 'breathing_rainbow' state.pixels.animation_mode = 'breathing_rainbow'
return state return state
def rgb_mode_rainbow(key, state, *args, **kwargs): def rgb_mode_rainbow(key, state, *args, **kwargs):
state.config.pixels.effect_init = True state.pixels.effect_init = True
state.config.pixels.animation_mode = 'rainbow' state.pixels.animation_mode = 'rainbow'
return state return state
def rgb_mode_swirl(key, state, *args, **kwargs): def rgb_mode_swirl(key, state, *args, **kwargs):
state.config.pixels.effect_init = True state.pixels.effect_init = True
state.config.pixels.animation_mode = 'swirl' state.pixels.animation_mode = 'swirl'
return state return state
def rgb_mode_knight(key, state, *args, **kwargs): def rgb_mode_knight(key, state, *args, **kwargs):
state.config.pixels.effect_init = True state.pixels.effect_init = True
state.config.pixels.animation_mode = 'knight' state.pixels.animation_mode = 'knight'
return state
def led_tog(key, state, *args, **kwargs):
if state.config.led.animation_mode == 'static_standby':
state.config.led.animation_mode = 'static'
state.config.led.enabled = not state.config.led.enabled
return state
def led_inc(key, state, *args, **kwargs):
state.config.led.increase_brightness()
return state
def led_dec(key, state, *args, **kwargs):
state.config.led.decrease_brightness()
return state
def led_ani(key, state, *args, **kwargs):
state.config.led.increase_ani()
return state
def led_and(key, state, *args, **kwargs):
state.config.led.decrease_ani()
return state
def led_mode_static(key, state, *args, **kwargs):
state.config.led.effect_init = True
state.config.led.animation_mode = 'static'
return state
def led_mode_breathe(key, state, *args, **kwargs):
state.config.led.effect_init = True
state.config.led.animation_mode = 'breathing'
return state
def bt_clear_bonds(key, state, *args, **kwargs):
state.config._hid_helper_inst.clear_bonds()
return state
def bt_next_conn(key, state, *args, **kwargs):
state.config._hid_helper_inst.next_connection()
return state
def bt_prev_conn(key, state, *args, **kwargs):
state.config._hid_helper_inst.previous_connection()
return state return state

View File

@ -1,290 +0,0 @@
from kmk.consts import LeaderMode
from kmk.keys import KC
from kmk.kmktime import ticks_ms
from kmk.matrix import intify_coordinate
from kmk.types import TapDanceKeyMeta
class InternalState:
keys_pressed = set()
coord_keys_pressed = {}
leader_pending = None
leader_last_len = 0
hid_pending = False
leader_mode_history = []
# this should almost always be PREpended to, replaces
# former use of reversed_active_layers which had pointless
# overhead (the underlying list was never used anyway)
active_layers = [0]
start_time = {'lt': None, 'tg': None, 'tt': None, 'lm': None, 'leader': None}
timeouts = {}
tapping = False
tap_dance_counts = {}
tap_side_effects = {}
def __init__(self, config):
self.config = config
def __repr__(self):
return (
'InternalState('
'keys_pressed={} '
'coord_keys_pressed={} '
'leader_pending={} '
'leader_last_len={} '
'hid_pending={} '
'leader_mode_history={} '
'active_layers={} '
'start_time={} '
'timeouts={} '
'tapping={} '
'tap_dance_counts={} '
'tap_side_effects={}'
')'
).format(
self.keys_pressed,
self.coord_keys_pressed,
self.leader_pending,
self.leader_last_len,
self.hid_pending,
self.leader_mode_history,
self.active_layers,
self.start_time,
self.timeouts,
self.tapping,
self.tap_dance_counts,
self.tap_side_effects,
)
def _find_key_in_map(self, row, col):
ic = intify_coordinate(row, col)
try:
idx = self.config.coord_mapping.index(ic)
except ValueError:
if self.config.debug_enabled:
print(
'CoordMappingNotFound(ic={}, row={}, col={})'.format(ic, row, col)
)
return None
for layer in self.active_layers:
layer_key = self.config.keymap[layer][idx]
if not layer_key or layer_key == KC.TRNS:
continue
if self.config.debug_enabled:
print('KeyResolution(key={})'.format(layer_key))
return layer_key
def set_timeout(self, after_ticks, callback):
if after_ticks is False:
# We allow passing False as an implicit "run this on the next process timeouts cycle"
timeout_key = ticks_ms()
else:
timeout_key = ticks_ms() + after_ticks
while timeout_key in self.timeouts:
timeout_key += 1
self.timeouts[timeout_key] = callback
return timeout_key
def cancel_timeout(self, timeout_key):
if timeout_key in self.timeouts:
del self.timeouts[timeout_key]
def process_timeouts(self):
if not self.timeouts:
return self
current_time = ticks_ms()
# cast this to a tuple to ensure that if a callback itself sets
# timeouts, we do not handle them on the current cycle
timeouts = tuple(self.timeouts.items())
for k, v in timeouts:
if k <= current_time:
v()
del self.timeouts[k]
return self
def matrix_changed(self, row, col, is_pressed):
if self.config.debug_enabled:
print('MatrixChange(col={} row={} pressed={})'.format(col, row, is_pressed))
int_coord = intify_coordinate(row, col)
kc_changed = self._find_key_in_map(row, col)
if kc_changed is None:
print('MatrixUndefinedCoordinate(col={} row={})'.format(col, row))
return self
return self.process_key(kc_changed, is_pressed, int_coord, (row, col))
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:
if is_pressed:
key._on_press(self, coord_int, coord_raw)
else:
key._on_release(self, coord_int, coord_raw)
if self.config.leader_mode % 2 == 1:
self._process_leader_mode()
return self
def remove_key(self, keycode):
self.keys_pressed.discard(keycode)
return self.process_key(keycode, False)
def add_key(self, keycode):
self.keys_pressed.add(keycode)
return self.process_key(keycode, True)
def tap_key(self, keycode):
self.add_key(keycode)
# On the next cycle, we'll remove the key.
self.set_timeout(False, lambda: self.remove_key(keycode))
return self
def resolve_hid(self):
self.hid_pending = False
return self
def _process_tap_dance(self, changed_key, is_pressed):
if is_pressed:
if not isinstance(changed_key.meta, TapDanceKeyMeta):
# If we get here, changed_key is not a TapDanceKey and thus
# the user kept typing elsewhere (presumably). End ALL of the
# currently outstanding tap dance runs.
for k, v in self.tap_dance_counts.items():
if v:
self._end_tap_dance(k)
return self
if (
changed_key not in self.tap_dance_counts
or not self.tap_dance_counts[changed_key]
):
self.tap_dance_counts[changed_key] = 1
self.set_timeout(
self.config.tap_time, lambda: self._end_tap_dance(changed_key)
)
self.tapping = True
else:
self.tap_dance_counts[changed_key] += 1
if changed_key not in self.tap_side_effects:
self.tap_side_effects[changed_key] = None
else:
has_side_effects = self.tap_side_effects[changed_key] is not None
hit_max_defined_taps = self.tap_dance_counts[changed_key] == len(
changed_key.codes
)
if has_side_effects or hit_max_defined_taps:
self._end_tap_dance(changed_key)
return self
def _end_tap_dance(self, td_key):
v = self.tap_dance_counts[td_key] - 1
if v >= 0:
if td_key in self.keys_pressed:
key_to_press = td_key.codes[v]
self.add_key(key_to_press)
self.tap_side_effects[td_key] = key_to_press
self.hid_pending = True
else:
if self.tap_side_effects[td_key]:
self.remove_key(self.tap_side_effects[td_key])
self.tap_side_effects[td_key] = None
self.hid_pending = True
self._cleanup_tap_dance(td_key)
else:
self.tap_key(td_key.codes[v])
self._cleanup_tap_dance(td_key)
return self
def _cleanup_tap_dance(self, td_key):
self.tap_dance_counts[td_key] = 0
self.tapping = any(count > 0 for count in self.tap_dance_counts.values())
return self
def _begin_leader_mode(self):
if self.config.leader_mode % 2 == 0:
self.keys_pressed.discard(KC.LEAD)
# All leader modes are one number higher when activating
self.config.leader_mode += 1
if self.config.leader_mode == LeaderMode.TIMEOUT_ACTIVE:
self.set_timeout(
self.config.leader_timeout, self._handle_leader_sequence
)
return self
def _handle_leader_sequence(self):
lmh = tuple(self.leader_mode_history)
# Will get caught in infinite processing loops if we don't
# exit leader mode before processing the target key
self._exit_leader_mode()
if lmh in self.config.leader_dictionary:
# Stack depth exceeded if try to use add_key here with a unicode sequence
self.process_key(self.config.leader_dictionary[lmh], True)
self.set_timeout(
False, lambda: self.remove_key(self.config.leader_dictionary[lmh])
)
return self
def _process_leader_mode(self):
keys_pressed = self.keys_pressed
if self.leader_last_len and self.leader_mode_history:
history_set = set(self.leader_mode_history)
keys_pressed = keys_pressed - history_set
self.leader_last_len = len(self.keys_pressed)
for key in keys_pressed:
if self.config.leader_mode == LeaderMode.ENTER_ACTIVE and key == KC.ENT:
self._handle_leader_sequence()
break
elif key == KC.ESC or key == KC.GESC:
# Clean self and turn leader mode off.
self._exit_leader_mode()
break
elif key == KC.LEAD:
break
else:
# Add key if not needing to escape
# This needs replaced later with a proper debounce
self.leader_mode_history.append(key)
self.hid_pending = False
return self
def _exit_leader_mode(self):
self.leader_mode_history.clear()
self.config.leader_mode -= 1
self.leader_last_len = 0
self.keys_pressed.clear()
return self

View File

@ -629,25 +629,6 @@ make_key(
make_key(names=('RGB_MODE_SWIRL', 'RGB_M_S'), on_press=handlers.rgb_mode_swirl) make_key(names=('RGB_MODE_SWIRL', 'RGB_M_S'), on_press=handlers.rgb_mode_swirl)
make_key(names=('RGB_MODE_KNIGHT', 'RGB_M_K'), on_press=handlers.rgb_mode_knight) make_key(names=('RGB_MODE_KNIGHT', 'RGB_M_K'), on_press=handlers.rgb_mode_knight)
make_key(names=('LED_TOG',), on_press=handlers.led_tog)
make_key(names=('LED_INC',), on_press=handlers.led_inc)
make_key(names=('LED_DEC',), on_press=handlers.led_dec)
make_key(names=('LED_ANI',), on_press=handlers.led_ani)
make_key(names=('LED_AND',), on_press=handlers.led_and)
make_key(names=('LED_MODE_PLAIN', 'LED_M_P'), on_press=handlers.led_mode_static)
make_key(names=('LED_MODE_BREATHE', 'LED_M_B'), on_press=handlers.led_mode_breathe)
make_key(names=('BT_CLEAR_BONDS', 'BT_CLR'), on_press=handlers.bt_clear_bonds)
make_key(names=('BT_NEXT_CONN', 'BT_NXT'), on_press=handlers.bt_next_conn)
make_key(names=('BT_PREV_CONN', 'BT_PRV'), on_press=handlers.bt_prev_conn)
make_key(
names=('LEADER', 'LEAD'),
on_press=handlers.leader_pressed,
on_release=handlers.passthrough,
)
# Layers # Layers
make_argumented_key( make_argumented_key(
validator=layer_key_validator, validator=layer_key_validator,

View File

@ -3,19 +3,20 @@
# a line into their keymaps. # a line into their keymaps.
import kmk.preload_imports # isort:skip # NOQA import kmk.preload_imports # isort:skip # NOQA
import busio import gc
from kmk import led, rgb from kmk import rgb
from kmk.consts import LeaderMode, UnicodeMode from kmk.consts import KMK_RELEASE, UnicodeMode
from kmk.hid import AbstractHID, HIDModes from kmk.hid import BLEHID, USBHID, AbstractHID, HIDModes
from kmk.internal_state import InternalState
from kmk.keys import KC from kmk.keys import KC
from kmk.kmktime import sleep_ms from kmk.kmktime import ticks_ms
from kmk.matrix import MatrixScanner from kmk.matrix import MatrixScanner, intify_coordinate
from kmk.matrix import intify_coordinate as ic from kmk.types import TapDanceKeyMeta
class KMKKeyboard: class KMKKeyboard:
#####
# User-configurable
debug_enabled = False debug_enabled = False
keymap = None keymap = None
@ -29,29 +30,27 @@ class KMKKeyboard:
unicode_mode = UnicodeMode.NOOP unicode_mode = UnicodeMode.NOOP
tap_time = 300 tap_time = 300
leader_mode = LeaderMode.TIMEOUT
leader_dictionary = {}
leader_timeout = 1000
# Split config
extra_data_pin = None
split_offsets = ()
split_flip = False
target_side = None
split_type = None
split_target_left = True
is_target = None
uart = None
uart_flip = True
uart_pin = None
# RGB config # RGB config
rgb_pixel_pin = None rgb_pixel_pin = None
rgb_config = rgb.rgb_config rgb_config = rgb.rgb_config
# led config (mono color) #####
led_pin = None # Internal State
led_config = led.led_config _keys_pressed = set()
_coord_keys_pressed = {}
_hid_pending = False
# this should almost always be PREpended to, replaces
# former use of reversed_active_layers which had pointless
# overhead (the underlying list was never used anyway)
_active_layers = [0]
_start_time = {'lt': None, 'tg': None, 'tt': None, 'lm': None}
_timeouts = {}
_tapping = False
_tap_dance_counts = {}
_tap_side_effects = {}
def __repr__(self): def __repr__(self):
return ( return (
@ -65,20 +64,16 @@ class KMKKeyboard:
'matrix_scanner={} ' 'matrix_scanner={} '
'unicode_mode={} ' 'unicode_mode={} '
'tap_time={} ' 'tap_time={} '
'leader_mode={} '
'leader_dictionary=truncated '
'leader_timeout={} '
'hid_helper={} ' 'hid_helper={} '
'extra_data_pin={} ' 'keys_pressed={} '
'split_offsets={} ' 'coord_keys_pressed={} '
'split_flip={} ' 'hid_pending={} '
'target_side={} ' 'active_layers={} '
'split_type={} ' 'start_time={} '
'split_target_left={} ' 'timeouts={} '
'is_target={} ' 'tapping={} '
'uart={} ' 'tap_dance_counts={} '
'uart_flip={} ' 'tap_side_effects={}'
'uart_pin={}'
')' ')'
).format( ).format(
self.debug_enabled, self.debug_enabled,
@ -90,51 +85,43 @@ class KMKKeyboard:
self.matrix_scanner, self.matrix_scanner,
self.unicode_mode, self.unicode_mode,
self.tap_time, self.tap_time,
self.leader_mode,
# self.leader_dictionary,
self.leader_timeout,
self.hid_helper.__name__, self.hid_helper.__name__,
self.extra_data_pin, # internal state
self.split_offsets, self._keys_pressed,
self.split_flip, self._coord_keys_pressed,
self.target_side, self._hid_pending,
self.split_type, self._active_layers,
self.split_target_left, self._start_time,
self.is_target, self._timeouts,
self.uart, self._tapping,
self.uart_flip, self._tap_dance_counts,
self.uart_pin, self._tap_side_effects,
) )
def _print_debug_cycle(self, init=False):
pre_alloc = gc.mem_alloc()
pre_free = gc.mem_free()
if self.debug_enabled:
if init:
print('KMKInit(release={})'.format(KMK_RELEASE))
print(self)
print(self)
print(
'GCStats(pre_alloc={} pre_free={} alloc={} free={})'.format(
pre_alloc, pre_free, gc.mem_alloc(), gc.mem_free()
)
)
def _send_hid(self): def _send_hid(self):
self._hid_helper_inst.create_report(self._state.keys_pressed).send() self._hid_helper_inst.create_report(self._keys_pressed).send()
self._state.resolve_hid() self._hid_pending = False
def _send_key(self, key):
if not getattr(key, 'no_press', None):
self._state.add_key(key)
self._send_hid()
if not getattr(key, 'no_release', None):
self._state.remove_key(key)
self._send_hid()
def _handle_matrix_report(self, update=None): def _handle_matrix_report(self, update=None):
'''
Bulk processing of update code for each cycle
:param update:
'''
if update is not None: if update is not None:
self._on_matrix_changed(update[0], update[1], update[2])
self._state.matrix_changed(update[0], update[1], update[2]) self.state_changed = True
def _send_to_target(self, update):
if self.split_target_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)
def _receive_from_initiator(self): def _receive_from_initiator(self):
if self.uart is not None and self.uart.in_waiting > 0 or self.uart_buffer: if self.uart is not None and self.uart.in_waiting > 0 or self.uart_buffer:
@ -144,14 +131,14 @@ class KMKKeyboard:
microcontroller.reset() microcontroller.reset()
while self.uart.in_waiting >= 3: while self._uart.in_waiting >= 3:
self.uart_buffer.append(self.uart.read(3)) self.uart_buffer.append(self._uart.read(3))
if self.uart_buffer: if self.uart_buffer:
update = bytearray(self.uart_buffer.pop(0)) update = bytearray(self.uart_buffer.pop(0))
# Built in debug mode switch # Built in debug mode switch
if update == b'DEB': if update == b'DEB':
print(self.uart.readline()) print(self._uart.readline())
return None return None
return update return update
@ -163,26 +150,207 @@ class KMKKeyboard:
be detected and handled differently than typical keypresses. be detected and handled differently than typical keypresses.
:param message: Debug message :param message: Debug message
''' '''
if self.uart is not None: if self._uart is not None:
self.uart.write('DEB') self._uart.write('DEB')
self.uart.write(message, '\n') self._uart.write(message, '\n')
def init_uart(self, pin, timeout=20): #####
if self.is_target: # SPLICE: INTERNAL STATE
return busio.UART(tx=None, rx=pin, timeout=timeout) # FIXME CLEAN THIS
#####
def _find_key_in_map(self, row, col):
ic = intify_coordinate(row, col)
try:
idx = self.coord_mapping.index(ic)
except ValueError:
if self.debug_enabled:
print(
'CoordMappingNotFound(ic={}, row={}, col={})'.format(ic, row, col)
)
return None
for layer in self._active_layers:
layer_key = self.keymap[layer][idx]
if not layer_key or layer_key == KC.TRNS:
continue
if self.debug_enabled:
print('KeyResolution(key={})'.format(layer_key))
return layer_key
def _on_matrix_changed(self, row, col, is_pressed):
if self.debug_enabled:
print('MatrixChange(col={} row={} pressed={})'.format(col, row, is_pressed))
int_coord = intify_coordinate(row, col)
kc_changed = self._find_key_in_map(row, col)
if kc_changed is None:
print('MatrixUndefinedCoordinate(col={} row={})'.format(col, row))
return self
return self._process_key(kc_changed, is_pressed, int_coord, (row, col))
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:
return busio.UART(tx=pin, rx=None, timeout=timeout) if is_pressed:
key._on_press(self, coord_int, coord_raw)
else:
key._on_release(self, coord_int, coord_raw)
def go(self, hid_type=HIDModes.USB, **kwargs): return self
def _remove_key(self, keycode):
self._keys_pressed.discard(keycode)
return self._process_key(keycode, False)
def _add_key(self, keycode):
self._keys_pressed.add(keycode)
return self._process_key(keycode, True)
def _tap_key(self, keycode):
self._add_key(keycode)
# On the next cycle, we'll remove the key.
self._set_timeout(False, lambda: self._remove_key(keycode))
return self
def _process_tap_dance(self, changed_key, is_pressed):
if is_pressed:
if not isinstance(changed_key.meta, TapDanceKeyMeta):
# If we get here, changed_key is not a TapDanceKey and thus
# the user kept typing elsewhere (presumably). End ALL of the
# currently outstanding tap dance runs.
for k, v in self._tap_dance_counts.items():
if v:
self._end_tap_dance(k)
return self
if (
changed_key not in self._tap_dance_counts
or not self._tap_dance_counts[changed_key]
):
self._tap_dance_counts[changed_key] = 1
self._set_timeout(
self.tap_time, lambda: self._end_tap_dance(changed_key)
)
self._tapping = True
else:
self._tap_dance_counts[changed_key] += 1
if changed_key not in self._tap_side_effects:
self._tap_side_effects[changed_key] = None
else:
has_side_effects = self._tap_side_effects[changed_key] is not None
hit_max_defined_taps = self._tap_dance_counts[changed_key] == len(
changed_key.codes
)
if has_side_effects or hit_max_defined_taps:
self._end_tap_dance(changed_key)
return self
def _end_tap_dance(self, td_key):
v = self._tap_dance_counts[td_key] - 1
if v >= 0:
if td_key in self._keys_pressed:
key_to_press = td_key.codes[v]
self._add_key(key_to_press)
self._tap_side_effects[td_key] = key_to_press
self._hid_pending = True
else:
if self._tap_side_effects[td_key]:
self._remove_key(self._tap_side_effects[td_key])
self._tap_side_effects[td_key] = None
self._hid_pending = True
self._cleanup_tap_dance(td_key)
else:
self._tap_key(td_key.codes[v])
self._cleanup_tap_dance(td_key)
return self
def _cleanup_tap_dance(self, td_key):
self._tap_dance_counts[td_key] = 0
self._tapping = any(count > 0 for count in self._tap_dance_counts.values())
return self
def _set_timeout(self, after_ticks, callback):
if after_ticks is False:
# We allow passing False as an implicit "run this on the next process timeouts cycle"
timeout_key = ticks_ms()
else:
timeout_key = ticks_ms() + after_ticks
while timeout_key in self._timeouts:
timeout_key += 1
self._timeouts[timeout_key] = callback
return timeout_key
def _cancel_timeout(self, timeout_key):
if timeout_key in self._timeouts:
del self._timeouts[timeout_key]
def _process_timeouts(self):
if not self._timeouts:
return self
current_time = ticks_ms()
# cast this to a tuple to ensure that if a callback itself sets
# timeouts, we do not handle them on the current cycle
timeouts = tuple(self._timeouts.items())
for k, v in timeouts:
if k <= current_time:
v()
del self._timeouts[k]
return self
#####
# SPLICE END: INTERNAL STATE
# TODO FIXME REMOVE THIS
#####
def _init_sanity_check(self):
'''
Ensure the provided configuration is *probably* bootable
'''
assert self.keymap, 'must define a keymap with at least one row' assert self.keymap, 'must define a keymap with at least one row'
assert self.row_pins, 'no GPIO pins defined for matrix rows' assert self.row_pins, 'no GPIO pins defined for matrix rows'
assert self.col_pins, 'no GPIO pins defined for matrix columns' assert self.col_pins, 'no GPIO pins defined for matrix columns'
assert self.diode_orientation is not None, 'diode orientation must be defined' assert self.diode_orientation is not None, 'diode orientation must be defined'
assert ( assert (
hid_type in HIDModes.ALL_MODES self.hid_type in HIDModes.ALL_MODES
), 'hid_type must be a value from kmk.consts.HIDModes' ), 'hid_type must be a value from kmk.consts.HIDModes'
# Attempt to sanely guess a coord_mapping if one is not provided return self
def _init_coord_mapping(self):
'''
Attempt to sanely guess a coord_mapping if one is not provided. No-op
if `kmk.extensions.split.Split` is used, it provides equivalent
functionality in `on_bootup`
To save RAM on boards that don't use Split, we don't import Split
and do an isinstance check, but instead do string detection
'''
if any(
x.__class__.__module__ == 'kmk.extensions.split' for x in self._extensions
):
return
if not self.coord_mapping: if not self.coord_mapping:
self.coord_mapping = [] self.coord_mapping = []
@ -190,19 +358,14 @@ class KMKKeyboard:
rows_to_calc = len(self.row_pins) rows_to_calc = len(self.row_pins)
cols_to_calc = len(self.col_pins) cols_to_calc = len(self.col_pins)
if self.split_offsets:
rows_to_calc *= 2
cols_to_calc *= 2
for ridx in range(rows_to_calc): for ridx in range(rows_to_calc):
for cidx in range(cols_to_calc): for cidx in range(cols_to_calc):
self.coord_mapping.append(ic(ridx, cidx)) self.coord_mapping.append(intify_coordinate(ridx, cidx))
self._state = InternalState(self) def _init_hid(self):
if self.hid_type == HIDModes.NOOP:
if hid_type == HIDModes.NOOP:
self.hid_helper = AbstractHID self.hid_helper = AbstractHID
elif hid_type == HIDModes.USB: elif self.hid_type == HIDModes.USB:
try: try:
from kmk.hid import USBHID from kmk.hid import USBHID
@ -210,7 +373,7 @@ class KMKKeyboard:
except ImportError: except ImportError:
self.hid_helper = AbstractHID self.hid_helper = AbstractHID
print('USB HID is unsupported ') print('USB HID is unsupported ')
elif hid_type == HIDModes.BLE: elif self.hid_type == HIDModes.BLE:
try: try:
from kmk.ble import BLEHID from kmk.ble import BLEHID
@ -221,92 +384,87 @@ class KMKKeyboard:
self._hid_helper_inst = self.hid_helper(**kwargs) self._hid_helper_inst = self.hid_helper(**kwargs)
# Split keyboard Init def _init_matrix(self):
if self.split_type is not None: self.matrix = MatrixScanner(
try:
# Working around https://github.com/adafruit/circuitpython/issues/1769
self._hid_helper_inst.create_report([]).send()
self.is_target = True
# Sleep 2s so target 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_target = False
if self.split_flip and not self.is_target:
self.col_pins = list(reversed(self.col_pins))
if self.target_side == 'Left':
self.split_target_left = self.is_target
elif self.target_side == 'Right':
self.split_target_left = not self.is_target
else:
self.is_target = True
if self.uart_pin is not None:
self.uart = self.init_uart(self.uart_pin)
if self.rgb_pixel_pin:
self.pixels = rgb.RGB(self.rgb_config, self.rgb_pixel_pin)
self.rgb_config = None # No longer needed
self.pixels.loopcounter = 0
else:
self.pixels = None
if self.led_pin:
self.led = led.led(self.led_pin, self.led_config)
self.led_config = None # No longer needed
else:
self.led = None
self.matrix = self.matrix_scanner(
cols=self.col_pins, cols=self.col_pins,
rows=self.row_pins, rows=self.row_pins,
diode_orientation=self.diode_orientation, diode_orientation=self.diode_orientation,
rollover_cols_every_rows=getattr(self, 'rollover_cols_every_rows', None), rollover_cols_every_rows=getattr(self, 'rollover_cols_every_rows', None),
) )
# Compile string leader sequences return self
for k, v in self.leader_dictionary.items():
if not isinstance(k, tuple):
new_key = tuple(KC[c] for c in k)
self.leader_dictionary[new_key] = v
for k, v in self.leader_dictionary.items(): def go(self, hid_type=HIDModes.USB, **kwargs):
if not isinstance(k, tuple): self._extensions = [] + getattr(self, 'extensions', [])
del self.leader_dictionary[k]
try:
del self.extensions
except Exception:
pass
finally:
gc.collect()
self.hid_type = hid_type
self._init_sanity_check()
self._init_coord_mapping()
self._init_hid()
for ext in self._extensions:
try:
ext.during_bootup(self)
except Exception:
# TODO FIXME log the exceptions or something
pass
self._init_matrix()
self._print_debug_cycle(init=True)
while True: while True:
if self.split_type is not None and self.is_target: self.state_changed = False
update = self._receive_from_initiator()
if update is not None:
self._handle_matrix_report(update)
update = self.matrix.scan_for_changes() for ext in self._extensions:
try:
self._handle_matrix_report(ext.before_matrix_scan(self))
except Exception as e:
print(e)
if update is not None: matrix_update = self.matrix.scan_for_changes()
if self.is_target: self._handle_matrix_report(matrix_update)
self._handle_matrix_report(update)
else:
# This keyboard is a initiator, and needs to send data to target
self._send_to_target(update)
if self._state.hid_pending: for ext in self._extensions:
try:
ext.after_matrix_scan(self, matrix_update)
except Exception as e:
print(e)
for ext in self._extensions:
try:
ext.before_hid_send(self)
except Exception:
# TODO FIXME log the exceptions or something
pass
if self._hid_pending:
self._send_hid() self._send_hid()
old_timeouts_len = len(self._state.timeouts) old_timeouts_len = len(self._timeouts)
self._state.process_timeouts() self._process_timeouts()
new_timeouts_len = len(self._state.timeouts) new_timeouts_len = len(self._timeouts)
if old_timeouts_len != new_timeouts_len: if old_timeouts_len != new_timeouts_len:
if self._state.hid_pending: self.state_changed = True
if self._hid_pending:
self._send_hid() self._send_hid()
if self.pixels and self.pixels.animation_mode: for ext in self._extensions:
self.pixels.loopcounter += 1 try:
if self.pixels.loopcounter >= 30: ext.after_hid_send(self)
self.pixels = self.pixels.animate() except Exception:
self.pixels.loopcounter = 0 # TODO FIXME log the exceptions or something
pass
if self.led and self.led.enabled and self.led.animation_mode: if self.state_changed:
self.led = self.led.animate() self._print_debug_cycle()

View File

@ -1,88 +1,105 @@
import pulseio import pulseio
from micropython import const
import time
from math import e, exp, pi, sin from math import e, exp, pi, sin
led_config = { from kmk.extensions import Extension, InvalidExtensionEnvironment
'brightness_step': 5, from kmk.keys import make_key
'brightness_limit': 100,
'breathe_center': 1.5,
'animation_mode': 'static',
'animation_speed': 1,
}
class led: class AnimationModes:
brightness = 0 OFF = 0
time = int(time.monotonic() * 1000) STATIC = 1
pos = 0 STATIC_STANDBY = 2
effect_init = False BREATHING = 3
USER = 4
led = None
brightness_step = 5
brightness_limit = 100
breathe_center = 1.5
animation_mode = 'static'
animation_speed = 1
enabled = True
user_animation = None
def __init__(self, led_pin, config): class LED(Extension):
self.led = pulseio.PWMOut(led_pin) def __init__(
self.brightness_step = const(config['brightness_step']) self,
self.brightness_limit = const(config['brightness_limit']) led_pin,
self.animation_mode = const(config['animation_mode']) brightness_step=5,
self.animation_speed = const(config['animation_speed']) brightness_limit=100,
self.breathe_center = const(config['breathe_center']) breathe_center=1.5,
if config.get('user_animation'): animation_mode=AnimationModes.STATIC,
self.user_animation = config['user_animation'] animation_speed=1,
user_animation=None,
):
try:
self._led = pulseio.PWMOut(led_pin)
except Exception as e:
print(e)
raise InvalidExtensionEnvironment(
'Unable to create pulseio.PWMOut() instance with provided led_pin'
)
self._brightness = 0
self._pos = 0
self._effect_init = False
self.brightness_step = brightness_step
self.brightness_limit = brightness_limit
self.animation_mode = animation_mode
self.animation_speed = animation_speed
self.breathe_center = breathe_center
if user_animation is not None:
self.user_animation = user_animation
make_key(names=('LED_TOG',), on_press=self._key_led_tog)
make_key(names=('LED_INC',), on_press=self._key_led_inc)
make_key(names=('LED_DEC',), on_press=self._key_led_dec)
make_key(names=('LED_ANI',), on_press=self._key_led_ani)
make_key(names=('LED_AND',), on_press=self._key_led_and)
make_key(
names=('LED_MODE_PLAIN', 'LED_M_P'), on_press=self._key_led_mode_static
)
make_key(
names=('LED_MODE_BREATHE', 'LED_M_B'), on_press=self._key_led_mode_breathe
)
def __repr__(self): def __repr__(self):
return 'LED({})'.format(self._to_dict()) return 'LED({})'.format(self._to_dict())
def _to_dict(self): def _to_dict(self):
return { # TODO FIXME remove
'led': self.led, pass
'brightness_step': self.brightness_step,
'brightness_limit': self.brightness_limit,
'animation_mode': self.animation_mode,
'animation_speed': self.animation_speed,
'breathe_center': self.breathe_center,
}
def _init_effect(self): def _init_effect(self):
self.pos = 0 self._pos = 0
self.effect_init = False self._effect_init = False
return self return self
def time_ms(self): def after_hid_send(self, keyboard):
return int(time.monotonic() * 1000) if self._enabled and self.animation_mode:
self.animate()
return keyboard
def set_brightness(self, percent): def set_brightness(self, percent):
self.led.duty_cycle = int(percent / 100 * 65535) self._led.duty_cycle = int(percent / 100 * 65535)
def increase_brightness(self, step=None): def increase_brightness(self, step=None):
if not step: if not step:
self.brightness += self.brightness_step self._brightness += self.brightness_step
else: else:
self.brightness += step self._brightness += step
if self.brightness > 100: if self._brightness > 100:
self.brightness = 100 self._brightness = 100
self.set_brightness(self.brightness) self.set_brightness(self._brightness)
def decrease_brightness(self, step=None): def decrease_brightness(self, step=None):
if not step: if not step:
self.brightness -= self.brightness_step self._brightness -= self.brightness_step
else: else:
self.brightness -= step self._brightness -= step
if self.brightness < 0: if self._brightness < 0:
self.brightness = 0 self._brightness = 0
self.set_brightness(self.brightness) self.set_brightness(self._brightness)
def off(self): def off(self):
self.set_brightness(0) self.set_brightness(0)
@ -110,18 +127,18 @@ class led:
def effect_breathing(self): def effect_breathing(self):
# http://sean.voisen.org/blog/2011/10/breathing-led-with-arduino/ # http://sean.voisen.org/blog/2011/10/breathing-led-with-arduino/
# https://github.com/qmk/qmk_firmware/blob/9f1d781fcb7129a07e671a46461e501e3f1ae59d/quantum/rgblight.c#L806 # https://github.com/qmk/qmk_firmware/blob/9f1d781fcb7129a07e671a46461e501e3f1ae59d/quantum/rgblight.c#L806
sined = sin((self.pos / 255.0) * pi) sined = sin((self._pos / 255.0) * pi)
multip_1 = exp(sined) - self.breathe_center / e multip_1 = exp(sined) - self.breathe_center / e
multip_2 = self.brightness_limit / (e - 1 / e) multip_2 = self.brightness_limit / (e - 1 / e)
self.brightness = int(multip_1 * multip_2) self._brightness = int(multip_1 * multip_2)
self.pos = (self.pos + self.animation_speed) % 256 self._pos = (self._pos + self.animation_speed) % 256
self.set_brightness(self.brightness) self.set_brightness(self._brightness)
return self return self
def effect_static(self): def effect_static(self):
self.set_brightness(self.brightness) self.set_brightness(self._brightness)
# Set animation mode to none to prevent cycles from being wasted # Set animation mode to none to prevent cycles from being wasted
self.animation_mode = None self.animation_mode = None
return self return self
@ -131,16 +148,49 @@ class led:
Activates a "step" in the animation based on the active mode Activates a "step" in the animation based on the active mode
:return: Returns the new state in animation :return: Returns the new state in animation
''' '''
if self.effect_init: if self._effect_init:
self._init_effect() self._init_effect()
if self.enabled: if self._enabled:
if self.animation_mode == 'breathing': if self.animation_mode == AnimationModes.BREATHING:
return self.effect_breathing() return self.effect_breathing()
elif self.animation_mode == 'static': elif self.animation_mode == AnimationModes.STATIC:
return self.effect_static() return self.effect_static()
elif self.animation_mode == 'user': elif self.animation_mode == AnimationModes.USER:
return self.user_animation(self) return self.user_animation(self)
else: else:
self.off() self.off()
return self return self
def _key_led_tog(self, key, state, *args, **kwargs):
if self.animation_mode == AnimationModes.STATIC_STANDBY:
self.animation_mode = AnimationModes.STATIC
self._enabled = not self._enabled
return state
def _key_led_inc(self, key, state, *args, **kwargs):
self.increase_brightness()
return state
def _key_led_dec(self, key, state, *args, **kwargs):
self.decrease_brightness()
return state
def _key_led_ani(self, key, state, *args, **kwargs):
self.increase_ani()
return state
def _key_led_and(self, key, state, *args, **kwargs):
self.decrease_ani()
return state
def _key_led_mode_static(self, key, state, *args, **kwargs):
self._effect_init = True
self.animation_mode = AnimationModes.STATIC
return state
def _key_led_mode_breathe(self, key, state, *args, **kwargs):
self._effect_init = True
self.animation_mode = AnimationModes.BREATHING
return state

View File

@ -27,19 +27,11 @@ import kmk.consts # isort:skip
import kmk.kmktime # isort:skip import kmk.kmktime # isort:skip
import kmk.types # isort:skip import kmk.types # isort:skip
from kmk.consts import LeaderMode, UnicodeMode, KMK_RELEASE # isort:skip
from kmk.hid import USBHID # isort:skip
from kmk.internal_state import InternalState # isort:skip
from kmk.keys import KC # isort:skip
from kmk.matrix import MatrixScanner # isort:skip
# Now handlers that will be used in keys later # Now handlers that will be used in keys later
import kmk.handlers.layers # isort:skip import kmk.handlers.layers # isort:skip
import kmk.handlers.stock # isort:skip import kmk.handlers.stock # isort:skip
# Now stuff that depends on the above (and so on) # Now stuff that depends on the above (and so on)
import kmk.hid # isort:skip
import kmk.keys # isort:skip import kmk.keys # isort:skip
import kmk.matrix # isort:skip import kmk.matrix # isort:skip
import kmk.hid # isort:skip
import kmk.internal_state # isort:skip

View File

@ -1,99 +1,84 @@
from micropython import const import neopixel
import time import time
from math import e, exp, pi, sin from math import e, exp, pi, sin
rgb_config = { from kmk.extensions import Extension
'pixels': None,
'num_pixels': 0, rgb_config = {}
'pixel_pin': None,
'val_limit': 255,
'hue_default': 0,
'sat_default': 100,
'rgb_order': (1, 0, 2), # GRB WS2812
'val_default': 100,
'hue_step': 1,
'sat_step': 1,
'val_step': 1,
'animation_speed': 1,
'breathe_center': 1.5, # 1.0-2.7
'knight_effect_length': 3,
'animation_mode': 'static',
}
class RGB: class AnimationModes:
hue = 0 OFF = 0
sat = 100 STATIC = 1
val = 80 STATIC_STANDBY = 2
BREATHING = 3
USER = 4
class RGB(Extension):
pos = 0 pos = 0
time = int(time.monotonic() * 10) time = int(time.monotonic() * 10)
intervals = (30, 20, 10, 5) intervals = (30, 20, 10, 5)
animation_speed = 1
enabled = True
neopixel = None
rgbw = False
reverse_animation = False
disable_auto_write = False
animation_mode = 'static'
# Set by config def __init__(
num_pixels = None self,
hue_step = None pixel_pin,
sat_step = None num_pixels=0,
val_step = None val_limit=255,
breathe_center = None # 1.0-2.7 hue_default=0,
knight_effect_length = None sat_default=100,
val_limit = None rgb_order=(1, 0, 2), # GRB WS2812
effect_init = False val_default=100,
user_animation = None hue_step=1,
sat_step=1,
val_step=1,
animation_speed=1,
breathe_center=1.5, # 1.0-2.7
knight_effect_length=3,
animation_mode=AnimationModes.STATIC,
effect_init=False,
reverse_animation=False,
user_animation=None,
disable_auto_write=False,
):
self.neopixel = neopixel.NeoPixel(
pixel_pin,
num_pixels,
pixel_order=rgb_order,
auto_write=not disable_auto_write,
)
def __init__(self, config, pixel_pin): if len(rgb_order) == 4:
try: self.rgbw = True
import neopixel
self.neopixel = neopixel.NeoPixel( self.num_pixels = num_pixels
pixel_pin, self.hue_step = hue_step
config['num_pixels'], self.sat_step = sat_step
pixel_order=config['rgb_order'], self.val_step = val_step
auto_write=False, self.hue = hue_default
) self.sat = sat_default
if len(config['rgb_order']) == 4: self.val = val_default
self.rgbw = True self.breathe_center = breathe_center
self.num_pixels = const(config['num_pixels']) self.knight_effect_length = knight_effect_length
self.hue_step = const(config['hue_step']) self.val_limit = val_limit
self.sat_step = const(config['sat_step']) self.animation_mode = animation_mode
self.val_step = const(config['val_step']) self.animation_speed = animation_speed
self.hue = const(config['hue_default']) self.reverse_animation = reverse_animation
self.sat = const(config['sat_default']) self.user_animation = user_animation
self.val = const(config['val_default']) self.disable_auto_write = disable_auto_write
self.breathe_center = const(config['breathe_center'])
self.knight_effect_length = const(config['knight_effect_length'])
self.val_limit = const(config['val_limit'])
self.animation_mode = config['animation_mode']
self.animation_speed = const(config['animation_speed'])
if 'user_animation' in config:
self.user_animation = config['user_animation']
except ImportError as e: def during_bootup(self, keyboard):
print(e) pass
def __repr__(self): def after_hid_send(self, keyboard):
return 'RGB({})'.format(self._to_dict()) if self.animation_mode:
self.loopcounter += 1
if self.loopcounter >= 7:
self.animate()
self.loopcounter = 0
def _to_dict(self): return keyboard
return {
'hue': self.hue,
'sat': self.sat,
'val': self.val,
'time': self.time,
'intervals': self.intervals,
'animation_mode': self.animation_mode,
'animation_speed': self.animation_speed,
'enabled': self.enabled,
'neopixel': self.neopixel,
'disable_auto_write': self.disable_auto_write,
}
def time_ms(self): def time_ms(self):
return int(time.monotonic() * 1000) return int(time.monotonic() * 1000)

View File

@ -1,5 +1,6 @@
from kmk.boards.klarank import KMKKeyboard from kmk.boards.klarank import KMKKeyboard
from kmk.consts import LeaderMode, UnicodeMode from kmk.consts import UnicodeMode
from kmk.extensions.leader import Leader, LeaderMode
from kmk.handlers.sequences import compile_unicode_string_sequences as cuss from kmk.handlers.sequences import compile_unicode_string_sequences as cuss
from kmk.handlers.sequences import send_string from kmk.handlers.sequences import send_string
from kmk.keys import KC, make_key from kmk.keys import KC, make_key
@ -42,8 +43,16 @@ emoticons = cuss({
WPM = send_string('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Bibendum arcu vitae elementum curabitur vitae nunc sed. Facilisis sed odio morbi quis.') WPM = send_string('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Bibendum arcu vitae elementum curabitur vitae nunc sed. Facilisis sed odio morbi quis.')
keyboard.leader_mode = LeaderMode.TIMEOUT _______ = KC.TRNS
keyboard.leader_dictionary = { xxxxxxx = KC.NO
HELLA_TD = KC.TD(
KC.A,
KC.B,
send_string('macros in a tap dance? I think yes'),
KC.TG(1),
)
leader_ext = Leader(mode=LeaderMode.ENTER, sequences={
'hello': send_string('hello world from kmk macros'), 'hello': send_string('hello world from kmk macros'),
'wpm': WPM, 'wpm': WPM,
'atf': emoticons.ANGRY_TABLE_FLIP, 'atf': emoticons.ANGRY_TABLE_FLIP,
@ -55,16 +64,9 @@ keyboard.leader_dictionary = {
'poop': emoticons.POOP, 'poop': emoticons.POOP,
'ls': KC.LGUI(KC.HOME), 'ls': KC.LGUI(KC.HOME),
'dbg': KC.DBG, 'dbg': KC.DBG,
} })
_______ = KC.TRNS keyboard.extensions = [leader_ext]
xxxxxxx = KC.NO
HELLA_TD = KC.TD(
KC.A,
KC.B,
send_string('macros in a tap dance? I think yes'),
KC.TG(1),
)
def shrek_is_life(*args, **kwargs): def shrek_is_life(*args, **kwargs):