2018-09-03 22:50:12 +02:00
|
|
|
import logging
|
2018-09-04 00:21:34 +02:00
|
|
|
import sys
|
2018-09-03 22:50:12 +02:00
|
|
|
|
2018-10-06 12:58:19 +02:00
|
|
|
from kmk.common import kmktime
|
Massive refactor largely to support Unicode on Mac
This does a bunch of crazy stuff:
- The ability to set a unicode mode (right now only Linux+ibus or
MacOS-RALT) in the keymap. This will be changeable at runtime soon, to
allow a single keyboard to be able to send table flips and whatever
other crazy stuff on any OS the board is plugged into (something that's
not currently doable on QMK, so yay us?)
- As part of the above, there is now just one user-facing macro for
unicode codepoint submission,
`kmk.common.macros.unicode.unicode_sequence`. Users should never use the
platform-specific macros, partly because they just outright won't work.
There's all sorts of fun stuff in these methods now, thank goodness
MicroPython supports the `yield from` construct.
- Keycode (these should really be renamed Keysym or something) objects
that are intended to not be pressed, or not be released. Right now these
properties are completely ignored if not part of a macro, and it's
probably sane to keep it that way. This was necessary to support MacOS's
"hold RALT while typing the codepoint characters" flow.
- Other refactor-y bits, like moving macro support to `kmk/common`
rather than sitting at the top level of the tree. One day `kmk/common`
may make sense to surface at top level `kmk/`, but that's a discussion
for another day.
2018-10-01 04:33:23 +02:00
|
|
|
from kmk.common.consts import DiodeOrientation, UnicodeModes
|
2018-09-23 06:49:58 +02:00
|
|
|
from kmk.common.event_defs import (HID_REPORT_EVENT, INIT_FIRMWARE_EVENT,
|
|
|
|
KEY_DOWN_EVENT, KEY_UP_EVENT,
|
2018-10-01 03:03:43 +02:00
|
|
|
KEYCODE_DOWN_EVENT, KEYCODE_UP_EVENT,
|
2018-09-28 23:35:52 +02:00
|
|
|
MACRO_COMPLETE_EVENT, NEW_MATRIX_EVENT,
|
|
|
|
PENDING_KEYCODE_POP_EVENT)
|
2018-10-06 12:58:19 +02:00
|
|
|
from kmk.common.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, Keycodes,
|
|
|
|
RawKeycodes)
|
|
|
|
|
|
|
|
GESC_TRIGGERS = {
|
|
|
|
Keycodes.Modifiers.KC_LSHIFT, Keycodes.Modifiers.KC_RSHIFT,
|
|
|
|
Keycodes.Modifiers.KC_LGUI, Keycodes.Modifiers.KC_RGUI,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class Store:
|
|
|
|
'''
|
|
|
|
A data store very loosely inspired by Redux, but with most of the fancy
|
|
|
|
functional and immutable abilities unavailable because microcontrollers.
|
|
|
|
This serves as the event dispatcher at the heart of KMK. All changes to the
|
|
|
|
state of the keyboard should be triggered by events (see event_defs.py)
|
|
|
|
dispatched through this store, and listened to (for side-effects or other
|
|
|
|
handling) by subscription functions.
|
|
|
|
'''
|
2018-09-03 22:50:12 +02:00
|
|
|
def __init__(self, reducer, log_level=logging.NOTSET):
|
|
|
|
self.reducer = reducer
|
|
|
|
self.logger = logging.getLogger(__name__)
|
|
|
|
self.logger.setLevel(log_level)
|
|
|
|
self.state = self.reducer(logger=self.logger)
|
2018-09-04 00:21:34 +02:00
|
|
|
self.callbacks = []
|
2018-09-03 22:50:12 +02:00
|
|
|
|
|
|
|
def dispatch(self, action):
|
2018-09-23 06:49:58 +02:00
|
|
|
if callable(action):
|
|
|
|
self.logger.debug('Received thunk')
|
|
|
|
action(self.dispatch, self.get_state)
|
|
|
|
self.logger.debug('Finished thunk')
|
|
|
|
return None
|
|
|
|
|
2018-10-01 10:07:57 +02:00
|
|
|
self.logger.debug('Dispatching action: Type {} >> {}'.format(action.type, action))
|
2018-09-23 06:49:58 +02:00
|
|
|
self.state = self.reducer(self.state, action, logger=self.logger)
|
2018-10-01 10:07:57 +02:00
|
|
|
self.logger.debug('Dispatching complete: Type {}'.format(action.type))
|
2018-09-03 22:50:12 +02:00
|
|
|
|
2018-09-17 08:20:16 +02:00
|
|
|
self.logger.debug('New state: {}'.format(self.state))
|
2018-09-04 00:21:34 +02:00
|
|
|
|
|
|
|
for cb in self.callbacks:
|
|
|
|
if cb is not None:
|
|
|
|
try:
|
|
|
|
cb(self.state, action)
|
|
|
|
except Exception as e:
|
|
|
|
self.logger.error('Callback failed, moving on')
|
2018-10-06 14:59:03 +02:00
|
|
|
sys.print_exception(e)
|
2018-09-04 00:21:34 +02:00
|
|
|
|
2018-09-03 22:50:12 +02:00
|
|
|
def get_state(self):
|
|
|
|
return self.state
|
|
|
|
|
2018-09-04 00:21:34 +02:00
|
|
|
def subscribe(self, callback):
|
|
|
|
self.callbacks.append(callback)
|
|
|
|
return len(self.callbacks) - 1
|
|
|
|
|
|
|
|
def unsubscribe(self, idx):
|
|
|
|
self.callbacks[idx] = None
|
|
|
|
|
2018-09-03 22:50:12 +02:00
|
|
|
|
|
|
|
class InternalState:
|
2018-10-01 09:31:45 +02:00
|
|
|
keys_pressed = set()
|
2018-09-28 23:35:52 +02:00
|
|
|
pending_keys = set()
|
Massive refactor largely to support Unicode on Mac
This does a bunch of crazy stuff:
- The ability to set a unicode mode (right now only Linux+ibus or
MacOS-RALT) in the keymap. This will be changeable at runtime soon, to
allow a single keyboard to be able to send table flips and whatever
other crazy stuff on any OS the board is plugged into (something that's
not currently doable on QMK, so yay us?)
- As part of the above, there is now just one user-facing macro for
unicode codepoint submission,
`kmk.common.macros.unicode.unicode_sequence`. Users should never use the
platform-specific macros, partly because they just outright won't work.
There's all sorts of fun stuff in these methods now, thank goodness
MicroPython supports the `yield from` construct.
- Keycode (these should really be renamed Keysym or something) objects
that are intended to not be pressed, or not be released. Right now these
properties are completely ignored if not part of a macro, and it's
probably sane to keep it that way. This was necessary to support MacOS's
"hold RALT while typing the codepoint characters" flow.
- Other refactor-y bits, like moving macro support to `kmk/common`
rather than sitting at the top level of the tree. One day `kmk/common`
may make sense to surface at top level `kmk/`, but that's a discussion
for another day.
2018-10-01 04:33:23 +02:00
|
|
|
macro_pending = None
|
2018-10-01 09:31:45 +02:00
|
|
|
hid_pending = False
|
Massive refactor largely to support Unicode on Mac
This does a bunch of crazy stuff:
- The ability to set a unicode mode (right now only Linux+ibus or
MacOS-RALT) in the keymap. This will be changeable at runtime soon, to
allow a single keyboard to be able to send table flips and whatever
other crazy stuff on any OS the board is plugged into (something that's
not currently doable on QMK, so yay us?)
- As part of the above, there is now just one user-facing macro for
unicode codepoint submission,
`kmk.common.macros.unicode.unicode_sequence`. Users should never use the
platform-specific macros, partly because they just outright won't work.
There's all sorts of fun stuff in these methods now, thank goodness
MicroPython supports the `yield from` construct.
- Keycode (these should really be renamed Keysym or something) objects
that are intended to not be pressed, or not be released. Right now these
properties are completely ignored if not part of a macro, and it's
probably sane to keep it that way. This was necessary to support MacOS's
"hold RALT while typing the codepoint characters" flow.
- Other refactor-y bits, like moving macro support to `kmk/common`
rather than sitting at the top level of the tree. One day `kmk/common`
may make sense to surface at top level `kmk/`, but that's a discussion
for another day.
2018-10-01 04:33:23 +02:00
|
|
|
unicode_mode = UnicodeModes.NOOP
|
2018-09-28 23:35:52 +02:00
|
|
|
tap_time = 300
|
2018-09-04 00:21:34 +02:00
|
|
|
keymap = []
|
|
|
|
row_pins = []
|
|
|
|
col_pins = []
|
|
|
|
matrix = []
|
|
|
|
diode_orientation = DiodeOrientation.COLUMNS
|
2018-09-22 02:22:03 +02:00
|
|
|
active_layers = [0]
|
2018-09-28 23:35:52 +02:00
|
|
|
start_time = {
|
|
|
|
'lt': None,
|
|
|
|
'tg': None,
|
|
|
|
'tt': None,
|
|
|
|
}
|
|
|
|
tick_time = {
|
|
|
|
'lt': None,
|
|
|
|
'tg': None,
|
|
|
|
'tt': None,
|
|
|
|
}
|
2018-09-22 09:46:14 +02:00
|
|
|
_oldstates = []
|
2018-09-04 00:21:34 +02:00
|
|
|
|
2018-09-22 09:46:14 +02:00
|
|
|
def __init__(self, preserve_intermediate_states=False):
|
|
|
|
self.preserve_intermediate_states = preserve_intermediate_states
|
|
|
|
|
2018-09-23 06:49:58 +02:00
|
|
|
def __enter__(self):
|
|
|
|
return self
|
|
|
|
|
|
|
|
def __exit__(self, type, value, traceback):
|
|
|
|
pass
|
|
|
|
|
2018-09-22 09:46:14 +02:00
|
|
|
def to_dict(self, verbose=False):
|
|
|
|
ret = {
|
2018-09-04 00:21:34 +02:00
|
|
|
'keys_pressed': self.keys_pressed,
|
2018-09-22 02:22:03 +02:00
|
|
|
'active_layers': self.active_layers,
|
Massive refactor largely to support Unicode on Mac
This does a bunch of crazy stuff:
- The ability to set a unicode mode (right now only Linux+ibus or
MacOS-RALT) in the keymap. This will be changeable at runtime soon, to
allow a single keyboard to be able to send table flips and whatever
other crazy stuff on any OS the board is plugged into (something that's
not currently doable on QMK, so yay us?)
- As part of the above, there is now just one user-facing macro for
unicode codepoint submission,
`kmk.common.macros.unicode.unicode_sequence`. Users should never use the
platform-specific macros, partly because they just outright won't work.
There's all sorts of fun stuff in these methods now, thank goodness
MicroPython supports the `yield from` construct.
- Keycode (these should really be renamed Keysym or something) objects
that are intended to not be pressed, or not be released. Right now these
properties are completely ignored if not part of a macro, and it's
probably sane to keep it that way. This was necessary to support MacOS's
"hold RALT while typing the codepoint characters" flow.
- Other refactor-y bits, like moving macro support to `kmk/common`
rather than sitting at the top level of the tree. One day `kmk/common`
may make sense to surface at top level `kmk/`, but that's a discussion
for another day.
2018-10-01 04:33:23 +02:00
|
|
|
'unicode_mode': self.unicode_mode,
|
2018-09-28 23:35:52 +02:00
|
|
|
'tap_time': self.tap_time,
|
|
|
|
'start_time': self.start_time,
|
|
|
|
'tick_time': self.tick_time,
|
2018-09-04 00:21:34 +02:00
|
|
|
}
|
2018-09-03 22:50:12 +02:00
|
|
|
|
2018-09-22 09:46:14 +02:00
|
|
|
if verbose:
|
|
|
|
ret.update({
|
|
|
|
'keymap': self.keymap,
|
|
|
|
'matrix': self.matrix,
|
|
|
|
'col_pins': self.col_pins,
|
|
|
|
'row_pins': self.row_pins,
|
|
|
|
'diode_orientation': self.diode_orientation,
|
|
|
|
})
|
2018-09-03 22:50:12 +02:00
|
|
|
|
2018-09-22 09:46:14 +02:00
|
|
|
return ret
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return 'InternalState({})'.format(self.to_dict())
|
2018-09-03 22:50:12 +02:00
|
|
|
|
2018-09-22 09:46:14 +02:00
|
|
|
def update(self, **kwargs):
|
|
|
|
if self.preserve_intermediate_states:
|
|
|
|
self._oldstates.append(repr(self.to_dict(verbose=True)))
|
2018-09-03 22:50:12 +02:00
|
|
|
|
2018-09-04 00:21:34 +02:00
|
|
|
for k, v in kwargs.items():
|
2018-09-22 09:46:14 +02:00
|
|
|
setattr(self, k, v)
|
2018-09-03 22:50:12 +02:00
|
|
|
|
2018-09-22 09:46:14 +02:00
|
|
|
return self
|
2018-09-03 22:50:12 +02:00
|
|
|
|
|
|
|
|
2018-09-23 06:49:58 +02:00
|
|
|
def find_key_in_map(state, row, col):
|
|
|
|
# Later-added layers have priority. Sift through the layers
|
|
|
|
# in reverse order until we find a valid keycode object
|
|
|
|
for layer in reversed(state.active_layers):
|
|
|
|
layer_key = state.keymap[layer][row][col]
|
|
|
|
|
|
|
|
if not layer_key or layer_key == Keycodes.KMK.KC_TRNS:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if layer_key == Keycodes.KMK.KC_NO:
|
|
|
|
break
|
|
|
|
|
|
|
|
return layer_key
|
|
|
|
|
|
|
|
|
2018-09-03 22:50:12 +02:00
|
|
|
def kmk_reducer(state=None, action=None, logger=None):
|
|
|
|
if state is None:
|
|
|
|
state = InternalState()
|
|
|
|
|
|
|
|
if logger is not None:
|
|
|
|
logger.debug('Reducer received state of None, creating new')
|
|
|
|
|
|
|
|
if action is None:
|
|
|
|
if logger is not None:
|
|
|
|
logger.debug('No action received, returning state unmodified')
|
|
|
|
|
|
|
|
return state
|
|
|
|
|
2018-10-01 10:07:57 +02:00
|
|
|
if action.type == NEW_MATRIX_EVENT:
|
2018-10-01 09:31:45 +02:00
|
|
|
matrix_keys_pressed = {
|
|
|
|
find_key_in_map(state, row, col)
|
2018-09-28 23:35:52 +02:00
|
|
|
for row, col, in action.matrix
|
2018-10-01 09:31:45 +02:00
|
|
|
}
|
2018-09-23 06:49:58 +02:00
|
|
|
|
2018-10-01 09:31:45 +02:00
|
|
|
pressed = matrix_keys_pressed - state.keys_pressed
|
|
|
|
released = state.keys_pressed - matrix_keys_pressed
|
2018-10-01 03:03:43 +02:00
|
|
|
|
2018-10-01 09:31:45 +02:00
|
|
|
if not pressed and not released:
|
2018-10-01 03:03:43 +02:00
|
|
|
return state
|
|
|
|
|
2018-10-01 09:31:45 +02:00
|
|
|
for changed_key in released:
|
|
|
|
if not changed_key:
|
|
|
|
continue
|
|
|
|
elif changed_key.code >= FIRST_KMK_INTERNAL_KEYCODE:
|
2018-09-28 23:35:52 +02:00
|
|
|
state = process_internal_key_event(state,
|
|
|
|
KEY_UP_EVENT,
|
|
|
|
changed_key,
|
|
|
|
logger=logger)
|
2018-09-22 08:44:30 +02:00
|
|
|
|
2018-10-01 09:31:45 +02:00
|
|
|
for changed_key in pressed:
|
|
|
|
if not changed_key:
|
|
|
|
continue
|
|
|
|
elif changed_key.code >= FIRST_KMK_INTERNAL_KEYCODE:
|
|
|
|
state = process_internal_key_event(
|
|
|
|
state,
|
|
|
|
KEY_DOWN_EVENT,
|
|
|
|
changed_key,
|
|
|
|
logger=logger,
|
2018-10-01 03:03:43 +02:00
|
|
|
)
|
|
|
|
|
2018-10-01 10:07:57 +02:00
|
|
|
state.matrix = action.matrix
|
2018-10-01 09:31:45 +02:00
|
|
|
state.keys_pressed |= pressed
|
|
|
|
state.keys_pressed -= released
|
|
|
|
state.hid_pending = True
|
2018-10-01 03:03:43 +02:00
|
|
|
|
2018-10-01 09:31:45 +02:00
|
|
|
return state
|
2018-09-04 00:21:34 +02:00
|
|
|
|
2018-10-01 10:07:57 +02:00
|
|
|
if action.type == KEYCODE_UP_EVENT:
|
|
|
|
state.keys_pressed.discard(action.keycode)
|
2018-10-01 09:31:45 +02:00
|
|
|
state.hid_pending = True
|
|
|
|
return state
|
2018-09-22 08:44:30 +02:00
|
|
|
|
2018-10-01 10:07:57 +02:00
|
|
|
if action.type == KEYCODE_DOWN_EVENT:
|
|
|
|
state.keys_pressed.add(action.keycode)
|
2018-10-01 09:31:45 +02:00
|
|
|
state.hid_pending = True
|
|
|
|
return state
|
2018-09-22 08:44:30 +02:00
|
|
|
|
2018-10-01 10:07:57 +02:00
|
|
|
if action.type == INIT_FIRMWARE_EVENT:
|
2018-09-22 09:46:14 +02:00
|
|
|
return state.update(
|
2018-10-01 07:50:04 +02:00
|
|
|
keymap=action.keymap,
|
|
|
|
row_pins=action.row_pins,
|
|
|
|
col_pins=action.col_pins,
|
|
|
|
diode_orientation=action.diode_orientation,
|
|
|
|
unicode_mode=action.unicode_mode,
|
2018-09-04 00:21:34 +02:00
|
|
|
)
|
2018-09-23 06:49:58 +02:00
|
|
|
|
|
|
|
# HID events are non-mutating, used exclusively for listeners to know
|
|
|
|
# they should be doing things. This could/should arguably be folded back
|
|
|
|
# into KEY_UP_EVENT and KEY_DOWN_EVENT, but for now it's nice to separate
|
|
|
|
# this out for debugging's sake.
|
2018-10-01 10:07:57 +02:00
|
|
|
if action.type == HID_REPORT_EVENT:
|
2018-10-01 09:31:45 +02:00
|
|
|
state.hid_pending = False
|
2018-09-23 06:49:58 +02:00
|
|
|
return state
|
|
|
|
|
2018-10-01 10:07:57 +02:00
|
|
|
if action.type == MACRO_COMPLETE_EVENT:
|
Massive refactor largely to support Unicode on Mac
This does a bunch of crazy stuff:
- The ability to set a unicode mode (right now only Linux+ibus or
MacOS-RALT) in the keymap. This will be changeable at runtime soon, to
allow a single keyboard to be able to send table flips and whatever
other crazy stuff on any OS the board is plugged into (something that's
not currently doable on QMK, so yay us?)
- As part of the above, there is now just one user-facing macro for
unicode codepoint submission,
`kmk.common.macros.unicode.unicode_sequence`. Users should never use the
platform-specific macros, partly because they just outright won't work.
There's all sorts of fun stuff in these methods now, thank goodness
MicroPython supports the `yield from` construct.
- Keycode (these should really be renamed Keysym or something) objects
that are intended to not be pressed, or not be released. Right now these
properties are completely ignored if not part of a macro, and it's
probably sane to keep it that way. This was necessary to support MacOS's
"hold RALT while typing the codepoint characters" flow.
- Other refactor-y bits, like moving macro support to `kmk/common`
rather than sitting at the top level of the tree. One day `kmk/common`
may make sense to surface at top level `kmk/`, but that's a discussion
for another day.
2018-10-01 04:33:23 +02:00
|
|
|
return state.update(macro_pending=None)
|
2018-10-01 03:03:43 +02:00
|
|
|
|
2018-09-28 23:35:52 +02:00
|
|
|
if action.type == PENDING_KEYCODE_POP_EVENT:
|
|
|
|
state.pending_keys.pop()
|
|
|
|
return state
|
|
|
|
|
2018-09-23 06:49:58 +02:00
|
|
|
# On unhandled events, log and do not mutate state
|
|
|
|
logger.warning('Unhandled event! Returning state unmodified.')
|
|
|
|
return state
|
2018-10-06 12:58:19 +02:00
|
|
|
|
|
|
|
|
|
|
|
def process_internal_key_event(state, action_type, changed_key, logger=None):
|
|
|
|
if logger is None:
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
# Since the key objects can be chained into new objects
|
|
|
|
# with, for example, no_press set, always check against
|
|
|
|
# the underlying code rather than comparing Keycode
|
|
|
|
# objects
|
|
|
|
|
|
|
|
if changed_key.code == RawKeycodes.KC_DF:
|
|
|
|
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:
|
|
|
|
return unicode_mode(state, action_type, changed_key, logger=logger)
|
|
|
|
elif changed_key.code == RawKeycodes.KC_MACRO:
|
|
|
|
return macro(state, action_type, changed_key, logger=logger)
|
|
|
|
else:
|
|
|
|
return state
|
|
|
|
|
|
|
|
|
|
|
|
def grave_escape(state, action_type, logger):
|
|
|
|
if action_type == KEY_DOWN_EVENT:
|
|
|
|
if any(key in GESC_TRIGGERS for key in state.keys_pressed):
|
|
|
|
# if Shift is held, KC_GRAVE will become KC_TILDE on OS level
|
|
|
|
state.keys_pressed.add(Keycodes.Common.KC_GRAVE)
|
|
|
|
return state
|
|
|
|
|
|
|
|
# else return KC_ESC
|
|
|
|
state.keys_pressed.add(Keycodes.Common.KC_ESCAPE)
|
|
|
|
return state
|
|
|
|
|
|
|
|
elif action_type == KEY_UP_EVENT:
|
|
|
|
state.keys_pressed.discard(Keycodes.Common.KC_ESCAPE)
|
|
|
|
state.keys_pressed.discard(Keycodes.Common.KC_GRAVE)
|
|
|
|
return state
|
|
|
|
|
|
|
|
return state
|
|
|
|
|
|
|
|
|
|
|
|
def df(state, action_type, changed_key, logger):
|
|
|
|
"""Switches the default layer"""
|
|
|
|
if action_type == KEY_DOWN_EVENT:
|
|
|
|
state.active_layers[0] = changed_key.layer
|
|
|
|
|
|
|
|
return state
|
|
|
|
|
|
|
|
|
|
|
|
def mo(state, action_type, changed_key, logger):
|
|
|
|
"""Momentarily activates layer, switches off when you let go"""
|
|
|
|
if action_type == KEY_UP_EVENT:
|
|
|
|
state.active_layers = [
|
|
|
|
layer for layer in state.active_layers
|
|
|
|
if layer != changed_key.layer
|
|
|
|
]
|
|
|
|
elif action_type == KEY_DOWN_EVENT:
|
|
|
|
state.active_layers.append(changed_key.layer)
|
|
|
|
|
|
|
|
return state
|
|
|
|
|
|
|
|
|
|
|
|
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(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):
|
|
|
|
"""Toggles the layer (enables it if not active, and vise versa)"""
|
|
|
|
if action_type == KEY_DOWN_EVENT:
|
|
|
|
if changed_key.layer in state.active_layers:
|
|
|
|
state.active_layers = [
|
|
|
|
layer for layer in state.active_layers
|
|
|
|
if layer != changed_key.layer
|
|
|
|
]
|
|
|
|
else:
|
|
|
|
state.active_layers.append(changed_key.layer)
|
|
|
|
|
|
|
|
return state
|
|
|
|
|
|
|
|
|
|
|
|
def to(state, action_type, changed_key, logger):
|
|
|
|
"""Activates layer and deactivates all other layers"""
|
|
|
|
if action_type == KEY_DOWN_EVENT:
|
|
|
|
state.active_layers = [changed_key.layer]
|
|
|
|
|
|
|
|
return state
|
|
|
|
|
|
|
|
|
|
|
|
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):
|
|
|
|
if action_type == KEY_DOWN_EVENT:
|
|
|
|
state.unicode_mode = changed_key.mode
|
|
|
|
|
|
|
|
return state
|
|
|
|
|
|
|
|
|
|
|
|
def macro(state, action_type, changed_key, logger):
|
|
|
|
if action_type == KEY_UP_EVENT:
|
|
|
|
if changed_key.keyup:
|
|
|
|
state.macro_pending = changed_key.keyup
|
|
|
|
return state
|
|
|
|
|
|
|
|
elif action_type == KEY_DOWN_EVENT:
|
|
|
|
if changed_key.keydown:
|
|
|
|
state.macro_pending = changed_key.keydown
|
|
|
|
return state
|
|
|
|
|
|
|
|
return state
|