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-09-04 00:21:34 +02:00
|
|
|
from kmk.common.consts import DiodeOrientation
|
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,
|
|
|
|
NEW_MATRIX_EVENT)
|
|
|
|
from kmk.common.internal_keycodes import process_internal_key_event
|
|
|
|
from kmk.common.keycodes import FIRST_KMK_INTERNAL_KEYCODE, Keycodes
|
2018-09-03 22:50:12 +02:00
|
|
|
|
|
|
|
|
|
|
|
class ReduxStore:
|
|
|
|
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
|
|
|
|
|
|
|
|
self.logger.debug('Dispatching action: Type {} >> {}'.format(action['type'], action))
|
|
|
|
self.state = self.reducer(self.state, action, logger=self.logger)
|
|
|
|
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')
|
|
|
|
print(sys.print_exception(e), file=sys.stderr)
|
|
|
|
|
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:
|
|
|
|
modifiers_pressed = frozenset()
|
|
|
|
keys_pressed = frozenset()
|
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-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,
|
|
|
|
'modifiers_pressed': self.modifiers_pressed,
|
2018-09-22 02:22:03 +02:00
|
|
|
'active_layers': self.active_layers,
|
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-09-23 06:49:58 +02:00
|
|
|
if action['type'] == NEW_MATRIX_EVENT:
|
|
|
|
return state.update(
|
|
|
|
matrix=action['matrix'],
|
|
|
|
)
|
|
|
|
|
2018-09-03 22:50:12 +02:00
|
|
|
if action['type'] == KEY_UP_EVENT:
|
2018-09-23 06:49:58 +02:00
|
|
|
row = action['row']
|
|
|
|
col = action['col']
|
|
|
|
|
|
|
|
changed_key = find_key_in_map(state, row, col)
|
|
|
|
|
|
|
|
logger.debug('Detected change to key: {}'.format(changed_key))
|
|
|
|
|
|
|
|
if not changed_key:
|
|
|
|
return state
|
|
|
|
|
2018-09-22 09:46:14 +02:00
|
|
|
newstate = state.update(
|
2018-09-04 00:21:34 +02:00
|
|
|
keys_pressed=frozenset(
|
2018-09-23 06:49:58 +02:00
|
|
|
key for key in state.keys_pressed if key != changed_key
|
2018-09-04 00:21:34 +02:00
|
|
|
),
|
|
|
|
)
|
2018-09-03 22:50:12 +02:00
|
|
|
|
2018-09-23 06:49:58 +02:00
|
|
|
if changed_key.code >= FIRST_KMK_INTERNAL_KEYCODE:
|
|
|
|
return process_internal_key_event(newstate, action, changed_key, logger=logger)
|
2018-09-22 08:44:30 +02:00
|
|
|
|
|
|
|
return newstate
|
|
|
|
|
2018-09-03 22:50:12 +02:00
|
|
|
if action['type'] == KEY_DOWN_EVENT:
|
2018-09-23 06:49:58 +02:00
|
|
|
row = action['row']
|
|
|
|
col = action['col']
|
|
|
|
|
|
|
|
changed_key = find_key_in_map(state, row, col)
|
|
|
|
|
|
|
|
logger.debug('Detected change to key: {}'.format(changed_key))
|
|
|
|
|
|
|
|
if not changed_key:
|
|
|
|
return state
|
|
|
|
|
2018-09-22 09:46:14 +02:00
|
|
|
newstate = state.update(
|
2018-09-04 00:21:34 +02:00
|
|
|
keys_pressed=(
|
2018-09-23 06:49:58 +02:00
|
|
|
state.keys_pressed | {changed_key}
|
2018-09-04 00:21:34 +02:00
|
|
|
),
|
|
|
|
)
|
|
|
|
|
2018-09-23 06:49:58 +02:00
|
|
|
if changed_key.code >= FIRST_KMK_INTERNAL_KEYCODE:
|
|
|
|
return process_internal_key_event(newstate, action, changed_key, logger=logger)
|
2018-09-22 08:44:30 +02:00
|
|
|
|
|
|
|
return newstate
|
|
|
|
|
2018-09-04 00:21:34 +02:00
|
|
|
if action['type'] == INIT_FIRMWARE_EVENT:
|
2018-09-22 09:46:14 +02:00
|
|
|
return state.update(
|
2018-09-04 00:21:34 +02:00
|
|
|
keymap=action['keymap'],
|
|
|
|
row_pins=action['row_pins'],
|
|
|
|
col_pins=action['col_pins'],
|
|
|
|
diode_orientation=action['diode_orientation'],
|
|
|
|
matrix=[
|
|
|
|
[False for c in action['col_pins']]
|
|
|
|
for r in action['row_pins']
|
|
|
|
],
|
|
|
|
)
|
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.
|
|
|
|
if action['type'] == HID_REPORT_EVENT:
|
|
|
|
return state
|
|
|
|
|
|
|
|
# On unhandled events, log and do not mutate state
|
|
|
|
logger.warning('Unhandled event! Returning state unmodified.')
|
|
|
|
return state
|