2018-10-19 10:49:37 +02:00
|
|
|
from kmk.consts import LeaderMode
|
2018-10-12 03:02:13 +02:00
|
|
|
from kmk.keycodes import FIRST_KMK_INTERNAL_KEYCODE, Keycodes, RawKeycodes
|
2018-10-16 13:04:39 +02:00
|
|
|
from kmk.kmktime import sleep_ms, ticks_diff, ticks_ms
|
2018-10-19 08:24:19 +02:00
|
|
|
from kmk.util import intify_coordinate
|
2018-10-06 12:58:19 +02:00
|
|
|
|
|
|
|
GESC_TRIGGERS = {
|
|
|
|
Keycodes.Modifiers.KC_LSHIFT, Keycodes.Modifiers.KC_RSHIFT,
|
|
|
|
Keycodes.Modifiers.KC_LGUI, Keycodes.Modifiers.KC_RGUI,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-09-03 22:50:12 +02:00
|
|
|
class InternalState:
|
2018-10-01 09:31:45 +02:00
|
|
|
keys_pressed = set()
|
2018-10-19 08:24:19 +02:00
|
|
|
coord_keys_pressed = {}
|
2018-10-17 07:50:41 +02:00
|
|
|
pending_keys = []
|
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-08 11:31:30 +02:00
|
|
|
leader_pending = None
|
2018-10-08 11:51:31 +02:00
|
|
|
leader_last_len = 0
|
2018-10-01 09:31:45 +02:00
|
|
|
hid_pending = False
|
2018-10-08 11:31:30 +02:00
|
|
|
leader_mode_history = []
|
2018-09-22 02:22:03 +02:00
|
|
|
active_layers = [0]
|
2018-10-16 13:04:39 +02:00
|
|
|
reversed_active_layers = list(reversed(active_layers))
|
2018-09-28 23:35:52 +02:00
|
|
|
start_time = {
|
|
|
|
'lt': None,
|
|
|
|
'tg': None,
|
|
|
|
'tt': None,
|
2018-10-08 11:31:30 +02:00
|
|
|
'lm': None,
|
|
|
|
'leader': None,
|
2018-09-28 23:35:52 +02:00
|
|
|
}
|
2018-10-19 10:49:37 +02:00
|
|
|
timeouts = {}
|
2018-09-22 09:46:14 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
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,
|
2018-10-19 09:58:17 +02:00
|
|
|
Keycodes.KMK.KC_DEBUG.code: self._kc_debug_mode,
|
2018-10-16 13:04:39 +02:00
|
|
|
}
|
2018-09-23 06:49:58 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
def __repr__(self):
|
|
|
|
return 'InternalState({})'.format(self._to_dict())
|
2018-09-23 06:49:58 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
def _to_dict(self):
|
2018-09-22 09:46:14 +02:00
|
|
|
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,
|
2018-10-08 11:31:30 +02:00
|
|
|
'leader_mode_history': self.leader_mode_history,
|
2018-10-19 10:49:37 +02:00
|
|
|
'leader_mode': self.config.leader_mode,
|
2018-09-28 23:35:52 +02:00
|
|
|
'start_time': self.start_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
|
|
|
return ret
|
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
def _find_key_in_map(self, row, col):
|
|
|
|
# Later-added layers have priority. Sift through the layers
|
|
|
|
# in reverse order until we find a valid keycode object
|
|
|
|
for layer in self.reversed_active_layers:
|
|
|
|
layer_key = self.config.keymap[layer][row][col]
|
2018-09-03 22:50:12 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
if not layer_key or layer_key == Keycodes.KMK.KC_TRNS:
|
|
|
|
continue
|
2018-09-03 22:50:12 +02:00
|
|
|
|
2018-10-19 08:24:19 +02:00
|
|
|
if self.config.debug_enabled:
|
|
|
|
print('Resolved key: {}'.format(layer_key))
|
2018-09-23 06:49:58 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
return layer_key
|
2018-09-23 06:49:58 +02:00
|
|
|
|
2018-10-19 10:49:37 +02:00
|
|
|
def set_timeout(self, after_ticks, callback):
|
|
|
|
timeout_key = ticks_ms() + after_ticks
|
|
|
|
self.timeouts[timeout_key] = callback
|
|
|
|
return self
|
|
|
|
|
|
|
|
def process_timeouts(self):
|
|
|
|
current_time = ticks_ms()
|
|
|
|
|
|
|
|
for k, v in self.timeouts.items():
|
|
|
|
if k <= current_time:
|
|
|
|
v()
|
|
|
|
del self.timeouts[k]
|
|
|
|
|
|
|
|
return self
|
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
def matrix_changed(self, row, col, is_pressed):
|
|
|
|
if self.config.debug_enabled:
|
|
|
|
print('Matrix changed (col, row, pressed?): {}, {}, {}'.format(
|
2018-10-19 08:24:19 +02:00
|
|
|
col, row, is_pressed,
|
2018-10-16 13:04:39 +02:00
|
|
|
))
|
2018-09-23 06:49:58 +02:00
|
|
|
|
2018-10-19 08:24:19 +02:00
|
|
|
int_coord = intify_coordinate(row, col)
|
2018-10-16 13:04:39 +02:00
|
|
|
kc_changed = self._find_key_in_map(row, col)
|
2018-09-23 06:49:58 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
if kc_changed is None:
|
|
|
|
print('No key accessible for col, row: {}, {}'.format(row, col))
|
|
|
|
return self
|
2018-09-23 06:49:58 +02:00
|
|
|
|
2018-10-19 10:49:37 +02:00
|
|
|
if is_pressed:
|
|
|
|
self.keys_pressed.add(kc_changed)
|
|
|
|
self.coord_keys_pressed[int_coord] = kc_changed
|
|
|
|
else:
|
|
|
|
self.keys_pressed.discard(kc_changed)
|
|
|
|
self.keys_pressed.discard(self.coord_keys_pressed[int_coord])
|
|
|
|
self.coord_keys_pressed[int_coord] = None
|
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
if kc_changed.code >= FIRST_KMK_INTERNAL_KEYCODE:
|
|
|
|
self._process_internal_key_event(
|
|
|
|
kc_changed,
|
|
|
|
is_pressed,
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
self.hid_pending = True
|
2018-09-03 22:50:12 +02:00
|
|
|
|
2018-10-19 10:49:37 +02:00
|
|
|
if self.config.leader_mode % 2 == 1:
|
|
|
|
self._process_leader_mode()
|
2018-09-03 22:50:12 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
return self
|
2018-09-03 22:50:12 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
def force_keycode_up(self, keycode):
|
|
|
|
self.keys_pressed.discard(keycode)
|
|
|
|
self.hid_pending = True
|
|
|
|
return self
|
2018-09-23 06:49:58 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
def force_keycode_down(self, keycode):
|
|
|
|
if keycode.code == Keycodes.KMK.KC_MACRO_SLEEP_MS:
|
|
|
|
sleep_ms(keycode.ms)
|
|
|
|
else:
|
|
|
|
self.keys_pressed.add(keycode)
|
|
|
|
self.hid_pending = True
|
|
|
|
return self
|
2018-10-01 03:03:43 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
def pending_key_handled(self):
|
|
|
|
popped = self.pending_keys.pop()
|
2018-10-01 03:03:43 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
if self.config.debug_enabled:
|
|
|
|
print('Popped pending key: {}'.format(popped))
|
|
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
def resolve_hid(self):
|
|
|
|
self.hid_pending = False
|
|
|
|
return self
|
|
|
|
|
|
|
|
def resolve_macro(self):
|
|
|
|
if self.config.debug_enabled:
|
|
|
|
print('Macro complete!')
|
|
|
|
|
|
|
|
self.macro_pending = None
|
|
|
|
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
|
|
|
|
# the underlying code rather than comparing Keycode
|
|
|
|
# objects
|
|
|
|
|
|
|
|
return self.internal_key_handlers[changed_key.code](
|
|
|
|
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)
|
2018-10-08 11:31:30 +02:00
|
|
|
else:
|
2018-10-16 13:04:39 +02:00
|
|
|
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)
|
2018-10-17 07:50:41 +02:00
|
|
|
else:
|
|
|
|
self.keys_pressed.discard(changed_key.kc)
|
|
|
|
self.start_time['lm'] = None
|
2018-10-16 13:04:39 +02:00
|
|
|
|
2018-10-17 07:36:01 +02:00
|
|
|
return self._layer_mo(changed_key, is_pressed)
|
2018-10-16 13:04:39 +02:00
|
|
|
|
|
|
|
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()
|
2018-10-17 07:50:41 +02:00
|
|
|
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.pending_keys.append(changed_key.kc)
|
|
|
|
|
|
|
|
self._layer_mo(changed_key, is_pressed)
|
|
|
|
self.start_time['lt'] = None
|
|
|
|
return self
|
2018-10-06 12:58:19 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
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)
|
2018-10-06 12:58:19 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
self.reversed_active_layers = list(reversed(self.active_layers))
|
2018-10-06 12:58:19 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
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()
|
2018-10-17 07:36:01 +02:00
|
|
|
return self._layer_mo(changed_key, is_pressed)
|
2018-10-17 07:39:17 +02:00
|
|
|
elif ticks_diff(ticks_ms(), self.start_time['tt']) < self.config.tap_time:
|
2018-10-16 13:04:39 +02:00
|
|
|
self.start_time['tt'] = None
|
|
|
|
return self.tg(changed_key, is_pressed)
|
|
|
|
elif (
|
|
|
|
self.start_time['tt'] is None or
|
2018-10-17 07:39:17 +02:00
|
|
|
ticks_diff(ticks_ms(), self.start_time['tt']) >= self.config.tap_time
|
2018-10-16 13:04:39 +02:00
|
|
|
):
|
|
|
|
# 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
|
2018-10-17 07:36:01 +02:00
|
|
|
return self._layer_mo(changed_key, is_pressed)
|
2018-10-16 13:04:39 +02:00
|
|
|
|
|
|
|
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.macro_pending = changed_key.keyup
|
2018-10-06 12:58:19 +02:00
|
|
|
else:
|
2018-10-16 13:04:39 +02:00
|
|
|
if changed_key.keydown:
|
|
|
|
self.macro_pending = changed_key.keydown
|
2018-10-06 12:58:19 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
return self
|
2018-10-06 12:58:19 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
def _kc_lead(self, changed_key, is_pressed):
|
|
|
|
if is_pressed:
|
|
|
|
self._begin_leader_mode()
|
2018-10-06 12:58:19 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
return self
|
2018-10-06 12:58:19 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
def _kc_gesc(self, changed_key, is_pressed):
|
|
|
|
self.hid_pending = True
|
2018-10-06 12:58:19 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
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
|
2018-10-06 12:58:19 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
# else return KC_ESC
|
|
|
|
self.keys_pressed.add(Keycodes.Common.KC_ESCAPE)
|
|
|
|
return self
|
2018-10-06 12:58:19 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
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
|
|
|
|
|
2018-10-19 09:58:17 +02:00
|
|
|
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
|
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
def _begin_leader_mode(self):
|
2018-10-19 10:49:37 +02:00
|
|
|
if self.config.leader_mode % 2 == 0:
|
2018-10-16 13:04:39 +02:00
|
|
|
self.keys_pressed.discard(Keycodes.KMK.KC_LEAD)
|
|
|
|
# All leader modes are one number higher when activating
|
2018-10-19 10:49:37 +02:00
|
|
|
self.config.leader_mode += 1
|
|
|
|
|
|
|
|
if self.config.leader_mode == LeaderMode.TIMEOUT_ACTIVE:
|
|
|
|
self.set_timeout(self.config.leader_timeout, self._handle_leader_sequence)
|
2018-10-06 12:58:19 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
return self
|
|
|
|
|
2018-10-19 10:49:37 +02:00
|
|
|
def _handle_leader_sequence(self):
|
|
|
|
lmh = tuple(self.leader_mode_history)
|
|
|
|
|
|
|
|
if lmh in self.config.leader_dictionary:
|
|
|
|
self.macro_pending = self.config.leader_dictionary[lmh].keydown
|
|
|
|
|
|
|
|
return self._exit_leader_mode()
|
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
def _process_leader_mode(self):
|
|
|
|
keys_pressed = self.keys_pressed
|
2018-10-06 12:58:19 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
if self.leader_last_len and self.leader_mode_history:
|
|
|
|
history_set = set(self.leader_mode_history)
|
2018-10-06 12:58:19 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
keys_pressed = keys_pressed - history_set
|
2018-10-06 12:58:19 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
self.leader_last_len = len(self.keys_pressed)
|
2018-10-06 12:58:19 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
for key in keys_pressed:
|
2018-10-19 10:49:37 +02:00
|
|
|
if (
|
|
|
|
self.config.leader_mode == LeaderMode.ENTER_ACTIVE and
|
|
|
|
key == Keycodes.Common.KC_ENT
|
|
|
|
):
|
|
|
|
self._handle_leader_sequence()
|
2018-10-16 13:04:39 +02:00
|
|
|
break
|
|
|
|
elif key == Keycodes.Common.KC_ESC or key == Keycodes.KMK.KC_GESC:
|
|
|
|
# Clean self and turn leader mode off.
|
|
|
|
self._exit_leader_mode()
|
|
|
|
break
|
|
|
|
elif key == Keycodes.KMK.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)
|
2018-10-08 11:31:30 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
self.hid_pending = False
|
|
|
|
return self
|
2018-10-08 11:31:30 +02:00
|
|
|
|
2018-10-16 13:04:39 +02:00
|
|
|
def _exit_leader_mode(self):
|
|
|
|
self.leader_mode_history.clear()
|
2018-10-19 10:49:37 +02:00
|
|
|
self.config.leader_mode -= 1
|
2018-10-16 13:04:39 +02:00
|
|
|
self.leader_last_len = 0
|
|
|
|
self.keys_pressed.clear()
|
|
|
|
return self
|