Merge pull request #47 from KMKfw/topic-leader-mode-clean

@kdb424's Leader Mode Enter work as a clean diff
This commit is contained in:
Josh Klar 2018-10-08 03:50:10 -07:00 committed by GitHub
commit 64888f1df8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 217 additions and 34 deletions

View File

@ -143,3 +143,10 @@ class UnicodeModes:
LINUX = IBUS = 1 LINUX = IBUS = 1
MACOS = OSX = RALT = 2 MACOS = OSX = RALT = 2
WINC = 3 WINC = 3
class LeaderMode:
Default = 0
Default_Active = 1
Enter = 2
Enter_Active = 3

View File

@ -25,7 +25,6 @@ InitFirmware = namedtuple('InitFirmware', (
'row_pins', 'row_pins',
'col_pins', 'col_pins',
'diode_orientation', 'diode_orientation',
'unicode_mode',
)) ))
KeyUpDown = namedtuple('KeyUpDown', ('type', 'row', 'col')) KeyUpDown = namedtuple('KeyUpDown', ('type', 'row', 'col'))
@ -34,14 +33,13 @@ NewMatrix = namedtuple('NewMatrix', ('type', 'matrix'))
BareEvent = namedtuple('BareEvent', ('type',)) BareEvent = namedtuple('BareEvent', ('type',))
def init_firmware(keymap, row_pins, col_pins, diode_orientation, unicode_mode): def init_firmware(keymap, row_pins, col_pins, diode_orientation):
return InitFirmware( return InitFirmware(
type=INIT_FIRMWARE_EVENT, type=INIT_FIRMWARE_EVENT,
keymap=keymap, keymap=keymap,
row_pins=row_pins, row_pins=row_pins,
col_pins=col_pins, col_pins=col_pins,
diode_orientation=diode_orientation, diode_orientation=diode_orientation,
unicode_mode=unicode_mode,
) )

View File

