Checkpoint alpha: Reflow macros and keycodes into a consistent structure. Most internal state functionality largely untouched (just moved)

This commit is contained in:
Josh Klar 2018-12-29 04:44:52 -08:00
parent af140a16a6
commit 39a6465658
No known key found for this signature in database
GPG Key ID: 220F99BD7DB7A99E
12 changed files with 767 additions and 967 deletions

View File

@ -202,16 +202,6 @@ class Firmware:
if old_timeouts_len != new_timeouts_len:
state_changed = True
if self._state.macros_pending:
# Blindly assume macros are going to change state, which is almost
# always a safe assumption
state_changed = True
for macro in self._state.macros_pending:
for key in macro(self):
self._send_key(key)
self._state.resolve_macro()
if self.debug_enabled and state_changed:
print('New State: {}'.format(self._state._to_dict()))

0
kmk/handlers/__init__.py Normal file
View File

108
kmk/handlers/layers.py Normal file
View File

@ -0,0 +1,108 @@
from kmk.kmktime import ticks_diff, ticks_ms
def df_pressed(key, state, *args, **kwargs):
"""Switches the default layer"""
state.active_layers[0] = key.meta.layer
state.reversed_active_layers = list(reversed(state.active_layers))
return state
def mo_pressed(key, state, *args, **kwargs):
"""Momentarily activates layer, switches off when you let go"""
state.active_layers.append(key.meta.layer)
state.reversed_active_layers = list(reversed(state.active_layers))
return state
def mo_released(key, state, KC, *args, **kwargs):
state.active_layers = [
layer for layer in state.active_layers
if layer != key.meta.layer
]
state.reversed_active_layers = list(reversed(state.active_layers))
return state
def lm_pressed(key, state, *args, **kwargs):
"""As MO(layer) but with mod active"""
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)
return mo_pressed(key, state, *args, **kwargs)
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
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()
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
):
state.hid_pending = True
state.tap_key(key.meta.kc)
mo_released(key, state, *args, **kwargs)
state.start_time['lt'] = None
return state
def tg_pressed(key, state, *args, **kwargs):
"""Toggles the layer (enables it if not active, and vise versa)"""
if key.meta.layer in state.active_layers:
state.active_layers = [
layer for layer in state.active_layers
if layer != key.meta.layer
]
else:
state.active_layers.append(key.meta.layer)
state.reversed_active_layers = list(reversed(state.active_layers))
return state
def to_pressed(key, state, *args, **kwargs):
"""Activates layer and deactivates all other layers"""
state.active_layers = [key.meta.kc]
state.reversed_active_layers = list(reversed(state.active_layers))
return state
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:
# Sets the timer start and acts like MO otherwise
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
return tg_pressed(key, state, *args, **kwargs)
def tt_released(key, state, *args, **kwargs):
if (
state.start_time['tt'] is None or
ticks_diff(ticks_ms(), state.start_time['tt']) >= state.config.tap_time
):
# 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
return mo_released(key, state, *args, **kwargs)
return state

40
kmk/handlers/sequences.py Normal file
View File

@ -0,0 +1,40 @@
from kmk.keycodes import ALL_KEYS, KC, make_key
from kmk.types import KeySequenceMeta
def sequence_press_handler(key, state, KC, *args, **kwargs):
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()
if not getattr(ikey, 'no_release', None):
state.process_key(ikey, False)
state.config._send_hid()
state.keys_pressed = old_keys_pressed
return state
def simple_key_sequence(seq):
return make_key(
meta=KeySequenceMeta(seq),
on_press=sequence_press_handler,
)
def send_string(message):
seq = []
for char in message:
kc = ALL_KEYS[char]
if char.isupper():
kc = KC.LSHIFT(kc)
seq.append(kc)
return simple_key_sequence(seq)

89
kmk/handlers/stock.py Normal file
View File

