Initial attempt to merge internal_state with kmk_keyboard. Seems to work on Plank so far
This commit is contained in:
parent
ea327f8f76
commit
9821f7bcc3
@ -9,10 +9,3 @@ class UnicodeMode:
|
||||
LINUX = IBUS = 1
|
||||
MACOS = OSX = RALT = 2
|
||||
WINC = 3
|
||||
|
||||
|
||||
class LeaderMode:
|
||||
TIMEOUT = 0
|
||||
TIMEOUT_ACTIVE = 1
|
||||
ENTER = 2
|
||||
ENTER_ACTIVE = 3
|
||||
|
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)
|
@ -5,7 +5,7 @@ def df_pressed(key, state, *args, **kwargs):
|
||||
'''
|
||||
Switches the default layer
|
||||
'''
|
||||
state.active_layers[-1] = key.meta.layer
|
||||
state._active_layers[-1] = key.meta.layer
|
||||
return state
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ def mo_pressed(key, state, *args, **kwargs):
|
||||
'''
|
||||
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
|
||||
|
||||
|
||||
@ -27,8 +27,8 @@ def mo_released(key, state, KC, *args, **kwargs):
|
||||
# triggered by MO() and then defaulting to the MO()'s layer
|
||||
# would result in no layers active
|
||||
try:
|
||||
del_idx = state.active_layers.index(key.meta.layer)
|
||||
del state.active_layers[del_idx]
|
||||
del_idx = state._active_layers.index(key.meta.layer)
|
||||
del state._active_layers[del_idx]
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
@ -39,10 +39,10 @@ def lm_pressed(key, state, *args, **kwargs):
|
||||
'''
|
||||
As MO(layer) but with mod active
|
||||
'''
|
||||
state.hid_pending = True
|
||||
state._hid_pending = True
|
||||
# Sets the timer start and acts like MO otherwise
|
||||
state.start_time['lm'] = ticks_ms()
|
||||
state.keys_pressed.add(key.meta.kc)
|
||||
state._start_time['lm'] = ticks_ms()
|
||||
state._keys_pressed.add(key.meta.kc)
|
||||
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
|
||||
'''
|
||||
state.hid_pending = True
|
||||
state.keys_pressed.discard(key.meta.kc)
|
||||
state.start_time['lm'] = None
|
||||
state._hid_pending = True
|
||||
state._keys_pressed.discard(key.meta.kc)
|
||||
state._start_time['lm'] = None
|
||||
return mo_released(key, state, *args, **kwargs)
|
||||
|
||||
|
||||
def lt_pressed(key, state, *args, **kwargs):
|
||||
# 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)
|
||||
|
||||
|
||||
def lt_released(key, state, *args, **kwargs):
|
||||
# On keyup, check timer, and press key if needed.
|
||||
if state.start_time['lt'] and (
|
||||
ticks_diff(ticks_ms(), state.start_time['lt']) < state.config.tap_time
|
||||
if state._start_time['lt'] and (
|
||||
ticks_diff(ticks_ms(), state._start_time['lt']) < state.tap_time
|
||||
):
|
||||
state.hid_pending = True
|
||||
state.tap_key(key.meta.kc)
|
||||
state._hid_pending = True
|
||||
state._tap_key(key.meta.kc)
|
||||
|
||||
mo_released(key, state, *args, **kwargs)
|
||||
state.start_time['lt'] = None
|
||||
state._start_time['lt'] = None
|
||||
return state
|
||||
|
||||
|
||||
@ -81,10 +81,10 @@ def tg_pressed(key, state, *args, **kwargs):
|
||||
'''
|
||||
# See mo_released for implementation details around this
|
||||
try:
|
||||
del_idx = state.active_layers.index(key.meta.layer)
|
||||
del state.active_layers[del_idx]
|
||||
del_idx = state._active_layers.index(key.meta.layer)
|
||||
del state._active_layers[del_idx]
|
||||
except ValueError:
|
||||
state.active_layers.insert(0, key.meta.layer)
|
||||
state._active_layers.insert(0, key.meta.layer)
|
||||
|
||||
return state
|
||||
|
||||
@ -93,8 +93,8 @@ def to_pressed(key, state, *args, **kwargs):
|
||||
'''
|
||||
Activates layer and deactivates all other layers
|
||||
'''
|
||||
state.active_layers.clear()
|
||||
state.active_layers.insert(0, key.meta.layer)
|
||||
state._active_layers.clear()
|
||||
state._active_layers.insert(0, key.meta.layer)
|
||||
|
||||
return state
|
||||
|
||||
@ -104,23 +104,21 @@ def tt_pressed(key, state, *args, **kwargs):
|
||||
Momentarily activates layer if held, toggles it if tapped repeatedly
|
||||
'''
|
||||
# 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
|
||||
state.start_time['tt'] = ticks_ms()
|
||||
state._start_time['tt'] = ticks_ms()
|
||||
return mo_pressed(key, state, *args, **kwargs)
|
||||
elif ticks_diff(ticks_ms(), state.start_time['tt']) < state.config.tap_time:
|
||||
state.start_time['tt'] = None
|
||||
elif ticks_diff(ticks_ms(), state._start_time['tt']) < state.tap_time:
|
||||
state._start_time['tt'] = None
|
||||
return tg_pressed(key, state, *args, **kwargs)
|
||||
|
||||
|
||||
def tt_released(key, state, *args, **kwargs):
|
||||
tap_timed_out = (
|
||||
ticks_diff(ticks_ms(), state.start_time['tt']) >= state.config.tap_time
|
||||
)
|
||||
if state.start_time['tt'] is None or tap_timed_out:
|
||||
tap_timed_out = ticks_diff(ticks_ms(), state._start_time['tt']) >= state.tap_time
|
||||
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
|
||||
# time window, then acts like TG.
|
||||
state.start_time['tt'] = None
|
||||
state._start_time['tt'] = None
|
||||
return mo_released(key, state, *args, **kwargs)
|
||||
|
||||
return state
|
||||
|
@ -3,21 +3,21 @@ from kmk.kmktime import ticks_diff, ticks_ms
|
||||
|
||||
def mt_pressed(key, state, *args, **kwargs):
|
||||
# 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
|
||||
|
||||
|
||||
def mt_released(key, state, *args, **kwargs):
|
||||
# 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'
|
||||
if state.start_time[timer_name] and (
|
||||
ticks_diff(ticks_ms(), state.start_time[timer_name]) < state.config.tap_time
|
||||
if state._start_time[timer_name] and (
|
||||
ticks_diff(ticks_ms(), state._start_time[timer_name]) < state.tap_time
|
||||
):
|
||||
state.hid_pending = True
|
||||
state.tap_key(key.meta.kc)
|
||||
state._hid_pending = True
|
||||
state._tap_key(key.meta.kc)
|
||||
|
||||
state.start_time[timer_name] = None
|
||||
state._start_time[timer_name] = None
|
||||
return state
|
||||
|
@ -12,18 +12,18 @@ def get_wide_ordinal(char):
|
||||
|
||||
|
||||
def sequence_press_handler(key, state, KC, *args, **kwargs):
|
||||
old_keys_pressed = state.keys_pressed
|
||||
state.keys_pressed = set()
|
||||
old_keys_pressed = state._keys_pressed
|
||||
state._keys_pressed = set()
|
||||
|
||||
for ikey in key.meta.seq:
|
||||
if not getattr(ikey, 'no_press', None):
|
||||
state.process_key(ikey, True)
|
||||
state.config._send_hid()
|
||||
state._process_key(ikey, True)
|
||||
state._send_hid()
|
||||
if not getattr(ikey, 'no_release', None):
|
||||
state.process_key(ikey, False)
|
||||
state.config._send_hid()
|
||||
state._process_key(ikey, False)
|
||||
state._send_hid()
|
||||
|
||||
state.keys_pressed = old_keys_pressed
|
||||
state._keys_pressed = old_keys_pressed
|
||||
|
||||
return state
|
||||
|
||||
@ -103,16 +103,16 @@ def unicode_codepoint_sequence(codepoints):
|
||||
kc_macros = [simple_key_sequence(kc_seq) for kc_seq in kc_seqs]
|
||||
|
||||
def _unicode_sequence(key, state, *args, **kwargs):
|
||||
if state.config.unicode_mode == UnicodeMode.IBUS:
|
||||
state.process_key(
|
||||
if state.unicode_mode == UnicodeMode.IBUS:
|
||||
state._process_key(
|
||||
simple_key_sequence(_ibus_unicode_sequence(kc_macros, state)), True
|
||||
)
|
||||
elif state.config.unicode_mode == UnicodeMode.RALT:
|
||||
state.process_key(
|
||||
elif state.unicode_mode == UnicodeMode.RALT:
|
||||
state._process_key(
|
||||
simple_key_sequence(_ralt_unicode_sequence(kc_macros, state)), True
|
||||
)
|
||||
elif state.config.unicode_mode == UnicodeMode.WINC:
|
||||
state.process_key(
|
||||
elif state.unicode_mode == UnicodeMode.WINC:
|
||||
state._process_key(
|
||||
simple_key_sequence(_winc_unicode_sequence(kc_macros, state)), True
|
||||
)
|
||||
|
||||
|
@ -6,23 +6,23 @@ def passthrough(key, state, *args, **kwargs):
|
||||
|
||||
|
||||
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:
|
||||
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
|
||||
|
||||
|
||||
def default_released(key, state, KC, coord_int=None, coord_raw=None):
|
||||
state.hid_pending = True
|
||||
state.keys_pressed.discard(key)
|
||||
state._hid_pending = True
|
||||
state._keys_pressed.discard(key)
|
||||
|
||||
if coord_int is not None:
|
||||
state.keys_pressed.discard(state.coord_keys_pressed.get(coord_int, None))
|
||||
state.coord_keys_pressed[coord_int] = None
|
||||
state._keys_pressed.discard(state._coord_keys_pressed.get(coord_int, None))
|
||||
state._coord_keys_pressed[coord_int] = None
|
||||
|
||||
return state
|
||||
|
||||
@ -53,12 +53,12 @@ def bootloader(*args, **kwargs):
|
||||
|
||||
|
||||
def debug_pressed(key, state, KC, *args, **kwargs):
|
||||
if state.config.debug_enabled:
|
||||
if state.debug_enabled:
|
||||
print('DebugDisable()')
|
||||
else:
|
||||
print('DebugEnable()')
|
||||
|
||||
state.config.debug_enabled = not state.config.debug_enabled
|
||||
state.debug_enabled = not state.debug_enabled
|
||||
|
||||
return state
|
||||
|
||||
@ -66,48 +66,48 @@ def debug_pressed(key, state, KC, *args, **kwargs):
|
||||
def gesc_pressed(key, state, KC, *args, **kwargs):
|
||||
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
|
||||
state.config._send_hid()
|
||||
state._send_hid()
|
||||
# if Shift is held, KC_GRAVE will become KC_TILDE on OS level
|
||||
state.keys_pressed.add(KC.GRAVE)
|
||||
state.hid_pending = True
|
||||
state._keys_pressed.add(KC.GRAVE)
|
||||
state._hid_pending = True
|
||||
return state
|
||||
|
||||
# else return KC_ESC
|
||||
state.keys_pressed.add(KC.ESCAPE)
|
||||
state.hid_pending = True
|
||||
state._keys_pressed.add(KC.ESCAPE)
|
||||
state._hid_pending = True
|
||||
|
||||
return state
|
||||
|
||||
|
||||
def gesc_released(key, state, KC, *args, **kwargs):
|
||||
state.keys_pressed.discard(KC.ESCAPE)
|
||||
state.keys_pressed.discard(KC.GRAVE)
|
||||
state.hid_pending = True
|
||||
state._keys_pressed.discard(KC.ESCAPE)
|
||||
state._keys_pressed.discard(KC.GRAVE)
|
||||
state._hid_pending = True
|
||||
return state
|
||||
|
||||
|
||||
def bkdl_pressed(key, state, KC, *args, **kwargs):
|
||||
BKDL_TRIGGERS = {KC.LGUI, KC.RGUI}
|
||||
|
||||
if BKDL_TRIGGERS.intersection(state.keys_pressed):
|
||||
state.config._send_hid()
|
||||
state.keys_pressed.add(KC.DEL)
|
||||
state.hid_pending = True
|
||||
if BKDL_TRIGGERS.intersection(state._keys_pressed):
|
||||
state._send_hid()
|
||||
state._keys_pressed.add(KC.DEL)
|
||||
state._hid_pending = True
|
||||
return state
|
||||
|
||||
# else return KC_ESC
|
||||
state.keys_pressed.add(KC.BKSP)
|
||||
state.hid_pending = True
|
||||
state._keys_pressed.add(KC.BKSP)
|
||||
state._hid_pending = True
|
||||
|
||||
return state
|
||||
|
||||
|
||||
def bkdl_released(key, state, KC, *args, **kwargs):
|
||||
state.keys_pressed.discard(KC.BKSP)
|
||||
state.keys_pressed.discard(KC.DEL)
|
||||
state.hid_pending = True
|
||||
state._keys_pressed.discard(KC.BKSP)
|
||||
state._keys_pressed.discard(KC.DEL)
|
||||
state._hid_pending = True
|
||||
return state
|
||||
|
||||
|
||||
@ -117,15 +117,11 @@ def sleep_pressed(key, state, KC, *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
|
||||
|
||||
|
||||
def leader_pressed(key, state, *args, **kwargs):
|
||||
return state._begin_leader_mode()
|
||||
|
||||
|
||||
def td_pressed(key, state, *args, **kwargs):
|
||||
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):
|
||||
if state.config.pixels.animation_mode == 'static_standby':
|
||||
state.config.pixels.animation_mode = 'static'
|
||||
state.config.pixels.enabled = not state.config.pixels.enabled
|
||||
if state.pixels.animation_mode == 'static_standby':
|
||||
state.pixels.animation_mode = 'static'
|
||||
state.pixels.enabled = not state.pixels.enabled
|
||||
return state
|
||||
|
||||
|
||||
def rgb_hui(key, state, *args, **kwargs):
|
||||
state.config.pixels.increase_hue()
|
||||
state.pixels.increase_hue()
|
||||
return state
|
||||
|
||||
|
||||
def rgb_hud(key, state, *args, **kwargs):
|
||||
state.config.pixels.decrease_hue()
|
||||
state.pixels.decrease_hue()
|
||||
return state
|
||||
|
||||
|
||||
def rgb_sai(key, state, *args, **kwargs):
|
||||
state.config.pixels.increase_sat()
|
||||
state.pixels.increase_sat()
|
||||
return state
|
||||
|
||||
|
||||
def rgb_sad(key, state, *args, **kwargs):
|
||||
state.config.pixels.decrease_sat()
|
||||
state.pixels.decrease_sat()
|
||||
return state
|
||||
|
||||
|
||||
def rgb_vai(key, state, *args, **kwargs):
|
||||
state.config.pixels.increase_val()
|
||||
state.pixels.increase_val()
|
||||
return state
|
||||
|
||||
|
||||
def rgb_vad(key, state, *args, **kwargs):
|
||||
state.config.pixels.decrease_val()
|
||||
state.pixels.decrease_val()
|
||||
return state
|
||||
|
||||
|
||||
def rgb_ani(key, state, *args, **kwargs):
|
||||
state.config.pixels.increase_ani()
|
||||
state.pixels.increase_ani()
|
||||
return state
|
||||
|
||||
|
||||
def rgb_and(key, state, *args, **kwargs):
|
||||
state.config.pixels.decrease_ani()
|
||||
state.pixels.decrease_ani()
|
||||
return state
|
||||
|
||||
|
||||
def rgb_mode_static(key, state, *args, **kwargs):
|
||||
state.config.pixels.effect_init = True
|
||||
state.config.pixels.animation_mode = 'static'
|
||||
state.pixels.effect_init = True
|
||||
state.pixels.animation_mode = 'static'
|
||||
return state
|
||||
|
||||
|
||||
def rgb_mode_breathe(key, state, *args, **kwargs):
|
||||
state.config.pixels.effect_init = True
|
||||
state.config.pixels.animation_mode = 'breathing'
|
||||
state.pixels.effect_init = True
|
||||
state.pixels.animation_mode = 'breathing'
|
||||
return state
|
||||
|
||||
|
||||
def rgb_mode_breathe_rainbow(key, state, *args, **kwargs):
|
||||
state.config.pixels.effect_init = True
|
||||
state.config.pixels.animation_mode = 'breathing_rainbow'
|
||||
state.pixels.effect_init = True
|
||||
state.pixels.animation_mode = 'breathing_rainbow'
|
||||
return state
|
||||
|
||||
|
||||
def rgb_mode_rainbow(key, state, *args, **kwargs):
|
||||
state.config.pixels.effect_init = True
|
||||
state.config.pixels.animation_mode = 'rainbow'
|
||||
state.pixels.effect_init = True
|
||||
state.pixels.animation_mode = 'rainbow'
|
||||
return state
|
||||
|
||||
|
||||
def rgb_mode_swirl(key, state, *args, **kwargs):
|
||||
state.config.pixels.effect_init = True
|
||||
state.config.pixels.animation_mode = 'swirl'
|
||||
state.pixels.effect_init = True
|
||||
state.pixels.animation_mode = 'swirl'
|
||||
return state
|
||||
|
||||
|
||||
def rgb_mode_knight(key, state, *args, **kwargs):
|
||||
state.config.pixels.effect_init = True
|
||||
state.config.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()
|
||||
state.pixels.effect_init = True
|
||||
state.pixels.animation_mode = 'knight'
|
||||
return state
|
||||
|
@ -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
|
19
kmk/keys.py
19
kmk/keys.py
@ -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_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
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator,
|
||||
|
@ -3,19 +3,20 @@
|
||||
# a line into their keymaps.
|
||||
import kmk.preload_imports # isort:skip # NOQA
|
||||
|
||||
import busio
|
||||
import gc
|
||||
|
||||
from kmk import led, rgb
|
||||
from kmk.consts import LeaderMode, UnicodeMode
|
||||
from kmk.hid import AbstractHID, HIDModes
|
||||
from kmk.internal_state import InternalState
|
||||
from kmk import rgb
|
||||
from kmk.consts import KMK_RELEASE, UnicodeMode
|
||||
from kmk.hid import BLEHID, USBHID, AbstractHID, HIDModes
|
||||
from kmk.keys import KC
|
||||
from kmk.kmktime import sleep_ms
|
||||
from kmk.matrix import MatrixScanner
|
||||
from kmk.matrix import intify_coordinate as ic
|
||||
from kmk.kmktime import ticks_ms
|
||||
from kmk.matrix import MatrixScanner, intify_coordinate
|
||||
from kmk.types import TapDanceKeyMeta
|
||||
|
||||
|
||||
class KMKKeyboard:
|
||||
#####
|
||||
# User-configurable
|
||||
debug_enabled = False
|
||||
|
||||
keymap = None
|
||||
@ -29,29 +30,27 @@ class KMKKeyboard:
|
||||
|
||||
unicode_mode = UnicodeMode.NOOP
|
||||
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_pixel_pin = None
|
||||
rgb_config = rgb.rgb_config
|
||||
|
||||
# led config (mono color)
|
||||
led_pin = None
|
||||
led_config = led.led_config
|
||||
#####
|
||||
# Internal State
|
||||
_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):
|
||||
return (
|
||||
@ -65,20 +64,16 @@ class KMKKeyboard:
|
||||
'matrix_scanner={} '
|
||||
'unicode_mode={} '
|
||||
'tap_time={} '
|
||||
'leader_mode={} '
|
||||
'leader_dictionary=truncated '
|
||||
'leader_timeout={} '
|
||||
'hid_helper={} '
|
||||
'extra_data_pin={} '
|
||||
'split_offsets={} '
|
||||
'split_flip={} '
|
||||
'target_side={} '
|
||||
'split_type={} '
|
||||
'split_target_left={} '
|
||||
'is_target={} '
|
||||
'uart={} '
|
||||
'uart_flip={} '
|
||||
'uart_pin={}'
|
||||
'keys_pressed={} '
|
||||
'coord_keys_pressed={} '
|
||||
'hid_pending={} '
|
||||
'active_layers={} '
|
||||
'start_time={} '
|
||||
'timeouts={} '
|
||||
'tapping={} '
|
||||
'tap_dance_counts={} '
|
||||
'tap_side_effects={}'
|
||||
')'
|
||||
).format(
|
||||
self.debug_enabled,
|
||||
@ -90,51 +85,43 @@ class KMKKeyboard:
|
||||
self.matrix_scanner,
|
||||
self.unicode_mode,
|
||||
self.tap_time,
|
||||
self.leader_mode,
|
||||
# self.leader_dictionary,
|
||||
self.leader_timeout,
|
||||
self.hid_helper.__name__,
|
||||
self.extra_data_pin,
|
||||
self.split_offsets,
|
||||
self.split_flip,
|
||||
self.target_side,
|
||||
self.split_type,
|
||||
self.split_target_left,
|
||||
self.is_target,
|
||||
self.uart,
|
||||
self.uart_flip,
|
||||
self.uart_pin,
|
||||
# internal state
|
||||
self._keys_pressed,
|
||||
self._coord_keys_pressed,
|
||||
self._hid_pending,
|
||||
self._active_layers,
|
||||
self._start_time,
|
||||
self._timeouts,
|
||||
self._tapping,
|
||||
self._tap_dance_counts,
|
||||
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):
|
||||
self._hid_helper_inst.create_report(self._state.keys_pressed).send()
|
||||
self._state.resolve_hid()
|
||||
|
||||
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()
|
||||
self._hid_helper_inst.create_report(self._keys_pressed).send()
|
||||
self._hid_pending = False
|
||||
|
||||
def _handle_matrix_report(self, update=None):
|
||||
'''
|
||||
Bulk processing of update code for each cycle
|
||||
:param update:
|
||||
'''
|
||||
if update is not None:
|
||||
|
||||
self._state.matrix_changed(update[0], update[1], update[2])
|
||||
|
||||
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)
|
||||
self._on_matrix_changed(update[0], update[1], update[2])
|
||||
self.state_changed = True
|
||||
|
||||
def _receive_from_initiator(self):
|
||||
if self.uart is not None and self.uart.in_waiting > 0 or self.uart_buffer:
|
||||
@ -144,14 +131,14 @@ class KMKKeyboard:
|
||||
|
||||
microcontroller.reset()
|
||||
|
||||
while self.uart.in_waiting >= 3:
|
||||
self.uart_buffer.append(self.uart.read(3))
|
||||
while self._uart.in_waiting >= 3:
|
||||
self.uart_buffer.append(self._uart.read(3))
|
||||
if self.uart_buffer:
|
||||
update = bytearray(self.uart_buffer.pop(0))
|
||||
|
||||
# Built in debug mode switch
|
||||
if update == b'DEB':
|
||||
print(self.uart.readline())
|
||||
print(self._uart.readline())
|
||||
return None
|
||||
return update
|
||||
|
||||
@ -163,26 +150,207 @@ class KMKKeyboard:
|
||||
be detected and handled differently than typical keypresses.
|
||||
:param message: Debug message
|
||||
'''
|
||||
if self.uart is not None:
|
||||
self.uart.write('DEB')
|
||||
self.uart.write(message, '\n')
|
||||
if self._uart is not None:
|
||||
self._uart.write('DEB')
|
||||
self._uart.write(message, '\n')
|
||||
|
||||
def init_uart(self, pin, timeout=20):
|
||||
if self.is_target:
|
||||
return busio.UART(tx=None, rx=pin, timeout=timeout)
|
||||
#####
|
||||
# SPLICE: INTERNAL STATE
|
||||
# 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:
|
||||
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.row_pins, 'no GPIO pins defined for matrix rows'
|
||||
assert self.col_pins, 'no GPIO pins defined for matrix columns'
|
||||
assert self.diode_orientation is not None, 'diode orientation must be defined'
|
||||
assert (
|
||||
hid_type in HIDModes.ALL_MODES
|
||||
self.hid_type in HIDModes.ALL_MODES
|
||||
), '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:
|
||||
self.coord_mapping = []
|
||||
@ -190,19 +358,14 @@ class KMKKeyboard:
|
||||
rows_to_calc = len(self.row_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 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)
|
||||
|
||||
if hid_type == HIDModes.NOOP:
|
||||
def _init_hid(self):
|
||||
if self.hid_type == HIDModes.NOOP:
|
||||
self.hid_helper = AbstractHID
|
||||
elif hid_type == HIDModes.USB:
|
||||
elif self.hid_type == HIDModes.USB:
|
||||
try:
|
||||
from kmk.hid import USBHID
|
||||
|
||||
@ -210,7 +373,7 @@ class KMKKeyboard:
|
||||
except ImportError:
|
||||
self.hid_helper = AbstractHID
|
||||
print('USB HID is unsupported ')
|
||||
elif hid_type == HIDModes.BLE:
|
||||
elif self.hid_type == HIDModes.BLE:
|
||||
try:
|
||||
from kmk.ble import BLEHID
|
||||
|
||||
@ -221,92 +384,87 @@ class KMKKeyboard:
|
||||
|
||||
self._hid_helper_inst = self.hid_helper(**kwargs)
|
||||
|
||||
# Split keyboard Init
|
||||
if self.split_type is not None:
|
||||
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(
|
||||
def _init_matrix(self):
|
||||
self.matrix = MatrixScanner(
|
||||
cols=self.col_pins,
|
||||
rows=self.row_pins,
|
||||
diode_orientation=self.diode_orientation,
|
||||
rollover_cols_every_rows=getattr(self, 'rollover_cols_every_rows', None),
|
||||
)
|
||||
|
||||
# Compile string leader sequences
|
||||
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
|
||||
return self
|
||||
|
||||
for k, v in self.leader_dictionary.items():
|
||||
if not isinstance(k, tuple):
|
||||
del self.leader_dictionary[k]
|
||||
def go(self, hid_type=HIDModes.USB, **kwargs):
|
||||
self._extensions = [] + getattr(self, 'extensions', [])
|
||||
|
||||
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:
|
||||
if self.split_type is not None and self.is_target:
|
||||
update = self._receive_from_initiator()
|
||||
if update is not None:
|
||||
self._handle_matrix_report(update)
|
||||
self.state_changed = False
|
||||
|
||||
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:
|
||||
if self.is_target:
|
||||
self._handle_matrix_report(update)
|
||||
else:
|
||||
# This keyboard is a initiator, and needs to send data to target
|
||||
self._send_to_target(update)
|
||||
matrix_update = self.matrix.scan_for_changes()
|
||||
self._handle_matrix_report(matrix_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()
|
||||
|
||||
old_timeouts_len = len(self._state.timeouts)
|
||||
self._state.process_timeouts()
|
||||
new_timeouts_len = len(self._state.timeouts)
|
||||
old_timeouts_len = len(self._timeouts)
|
||||
self._process_timeouts()
|
||||
new_timeouts_len = len(self._timeouts)
|
||||
|
||||
if old_timeouts_len != new_timeouts_len:
|
||||
if self._state.hid_pending:
|
||||
self.state_changed = True
|
||||
|
||||
if self._hid_pending:
|
||||
self._send_hid()
|
||||
|
||||
if self.pixels and self.pixels.animation_mode:
|
||||
self.pixels.loopcounter += 1
|
||||
if self.pixels.loopcounter >= 30:
|
||||
self.pixels = self.pixels.animate()
|
||||
self.pixels.loopcounter = 0
|
||||
for ext in self._extensions:
|
||||
try:
|
||||
ext.after_hid_send(self)
|
||||
except Exception:
|
||||
# TODO FIXME log the exceptions or something
|
||||
pass
|
||||
|
||||
if self.led and self.led.enabled and self.led.animation_mode:
|
||||
self.led = self.led.animate()
|
||||
if self.state_changed:
|
||||
self._print_debug_cycle()
|
||||
|
178
kmk/led.py
178
kmk/led.py
@ -1,88 +1,105 @@
|
||||
import pulseio
|
||||
from micropython import const
|
||||
|
||||
import time
|
||||
from math import e, exp, pi, sin
|
||||
|
||||
led_config = {
|
||||
'brightness_step': 5,
|
||||
'brightness_limit': 100,
|
||||
'breathe_center': 1.5,
|
||||
'animation_mode': 'static',
|
||||
'animation_speed': 1,
|
||||
}
|
||||
from kmk.extensions import Extension, InvalidExtensionEnvironment
|
||||
from kmk.keys import make_key
|
||||
|
||||
|
||||
class led:
|
||||
brightness = 0
|
||||
time = int(time.monotonic() * 1000)
|
||||
pos = 0
|
||||
effect_init = False
|
||||
class AnimationModes:
|
||||
OFF = 0
|
||||
STATIC = 1
|
||||
STATIC_STANDBY = 2
|
||||
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):
|
||||
self.led = pulseio.PWMOut(led_pin)
|
||||
self.brightness_step = const(config['brightness_step'])
|
||||
self.brightness_limit = const(config['brightness_limit'])
|
||||
self.animation_mode = const(config['animation_mode'])
|
||||
self.animation_speed = const(config['animation_speed'])
|
||||
self.breathe_center = const(config['breathe_center'])
|
||||
if config.get('user_animation'):
|
||||
self.user_animation = config['user_animation']
|
||||
class LED(Extension):
|
||||
def __init__(
|
||||
self,
|
||||
led_pin,
|
||||
brightness_step=5,
|
||||
brightness_limit=100,
|
||||
breathe_center=1.5,
|
||||
animation_mode=AnimationModes.STATIC,
|
||||
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):
|
||||
return 'LED({})'.format(self._to_dict())
|
||||
|
||||
def _to_dict(self):
|
||||
return {
|
||||
'led': self.led,
|
||||
'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,
|
||||
}
|
||||
# TODO FIXME remove
|
||||
pass
|
||||
|
||||
def _init_effect(self):
|
||||
self.pos = 0
|
||||
self.effect_init = False
|
||||
self._pos = 0
|
||||
self._effect_init = False
|
||||
return self
|
||||
|
||||
def time_ms(self):
|
||||
return int(time.monotonic() * 1000)
|
||||
def after_hid_send(self, keyboard):
|
||||
if self._enabled and self.animation_mode:
|
||||
self.animate()
|
||||
|
||||
return keyboard
|
||||
|
||||
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):
|
||||
if not step:
|
||||
self.brightness += self.brightness_step
|
||||
self._brightness += self.brightness_step
|
||||
else:
|
||||
self.brightness += step
|
||||
self._brightness += step
|
||||
|
||||
if self.brightness > 100:
|
||||
self.brightness = 100
|
||||
if self._brightness > 100:
|
||||
self._brightness = 100
|
||||
|
||||
self.set_brightness(self.brightness)
|
||||
self.set_brightness(self._brightness)
|
||||
|
||||
def decrease_brightness(self, step=None):
|
||||
if not step:
|
||||
self.brightness -= self.brightness_step
|
||||
self._brightness -= self.brightness_step
|
||||
else:
|
||||
self.brightness -= step
|
||||
self._brightness -= step
|
||||
|
||||
if self.brightness < 0:
|
||||
self.brightness = 0
|
||||
if self._brightness < 0:
|
||||
self._brightness = 0
|
||||
|
||||
self.set_brightness(self.brightness)
|
||||
self.set_brightness(self._brightness)
|
||||
|
||||
def off(self):
|
||||
self.set_brightness(0)
|
||||
@ -110,18 +127,18 @@ class led:
|
||||
def effect_breathing(self):
|
||||
# http://sean.voisen.org/blog/2011/10/breathing-led-with-arduino/
|
||||
# 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_2 = self.brightness_limit / (e - 1 / e)
|
||||
|
||||
self.brightness = int(multip_1 * multip_2)
|
||||
self.pos = (self.pos + self.animation_speed) % 256
|
||||
self.set_brightness(self.brightness)
|
||||
self._brightness = int(multip_1 * multip_2)
|
||||
self._pos = (self._pos + self.animation_speed) % 256
|
||||
self.set_brightness(self._brightness)
|
||||
|
||||
return 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
|
||||
self.animation_mode = None
|
||||
return self
|
||||
@ -131,16 +148,49 @@ class led:
|
||||
Activates a "step" in the animation based on the active mode
|
||||
:return: Returns the new state in animation
|
||||
'''
|
||||
if self.effect_init:
|
||||
if self._effect_init:
|
||||
self._init_effect()
|
||||
if self.enabled:
|
||||
if self.animation_mode == 'breathing':
|
||||
if self._enabled:
|
||||
if self.animation_mode == AnimationModes.BREATHING:
|
||||
return self.effect_breathing()
|
||||
elif self.animation_mode == 'static':
|
||||
elif self.animation_mode == AnimationModes.STATIC:
|
||||
return self.effect_static()
|
||||
elif self.animation_mode == 'user':
|
||||
elif self.animation_mode == AnimationModes.USER:
|
||||
return self.user_animation(self)
|
||||
else:
|
||||
self.off()
|
||||
|
||||
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
|
||||
|
@ -27,19 +27,11 @@ import kmk.consts # isort:skip
|
||||
import kmk.kmktime # 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
|
||||
import kmk.handlers.layers # isort:skip
|
||||
import kmk.handlers.stock # isort:skip
|
||||
|
||||
# Now stuff that depends on the above (and so on)
|
||||
import kmk.hid # isort:skip
|
||||
import kmk.keys # isort:skip
|
||||
import kmk.matrix # isort:skip
|
||||
|
||||
import kmk.hid # isort:skip
|
||||
import kmk.internal_state # isort:skip
|
||||
|
147
kmk/rgb.py
147
kmk/rgb.py
@ -1,99 +1,84 @@
|
||||
from micropython import const
|
||||
import neopixel
|
||||
|
||||
import time
|
||||
from math import e, exp, pi, sin
|
||||
|
||||
rgb_config = {
|
||||
'pixels': None,
|
||||
'num_pixels': 0,
|
||||
'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',
|
||||
}
|
||||
from kmk.extensions import Extension
|
||||
|
||||
rgb_config = {}
|
||||
|
||||
|
||||
class RGB:
|
||||
hue = 0
|
||||
sat = 100
|
||||
val = 80
|
||||
class AnimationModes:
|
||||
OFF = 0
|
||||
STATIC = 1
|
||||
STATIC_STANDBY = 2
|
||||
BREATHING = 3
|
||||
USER = 4
|
||||
|
||||
|
||||
class RGB(Extension):
|
||||
pos = 0
|
||||
time = int(time.monotonic() * 10)
|
||||
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
|
||||
num_pixels = None
|
||||
hue_step = None
|
||||
sat_step = None
|
||||
val_step = None
|
||||
breathe_center = None # 1.0-2.7
|
||||
knight_effect_length = None
|
||||
val_limit = None
|
||||
effect_init = False
|
||||
user_animation = None
|
||||
def __init__(
|
||||
self,
|
||||
pixel_pin,
|
||||
num_pixels=0,
|
||||
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=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):
|
||||
try:
|
||||
import neopixel
|
||||
if len(rgb_order) == 4:
|
||||
self.rgbw = True
|
||||
|
||||
self.neopixel = neopixel.NeoPixel(
|
||||
pixel_pin,
|
||||
config['num_pixels'],
|
||||
pixel_order=config['rgb_order'],
|
||||
auto_write=False,
|
||||
)
|
||||
if len(config['rgb_order']) == 4:
|
||||
self.rgbw = True
|
||||
self.num_pixels = const(config['num_pixels'])
|
||||
self.hue_step = const(config['hue_step'])
|
||||
self.sat_step = const(config['sat_step'])
|
||||
self.val_step = const(config['val_step'])
|
||||
self.hue = const(config['hue_default'])
|
||||
self.sat = const(config['sat_default'])
|
||||
self.val = const(config['val_default'])
|
||||
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']
|
||||
self.num_pixels = num_pixels
|
||||
self.hue_step = hue_step
|
||||
self.sat_step = sat_step
|
||||
self.val_step = val_step
|
||||
self.hue = hue_default
|
||||
self.sat = sat_default
|
||||
self.val = val_default
|
||||
self.breathe_center = breathe_center
|
||||
self.knight_effect_length = knight_effect_length
|
||||
self.val_limit = val_limit
|
||||
self.animation_mode = animation_mode
|
||||
self.animation_speed = animation_speed
|
||||
self.reverse_animation = reverse_animation
|
||||
self.user_animation = user_animation
|
||||
self.disable_auto_write = disable_auto_write
|
||||
|
||||
except ImportError as e:
|
||||
print(e)
|
||||
def during_bootup(self, keyboard):
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return 'RGB({})'.format(self._to_dict())
|
||||
def after_hid_send(self, keyboard):
|
||||
if self.animation_mode:
|
||||
self.loopcounter += 1
|
||||
if self.loopcounter >= 7:
|
||||
self.animate()
|
||||
self.loopcounter = 0
|
||||
|
||||
def _to_dict(self):
|
||||
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,
|
||||
}
|
||||
return keyboard
|
||||
|
||||
def time_ms(self):
|
||||
return int(time.monotonic() * 1000)
|
||||
|
@ -1,5 +1,6 @@
|
||||
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 send_string
|
||||
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.')
|
||||
|
||||
keyboard.leader_mode = LeaderMode.TIMEOUT
|
||||
keyboard.leader_dictionary = {
|
||||
_______ = KC.TRNS
|
||||
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'),
|
||||
'wpm': WPM,
|
||||
'atf': emoticons.ANGRY_TABLE_FLIP,
|
||||
@ -55,16 +64,9 @@ keyboard.leader_dictionary = {
|
||||
'poop': emoticons.POOP,
|
||||
'ls': KC.LGUI(KC.HOME),
|
||||
'dbg': KC.DBG,
|
||||
}
|
||||
})
|
||||
|
||||
_______ = KC.TRNS
|
||||
xxxxxxx = KC.NO
|
||||
HELLA_TD = KC.TD(
|
||||
KC.A,
|
||||
KC.B,
|
||||
send_string('macros in a tap dance? I think yes'),
|
||||
KC.TG(1),
|
||||
)
|
||||
keyboard.extensions = [leader_ext]
|
||||
|
||||
|
||||
def shrek_is_life(*args, **kwargs):
|
||||
|
Loading…
Reference in New Issue
Block a user