@ -2,7 +2,7 @@ import logging
import sys import sys
from kmk.common import kmktime from kmk.common import kmktime
from kmk.common.consts import DiodeOrientation, UnicodeModes from kmk.common.consts import DiodeOrientation, LeaderMode, UnicodeModes
from kmk.common.event_defs import (HID_REPORT_EVENT, INIT_FIRMWARE_EVENT, from kmk.common.event_defs import (HID_REPORT_EVENT, INIT_FIRMWARE_EVENT,
KEY_DOWN_EVENT, KEY_UP_EVENT, KEY_DOWN_EVENT, KEY_UP_EVENT,
KEYCODE_DOWN_EVENT, KEYCODE_UP_EVENT, KEYCODE_DOWN_EVENT, KEYCODE_UP_EVENT,
@ -69,28 +69,31 @@ class InternalState:
keys_pressed = set() keys_pressed = set()
pending_keys = set() pending_keys = set()
macro_pending = None macro_pending = None
leader_pending = None
leader_last_len = 0
hid_pending = False hid_pending = False
unicode_mode = UnicodeModes.NOOP
tap_time = 300
keymap = [] keymap = []
row_pins = [] row_pins = []
col_pins = [] col_pins = []
matrix = [] matrix = []
diode_orientation = DiodeOrientation.COLUMNS diode_orientation = DiodeOrientation.COLUMNS
leader_mode_history = []
active_layers = [0] active_layers = [0]
start_time = { start_time = {
'lt': None, 'lt': None,
'tg': None, 'tg': None,
'tt': None, 'tt': None,
} 'lm': None,
tick_time = { 'leader': None,
'lt': None,
'tg': None,
'tt': None,
} }
_oldstates = [] _oldstates = []
def __init__(self, preserve_intermediate_states=False): def __init__(self, preserve_intermediate_states=False):
import kmk_keyboard_user
self.unicode_mode = getattr(kmk_keyboard_user, 'unicode_mode', UnicodeModes.NOOP)
self.tap_time = getattr(kmk_keyboard_user, 'tap_time', 300)
self.leader_mode = getattr(kmk_keyboard_user, 'leader_mode', LeaderMode.Enter)
self.LEADER_DICTIONARY = getattr(kmk_keyboard_user, 'LEADER_DICTIONARY', {})
self.preserve_intermediate_states = preserve_intermediate_states self.preserve_intermediate_states = preserve_intermediate_states
def __enter__(self): def __enter__(self):
@ -105,8 +108,8 @@ class InternalState:
'active_layers': self.active_layers, 'active_layers': self.active_layers,
'unicode_mode': self.unicode_mode, 'unicode_mode': self.unicode_mode,
'tap_time': self.tap_time, 'tap_time': self.tap_time,
'leader_mode_history': self.leader_mode_history,
'start_time': self.start_time, 'start_time': self.start_time,
'tick_time': self.tick_time,
} }
if verbose: if verbose:
@ -196,18 +199,19 @@ def kmk_reducer(state=None, action=None, logger=None):
state.matrix = action.matrix state.matrix = action.matrix
state.keys_pressed |= pressed state.keys_pressed |= pressed
state.keys_pressed -= released state.keys_pressed -= released
state.hid_pending = True if state.leader_mode % 2 == 1:
state.hid_pending = False
else:
state.hid_pending = True
return state return state
if action.type == KEYCODE_UP_EVENT: if action.type == KEYCODE_UP_EVENT:
state.keys_pressed.discard(action.keycode) state.keys_pressed.discard(action.keycode)
state.hid_pending = True
return state return state
if action.type == KEYCODE_DOWN_EVENT: if action.type == KEYCODE_DOWN_EVENT:
state.keys_pressed.add(action.keycode) state.keys_pressed.add(action.keycode)
state.hid_pending = True
return state return state
if action.type == INIT_FIRMWARE_EVENT: if action.type == INIT_FIRMWARE_EVENT:
@ -216,7 +220,6 @@ def kmk_reducer(state=None, action=None, logger=None):
row_pins=action.row_pins, row_pins=action.row_pins,
col_pins=action.col_pins, col_pins=action.col_pins,
diode_orientation=action.diode_orientation, diode_orientation=action.diode_orientation,
unicode_mode=action.unicode_mode,
) )
# HID events are non-mutating, used exclusively for listeners to know # HID events are non-mutating, used exclusively for listeners to know
@ -224,7 +227,6 @@ def kmk_reducer(state=None, action=None, logger=None):
# into KEY_UP_EVENT and KEY_DOWN_EVENT, but for now it's nice to separate # into KEY_UP_EVENT and KEY_DOWN_EVENT, but for now it's nice to separate
# this out for debugging's sake. # this out for debugging's sake.
if action.type == HID_REPORT_EVENT: if action.type == HID_REPORT_EVENT:
state.hid_pending = False
return state return state
if action.type == MACRO_COMPLETE_EVENT: if action.type == MACRO_COMPLETE_EVENT:
@ -268,6 +270,8 @@ def process_internal_key_event(state, action_type, changed_key, logger=None):
return unicode_mode(state, action_type, changed_key, logger=logger) return unicode_mode(state, action_type, changed_key, logger=logger)
elif changed_key.code == RawKeycodes.KC_MACRO: elif changed_key.code == RawKeycodes.KC_MACRO:
return macro(state, action_type, changed_key, logger=logger) return macro(state, action_type, changed_key, logger=logger)
elif changed_key.code == Keycodes.KMK.KC_LEAD.code:
return leader(state)
else: else:
return state return state
@ -410,3 +414,12 @@ def macro(state, action_type, changed_key, logger):
return state return state
return state return state
def leader(state):
if state.leader_mode % 2 == 0:
state.keys_pressed.discard(Keycodes.KMK.KC_LEAD)
# All leader modes are one number higher when activating
state.leader_mode += 1
return state

86
kmk/common/leader_mode.py Normal file
View File