@ -0,0 +1,89 @@
from kmk.kmktime import sleep_ms
from kmk.util import reset_bootloader, reset_keyboard
def passthrough(key, state, *args, **kwargs):
return state
def default_pressed(key, state, KC, coord_int=None, coord_raw=None):
if coord_int is not None:
state.coord_keys_pressed[coord_int] = key
state.add_key(key)
return state
def default_released(key, state, KC, coord_int=None, coord_raw=None):
state.remove_key(key)
if coord_int is not None:
state.keys_pressed.discard(key.coord_keys_pressed.get(coord_int, None))
state.coord_keys_pressed[coord_int] = None
return state
def reset(*args, **kwargs):
reset_keyboard()
def bootloader(*args, **kwargs):
reset_bootloader()
def debug_pressed(key, state, KC, *args, **kwargs):
if state.config.debug_enabled:
print('Disabling debug mode, bye!')
else:
print('Enabling debug mode. Welcome to the jungle.')
state.config.debug_enabled = not state.config.debug_enabled
return state
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 Shift is held, KC_GRAVE will become KC_TILDE on OS level
state.keys_pressed.add(KC.GRAVE)
return state
# else return KC_ESC
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
return state
def sleep_pressed(key, state, KC, *args, **kwargs):
sleep_ms(key.meta.ms)
return state
def uc_mode_pressed(key, state, *args, **kwargs):
state.config.unicode_mode = key.meta.mode
return state
def leader_pressed(key, state, *args, **kwargs):
return state._begin_leader_mode()
def tap_dance_pressed(key, state, *args, **kwargs):
return state._process_tap_dance(key, True)
def tap_dance_released(key, state, *args, **kwargs):
return state._process_tap_dance(key, False)

View File

