Checkpoint alpha: Reflow macros and keycodes into a consistent structure. Most internal state functionality largely untouched (just moved)
This commit is contained in:
		| @@ -202,16 +202,6 @@ class Firmware: | |||||||
|             if old_timeouts_len != new_timeouts_len: |             if old_timeouts_len != new_timeouts_len: | ||||||
|                 state_changed = True |                 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: |             if self.debug_enabled and state_changed: | ||||||
|                 print('New State: {}'.format(self._state._to_dict())) |                 print('New State: {}'.format(self._state._to_dict())) | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								kmk/handlers/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								kmk/handlers/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										108
									
								
								kmk/handlers/layers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								kmk/handlers/layers.py
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										40
									
								
								kmk/handlers/sequences.py
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										89
									
								
								kmk/handlers/stock.py
									
									
									
									
									
										Normal 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) | ||||||
| @@ -1,19 +1,13 @@ | |||||||
| from kmk.consts import LeaderMode | from kmk.consts import LeaderMode | ||||||
| from kmk.keycodes import (FIRST_KMK_INTERNAL_KEYCODE, Keycodes, RawKeycodes, | from kmk.keycodes import KC | ||||||
|                           TapDanceKeycode) | from kmk.kmktime import ticks_ms | ||||||
| from kmk.kmktime import sleep_ms, ticks_diff, ticks_ms | from kmk.types import TapDanceKeyMeta | ||||||
| from kmk.util import intify_coordinate | 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: | class InternalState: | ||||||
|     keys_pressed = set() |     keys_pressed = set() | ||||||
|     coord_keys_pressed = {} |     coord_keys_pressed = {} | ||||||
|     macros_pending = [] |  | ||||||
|     leader_pending = None |     leader_pending = None | ||||||
|     leader_last_len = 0 |     leader_last_len = 0 | ||||||
|     hid_pending = False |     hid_pending = False | ||||||
| @@ -34,22 +28,6 @@ class InternalState: | |||||||
|  |  | ||||||
|     def __init__(self, config): |     def __init__(self, config): | ||||||
|         self.config = 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): |     def __repr__(self): | ||||||
|         return 'InternalState({})'.format(self._to_dict()) |         return 'InternalState({})'.format(self._to_dict()) | ||||||
| @@ -74,7 +52,7 @@ class InternalState: | |||||||
|         for layer in self.reversed_active_layers: |         for layer in self.reversed_active_layers: | ||||||
|             layer_key = self.config.keymap[layer][row][col] |             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 |                 continue | ||||||
|  |  | ||||||
|             if self.config.debug_enabled: |             if self.config.debug_enabled: | ||||||
| @@ -122,16 +100,16 @@ class InternalState: | |||||||
|             print('No key accessible for col, row: {}, {}'.format(row, col)) |             print('No key accessible for col, row: {}, {}'.format(row, col)) | ||||||
|             return self |             return self | ||||||
|  |  | ||||||
|         if self.tapping and not isinstance(kc_changed, TapDanceKeycode): |         return self.process_key(kc_changed, is_pressed, int_coord, (row, col)) | ||||||
|             self._process_tap_dance(kc_changed, is_pressed) |  | ||||||
|  |     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: |         else: | ||||||
|             if is_pressed: |             if is_pressed: | ||||||
|                 self.coord_keys_pressed[int_coord] = kc_changed |                 key.on_press(self, coord_int, coord_raw) | ||||||
|                 self.add_key(kc_changed) |  | ||||||
|             else: |             else: | ||||||
|                 self.remove_key(kc_changed) |                 key.on_release(self, coord_int, coord_raw) | ||||||
|                 self.keys_pressed.discard(self.coord_keys_pressed.get(int_coord, None)) |  | ||||||
|                 self.coord_keys_pressed[int_coord] = None |  | ||||||
|  |  | ||||||
|             if self.config.leader_mode % 2 == 1: |             if self.config.leader_mode % 2 == 1: | ||||||
|                 self._process_leader_mode() |                 self._process_leader_mode() | ||||||
| @@ -140,27 +118,11 @@ class InternalState: | |||||||
|  |  | ||||||
|     def remove_key(self, keycode): |     def remove_key(self, keycode): | ||||||
|         self.keys_pressed.discard(keycode) |         self.keys_pressed.discard(keycode) | ||||||
|  |         return self.process_key(keycode, False) | ||||||
|         if keycode.code >= FIRST_KMK_INTERNAL_KEYCODE: |  | ||||||
|             self._process_internal_key_event(keycode, False) |  | ||||||
|         else: |  | ||||||
|             self.hid_pending = True |  | ||||||
|  |  | ||||||
|         return self |  | ||||||
|  |  | ||||||
|     def add_key(self, keycode): |     def add_key(self, keycode): | ||||||
|         # TODO Make this itself a macro keycode with a keyup handler |         self.keys_pressed.add(keycode) | ||||||
|         #      rather than handling this inline here. Gross. |         return self.process_key(keycode, True) | ||||||
|         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 |  | ||||||
|  |  | ||||||
|     def tap_key(self, keycode): |     def tap_key(self, keycode): | ||||||
|         self.add_key(keycode) |         self.add_key(keycode) | ||||||
| @@ -175,13 +137,6 @@ class InternalState: | |||||||
|         self.hid_pending = False |         self.hid_pending = False | ||||||
|         return self |         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): |     def _process_internal_key_event(self, changed_key, is_pressed): | ||||||
|         # Since the key objects can be chained into new objects |         # Since the key objects can be chained into new objects | ||||||
|         # with, for example, no_press set, always check against |         # with, for example, no_press set, always check against | ||||||
| @@ -192,164 +147,9 @@ class InternalState: | |||||||
|             changed_key, is_pressed, |             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): |     def _process_tap_dance(self, changed_key, is_pressed): | ||||||
|         if 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 |                 # If we get here, changed_key is not a TapDanceKeycode and thus | ||||||
|                 # the user kept typing elsewhere (presumably).  End ALL of the |                 # the user kept typing elsewhere (presumably).  End ALL of the | ||||||
|                 # currently outstanding tap dance runs. |                 # currently outstanding tap dance runs. | ||||||
| @@ -408,7 +208,7 @@ class InternalState: | |||||||
|  |  | ||||||
|     def _begin_leader_mode(self): |     def _begin_leader_mode(self): | ||||||
|         if self.config.leader_mode % 2 == 0: |         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 |             # All leader modes are one number higher when activating | ||||||
|             self.config.leader_mode += 1 |             self.config.leader_mode += 1 | ||||||
|  |  | ||||||
| @@ -421,7 +221,7 @@ class InternalState: | |||||||
|         lmh = tuple(self.leader_mode_history) |         lmh = tuple(self.leader_mode_history) | ||||||
|  |  | ||||||
|         if lmh in self.config.leader_dictionary: |         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() |         return self._exit_leader_mode() | ||||||
|  |  | ||||||
| @@ -438,15 +238,15 @@ class InternalState: | |||||||
|         for key in keys_pressed: |         for key in keys_pressed: | ||||||
|             if ( |             if ( | ||||||
|                 self.config.leader_mode == LeaderMode.ENTER_ACTIVE and |                 self.config.leader_mode == LeaderMode.ENTER_ACTIVE and | ||||||
|                 key == Keycodes.Common.KC_ENT |                 key == KC.ENT | ||||||
|             ): |             ): | ||||||
|                 self._handle_leader_sequence() |                 self._handle_leader_sequence() | ||||||
|                 break |                 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. |                 # Clean self and turn leader mode off. | ||||||
|                 self._exit_leader_mode() |                 self._exit_leader_mode() | ||||||
|                 break |                 break | ||||||
|             elif key == Keycodes.KMK.KC_LEAD: |             elif key == KC.LEAD: | ||||||
|                 break |                 break | ||||||
|             else: |             else: | ||||||
|                 # Add key if not needing to escape |                 # Add key if not needing to escape | ||||||
|   | |||||||
							
								
								
									
										1025
									
								
								kmk/keycodes.py
									
									
									
									
									
								
							
							
						
						
									
										1025
									
								
								kmk/keycodes.py
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -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 |  | ||||||
| @@ -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) |  | ||||||
| @@ -1,16 +1,15 @@ | |||||||
| from kmk.consts import UnicodeMode | from kmk.consts import UnicodeMode | ||||||
| from kmk.keycodes import (Common, Macro, Modifiers, | from kmk.keycodes import ALL_KEYS, KC, Macro | ||||||
|                           generate_codepoint_keysym_seq) |  | ||||||
| from kmk.macros.simple import simple_key_sequence | from kmk.macros.simple import simple_key_sequence | ||||||
| from kmk.types import AttrDict | from kmk.types import AttrDict | ||||||
| from kmk.util import get_wide_ordinal | from kmk.util import get_wide_ordinal | ||||||
|  |  | ||||||
| IBUS_KEY_COMBO = Modifiers.KC_LCTRL(Modifiers.KC_LSHIFT(Common.KC_U)) | IBUS_KEY_COMBO = KC.LCTRL(KC.LSHIFT(KC.U)) | ||||||
| RALT_KEY = Modifiers.KC_RALT | RALT_KEY = KC.RALT | ||||||
| U_KEY = Common.KC_U | U_KEY = KC.U | ||||||
| ENTER_KEY = Common.KC_ENTER | ENTER_KEY = KC.ENTER | ||||||
| RALT_DOWN_NO_RELEASE = Modifiers.KC_RALT(no_release=True) | RALT_DOWN_NO_RELEASE = KC.RALT(no_release=True) | ||||||
| RALT_UP_NO_PRESS = Modifiers.KC_RALT(no_press=True) | RALT_UP_NO_PRESS = KC.RALT(no_press=True) | ||||||
|  |  | ||||||
|  |  | ||||||
| def compile_unicode_string_sequences(string_table): | 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): | def unicode_codepoint_sequence(codepoints): | ||||||
|     kc_seqs = ( |     kc_seqs = ( | ||||||
|         generate_codepoint_keysym_seq(codepoint) |         generate_codepoint_keysym_seq(codepoint) | ||||||
|   | |||||||
| @@ -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 |  | ||||||
							
								
								
									
										26
									
								
								kmk/types.py
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								kmk/types.py
									
									
									
									
									
								
							| @@ -24,3 +24,29 @@ class Anything: | |||||||
| class Passthrough: | class Passthrough: | ||||||
|     def __getattr__(self, attr): |     def __getattr__(self, attr): | ||||||
|         return Anything(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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user