@ -0,0 +1,86 @@
import logging
from kmk.common.keycodes import Keycodes
class LeaderHelper:
"""
Acts as a hid to absorb keypress, and perform macros when a timer
or enter key is pressed depending on the mode set.
"""
def __init__(self, store, log_level=logging.NOTSET):
self.logger = logging.getLogger(__name__)
self.logger.setLevel(log_level)
self.store = store
self.store.subscribe(
lambda state, action: self._subscription(state, action),
)
def _subscription(self, state, action):
"""
Subscribes to the state machine, and dispatches actions based
based on incoming keypresses, or when a timer runs out depending
on the mode.
:param state:
:param action:
:return state:
"""
if state.leader_mode % 2 == 1:
keys_pressed = state.keys_pressed
if state.leader_last_len and state.leader_mode_history:
history_set = set(state.leader_mode_history)
keys_pressed = keys_pressed - history_set
state.leader_last_len = len(state.keys_pressed)
for key in keys_pressed:
if key == Keycodes.Common.KC_ENT:
# Process the action and remove the extra KC.ENT that was added to get here
state = process(state)
return clean_exit(state)
elif key == Keycodes.Common.KC_ESC:
# Clean state and turn leader mode off.
return clean_exit(state)
elif key == Keycodes.KMK.KC_LEAD:
return state
else:
# Add key if not needing to escape
# This needs replaced later with a proper debounce
state.leader_mode_history.append(key)
return state
return state
def clean_exit(state):
"""
Cleans up the state and hands the HID control back.
:param state:
:return state:
"""
state.leader_mode_history = []
state.leader_mode -= 1
state.leader_last_len = 0
state.keys_pressed.clear()
return state
def process(state):
"""
Checks if there are iny matching sequences of keys, and
performs the macro specified by the user.
:param state:
:param leader_dictionary:
:return state:
"""
lmh = tuple(state.leader_mode_history)
if lmh in state.LEADER_DICTIONARY:
state.macro_pending = state.LEADER_DICTIONARY[lmh].keydown
state.keys_pressed.clear()
return state

View File

@ -1,19 +1,28 @@
import sys import sys
from logging import DEBUG
from kmk.common.consts import UnicodeModes import gc
from kmk.firmware import Firmware from kmk.firmware import Firmware
from kmk.micropython.matrix import MatrixScanner from kmk.micropython.matrix import MatrixScanner
from kmk.micropython.pyb_hid import HIDHelper from kmk.micropython.pyb_hid import HIDHelper
def main(): def main():
from kmk_keyboard_user import cols, diode_orientation, keymap, rows import kmk_keyboard_user
cols = getattr(kmk_keyboard_user, 'cols')
diode_orientation = getattr(kmk_keyboard_user, 'diode_orientation')
keymap = getattr(kmk_keyboard_user, 'keymap')
rows = getattr(kmk_keyboard_user, 'rows')
try: DEBUG_ENABLE = getattr(kmk_keyboard_user, 'DEBUG_ENABLE', False)
from kmk_keyboard_user import unicode_mode
except Exception: if DEBUG_ENABLE:
unicode_mode = UnicodeModes.NOOP from logging import DEBUG
else:
from logging import ERROR as DEBUG
# This will run out of ram at this point unless you manually GC
gc.collect()
try: try:
firmware = Firmware( firmware = Firmware(
@ -21,11 +30,12 @@ def main():
row_pins=rows, row_pins=rows,
col_pins=cols, col_pins=cols,
diode_orientation=diode_orientation, diode_orientation=diode_orientation,
unicode_mode=unicode_mode,
hid=HIDHelper, hid=HIDHelper,
log_level=DEBUG, log_level=DEBUG,
matrix_scanner=MatrixScanner, matrix_scanner=MatrixScanner,
) )
# This will run out of ram at this point unless you manually GC
gc.collect()
firmware.go() firmware.go()
except Exception as e: except Exception as e:

View File

