diff --git a/kmk/common/event_defs.py b/kmk/common/event_defs.py index e7102f9..0a232e6 100644 --- a/kmk/common/event_defs.py +++ b/kmk/common/event_defs.py @@ -13,6 +13,7 @@ HID_REPORT_EVENT = const(5) KEYCODE_UP_EVENT = const(6) KEYCODE_DOWN_EVENT = const(7) MACRO_COMPLETE_EVENT = const(8) +PENDING_KEYCODE_POP_EVENT = const(9) logger = logging.getLogger(__name__) @@ -100,6 +101,12 @@ def macro_complete_event(): ) +def pending_keycode_pop_event(): + return BareEvent( + type=PENDING_KEYCODE_POP_EVENT, + ) + + def matrix_changed(new_pressed): def _key_pressed(dispatch, get_state): dispatch(new_matrix_event(new_pressed)) @@ -116,6 +123,18 @@ def matrix_changed(new_pressed): except ImportError: logger.warning('Tried to reset to bootloader, but not supported on this chip?') + if state.pending_keys: + for key in state.pending_keys: + if not key.no_press: + dispatch(keycode_down_event(key)) + dispatch(hid_report_event()) + + if not key.no_release: + dispatch(keycode_up_event(key)) + dispatch(hid_report_event()) + + dispatch(pending_keycode_pop_event()) + if state.macro_pending: macro = state.macro_pending diff --git a/kmk/common/internal_keycodes.py b/kmk/common/internal_keycodes.py index 1d73004..b34fb97 100644 --- a/kmk/common/internal_keycodes.py +++ b/kmk/common/internal_keycodes.py @@ -1,5 +1,6 @@ import logging +from kmk.common import kmktime from kmk.common.event_defs import KEY_DOWN_EVENT, KEY_UP_EVENT from kmk.common.keycodes import Keycodes, RawKeycodes @@ -22,10 +23,16 @@ def process_internal_key_event(state, action_type, changed_key, logger=None): return df(state, action_type, changed_key, logger=logger) elif changed_key.code == RawKeycodes.KC_MO: return mo(state, action_type, changed_key, logger=logger) + elif changed_key.code == RawKeycodes.KC_LM: + return lm(state, action_type, changed_key, logger=logger) + elif changed_key.code == RawKeycodes.KC_LT: + return lt(state, action_type, changed_key, logger=logger) elif changed_key.code == RawKeycodes.KC_TG: return tg(state, action_type, changed_key, logger=logger) elif changed_key.code == RawKeycodes.KC_TO: return to(state, action_type, changed_key, logger=logger) + elif changed_key.code == RawKeycodes.KC_TT: + return tt(state, action_type, changed_key, logger=logger) elif changed_key.code == Keycodes.KMK.KC_GESC.code: return grave_escape(state, action_type, logger=logger) elif changed_key.code == RawKeycodes.KC_UC_MODE: @@ -52,6 +59,8 @@ def grave_escape(state, action_type, logger): state.keys_pressed.discard(Keycodes.Common.KC_GRAVE) return state + return state + def df(state, action_type, changed_key, logger): """Switches the default layer""" @@ -74,12 +83,38 @@ def mo(state, action_type, changed_key, logger): return state -def lm(layer, mod): +def lm(state, action_type, changed_key, logger): """As MO(layer) but with mod active""" + if action_type == KEY_DOWN_EVENT: + # Sets the timer start and acts like MO otherwise + state.start_time['lm'] = kmktime.ticks_ms() + state.keys_pressed.add(changed_key.kc) + state = mo(state, action_type, changed_key, logger) + elif action_type == KEY_UP_EVENT: + state.keys_pressed.discard(changed_key.kc) + state.start_time['lm'] = None + state = mo(state, action_type, changed_key) + + return state -def lt(layer, kc): +def lt(state, action_type, changed_key, logger): """Momentarily activates layer if held, sends kc if tapped""" + if action_type == KEY_DOWN_EVENT: + # Sets the timer start and acts like MO otherwise + state.start_time['lt'] = kmktime.ticks_ms() + state = mo(state, action_type, changed_key, logger) + elif action_type == KEY_UP_EVENT: + # On keyup, check timer, and press key if needed. + if state.start_time['lt'] and ( + kmktime.ticks_diff(kmktime.ticks_ms(), state.start_time['lt']) < state.tap_time + ): + state.pending_keys.add(changed_key.kc) + + state.start_time['lt'] = None + state = mo(state, action_type, changed_key, logger) + + return state def tg(state, action_type, changed_key, logger): @@ -104,8 +139,27 @@ def to(state, action_type, changed_key, logger): return state -def tt(layer): +def tt(state, action_type, changed_key, logger): """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 action_type == KEY_DOWN_EVENT: + if state.start_time['tt'] is None: + # Sets the timer start and acts like MO otherwise + state.start_time['tt'] = kmktime.ticks_ms() + state = mo(state, action_type, changed_key, logger) + elif kmktime.ticks_diff(kmktime.ticks_ms(), state.start_time['tt']) < state.tap_time: + state.start_time['tt'] = None + state = tg(state, action_type, changed_key, logger) + elif action_type == KEY_UP_EVENT and ( + state.start_time['tt'] is None or + kmktime.ticks_diff(kmktime.ticks_ms(), state.start_time['tt']) >= state.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 + state = mo(state, action_type, changed_key, logger) + + return state def unicode_mode(state, action_type, changed_key, logger): diff --git a/kmk/common/internal_state.py b/kmk/common/internal_state.py index cc6a946..a80c471 100644 --- a/kmk/common/internal_state.py +++ b/kmk/common/internal_state.py @@ -5,7 +5,8 @@ from kmk.common.consts import DiodeOrientation, UnicodeModes from kmk.common.event_defs import (HID_REPORT_EVENT, INIT_FIRMWARE_EVENT, KEY_DOWN_EVENT, KEY_UP_EVENT, KEYCODE_DOWN_EVENT, KEYCODE_UP_EVENT, - MACRO_COMPLETE_EVENT, NEW_MATRIX_EVENT) + MACRO_COMPLETE_EVENT, NEW_MATRIX_EVENT, + PENDING_KEYCODE_POP_EVENT) from kmk.common.internal_keycodes import process_internal_key_event from kmk.common.keycodes import FIRST_KMK_INTERNAL_KEYCODE, Keycodes @@ -52,15 +53,27 @@ class ReduxStore: class InternalState: keys_pressed = set() + pending_keys = set() macro_pending = None hid_pending = False unicode_mode = UnicodeModes.NOOP + tap_time = 300 keymap = [] row_pins = [] col_pins = [] matrix = [] diode_orientation = DiodeOrientation.COLUMNS active_layers = [0] + start_time = { + 'lt': None, + 'tg': None, + 'tt': None, + } + tick_time = { + 'lt': None, + 'tg': None, + 'tt': None, + } _oldstates = [] def __init__(self, preserve_intermediate_states=False): @@ -77,6 +90,9 @@ class InternalState: 'keys_pressed': self.keys_pressed, 'active_layers': self.active_layers, 'unicode_mode': self.unicode_mode, + 'tap_time': self.tap_time, + 'start_time': self.start_time, + 'tick_time': self.tick_time, } if verbose: @@ -134,7 +150,7 @@ def kmk_reducer(state=None, action=None, logger=None): if action.type == NEW_MATRIX_EVENT: matrix_keys_pressed = { find_key_in_map(state, row, col) - for row, col in action.matrix + for row, col, in action.matrix } pressed = matrix_keys_pressed - state.keys_pressed @@ -147,7 +163,10 @@ def kmk_reducer(state=None, action=None, logger=None): if not changed_key: continue elif changed_key.code >= FIRST_KMK_INTERNAL_KEYCODE: - state = process_internal_key_event(state, KEY_UP_EVENT, changed_key, logger=logger) + state = process_internal_key_event(state, + KEY_UP_EVENT, + changed_key, + logger=logger) for changed_key in pressed: if not changed_key: @@ -197,6 +216,10 @@ def kmk_reducer(state=None, action=None, logger=None): if action.type == MACRO_COMPLETE_EVENT: return state.update(macro_pending=None) + if action.type == PENDING_KEYCODE_POP_EVENT: + state.pending_keys.pop() + return state + # On unhandled events, log and do not mutate state logger.warning('Unhandled event! Returning state unmodified.') return state diff --git a/kmk/common/keycodes.py b/kmk/common/keycodes.py index b6a653a..561f383 100644 --- a/kmk/common/keycodes.py +++ b/kmk/common/keycodes.py @@ -48,7 +48,7 @@ class RawKeycodes: # such as no_press, because they modify KMK internal state in # ways we need to tightly control. Thus, we can get away with # a lighter-weight namedtuple implementation here -LayerKeycode = namedtuple('LayerKeycode', ('code', 'layer')) +LayerKeycode = namedtuple('LayerKeycode', ('code', 'layer', 'kc')) MacroSleepKeycode = namedtuple('MacroSleepKeycode', ('code', 'ms')) @@ -77,6 +77,9 @@ class Keycode: no_release=no_release, ) + def __repr__(self): + return 'Keycode(code={}, has_modifiers={})'.format(self.code, self.has_modifiers) + class ModifierKeycode(Keycode): def __call__(self, modified_code=None, no_press=None, no_release=None): @@ -292,8 +295,8 @@ class ShiftedKeycodes(KeycodeCategory): KC_PIPE = Modifiers.KC_LSHIFT(Common.KC_BACKSLASH) KC_COLON = KC_COLN = Modifiers.KC_LSHIFT(Common.KC_SEMICOLON) KC_DOUBLE_QUOTE = KC_DQUO = KC_DQT = Modifiers.KC_LSHIFT(Common.KC_QUOTE) - KC_LEFT_ANGLE_BRACKET = KC_LABK = KC_LT = Modifiers.KC_LSHIFT(Common.KC_COMMA) - KC_RIGHT_ANGLE_BRACKET = KC_RABK = KC_GT = Modifiers.KC_LSHIFT(Common.KC_DOT) + KC_LEFT_ANGLE_BRACKET = KC_LABK = Modifiers.KC_LSHIFT(Common.KC_COMMA) + KC_RIGHT_ANGLE_BRACKET = KC_RABK = Modifiers.KC_LSHIFT(Common.KC_DOT) KC_QUESTION = KC_QUES = Modifiers.KC_LSHIFT(Common.KC_DOT) @@ -484,31 +487,31 @@ class KMK(KeycodeCategory): class Layers(KeycodeCategory): @staticmethod def KC_DF(layer): - return LayerKeycode(RawKeycodes.KC_DF, layer) + return LayerKeycode(RawKeycodes.KC_DF, layer, KC.NO) @staticmethod def KC_MO(layer): - return LayerKeycode(RawKeycodes.KC_MO, layer) + return LayerKeycode(RawKeycodes.KC_MO, layer, KC.NO) @staticmethod - def KC_LM(layer): - return LayerKeycode(RawKeycodes.KC_LM, layer) + def KC_LM(layer, kc): + return LayerKeycode(RawKeycodes.KC_LM, layer, kc) @staticmethod - def KC_LT(layer): - return LayerKeycode(RawKeycodes.KC_LT, layer) + def KC_LT(layer, kc): + return LayerKeycode(RawKeycodes.KC_LT, layer, kc) @staticmethod def KC_TG(layer): - return LayerKeycode(RawKeycodes.KC_TG, layer) + return LayerKeycode(RawKeycodes.KC_TG, layer, KC.NO) @staticmethod def KC_TO(layer): - return LayerKeycode(RawKeycodes.KC_TO, layer) + return LayerKeycode(RawKeycodes.KC_TO, layer, KC.NO) @staticmethod def KC_TT(layer): - return LayerKeycode(RawKeycodes.KC_TT, layer) + return LayerKeycode(RawKeycodes.KC_TT, layer, KC.NO) class Keycodes(KeycodeCategory): diff --git a/kmk/common/kmktime.py b/kmk/common/kmktime.py new file mode 100644 index 0000000..163b330 --- /dev/null +++ b/kmk/common/kmktime.py @@ -0,0 +1,13 @@ + +try: + import utime +except ImportError: + pass + + +def ticks_ms(): + return utime.ticks_ms() + + +def ticks_diff(new, old): + return utime.ticks_diff(new, old) diff --git a/user_keymaps/kdb424/handwire_planck_pyboard.py b/user_keymaps/kdb424/handwire_planck_pyboard.py index 1c011e0..c01b9a6 100644 --- a/user_keymaps/kdb424/handwire_planck_pyboard.py +++ b/user_keymaps/kdb424/handwire_planck_pyboard.py @@ -1,7 +1,8 @@ import machine -from kmk.common.consts import DiodeOrientation +from kmk.common.consts import DiodeOrientation, UnicodeModes from kmk.common.keycodes import KC +from kmk.common.macros.unicode import unicode_sequence from kmk.entrypoints.handwire.pyboard import main p = machine.Pin.board @@ -11,13 +12,30 @@ rows = (p.Y1, p.Y2, p.Y3, p.Y4) diode_orientation = DiodeOrientation.COLUMNS +unicode_mode = UnicodeModes.LINUX +tap_time = 180 + +FLIP = unicode_sequence([ + "28", + "30ce", + "ca0", + "75ca", + "ca0", + "29", + "30ce", + "5f61", + "253b", + "2501", + "253b", +]) + keymap = [ [ # Default [KC.GESC, KC.QUOTE, KC.COMMA, KC.DOT, KC.P, KC.Y, KC.F, KC.G, KC.C, KC.R, KC.L, KC.BKSP], [KC.TAB, KC.A, KC.O, KC.E, KC.U, KC.I, KC.D, KC.H, KC.T, KC.N, KC.S, KC.ENT], [KC.LSFT, KC.SCLN, KC.Q, KC.J, KC.K, KC.X, KC.B, KC.M, KC.W, KC.V, KC.Z, KC.SLSH], - [KC.LCTRL, KC.LGUI, KC.LALT, KC.MO(2), KC.MO(3), KC.SPC, KC.SPC, KC.MO(4), KC.LEFT, KC.DOWN, KC.UP, KC.RIGHT], + [KC.LCTRL, KC.LGUI, KC.LALT, KC.NO, KC.MO(2), KC.LT(3, KC.SPC), KC.LT(3, KC.SPC), KC.MO(4), KC.LEFT, KC.DOWN, KC.UP, KC.RIGHT], ], [ # Gaming @@ -42,7 +60,7 @@ keymap = [ ], [ # Raise3 - [KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.F10, KC.F11, KC.F12, KC.NO], + [KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, FLIP, KC.TRNS, KC.F10, KC.F11, KC.F12, KC.LSHIFT(KC.INS)], [KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.F7, KC.F8, KC.F9, KC.NO], [KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.F4, KC.F5, KC.F6, KC.NO], [KC.DF(0), KC.DF(1), KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.TRNS, KC.F1, KC.F2, KC.F3, KC.NO], diff --git a/user_keymaps/klardotsh/threethree_matrix_pyboard.py b/user_keymaps/klardotsh/threethree_matrix_pyboard.py index cb48d77..12260e5 100644 --- a/user_keymaps/klardotsh/threethree_matrix_pyboard.py +++ b/user_keymaps/klardotsh/threethree_matrix_pyboard.py @@ -48,9 +48,9 @@ ANGRY_TABLE_FLIP = unicode_sequence([ keymap = [ [ - [KC.MO(1), KC.GESC, KC.RESET], - [KC.MO(2), KC.HASH, KC.ENTER], - [KC.MO(3), KC.SPACE, KC.LSHIFT], + [KC.MO(1), KC.GESC, KC.RESET], + [KC.LT(2, KC.EXCLAIM), KC.HASH, KC.ENTER], + [KC.TT(3), KC.SPACE, KC.LSHIFT], ], [ [KC.TRNS, KC.B, KC.C],