@ -1,19 +1,13 @@
from kmk.consts import LeaderMode
from kmk.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, Keycodes, RawKeycodes,
TapDanceKeycode)
from kmk.kmktime import sleep_ms, ticks_diff, ticks_ms
from kmk.keycodes import KC
from kmk.kmktime import ticks_ms
from kmk.types import TapDanceKeyMeta
from kmk.util import intify_coordinate
GESC_TRIGGERS = {
Keycodes.Modifiers.KC_LSHIFT, Keycodes.Modifiers.KC_RSHIFT,
Keycodes.Modifiers.KC_LGUI, Keycodes.Modifiers.KC_RGUI,
}
class InternalState:
keys_pressed = set()
coord_keys_pressed = {}
macros_pending = []
leader_pending = None
leader_last_len = 0
hid_pending = False
@ -34,22 +28,6 @@ class InternalState:
def __init__(self, config):
self.config = config
self.internal_key_handlers = {
RawKeycodes.KC_DF: self._layer_df,
RawKeycodes.KC_MO: self._layer_mo,
RawKeycodes.KC_LM: self._layer_lm,
RawKeycodes.KC_LT: self._layer_lt,
RawKeycodes.KC_TG: self._layer_tg,
RawKeycodes.KC_TO: self._layer_to,
RawKeycodes.KC_TT: self._layer_tt,
Keycodes.KMK.KC_GESC.code: self._kc_gesc,
RawKeycodes.KC_UC_MODE: self._kc_uc_mode,
RawKeycodes.KC_MACRO: self._kc_macro,
Keycodes.KMK.KC_LEAD.code: self._kc_lead,
Keycodes.KMK.KC_NO.code: self._kc_no,
Keycodes.KMK.KC_DEBUG.code: self._kc_debug_mode,
RawKeycodes.KC_TAP_DANCE: self._kc_tap_dance,
}
def __repr__(self):
return 'InternalState({})'.format(self._to_dict())
@ -74,7 +52,7 @@ class InternalState:
for layer in self.reversed_active_layers:
layer_key = self.config.keymap[layer][row][col]
if not layer_key or layer_key == Keycodes.KMK.KC_TRNS:
if not layer_key or layer_key == KC.TRNS:
continue
if self.config.debug_enabled:
@ -122,16 +100,16 @@ class InternalState:
print('No key accessible for col, row: {}, {}'.format(row, col))
return self
if self.tapping and not isinstance(kc_changed, TapDanceKeycode):
self._process_tap_dance(kc_changed, is_pressed)
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:
self.coord_keys_pressed[int_coord] = kc_changed
self.add_key(kc_changed)
key.on_press(self, coord_int, coord_raw)
else:
self.remove_key(kc_changed)
self.keys_pressed.discard(self.coord_keys_pressed.get(int_coord, None))
self.coord_keys_pressed[int_coord] = None
key.on_release(self, coord_int, coord_raw)
if self.config.leader_mode % 2 == 1:
self._process_leader_mode()
@ -140,27 +118,11 @@ class InternalState:
def remove_key(self, keycode):
self.keys_pressed.discard(keycode)
if keycode.code >= FIRST_KMK_INTERNAL_KEYCODE:
self._process_internal_key_event(keycode, False)
else:
self.hid_pending = True
return self
return self.process_key(keycode, False)
def add_key(self, keycode):
# TODO Make this itself a macro keycode with a keyup handler
# rather than handling this inline here. Gross.
if keycode.code == Keycodes.KMK.KC_MACRO_SLEEP_MS:
sleep_ms(keycode.ms)
else:
self.keys_pressed.add(keycode)
if keycode.code >= FIRST_KMK_INTERNAL_KEYCODE:
self._process_internal_key_event(keycode, True)
else:
self.hid_pending = True
return self
self.keys_pressed.add(keycode)
return self.process_key(keycode, True)
def tap_key(self, keycode):
self.add_key(keycode)
@ -175,13 +137,6 @@ class InternalState:
self.hid_pending = False
return self
def resolve_macro(self):
if self.config.debug_enabled:
print('Macro complete!')
self.macros_pending.pop()
return self
def _process_internal_key_event(self, changed_key, is_pressed):
# Since the key objects can be chained into new objects
# with, for example, no_press set, always check against
@ -192,164 +147,9 @@ class InternalState:
changed_key, is_pressed,
)
def _layer_df(self, changed_key, is_pressed):
"""Switches the default layer"""
if is_pressed:
self.active_layers[0] = changed_key.layer
self.reversed_active_layers = list(reversed(self.active_layers))
return self
def _layer_mo(self, changed_key, is_pressed):
"""Momentarily activates layer, switches off when you let go"""
if is_pressed:
self.active_layers.append(changed_key.layer)
else:
self.active_layers = [
layer for layer in self.active_layers
if layer != changed_key.layer
]
self.reversed_active_layers = list(reversed(self.active_layers))
return self
def _layer_lm(self, changed_key, is_pressed):
"""As MO(layer) but with mod active"""
self.hid_pending = True
if is_pressed:
# Sets the timer start and acts like MO otherwise
self.start_time['lm'] = ticks_ms()
self.keys_pressed.add(changed_key.kc)
else:
self.keys_pressed.discard(changed_key.kc)
self.start_time['lm'] = None
return self._layer_mo(changed_key, is_pressed)
def _layer_lt(self, changed_key, is_pressed):
"""Momentarily activates layer if held, sends kc if tapped"""
if is_pressed:
# Sets the timer start and acts like MO otherwise
self.start_time['lt'] = ticks_ms()
self._layer_mo(changed_key, is_pressed)
else:
# On keyup, check timer, and press key if needed.
if self.start_time['lt'] and (
ticks_diff(ticks_ms(), self.start_time['lt']) < self.config.tap_time
):
self.hid_pending = True
self.tap_key(changed_key.kc)
self._layer_mo(changed_key, is_pressed)
self.start_time['lt'] = None
return self
def _layer_tg(self, changed_key, is_pressed):
"""Toggles the layer (enables it if not active, and vise versa)"""
if is_pressed:
if changed_key.layer in self.active_layers:
self.active_layers = [
layer for layer in self.active_layers
if layer != changed_key.layer
]
else:
self.active_layers.append(changed_key.layer)
self.reversed_active_layers = list(reversed(self.active_layers))
return self
def _layer_to(self, changed_key, is_pressed):
"""Activates layer and deactivates all other layers"""
if is_pressed:
self.active_layers = [changed_key.layer]
self.reversed_active_layers = list(reversed(self.active_layers))
return self
def _layer_tt(self, changed_key, is_pressed):
"""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 is_pressed:
if self.start_time['tt'] is None:
# Sets the timer start and acts like MO otherwise
self.start_time['tt'] = ticks_ms()
return self._layer_mo(changed_key, is_pressed)
elif ticks_diff(ticks_ms(), self.start_time['tt']) < self.config.tap_time:
self.start_time['tt'] = None
return self.tg(changed_key, is_pressed)
elif (
self.start_time['tt'] is None or
ticks_diff(ticks_ms(), self.start_time['tt']) >= self.config.tap_time
):
# On first press, works like MO. On second press, does nothing unless let up within
# time window, then acts like TG.
self.start_time['tt'] = None
return self._layer_mo(changed_key, is_pressed)
return self
def _kc_uc_mode(self, changed_key, is_pressed):
if is_pressed:
self.config.unicode_mode = changed_key.mode
return self
def _kc_macro(self, changed_key, is_pressed):
if is_pressed:
if changed_key.keyup:
self.macros_pending.append(changed_key.keyup)
else:
if changed_key.keydown:
self.macros_pending.append(changed_key.keydown)
return self
def _kc_lead(self, changed_key, is_pressed):
if is_pressed:
self._begin_leader_mode()
return self
def _kc_gesc(self, changed_key, is_pressed):
self.hid_pending = True
if is_pressed:
if GESC_TRIGGERS.intersection(self.keys_pressed):
# if Shift is held, KC_GRAVE will become KC_TILDE on OS level
self.keys_pressed.add(Keycodes.Common.KC_GRAVE)
return self
# else return KC_ESC
self.keys_pressed.add(Keycodes.Common.KC_ESCAPE)
return self
self.keys_pressed.discard(Keycodes.Common.KC_ESCAPE)
self.keys_pressed.discard(Keycodes.Common.KC_GRAVE)
return self
def _kc_no(self, changed_key, is_pressed):
return self
def _kc_debug_mode(self, changed_key, is_pressed):
if is_pressed:
if self.config.debug_enabled:
print('Disabling debug mode, bye!')
else:
print('Enabling debug mode. Welcome to the jungle.')
self.config.debug_enabled = not self.config.debug_enabled
return self
def _kc_tap_dance(self, changed_key, is_pressed):
return self._process_tap_dance(changed_key, is_pressed)
def _process_tap_dance(self, changed_key, is_pressed):
if is_pressed:
if not isinstance(changed_key, TapDanceKeycode):
if not isinstance(changed_key.meta, TapDanceKeyMeta):
# If we get here, changed_key is not a TapDanceKeycode and thus
# the user kept typing elsewhere (presumably). End ALL of the
# currently outstanding tap dance runs.
@ -408,7 +208,7 @@ class InternalState:
def _begin_leader_mode(self):
if self.config.leader_mode % 2 == 0:
self.keys_pressed.discard(Keycodes.KMK.KC_LEAD)
self.keys_pressed.discard(KC.LEAD)
# All leader modes are one number higher when activating
self.config.leader_mode += 1
@ -421,7 +221,7 @@ class InternalState:
lmh = tuple(self.leader_mode_history)
if lmh in self.config.leader_dictionary:
self.macros_pending.append(self.config.leader_dictionary[lmh].keydown)
self.process_key(self.config.leader_dictionary[lmh], True)
return self._exit_leader_mode()
@ -438,15 +238,15 @@ class InternalState:
for key in keys_pressed:
if (
self.config.leader_mode == LeaderMode.ENTER_ACTIVE and
key == Keycodes.Common.KC_ENT
key == KC.ENT
):
self._handle_leader_sequence()
break
elif key == Keycodes.Common.KC_ESC or key == Keycodes.KMK.KC_GESC:
elif key == KC.ESC or key == KC.GESC:
# Clean self and turn leader mode off.
self._exit_leader_mode()
break
elif key == Keycodes.KMK.KC_LEAD:
elif key == KC.LEAD:
break
else:
# Add key if not needing to escape

File diff suppressed because it is too large Load Diff

View File

@ -1,76 +0,0 @@
import math
from kmk.event_defs import (hid_report_event, keycode_down_event,
keycode_up_event)
from kmk.keycodes import Media
from kmk.rotary_encoder import RotaryEncoder
VAL_FALSE = False + 1
VAL_NONE = True + 2
VAL_TRUE = True + 1
VOL_UP_PRESS = keycode_down_event(Media.KC_AUDIO_VOL_UP)
VOL_UP_RELEASE = keycode_up_event(Media.KC_AUDIO_VOL_UP)
VOL_DOWN_PRESS = keycode_down_event(Media.KC_AUDIO_VOL_DOWN)
VOL_DOWN_RELEASE = keycode_up_event(Media.KC_AUDIO_VOL_DOWN)
class RotaryEncoderMacro:
def __init__(self, pos_pin, neg_pin, slop_history=1, slop_threshold=1):
self.encoder = RotaryEncoder(pos_pin, neg_pin)
self.max_history = slop_history
self.history = bytearray(slop_history)
self.history_idx = 0
self.history_threshold = math.floor(slop_threshold * slop_history)
def scan(self):
# Anti-slop logic
self.history[self.history_idx] = 0
reading = self.encoder.direction()
self.history[self.history_idx] = VAL_NONE if reading is None else reading + 1
self.history_idx += 1
if self.history_idx >= self.max_history:
self.history_idx = 0
nones = 0
trues = 0
falses = 0
for val in self.history:
if val == VAL_NONE:
nones += 1
elif val == VAL_TRUE:
trues += 1
elif val == VAL_FALSE:
falses += 1
if nones >= self.history_threshold:
return None
if trues >= self.history_threshold:
return self.on_increase()
if falses >= self.history_threshold:
return self.on_decrease()
def on_decrease(self):
pass
def on_increase(self):
pass
class VolumeRotaryEncoder(RotaryEncoderMacro):
def on_decrease(self):
yield VOL_DOWN_PRESS
yield hid_report_event
yield VOL_DOWN_RELEASE
yield hid_report_event
def on_increase(self):
yield VOL_UP_PRESS
yield hid_report_event
yield VOL_UP_RELEASE
yield hid_report_event

View File

@ -1,28 +0,0 @@
from kmk.keycodes import Keycodes, Macro, char_lookup, lookup_kc_with_cache
from kmk.string import ascii_letters, digits
def simple_key_sequence(seq):
def _simple_key_sequence(state):
return seq
return Macro(keydown=_simple_key_sequence)
def send_string(message):
seq = []
for char in message:
kc = None
if char in char_lookup:
kc = char_lookup[char]
elif char in ascii_letters + digits:
kc = lookup_kc_with_cache(char)
if char.isupper():
kc = Keycodes.Modifiers.KC_LSHIFT(kc)
seq.append(kc)
return simple_key_sequence(seq)

View File

@ -1,16 +1,15 @@
from kmk.consts import UnicodeMode
from kmk.keycodes import (Common, Macro, Modifiers,
generate_codepoint_keysym_seq)
from kmk.keycodes import ALL_KEYS, KC, Macro
from kmk.macros.simple import simple_key_sequence
from kmk.types import AttrDict
from kmk.util import get_wide_ordinal
IBUS_KEY_COMBO = Modifiers.KC_LCTRL(Modifiers.KC_LSHIFT(Common.KC_U))
RALT_KEY = Modifiers.KC_RALT
U_KEY = Common.KC_U
ENTER_KEY = Common.KC_ENTER
RALT_DOWN_NO_RELEASE = Modifiers.KC_RALT(no_release=True)
RALT_UP_NO_PRESS = Modifiers.KC_RALT(no_press=True)
IBUS_KEY_COMBO = KC.LCTRL(KC.LSHIFT(KC.U))
RALT_KEY = KC.RALT
U_KEY = KC.U
ENTER_KEY = KC.ENTER
RALT_DOWN_NO_RELEASE = KC.RALT(no_release=True)
RALT_UP_NO_PRESS = KC.RALT(no_press=True)
def compile_unicode_string_sequences(string_table):
@ -31,6 +30,26 @@ def unicode_string_sequence(unistring):
])
def generate_codepoint_keysym_seq(codepoint, expected_length=4):
# To make MacOS and Windows happy, always try to send
# sequences that are of length 4 at a minimum
# On Linux systems, we can happily send longer strings.
# They will almost certainly break on MacOS and Windows,
# but this is a documentation problem more than anything.
# Not sure how to send emojis on Mac/Windows like that,
# though, since (for example) the Canadian flag is assembled
# from two five-character codepoints, 1f1e8 and 1f1e6
#
# As a bonus, this function can be pretty useful for
# leader dictionary keys as strings.
seq = [KC.N0 for _ in range(max(len(codepoint), expected_length))]
for idx, codepoint_fragment in enumerate(reversed(codepoint)):
seq[-(idx + 1)] = ALL_KEYS.get(codepoint_fragment)
return seq
def unicode_codepoint_sequence(codepoints):
kc_seqs = (
generate_codepoint_keysym_seq(codepoint)

View File

@ -1,57 +0,0 @@
from kmk.pins import PULL_UP
class RotaryEncoder:
# Please don't ask. I don't know. All I know is bit_value
# works as expected. Here be dragons, etc. etc.
MIN_VALUE = False + 1 << 1 | True + 1
MAX_VALUE = True + 1 << 1 | True + 1
def __init__(self, pos_pin, neg_pin):
self.pos_pin = pos_pin
self.neg_pin = neg_pin
self.pos_pin.switch_to_input(pull=PULL_UP)
self.neg_pin.switch_to_input(pull=PULL_UP)
self.prev_bit_value = self.bit_value()
def value(self):
return (self.pos_pin.value(), self.neg_pin.value())
def bit_value(self):
'''
Returns 2, 3, 5, or 6 based on the state of the rotary encoder's two
bits. This is a total hack but it does what we need pretty efficiently.
Shrug.
'''
return self.pos_pin.value() + 1 << 1 | self.neg_pin.value() + 1
def direction(self):
'''
Compares the current rotary position against the last seen position.
Returns True if we're rotating "positively", False if we're rotating "negatively",
and None if no change could safely be detected for any reason (usually this
means the encoder itself did not change)
'''
new_value = self.bit_value()
rolling_under = self.prev_bit_value == self.MIN_VALUE and new_value == self.MAX_VALUE
rolling_over = self.prev_bit_value == self.MAX_VALUE and new_value == self.MIN_VALUE
increasing = new_value > self.prev_bit_value
decreasing = new_value < self.prev_bit_value
self.prev_bit_value = new_value
if rolling_over:
return True
elif rolling_under:
return False
if increasing:
return True
if decreasing:
return False
# Either no change, or not a type of change we can safely detect,
# so safely do nothing
return None

View File

@ -24,3 +24,29 @@ class Anything:
class Passthrough:
def __getattr__(self, attr):
return Anything(attr)
class LayerKeyMeta:
def __init__(self, layer, kc=None):
self.layer = layer
self.kc = kc
class KeySequenceMeta:
def __init__(self, seq):
self.seq = seq
class KeySeqSleepMeta:
def __init__(self, ms):
self.ms = ms
class UnicodeModeKeyMeta:
def __init__(self, mode):
self.mode = mode
class TapDanceKeyMeta:
def __init__(self, codes):
self.codes = codes