@ -2,14 +2,16 @@ import logging
from kmk.common.event_defs import init_firmware from kmk.common.event_defs import init_firmware
from kmk.common.internal_state import Store, kmk_reducer from kmk.common.internal_state import Store, kmk_reducer
from kmk.common.leader_mode import LeaderHelper
class Firmware: class Firmware:
def __init__( def __init__(
self, keymap, row_pins, col_pins, self, keymap, row_pins, col_pins,
diode_orientation, unicode_mode=None, diode_orientation, unicode_mode=None,
hid=None, log_level=logging.NOTSET, hid=None,
matrix_scanner=None, log_level=logging.NOTSET,
matrix_scanner=None,
): ):
assert matrix_scanner is not None assert matrix_scanner is not None
self.matrix_scanner = matrix_scanner self.matrix_scanner = matrix_scanner
@ -32,12 +34,13 @@ class Firmware:
"Board will run in debug mode", "Board will run in debug mode",
) )
self.leader_helper = LeaderHelper(store=self.store, log_level=log_level)
self.store.dispatch(init_firmware( self.store.dispatch(init_firmware(
keymap=keymap, keymap=keymap,
row_pins=row_pins, row_pins=row_pins,
col_pins=col_pins, col_pins=col_pins,
diode_orientation=diode_orientation, diode_orientation=diode_orientation,
unicode_mode=unicode_mode,
)) ))
def _subscription(self, state, action): def _subscription(self, state, action):

View File

@ -34,6 +34,6 @@ class HIDHelper(AbstractHidHelper):
# It'd be real awesome if pyb.USB_HID.send/recv would support # It'd be real awesome if pyb.USB_HID.send/recv would support
# uselect.poll or uselect.select to more safely determine when # uselect.poll or uselect.select to more safely determine when
# it is safe to write to the host again... # it is safe to write to the host again...
delay(5) delay(1)
return self return self

View File

@ -1,4 +1,4 @@
from kmk.common.consts import DiodeOrientation, UnicodeModes from kmk.common.consts import DiodeOrientation, LeaderMode, UnicodeModes
from kmk.common.keycodes import KC from kmk.common.keycodes import KC
from kmk.common.macros.unicode import unicode_sequence from kmk.common.macros.unicode import unicode_sequence
from kmk.common.pins import Pin as P from kmk.common.pins import Pin as P
@ -9,9 +9,14 @@ rows = (P.Y1, P.Y2, P.Y3, P.Y4)
diode_orientation = DiodeOrientation.COLUMNS diode_orientation = DiodeOrientation.COLUMNS
unicode_mode = UnicodeModes.LINUX
tap_time = 180
# ------------------User level config variables ---------------------------------------
unicode_mode = UnicodeModes.LINUX
tap_time = 150
leader_timeout = 2000
DEBUG_ENABLE = True
# -------------------------------Macros -----------------------------------------------
FLIP = unicode_sequence([ FLIP = unicode_sequence([
"28", "28",
"30ce", "30ce",
@ -26,13 +31,74 @@ FLIP = unicode_sequence([
"253b", "253b",
]) ])
# ---------------------- Leader Key Macros --------------------------------------------
LEADER_DICTIONARY = {
(KC.F, KC.L, KC.I, KC.P):
unicode_sequence([
"28",
"30ce",
"ca0",
"75ca",
"ca0",
"29",
"30ce",
"5f61",
"253b",
"2501",
"253b",
]),
(KC.C, KC.H, KC.E, KC.E, KC.R):
unicode_sequence([
'002B',
'FF61',
'003A',
'002E',
'FF9F',
'30FD',
'0028',
'00B4',
'2200',
'FF61',
'0029',
'FF89',
'FF9F',
'002E',
'003A',
'FF61',
'002B',
'FF9F',
'FF9F',
'002B',
'FF61',
'003A',
'002E',
'FF9F',
'30FD',
'0028',
'002A',
'00B4',
'2200',
'0029',
'FF89',
'FF9F',
'002E',
'003A',
'FF61',
'002B',
'FF9F',
]),
}
# ---------------------- Keymap ---------------------------------------------------------
keymap = [ keymap = [
[ [
# Default # 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.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.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.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.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], [KC.LCTRL, KC.LGUI, KC.LALT, KC.LEAD, 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 # Gaming