1 Commits

Author SHA1 Message Date
xs5871
20bcfcdbb9 Add boilerplate method for board configuration at boot time 2023-03-06 21:08:06 +00:00
31 changed files with 365 additions and 790 deletions

View File

@@ -1,27 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]"
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
* Setup and configuration of the affected parts of the firmware (avoid copy-pasting the entire configuration if possible)
* Setup and configuration of peripherals
* Input: keys pressed, ...
(Choose which are applicable.)
**Expected behavior**
A clear and concise description of what you expected to happen.
**Debug output**
If applicable, add debug output from the serial console to help explain your problem.
**Additional context**
Add any other context about the problem here.

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Enhancement]"
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -9,15 +9,10 @@ KMK is a feature-rich and beginner-friendly firmware for computer keyboards
written and configured in written and configured in
[CircuitPython](https://github.com/adafruit/circuitpython). [CircuitPython](https://github.com/adafruit/circuitpython).
## Support
For asynchronous support and chatter about KMK, [join our Zulip For asynchronous support and chatter about KMK, [join our Zulip
community](https://kmkfw.zulipchat.com)! community](https://kmkfw.zulipchat.com)! In particular, swing by the Zulip chat
*before* opening a GitHub Issue about configuration, documentation, etc.
If you ask for help in chat or open a bug report, if possible concerns.
make sure your copy of KMK is up-to-date.
In particular, swing by the Zulip chat *before* opening a GitHub Issue about
configuration, documentation, etc. concerns.
> The former Matrix and Discord rooms once linked to in this README are no > The former Matrix and Discord rooms once linked to in this README are no
> longer officially supported, please do not use them! > longer officially supported, please do not use them!

View File

@@ -20,6 +20,8 @@ encoder_handler.pins = ((board.D1, board.D2, board.D0),)
encoder_handler.map = (((KC.VOLD, KC.VOLU, KC.MUTE),),) encoder_handler.map = (((KC.VOLD, KC.VOLU, KC.MUTE),),)
knob.modules.append(encoder_handler) knob.modules.append(encoder_handler)
print('ANAVI Knob 1')
rgb_ext = RGB( rgb_ext = RGB(
pixel_pin=board.NEOPIXEL, pixel_pin=board.NEOPIXEL,
num_pixels=1, num_pixels=1,

View File

@@ -1,98 +0,0 @@
## Combo Layers
Combo Layers is when you hold down 2 or more KC.MO() or KC.LM() keys at a time, and it goes to a defined layer.
By default combo layers is not activated. You can activate combo layers by adding this to your `main.py` file.
The combolayers NEEDS to be above the `keyboard.modules.append(Layers(combolayers))`
```python
combo_layers = {
(1, 2): 3,
}
keyboard.modules.append(Layers(combo_layers))
```
In the above code, when layer 1 and 2 are held, layer 3 will activate. If you release 1 or 2 it will go to whatever key is still being held, if both are released it goes to the default (0) layer.
You should also notice that if you already have the layers Module activated, you can just add combolayers into `(Layers())`
You can add more, and even add more than 2 layers at a time.
```python
combo_layers = {
(1, 2): 3,
(1, 2, 3): 4,
}
```
## Limitations
There can only be one combo layer active at a time and for overlapping matches
the first matching combo in `combo_layers` takes precedence.
Example:
```python
layers = Layers()
layers.combo_layers = {
(1, 2, 3): 8,
(1, 2): 9,
}
keyboard.modules.append(Layers(combo_layers))
```
* If you activate layers 1 then 2, your active layer will be layer number 9.
* If you activate layers 1 then 2, then 3, your active layer will be layer
number 3 (because the layer combo `(1,2)` has been activated, but layer 3
stacks on top).
* deactivate 1: you're on layer 3
* deactivate 2: you're on layer 3
* deactivate 3: you're on layer 8
* If you activate layers 3 then 1, then 2, your active layer will be layer
number 8. Deativate layer
* deactivate any of 1/2/3: you're on layer 0
## Fully Working Example code
Below is an example of a fully working keypad that uses combo layers.
```python
print("Starting")
import board
from kmk.kmk_keyboard import KMKKeyboard
from kmk.keys import KC
combo_layers = {
(1, 2): 3,
keyboard.modules.append(Layers(combo_layers))
keyboard = KMKKeyboard()
keyboard.keymap = [
[ #Default
KC.A, KC.B KC.C KC.D,
KC.E, KC.F KC.G KC.H,
KC.MO(1), KC.J, KC.K, KC.MO(2),
],
[ #Layer 1
KC.N1, KC.N2, KC.N3, KC.N4,
KC.N5, KC.N6, KC.N7, KC.8,
KC.MO(1), KC.N9, KC.N0, KC.MO(2),
],
[ #Layer 2
KC.EXLM, KC.AT, KC.HASH, KC.DLR,
KC.PERC, KC.CIRC, KC.AMPR, KC.ASTR,
KC.MO(1), KC.LPRN, KC.RPRN, KC.MO(2),
],
[ #Layer 3
KC.F1, KC.F2, KC.F3, KC.F4,
KC.F5, KC.F6, KC.F7, KC.F8,
KC.MO(1) KC.F9, KC.F10, KC.MO(2)
]
]
if __name__ == '__main__':
keyboard.go()
```

View File

@@ -33,11 +33,6 @@ Some helpful guidelines to keep in mind as you design your layers:
- Only reference higher-numbered layers from a given layer - Only reference higher-numbered layers from a given layer
- Leave keys as `KC.TRNS` in higher layers when they would overlap with a layer-switch - Leave keys as `KC.TRNS` in higher layers when they would overlap with a layer-switch
## Using Combo Layers
Combo Layers allow you to activate a corresponding layer based on the activation of 2 or more other layers.
The advantage of using Combo layers is that when you release one of the layer keys, it stays on whatever layer is still being held.
See [combo layers documentation](combolayers.md) for more information on it's function and to see examples.
### Using Multiple Base Layers ### Using Multiple Base Layers
In some cases, you may want to have more than one base layer (for instance you want to use In some cases, you may want to have more than one base layer (for instance you want to use
both QWERTY and Dvorak layouts, or you have a custom gamepad that can switch between both QWERTY and Dvorak layouts, or you have a custom gamepad that can switch between
@@ -45,7 +40,6 @@ different games). In this case, best practice is to have these layers be the low
defined first in your keymap. These layers are mutually-exclusive, so treat changing default defined first in your keymap. These layers are mutually-exclusive, so treat changing default
layers with `KC.DF()` the same way that you would treat using `KC.TO()` layers with `KC.DF()` the same way that you would treat using `KC.TO()`
## Example Code ## Example Code
For our example, let's take a simple 3x3 macropad with two layers as follows: For our example, let's take a simple 3x3 macropad with two layers as follows:

View File

@@ -7,6 +7,3 @@ If you ask for help in chat or open a bug report, if possible
make sure your copy of KMK is up-to-date. make sure your copy of KMK is up-to-date.
In particular, swing by the Zulip chat *before* opening a GitHub Issue about In particular, swing by the Zulip chat *before* opening a GitHub Issue about
configuration, documentation, etc. concerns. configuration, documentation, etc. concerns.
> The former Matrix and Discord rooms once linked to in this README are no
> longer officially supported, please do not use them!

60
kmk/bootcfg.py Normal file
View File

@@ -0,0 +1,60 @@
import digitalio
import microcontroller
def bootcfg(
sense,
source=None,
no_cdc=True,
no_hid=False,
no_midi=True,
no_storage=True,
usb_id=None,
):
if isinstance(sense, microcontroller.Pin):
sense = digitalio.DigitalInOut(sense)
sense.direction = digitalio.Direction.INPUT
sense.pull = digitalio.Pull.UP
if isinstance(source, microcontroller.Pin):
source = digitalio.DigitalInOut(source)
source.direction = digitalio.Direction.OUTPUT
source.value = False
else:
return False
# sense pulled low -> skip boot configuration
if not sense.value:
return False
if no_cdc:
import usb_cdc
usb_cdc.disable()
if no_hid:
import usb_hid
usb_hid.disable()
if no_midi:
import usb_midi
usb_midi.disable()
if isinstance(usb_id, tuple):
import supervisor
if hasattr(supervisor, 'set_usb_identification'):
supervisor.set_usb_identification(*usb_id)
# The no_storage entry is intentionally evaluated last to ensure the drive
# is mountable and rescueable, in case any of the previous code throws an
# exception.
if no_storage:
import storage
storage.disable_usb_drive()
return True

View File

@@ -49,6 +49,3 @@ class Extension:
def on_powersave_disable(self, keyboard): def on_powersave_disable(self, keyboard):
raise NotImplementedError raise NotImplementedError
def deinit(self, keyboard):
pass

View File

@@ -90,10 +90,10 @@ class RGB(Extension):
self, self,
pixel_pin, pixel_pin,
num_pixels=0, num_pixels=0,
rgb_order=(1, 0, 2), # GRB WS2812
val_limit=255, val_limit=255,
hue_default=0, hue_default=0,
sat_default=255, sat_default=255,
rgb_order=(1, 0, 2), # GRB WS2812
val_default=255, val_default=255,
hue_step=4, hue_step=4,
sat_step=13, sat_step=13,
@@ -109,9 +109,32 @@ class RGB(Extension):
pixels=None, pixels=None,
refresh_rate=60, refresh_rate=60,
): ):
self.pixel_pin = pixel_pin if pixels is None:
import neopixel
pixels = neopixel.NeoPixel(
pixel_pin,
num_pixels,
pixel_order=rgb_order,
auto_write=not disable_auto_write,
)
self.pixels = pixels
self.num_pixels = num_pixels self.num_pixels = num_pixels
self.rgb_order = rgb_order
# PixelBuffer are already iterable, can't do the usual `try: iter(...)`
if issubclass(self.pixels.__class__, PixelBuf):
self.pixels = (self.pixels,)
if self.num_pixels == 0:
for pixels in self.pixels:
self.num_pixels += len(pixels)
if debug.enabled:
for n, pixels in enumerate(self.pixels):
debug(f'pixels[{n}] = {pixels.__class__}[{len(pixels)}]')
self.rgbw = bool(len(rgb_order) == 4)
self.hue_step = hue_step self.hue_step = hue_step
self.sat_step = sat_step self.sat_step = sat_step
self.val_step = val_step self.val_step = val_step
@@ -130,11 +153,8 @@ class RGB(Extension):
self.reverse_animation = reverse_animation self.reverse_animation = reverse_animation
self.user_animation = user_animation self.user_animation = user_animation
self.disable_auto_write = disable_auto_write self.disable_auto_write = disable_auto_write
self.pixels = pixels
self.refresh_rate = refresh_rate self.refresh_rate = refresh_rate
self.rgbw = bool(len(rgb_order) == 4)
self._substep = 0 self._substep = 0
make_key( make_key(
@@ -207,28 +227,6 @@ class RGB(Extension):
return return
def during_bootup(self, sandbox): def during_bootup(self, sandbox):
if self.pixels is None:
import neopixel
self.pixels = neopixel.NeoPixel(
self.pixel_pin,
self.num_pixels,
pixel_order=self.rgb_order,
auto_write=not self.disable_auto_write,
)
# PixelBuffer are already iterable, can't do the usual `try: iter(...)`
if issubclass(self.pixels.__class__, PixelBuf):
self.pixels = (self.pixels,)
if self.num_pixels == 0:
for pixels in self.pixels:
self.num_pixels += len(pixels)
if debug.enabled:
for n, pixels in enumerate(self.pixels):
debug(f'pixels[{n}] = {pixels.__class__}[{len(pixels)}]')
self._timer = PeriodicTimer(1000 // self.refresh_rate) self._timer = PeriodicTimer(1000 // self.refresh_rate)
def before_matrix_scan(self, sandbox): def before_matrix_scan(self, sandbox):
@@ -249,10 +247,6 @@ class RGB(Extension):
def on_powersave_disable(self, sandbox): def on_powersave_disable(self, sandbox):
self._do_update() self._do_update()
def deinit(self, sandbox):
for pixel in self.pixels:
pixel.deinit()
def set_hsv(self, hue, sat, val, index): def set_hsv(self, hue, sat, val, index):
''' '''
Takes HSV values and displays it on a single LED/Neopixel Takes HSV values and displays it on a single LED/Neopixel

View File

@@ -475,9 +475,7 @@ class KeyAttrDict:
break break
if not maybe_key: if not maybe_key:
if debug.enabled: raise ValueError(f'Invalid key: {name}')
debug(f'Invalid key: {name}')
return KC.NO
if debug.enabled: if debug.enabled:
debug(f'{name}: {maybe_key}') debug(f'{name}: {maybe_key}')

View File

@@ -16,20 +16,13 @@ from kmk.modules import Module
from kmk.scanners.keypad import MatrixScanner from kmk.scanners.keypad import MatrixScanner
from kmk.utils import Debug from kmk.utils import Debug
debug = Debug('kmk.keyboard') debug = Debug(__name__)
KeyBufferFrame = namedtuple( KeyBufferFrame = namedtuple(
'KeyBufferFrame', ('key', 'is_pressed', 'int_coord', 'index') 'KeyBufferFrame', ('key', 'is_pressed', 'int_coord', 'index')
) )
def debug_error(module, message: str, error: Exception):
if debug.enabled:
debug(
message, ': ', error.__class__.__name__, ': ', error, name=module.__module__
)
class Sandbox: class Sandbox:
matrix_update = None matrix_update = None
secondary_matrix_update = None secondary_matrix_update = None
@@ -66,6 +59,7 @@ class KMKKeyboard:
matrix_update = None matrix_update = None
secondary_matrix_update = None secondary_matrix_update = None
matrix_update_queue = [] matrix_update_queue = []
state_changed = False
_trigger_powersave_enable = False _trigger_powersave_enable = False
_trigger_powersave_disable = False _trigger_powersave_disable = False
i2c_deinit_count = 0 i2c_deinit_count = 0
@@ -81,24 +75,47 @@ class KMKKeyboard:
_timeouts = {} _timeouts = {}
# on some M4 setups (such as klardotsh/klarank_feather_m4, CircuitPython
# 6.0rc1) this runs out of RAM every cycle and takes down the board. no
# real known fix yet other than turning off debug, but M4s have always been
# tight on RAM so....
def __repr__(self) -> str: def __repr__(self) -> str:
return self.__class__.__name__ return ''.join(
[
'KMKKeyboard(\n',
f' debug_enabled={self.debug_enabled}, ',
f'diode_orientation={self.diode_orientation}, ',
f'matrix={self.matrix},\n',
f' unicode_mode={self.unicode_mode}, ',
f'_hid_helper={self._hid_helper},\n',
f' keys_pressed={self.keys_pressed},\n',
f' axes={self.axes},\n',
f' _coordkeys_pressed={self._coordkeys_pressed},\n',
f' hid_pending={self.hid_pending}, ',
f'active_layers={self.active_layers}, ',
f'_timeouts={self._timeouts}\n',
')',
]
)
def _print_debug_cycle(self, init: bool = False) -> None:
if debug.enabled:
debug(f'coordkeys_pressed={self._coordkeys_pressed}')
debug(f'keys_pressed={self.keys_pressed}')
def _send_hid(self) -> None: def _send_hid(self) -> None:
if not self._hid_send_enabled: if not self._hid_send_enabled:
return return
if debug.enabled: if self.axes and debug.enabled:
if self.keys_pressed: debug(f'axes={self.axes}')
debug('keys_pressed=', self.keys_pressed)
if self.axes:
debug('axes=', self.axes)
self._hid_helper.create_report(self.keys_pressed, self.axes) self._hid_helper.create_report(self.keys_pressed, self.axes)
try: try:
self._hid_helper.send() self._hid_helper.send()
except Exception as err: except KeyError as e:
debug_error(self._hid_helper, 'send', err) if debug.enabled:
debug(f'HidNotFound(HIDReportType={e})')
self.hid_pending = False self.hid_pending = False
@@ -108,32 +125,35 @@ class KMKKeyboard:
def _handle_matrix_report(self, kevent: KeyEvent) -> None: def _handle_matrix_report(self, kevent: KeyEvent) -> None:
if kevent is not None: if kevent is not None:
self._on_matrix_changed(kevent) self._on_matrix_changed(kevent)
self.state_changed = True
def _find_key_in_map(self, int_coord: int) -> Key: def _find_key_in_map(self, int_coord: int) -> Key:
try: try:
idx = self.coord_mapping.index(int_coord) idx = self.coord_mapping.index(int_coord)
except ValueError: except ValueError:
if debug.enabled: if debug.enabled:
debug('no such int_coord: ', int_coord) debug(f'CoordMappingNotFound(ic={int_coord})')
return None return None
for layer in self.active_layers: for layer in self.active_layers:
try: try:
key = self.keymap[layer][idx] layer_key = self.keymap[layer][idx]
except IndexError: except IndexError:
key = None layer_key = None
if debug.enabled: if debug.enabled:
debug('keymap IndexError: idx=', idx, ' layer=', layer) debug(f'KeymapIndexError(idx={idx}, layer={layer})')
if not key or key == KC.TRNS: if not layer_key or layer_key == KC.TRNS:
continue continue
return key return layer_key
def _on_matrix_changed(self, kevent: KeyEvent) -> None: def _on_matrix_changed(self, kevent: KeyEvent) -> None:
int_coord = kevent.key_number int_coord = kevent.key_number
is_pressed = kevent.pressed is_pressed = kevent.pressed
if debug.enabled:
debug(f'MatrixChange(ic={int_coord}, pressed={is_pressed})')
key = None key = None
if not is_pressed: if not is_pressed:
@@ -141,16 +161,18 @@ class KMKKeyboard:
key = self._coordkeys_pressed[int_coord] key = self._coordkeys_pressed[int_coord]
except KeyError: except KeyError:
if debug.enabled: if debug.enabled:
debug('release w/o press: ', int_coord) debug(f'KeyNotPressed(ic={int_coord})')
if key is None: if key is None:
key = self._find_key_in_map(int_coord) key = self._find_key_in_map(int_coord)
if key is None: if key is None:
return if debug.enabled:
debug(f'MatrixUndefinedCoordinate(ic={int_coord})')
return self
if debug.enabled: if debug.enabled:
debug(kevent, ': ', key) debug(f'KeyResolution(key={key})')
self.pre_process_key(key, is_pressed, int_coord) self.pre_process_key(key, is_pressed, int_coord)
@@ -175,7 +197,7 @@ class KMKKeyboard:
key = ksf.key key = ksf.key
# Handle any unaccounted-for layer shifts by looking up the key resolution again. # Handle any unaccounted-for layer shifts by looking up the key resolution again.
if ksf.int_coord is not None: if ksf.int_coord in self._coordkeys_pressed.keys():
key = self._find_key_in_map(ksf.int_coord) key = self._find_key_in_map(ksf.int_coord)
# Resume the processing of the key event and update the HID report # Resume the processing of the key event and update the HID report
@@ -216,7 +238,8 @@ class KMKKeyboard:
if key is None: if key is None:
break break
except Exception as err: except Exception as err:
debug_error(module, 'process_key', err) if debug.enabled:
debug(f'Error in {module}.process_key: {err}')
if int_coord is not None: if int_coord is not None:
if is_pressed: if is_pressed:
@@ -226,20 +249,18 @@ class KMKKeyboard:
del self._coordkeys_pressed[int_coord] del self._coordkeys_pressed[int_coord]
except KeyError: except KeyError:
if debug.enabled: if debug.enabled:
debug('release w/o press:', int_coord) debug(f'ReleaseKeyError(ic={int_coord})')
if debug.enabled:
debug('coordkeys_pressed=', self._coordkeys_pressed)
if key: if key:
self.process_key(key, is_pressed, int_coord) self.process_key(key, is_pressed, int_coord)
def process_key( def process_key(
self, key: Key, is_pressed: bool, int_coord: Optional[int] = None self, key: Key, is_pressed: bool, coord_int: Optional[int] = None
) -> None: ) -> None:
if is_pressed: if is_pressed:
key.on_press(self, int_coord) key.on_press(self, coord_int)
else: else:
key.on_release(self, int_coord) key.on_release(self, coord_int)
def resume_process_key( def resume_process_key(
self, self,
@@ -247,9 +268,8 @@ class KMKKeyboard:
key: Key, key: Key,
is_pressed: bool, is_pressed: bool,
int_coord: Optional[int] = None, int_coord: Optional[int] = None,
reprocess: Optional[bool] = False,
) -> None: ) -> None:
index = self.modules.index(module) + (0 if reprocess else 1) index = self.modules.index(module) + 1
ksf = KeyBufferFrame( ksf = KeyBufferFrame(
key=key, is_pressed=is_pressed, int_coord=int_coord, index=index key=key, is_pressed=is_pressed, int_coord=int_coord, index=index
) )
@@ -366,15 +386,14 @@ class KMKKeyboard:
self._hid_helper = self._hid_helper(**self._go_args) self._hid_helper = self._hid_helper(**self._go_args)
self._hid_send_enabled = True self._hid_send_enabled = True
if debug.enabled:
debug('hid=', self._hid_helper)
def _deinit_hid(self) -> None: def _deinit_hid(self) -> None:
self._hid_helper.clear_all() self._hid_helper.clear_all()
self._hid_helper.send() self._hid_helper.send()
def _init_matrix(self) -> None: def _init_matrix(self) -> None:
if self.matrix is None: if self.matrix is None:
if debug.enabled:
debug('Initialising default matrix scanner.')
self.matrix = MatrixScanner( self.matrix = MatrixScanner(
column_pins=self.col_pins, column_pins=self.col_pins,
row_pins=self.row_pins, row_pins=self.row_pins,
@@ -390,124 +409,96 @@ class KMKKeyboard:
except TypeError: except TypeError:
self.matrix = (self.matrix,) self.matrix = (self.matrix,)
if debug.enabled:
debug('matrix=', [_.__class__.__name__ for _ in self.matrix])
def during_bootup(self) -> None:
# Modules and extensions that fail `during_bootup` get removed from
# their respective lists. This serves as a self-check mechanism; any
# modules or extensions that initialize peripherals or data structures
# should do that in `during_bootup`.
for idx, module in enumerate(self.modules):
try:
module.during_bootup(self)
except Exception as err:
debug_error(module, 'during_bootup', err)
del self.modules[idx]
if debug.enabled:
debug('modules=', [_.__class__.__name__ for _ in self.modules])
for idx, ext in enumerate(self.extensions):
try:
ext.during_bootup(self)
except Exception as err:
debug_error(ext, 'during_bootup', err)
del self.extensions[idx]
if debug.enabled:
debug('extensions=', [_.__class__.__name__ for _ in self.extensions])
def before_matrix_scan(self) -> None: def before_matrix_scan(self) -> None:
for module in self.modules: for module in self.modules:
try: try:
module.before_matrix_scan(self) module.before_matrix_scan(self)
except Exception as err: except Exception as err:
debug_error(module, 'before_matrix_scan', err) if debug.enabled:
debug(f'Error in {module}.before_matrix_scan: {err}')
for ext in self.extensions: for ext in self.extensions:
try: try:
ext.before_matrix_scan(self.sandbox) ext.before_matrix_scan(self.sandbox)
except Exception as err: except Exception as err:
debug_error(ext, 'before_matrix_scan', err) if debug.enabled:
debug(f'Error in {ext}.before_matrix_scan: {err}')
def after_matrix_scan(self) -> None: def after_matrix_scan(self) -> None:
for module in self.modules: for module in self.modules:
try: try:
module.after_matrix_scan(self) module.after_matrix_scan(self)
except Exception as err: except Exception as err:
debug_error(module, 'after_matrix_scan', err) if debug.enabled:
debug(f'Error in {module}.after_matrix_scan: {err}')
for ext in self.extensions: for ext in self.extensions:
try: try:
ext.after_matrix_scan(self.sandbox) ext.after_matrix_scan(self.sandbox)
except Exception as err: except Exception as err:
debug_error(ext, 'after_matrix_scan', err) if debug.enabled:
debug(f'Error in {ext}.after_matrix_scan: {err}')
def before_hid_send(self) -> None: def before_hid_send(self) -> None:
for module in self.modules: for module in self.modules:
try: try:
module.before_hid_send(self) module.before_hid_send(self)
except Exception as err: except Exception as err:
debug_error(module, 'before_hid_send', err) if debug.enabled:
debug(f'Error in {module}.before_hid_send: {err}')
for ext in self.extensions: for ext in self.extensions:
try: try:
ext.before_hid_send(self.sandbox) ext.before_hid_send(self.sandbox)
except Exception as err: except Exception as err:
debug_error(ext, 'before_hid_send', err) if debug.enabled:
debug(
f'Error in {ext}.before_hid_send: {err}',
)
def after_hid_send(self) -> None: def after_hid_send(self) -> None:
for module in self.modules: for module in self.modules:
try: try:
module.after_hid_send(self) module.after_hid_send(self)
except Exception as err: except Exception as err:
debug_error(module, 'after_hid_send', err) if debug.enabled:
debug(f'Error in {module}.after_hid_send: {err}')
for ext in self.extensions: for ext in self.extensions:
try: try:
ext.after_hid_send(self.sandbox) ext.after_hid_send(self.sandbox)
except Exception as err: except Exception as err:
debug_error(ext, 'after_hid_send', err) if debug.enabled:
debug(f'Error in {ext}.after_hid_send: {err}')
def powersave_enable(self) -> None: def powersave_enable(self) -> None:
for module in self.modules: for module in self.modules:
try: try:
module.on_powersave_enable(self) module.on_powersave_enable(self)
except Exception as err: except Exception as err:
debug_error(module, 'powersave_enable', err) if debug.enabled:
debug(f'Error in {module}.on_powersave: {err}')
for ext in self.extensions: for ext in self.extensions:
try: try:
ext.on_powersave_enable(self.sandbox) ext.on_powersave_enable(self.sandbox)
except Exception as err: except Exception as err:
debug_error(ext, 'powersave_enable', err) if debug.enabled:
debug(f'Error in {ext}.powersave_enable: {err}')
def powersave_disable(self) -> None: def powersave_disable(self) -> None:
for module in self.modules: for module in self.modules:
try: try:
module.on_powersave_disable(self) module.on_powersave_disable(self)
except Exception as err: except Exception as err:
debug_error(module, 'powersave_disable', err) if debug.enabled:
debug(f'Error in {module}.powersave_disable: {err}')
for ext in self.extensions: for ext in self.extensions:
try: try:
ext.on_powersave_disable(self.sandbox) ext.on_powersave_disable(self.sandbox)
except Exception as err: except Exception as err:
debug_error(ext, 'powersave_disable', err) if debug.enabled:
debug(f'Error in {ext}.powersave_disable: {err}')
def deinit(self) -> None:
for module in self.modules:
try:
module.deinit(self)
except Exception as err:
debug_error(module, 'deinit', err)
for ext in self.extensions:
try:
ext.deinit(self.sandbox)
except Exception as err:
debug_error(ext, 'deinit', err)
def go(self, hid_type=HIDModes.USB, secondary_hid_type=None, **kwargs) -> None: def go(self, hid_type=HIDModes.USB, secondary_hid_type=None, **kwargs) -> None:
self._init(hid_type=hid_type, secondary_hid_type=secondary_hid_type, **kwargs) self._init(hid_type=hid_type, secondary_hid_type=secondary_hid_type, **kwargs)
@@ -517,7 +508,6 @@ class KMKKeyboard:
finally: finally:
debug('Unexpected error: cleaning up') debug('Unexpected error: cleaning up')
self._deinit_hid() self._deinit_hid()
self.deinit()
def _init( def _init(
self, self,
@@ -529,22 +519,29 @@ class KMKKeyboard:
self.hid_type = hid_type self.hid_type = hid_type
self.secondary_hid_type = secondary_hid_type self.secondary_hid_type = secondary_hid_type
if debug.enabled: self._init_sanity_check()
debug('Initialising ', self)
debug('unicode_mode=', self.unicode_mode)
self._init_hid() self._init_hid()
self._init_matrix() self._init_matrix()
self._init_coord_mapping() self._init_coord_mapping()
self.during_bootup()
for module in self.modules:
try:
module.during_bootup(self)
except Exception as err:
if debug.enabled:
debug(f'Failed to load module {module}: {err}')
for ext in self.extensions:
try:
ext.during_bootup(self)
except Exception as err:
if debug.enabled:
debug(f'Failed to load extensions {module}: {err}')
if debug.enabled: if debug.enabled:
import gc debug(f'init: {self}')
gc.collect()
debug('mem_info used:', gc.mem_alloc(), ' free:', gc.mem_free())
def _main_loop(self) -> None: def _main_loop(self) -> None:
self.state_changed = False
self.sandbox.active_layers = self.active_layers.copy() self.sandbox.active_layers = self.active_layers.copy()
self.before_matrix_scan() self.before_matrix_scan()
@@ -582,6 +579,7 @@ class KMKKeyboard:
if self.hid_pending: if self.hid_pending:
self._send_hid() self._send_hid()
self.state_changed = True
self.after_hid_send() self.after_hid_send()
@@ -590,3 +588,6 @@ class KMKKeyboard:
if self._trigger_powersave_disable: if self._trigger_powersave_disable:
self.powersave_disable() self.powersave_disable()
if self.state_changed:
self._print_debug_cycle()

View File

@@ -41,6 +41,3 @@ class Module:
def on_powersave_disable(self, keyboard): def on_powersave_disable(self, keyboard):
raise NotImplementedError raise NotImplementedError
def deinit(self, keyboard):
pass

View File

@@ -8,9 +8,6 @@ import kmk.handlers.stock as handlers
from kmk.keys import Key, make_key from kmk.keys import Key, make_key
from kmk.kmk_keyboard import KMKKeyboard from kmk.kmk_keyboard import KMKKeyboard
from kmk.modules import Module from kmk.modules import Module
from kmk.utils import Debug
debug = Debug(__name__)
class _ComboState: class _ComboState:
@@ -217,7 +214,7 @@ class Combos(Module):
combo.insert(key, int_coord) combo.insert(key, int_coord)
combo._state = _ComboState.MATCHING combo._state = _ComboState.MATCHING
key = None key = combo.result
break break
else: else:
@@ -304,14 +301,10 @@ class Combos(Module):
keyboard.resume_process_key(self, key, is_pressed, int_coord) keyboard.resume_process_key(self, key, is_pressed, int_coord)
def activate(self, keyboard, combo): def activate(self, keyboard, combo):
if debug.enabled:
debug('activate', combo)
combo.result.on_press(keyboard) combo.result.on_press(keyboard)
combo._state = _ComboState.ACTIVE combo._state = _ComboState.ACTIVE
def deactivate(self, keyboard, combo): def deactivate(self, keyboard, combo):
if debug.enabled:
debug('deactivate', combo)
combo.result.on_release(keyboard) combo.result.on_release(keyboard)
combo._state = _ComboState.IDLE combo._state = _ComboState.IDLE

View File

@@ -1,212 +0,0 @@
try:
from typing import Callable
except ImportError:
pass
from micropython import const
from kmk.keys import AX
from kmk.modules import Module
from kmk.utils import Debug
debug = Debug(__name__)
_ADDRESS = const(0x2A)
_MASK_READ = const(0xA0)
_MASK_WRITE = const(0x80)
_REG_FW_ID = const(0x00)
_REG_FW_VER = const(0x01)
_REG_STATUS = const(0x02)
_REG_SYS_CFG = const(0x03)
_REG_FEED_CFG1 = const(0x04)
_REG_FEED_CFG2 = const(0x05)
_REG_CAL_CFG = const(0x07)
_REG_AUX_CTL = const(0x08)
_REG_SAMPLE_RATE = const(0x09)
_REG_ZIDLE = const(0x0A)
_REG_Z_SCALER = const(0x0B)
_REG_SLEEP_INTERVAL = const(0x0C)
_REG_SLEEP_TIMER = const(0x0D)
_REG_DATA = const(0x12)
_FEED1_ENABLE = const(0x01)
_FEED1_ABSOLUTE = const(0x02)
_FEED1_NO_FILTER = const(0x04)
_FEED1_NO_X_DATA = const(0x08)
_FEED1_NO_Y_DATA = const(0x10)
_FEED1_INVERT_X = const(0x40)
_FEED1_INVERT_Y = const(0x80)
_FEED2_INTELLIMOUSE = const(0x01)
_FEED2_NO_TAPS = const(0x02)
_FEED2_NO_SEC_TAPS = const(0x04)
_FEED2_NO_SCROLL = const(0x08)
_FEED2_NO_GLIDEEXTEND = const(0x10)
_FEED2_SWAP_XY = const(0x80)
def with_i2c_lock(rw: Callable[[object, int], None]) -> Callable[[object, int], None]:
def _(self, n: int) -> None:
if not self._i2c.try_lock():
if debug.enabled:
debug("can't acquire lock")
return
try:
rw(self, n)
finally:
self._i2c.unlock()
return _
class AbsoluteHandler:
cfg = (
_REG_FEED_CFG2,
0x00,
_REG_FEED_CFG1,
_FEED1_ENABLE | _FEED1_NO_FILTER | _FEED1_ABSOLUTE | _FEED1_INVERT_X,
)
def handle(buffer: bytearray, keyboard) -> None:
button = buffer[0]
x_low = buffer[2]
y_low = buffer[3]
high = buffer[4]
z_lvl = buffer[5]
x_pos = ((high & 0x0F) << 8) | x_low
y_pos = ((high & 0xF0) << 4) | y_low
if debug.enabled:
debug(
'buttons:',
bin(button),
' x_pos:',
x_pos,
' y_pos:',
y_pos,
'z_lvl:',
z_lvl,
)
class RelativeHandler:
cfg = (
_REG_FEED_CFG2,
0x00,
_REG_FEED_CFG1,
_FEED1_ENABLE | _FEED1_NO_FILTER | _FEED1_INVERT_X,
)
def handle(buffer: bytearray, keyboard) -> None:
button = buffer[0] & 0b00000111
x_sign = buffer[0] & 0b00010000
y_sign = buffer[0] & 0b00100000
x_delta = buffer[1]
y_delta = buffer[2]
w_delta = buffer[3]
if x_sign:
x_delta -= 0xFF
if y_sign:
y_delta -= 0xFF
if x_delta != 0:
AX.X.move(keyboard, x_delta)
if y_delta != 0:
AX.Y.move(keyboard, y_delta)
if debug.enabled:
debug(
'buttons:',
bin(button),
' x_delta:',
x_delta,
' y_delta:',
y_delta,
' w_delta',
w_delta,
)
class GlidePoint(Module):
def __init__(self, i2c):
self._i2c = i2c
self._buffer = bytearray(8)
# self.handler = RelativeHandler
self.handler = AbsoluteHandler
@with_i2c_lock
def _read(self, n: int) -> None:
self._buffer[0] |= _MASK_READ
self._i2c.writeto_then_readfrom(
_ADDRESS, self._buffer, self._buffer, out_end=1, in_end=n
)
@with_i2c_lock
def _write(self, n: int) -> None:
for i in range(0, n, 2):
self._buffer[i] |= _MASK_WRITE
self._i2c.writeto(_ADDRESS, self._buffer, end=n)
def _check_firmware(self) -> bool:
self._buffer[0] = _REG_FW_ID
self._read(2)
return self._buffer[:2] == b'\x07:'
def _clear_status_flags(self) -> None:
self._buffer[0] = _REG_STATUS
self._buffer[1] = 0x00
self._write(2)
def _configure(self) -> None:
self._buffer[0] = _REG_SYS_CFG
self._buffer[1] = 0x00
for idx, val in enumerate(self.handler.cfg):
self._buffer[idx + 2] = val
self._write(2 + len(self.handler.cfg))
def _data_ready(self) -> bool:
self._buffer[0] = _REG_STATUS
self._read(1)
return self._buffer[0] != 0x00
def during_bootup(self, keyboard):
if not self._check_firmware:
raise OSError('Firmware ID mismatch')
self._clear_status_flags()
self._configure()
def before_matrix_scan(self, keyboard):
pass
def after_matrix_scan(self, keyboard):
if not self._data_ready():
return
self._buffer[0] = _REG_DATA
self._read(6)
self.handler.handle(self._buffer, keyboard)
self._clear_status_flags()
def before_hid_send(self, keyboard):
pass
def after_hid_send(self, keyboard):
pass
def on_powersave_enable(self, keyboard):
self._buffer[0] = _REG_SYS_CFG
self._buffer[1] = 0x04
self._write(2)
def on_powersave_disable(self, keyboard):
self._buffer[0] = _REG_SYS_CFG
self._buffer[1] = 0x00
self._write(2)

View File

@@ -54,7 +54,7 @@ class HoldTap(Module):
def __init__(self): def __init__(self):
self.key_buffer = [] self.key_buffer = []
self.key_states = {} self.key_states = {}
if KC.get('HT') == KC.NO: if not KC.get('HT'):
make_argumented_key( make_argumented_key(
validator=HoldTapKeyMeta, validator=HoldTapKeyMeta,
names=('HT',), names=('HT',),
@@ -83,11 +83,6 @@ class HoldTap(Module):
if state.activated != ActivationType.PRESSED: if state.activated != ActivationType.PRESSED:
continue continue
# holdtap isn't interruptable, resolves on ht_release or timeout.
if not key.meta.tap_interrupted and not key.meta.prefer_hold:
append_buffer = True
continue
# holdtap is interrupted by another key event. # holdtap is interrupted by another key event.
if (is_pressed and not key.meta.tap_interrupted) or ( if (is_pressed and not key.meta.tap_interrupted) or (
not is_pressed and key.meta.tap_interrupted and self.key_buffer not is_pressed and key.meta.tap_interrupted and self.key_buffer
@@ -98,12 +93,15 @@ class HoldTap(Module):
self.ht_activate_on_interrupt( self.ht_activate_on_interrupt(
key, keyboard, *state.args, **state.kwargs key, keyboard, *state.args, **state.kwargs
) )
append_buffer = True
send_buffer = True send_buffer = True
# if interrupt on release: store interrupting keys until one of them # if interrupt on release: store interrupting keys until one of them
# is released. # is released.
if key.meta.tap_interrupted and is_pressed: if (
key.meta.tap_interrupted
and is_pressed
and not isinstance(current_key.meta, HoldTapKeyMeta)
):
append_buffer = True append_buffer = True
# apply changes with 'side-effects' on key_states or the loop behaviour # apply changes with 'side-effects' on key_states or the loop behaviour
@@ -112,8 +110,10 @@ class HoldTap(Module):
self.key_buffer.append((int_coord, current_key, is_pressed)) self.key_buffer.append((int_coord, current_key, is_pressed))
current_key = None current_key = None
if send_buffer: elif send_buffer:
self.send_key_buffer(keyboard) self.send_key_buffer(keyboard)
keyboard.resume_process_key(self, current_key, is_pressed, int_coord)
current_key = None
return current_key return current_key
@@ -221,11 +221,8 @@ class HoldTap(Module):
if not self.key_buffer: if not self.key_buffer:
return return
reprocess = False
for (int_coord, key, is_pressed) in self.key_buffer: for (int_coord, key, is_pressed) in self.key_buffer:
keyboard.resume_process_key(self, key, is_pressed, int_coord, reprocess) keyboard.resume_process_key(self, key, is_pressed, int_coord)
if isinstance(key.meta, HoldTapKeyMeta):
reprocess = True
self.key_buffer.clear() self.key_buffer.clear()

View File

@@ -36,15 +36,9 @@ class LayerKeyMeta:
class Layers(HoldTap): class Layers(HoldTap):
'''Gives access to the keys used to enable the layer system''' '''Gives access to the keys used to enable the layer system'''
_active_combo = None def __init__(self):
def __init__(
self,
combo_layers=None,
):
# Layers # Layers
super().__init__() super().__init__()
self.combo_layers = combo_layers
make_argumented_key( make_argumented_key(
validator=layer_key_validator, validator=layer_key_validator,
names=('MO',), names=('MO',),
@@ -52,7 +46,9 @@ class Layers(HoldTap):
on_release=self._mo_released, on_release=self._mo_released,
) )
make_argumented_key( make_argumented_key(
validator=layer_key_validator, names=('DF',), on_press=self._df_pressed validator=layer_key_validator,
names=('DF',),
on_press=self._df_pressed,
) )
make_argumented_key( make_argumented_key(
validator=layer_key_validator, validator=layer_key_validator,
@@ -61,10 +57,14 @@ class Layers(HoldTap):
on_release=self._lm_released, on_release=self._lm_released,
) )
make_argumented_key( make_argumented_key(
validator=layer_key_validator, names=('TG',), on_press=self._tg_pressed validator=layer_key_validator,
names=('TG',),
on_press=self._tg_pressed,
) )
make_argumented_key( make_argumented_key(
validator=layer_key_validator, names=('TO',), on_press=self._to_pressed validator=layer_key_validator,
names=('TO',),
on_press=self._to_pressed,
) )
make_argumented_key( make_argumented_key(
validator=layer_key_validator_lt, validator=layer_key_validator_lt,
@@ -83,102 +83,67 @@ class Layers(HoldTap):
''' '''
Switches the default layer Switches the default layer
''' '''
self.activate_layer(keyboard, key.meta.layer, as_default=True) keyboard.active_layers[-1] = key.meta.layer
self._print_debug(keyboard)
def _mo_pressed(self, key, keyboard, *args, **kwargs): def _mo_pressed(self, key, keyboard, *args, **kwargs):
''' '''
Momentarily activates layer, switches off when you let go Momentarily activates layer, switches off when you let go
''' '''
self.activate_layer(keyboard, key.meta.layer) keyboard.active_layers.insert(0, key.meta.layer)
self._print_debug(keyboard)
def _mo_released(self, key, keyboard, *args, **kwargs): @staticmethod
self.deactivate_layer(keyboard, key.meta.layer) def _mo_released(key, keyboard, *args, **kwargs):
# remove the first instance of the target layer
# from the active list
# under almost all normal use cases, this will
# disable the layer (but preserve it if it was triggered
# as a default layer, etc.)
# this also resolves an issue where using DF() on a layer
# triggered by MO() and then defaulting to the MO()'s layer
# would result in no layers active
try:
del_idx = keyboard.active_layers.index(key.meta.layer)
del keyboard.active_layers[del_idx]
except ValueError:
pass
__class__._print_debug(__class__, keyboard)
def _lm_pressed(self, key, keyboard, *args, **kwargs): def _lm_pressed(self, key, keyboard, *args, **kwargs):
''' '''
As MO(layer) but with mod active As MO(layer) but with mod active
''' '''
keyboard.hid_pending = True # Sets the timer start and acts like MO otherwise
keyboard.keys_pressed.add(key.meta.kc) keyboard.add_key(key.meta.kc)
self.activate_layer(keyboard, key.meta.layer) self._mo_pressed(key, keyboard, *args, **kwargs)
def _lm_released(self, key, keyboard, *args, **kwargs): def _lm_released(self, key, keyboard, *args, **kwargs):
''' '''
As MO(layer) but with mod active As MO(layer) but with mod active
''' '''
keyboard.hid_pending = True keyboard.remove_key(key.meta.kc)
keyboard.keys_pressed.discard(key.meta.kc) self._mo_released(key, keyboard, *args, **kwargs)
self.deactivate_layer(keyboard, key.meta.layer)
def _tg_pressed(self, key, keyboard, *args, **kwargs): def _tg_pressed(self, key, keyboard, *args, **kwargs):
''' '''
Toggles the layer (enables it if not active, and vise versa) Toggles the layer (enables it if not active, and vise versa)
''' '''
# See mo_released for implementation details around this # See mo_released for implementation details around this
if key.meta.layer in keyboard.active_layers: try:
self.deactivate_layer(keyboard, key.meta.layer) del_idx = keyboard.active_layers.index(key.meta.layer)
else: del keyboard.active_layers[del_idx]
self.activate_layer(keyboard, key.meta.layer) except ValueError:
keyboard.active_layers.insert(0, key.meta.layer)
def _to_pressed(self, key, keyboard, *args, **kwargs): def _to_pressed(self, key, keyboard, *args, **kwargs):
''' '''
Activates layer and deactivates all other layers Activates layer and deactivates all other layers
''' '''
self._active_combo = None
keyboard.active_layers.clear() keyboard.active_layers.clear()
keyboard.active_layers.insert(0, key.meta.layer) keyboard.active_layers.insert(0, key.meta.layer)
def _print_debug(self, keyboard): def _print_debug(self, keyboard):
# debug(f'__getitem__ {key}')
if debug.enabled: if debug.enabled:
debug(f'active_layers={keyboard.active_layers}') debug(f'active_layers={keyboard.active_layers}')
def activate_layer(self, keyboard, layer, as_default=False):
if as_default:
keyboard.active_layers[-1] = layer
else:
keyboard.active_layers.insert(0, layer)
if self.combo_layers:
self._activate_combo_layer(keyboard)
self._print_debug(keyboard)
def deactivate_layer(self, keyboard, layer):
# Remove the first instance of the target layer from the active list
# under almost all normal use cases, this will disable the layer (but
# preserve it if it was triggered as a default layer, etc.).
# This also resolves an issue where using DF() on a layer
# triggered by MO() and then defaulting to the MO()'s layer
# would result in no layers active.
try:
del_idx = keyboard.active_layers.index(layer)
del keyboard.active_layers[del_idx]
except ValueError:
if debug.enabled:
debug(f'_mo_released: layer {layer} not active')
if self.combo_layers:
self._deactivate_combo_layer(keyboard, layer)
self._print_debug(keyboard)
def _activate_combo_layer(self, keyboard):
if self._active_combo:
return
for combo, result in self.combo_layers.items():
matching = True
for layer in combo:
if layer not in keyboard.active_layers:
matching = False
break
if matching:
self._active_combo = combo
keyboard.active_layers.insert(0, result)
break
def _deactivate_combo_layer(self, keyboard, layer):
if self._active_combo and layer in self._active_combo:
keyboard.active_layers.remove(self.combo_layers[self._active_combo])
self._active_combo = None

View File

@@ -53,7 +53,7 @@ class OneShot(HoldTap):
elif state.activated == ActivationType.INTERRUPTED: elif state.activated == ActivationType.INTERRUPTED:
if is_pressed: if is_pressed:
send_buffer = True send_buffer = True
self.key_buffer.insert(0, (None, key, False)) self.key_buffer.insert(0, (0, key, False))
if send_buffer: if send_buffer:
self.key_buffer.append((int_coord, current_key, is_pressed)) self.key_buffer.append((int_coord, current_key, is_pressed))

View File

@@ -42,11 +42,12 @@ class Phrase:
self._characters: list[Character] = [] self._characters: list[Character] = []
self._index: int = 0 self._index: int = 0
for char in string: for char in string:
key_code = KC[char] try:
if key_code == KC.NO: key_code = KC[char]
shifted = char.isupper() or key_code.has_modifiers == {2}
self._characters.append(Character(key_code, shifted))
except ValueError:
raise ValueError(f'Invalid character in dictionary: {char}') raise ValueError(f'Invalid character in dictionary: {char}')
shifted = char.isupper() or key_code.has_modifiers == {2}
self._characters.append(Character(key_code, shifted))
def next_character(self) -> None: def next_character(self) -> None:
'''Increment the current index for this phrase''' '''Increment the current index for this phrase'''

View File

@@ -17,7 +17,7 @@ class TapDanceKeyMeta:
ht_key = KC.HT( ht_key = KC.HT(
tap=key, tap=key,
hold=key, hold=key,
prefer_hold=True, prefer_hold=False,
tap_interrupted=False, tap_interrupted=False,
tap_time=self.tap_time, tap_time=self.tap_time,
) )

View File

@@ -1,8 +1,3 @@
try:
from typing import Optional
except ImportError:
pass
from supervisor import ticks_ms from supervisor import ticks_ms
@@ -21,12 +16,8 @@ class Debug:
def __init__(self, name: str = __name__): def __init__(self, name: str = __name__):
self.name = name self.name = name
def __call__(self, *message: str, name: Optional[str] = None) -> None: def __call__(self, message: str) -> None:
if not name: print(f'{ticks_ms()} {self.name}: {message}')
name = self.name
print(ticks_ms(), end=' ')
print(name, end=': ')
print(*message, sep='')
@property @property
def enabled(self) -> bool: def enabled(self) -> bool:

View File

@@ -23,8 +23,6 @@ def code2name(code):
class KeyboardTest: class KeyboardTest:
loop_delay_ms = 2
def __init__( def __init__(
self, self,
modules, modules,
@@ -130,4 +128,4 @@ class KeyboardTest:
def do_main_loop(self): def do_main_loop(self):
self.keyboard._main_loop() self.keyboard._main_loop()
time.sleep(self.loop_delay_ms / 1000) time.sleep(0.002)

View File

@@ -10,7 +10,7 @@ from tests.keyboard_test import KeyboardTest
class TestCapsWord(unittest.TestCase): class TestCapsWord(unittest.TestCase):
def setUp(self): def setUp(self):
self.kb = KeyboardTest( self.kb = KeyboardTest(
[CapsWord(timeout=2 * KeyboardTest.loop_delay_ms)], [CapsWord()],
[ [
[KC.CW, KC.A, KC.Z, KC.N1, KC.N0, KC.SPC], [KC.CW, KC.A, KC.Z, KC.N1, KC.N0, KC.SPC],
], ],

View File

@@ -1,36 +1,28 @@
import unittest import unittest
from kmk.keys import KC from kmk.keys import KC
from kmk.modules.combos import Chord, Combo, Combos, Sequence from kmk.modules.combos import Chord, Combos, Sequence
from kmk.modules.layers import Layers from kmk.modules.layers import Layers
from tests.keyboard_test import KeyboardTest from tests.keyboard_test import KeyboardTest
class TestCombo(unittest.TestCase): class TestCombo(unittest.TestCase):
def setUp(self): def setUp(self):
self.t_within = 2 * KeyboardTest.loop_delay_ms
self.t_after = 7 * KeyboardTest.loop_delay_ms
timeout = (self.t_after + self.t_within) // 2
# overide default timeouts
Combo.timeout = timeout
Sequence.timeout = timeout
combos = Combos() combos = Combos()
layers = Layers() layers = Layers()
KCMO = KC.MO(1) KCMO = KC.MO(1)
combos.combos = [ combos.combos = [
Chord((KC.A, KC.B, KC.C), KC.Y), Chord((KC.A, KC.B, KC.C), KC.Y),
Chord((KC.A, KC.B), KC.X), Chord((KC.A, KC.B), KC.X),
Chord((KC.C, KC.D), KC.Z, timeout=2 * timeout), Chord((KC.C, KC.D), KC.Z, timeout=80),
Chord((KC.C, KCMO), KC.Z), Chord((KC.C, KCMO), KC.Z),
Chord((KC.F, KC.G), KC.Z, timeout=3 * timeout), Chord((KC.F, KC.G), KC.Z, timeout=130),
Sequence((KC.N1, KC.N2, KC.N3), KC.Y), Sequence((KC.N1, KC.N2, KC.N3), KC.Y, timeout=50),
Sequence((KC.N1, KC.N2), KC.X), Sequence((KC.N1, KC.N2), KC.X, timeout=50),
Sequence((KC.N3, KC.N4), KC.Z, timeout=2 * timeout), Sequence((KC.N3, KC.N4), KC.Z, timeout=100),
Sequence((KC.N1, KC.N1, KC.N1), KC.W), Sequence((KC.N1, KC.N1, KC.N1), KC.W, timeout=50),
Sequence((KC.N3, KC.N2, KC.N1), KC.Y, fast_reset=False), Sequence((KC.N3, KC.N2, KC.N1), KC.Y, timeout=50, fast_reset=False),
Sequence((KC.LEADER, KC.N1), KC.V), Sequence((KC.LEADER, KC.N1), KC.V, timeout=50),
] ]
self.keyboard = KeyboardTest( self.keyboard = KeyboardTest(
[combos, layers], [combos, layers],
@@ -41,6 +33,9 @@ class TestCombo(unittest.TestCase):
debug_enabled=False, debug_enabled=False,
) )
self.t_within = 40
self.t_after = 60
def test_chord(self): def test_chord(self):
keyboard = self.keyboard keyboard = self.keyboard
t_within = self.t_within t_within = self.t_within

View File

@@ -10,37 +10,20 @@ class TestHoldTap(unittest.TestCase):
def setUp(self): def setUp(self):
KC.clear() KC.clear()
self.t_within = 2 * KeyboardTest.loop_delay_ms
self.t_after = 6 * KeyboardTest.loop_delay_ms
tap_time = 5 * KeyboardTest.loop_delay_ms
# overide default timeouts
HoldTap.tap_time = tap_time
def test_holdtap(self): def test_holdtap(self):
t_within = self.t_within
t_after = self.t_after
keyboard = KeyboardTest( keyboard = KeyboardTest(
[Layers(), HoldTap()], [Layers(), HoldTap()],
[ [
[ [KC.HT(KC.A, KC.LCTL), KC.LT(1, KC.B), KC.C, KC.D],
KC.HT(KC.A, KC.LCTL),
KC.LT(1, KC.B),
KC.C,
KC.D,
],
[KC.N1, KC.N2, KC.N3, KC.N4], [KC.N1, KC.N2, KC.N3, KC.N4],
], ],
debug_enabled=False, debug_enabled=False,
) )
keyboard.test( keyboard.test('HT tap behaviour', [(0, True), 100, (0, False)], [{KC.A}, {}])
'HT tap behaviour', [(0, True), t_within, (0, False)], [{KC.A}, {}]
)
keyboard.test( keyboard.test(
'HT hold behaviour', [(0, True), t_after, (0, False)], [{KC.LCTL}, {}] 'HT hold behaviour', [(0, True), 350, (0, False)], [{KC.LCTL}, {}]
) )
# TODO test multiple mods being held # TODO test multiple mods being held
@@ -48,74 +31,74 @@ class TestHoldTap(unittest.TestCase):
# HT # HT
keyboard.test( keyboard.test(
'HT within tap time sequential -> tap behavior', 'HT within tap time sequential -> tap behavior',
[(0, True), t_within, (0, False), (3, True), (3, False)], [(0, True), 100, (0, False), (3, True), (3, False)],
[{KC.A}, {}, {KC.D}, {}], [{KC.A}, {}, {KC.D}, {}],
) )
keyboard.test( keyboard.test(
'HT within tap time rolling -> hold behavior', 'HT within tap time rolling -> hold behavior',
[(0, True), t_within, (3, True), t_after, (0, False), (3, False)], [(0, True), 100, (3, True), 250, (0, False), (3, False)],
[{KC.LCTL}, {KC.LCTL, KC.D}, {KC.D}, {}], [{KC.LCTL}, {KC.LCTL, KC.D}, {KC.D}, {}],
) )
keyboard.test( keyboard.test(
'HT within tap time nested -> hold behavior', 'HT within tap time nested -> hold behavior',
[(0, True), t_within, (3, True), (3, False), t_after, (0, False)], [(0, True), 100, (3, True), (3, False), 250, (0, False)],
[{KC.LCTL}, {KC.LCTL, KC.D}, {KC.LCTL}, {}], [{KC.LCTL}, {KC.LCTL, KC.D}, {KC.LCTL}, {}],
) )
keyboard.test( keyboard.test(
'HT after tap time sequential -> hold behavior', 'HT after tap time sequential -> hold behavior',
[(0, True), t_after, (0, False), (3, True), (3, False)], [(0, True), 350, (0, False), (3, True), (3, False)],
[{KC.LCTL}, {}, {KC.D}, {}], [{KC.LCTL}, {}, {KC.D}, {}],
) )
keyboard.test( keyboard.test(
'HT after tap time rolling -> hold behavior', 'HT after tap time rolling -> hold behavior',
[(0, True), t_after, (3, True), (0, False), (3, False)], [(0, True), 350, (3, True), (0, False), (3, False)],
[{KC.LCTL}, {KC.LCTL, KC.D}, {KC.D}, {}], [{KC.LCTL}, {KC.LCTL, KC.D}, {KC.D}, {}],
) )
keyboard.test( keyboard.test(
'HT after tap time nested -> hold behavior', 'HT after tap time nested -> hold behavior',
[(0, True), t_after, (3, True), (3, False), (0, False)], [(0, True), 350, (3, True), (3, False), (0, False)],
[{KC.LCTL}, {KC.LCTL, KC.D}, {KC.LCTL}, {}], [{KC.LCTL}, {KC.LCTL, KC.D}, {KC.LCTL}, {}],
) )
# LT # LT
keyboard.test( keyboard.test(
'LT within tap time sequential -> tap behavior', 'LT within tap time sequential -> tap behavior',
[(1, True), t_within, (1, False), (3, True), (3, False)], [(1, True), 100, (1, False), (3, True), (3, False)],
[{KC.B}, {}, {KC.D}, {}], [{KC.B}, {}, {KC.D}, {}],
) )
keyboard.test( keyboard.test(
'LT within tap time rolling -> tap behavior', 'LT within tap time rolling -> tap behavior',
[(1, True), t_within, (3, True), (1, False), (3, False)], [(1, True), 100, (3, True), 250, (1, False), (3, False)],
[{KC.B}, {KC.B, KC.D}, {KC.D}, {}], [{KC.B}, {KC.B, KC.D}, {KC.D}, {}],
) )
keyboard.test( keyboard.test(
'LT within tap time nested -> tap behavior', 'LT within tap time nested -> tap behavior',
[(1, True), t_within, (3, True), (3, False), (1, False)], [(1, True), 100, (3, True), (3, False), 250, (1, False)],
[{KC.B}, {KC.B, KC.D}, {KC.B}, {}], [{KC.B}, {KC.B, KC.D}, {KC.B}, {}],
) )
keyboard.test( keyboard.test(
'LT after tap time sequential -> hold behavior', 'LT after tap time sequential -> hold behavior',
[(1, True), t_after, (1, False), (3, True), (3, False)], [(1, True), 350, (1, False), (3, True), (3, False)],
[{KC.D}, {}], [{KC.D}, {}],
) )
keyboard.test( keyboard.test(
'LT after tap time rolling -> hold behavior', 'LT after tap time rolling -> hold behavior',
[(1, True), t_after, (3, True), (1, False), (3, False)], [(1, True), 350, (3, True), (1, False), (3, False)],
[{KC.N4}, {}], [{KC.N4}, {}],
) )
keyboard.test( keyboard.test(
'LT after tap time nested -> hold behavior', 'LT after tap time nested -> hold behavior',
[(1, True), t_after, (3, True), (3, False), (1, False)], [(1, True), 350, (3, True), (3, False), (1, False)],
[{KC.N4}, {}], [{KC.N4}, {}],
) )
@@ -123,9 +106,9 @@ class TestHoldTap(unittest.TestCase):
'LT after tap time nested -> hold behavior', 'LT after tap time nested -> hold behavior',
[ [
(0, True), (0, True),
t_after, 350,
(1, True), (1, True),
t_after, 350,
(3, True), (3, True),
(3, False), (3, False),
(1, False), (1, False),
@@ -135,25 +118,26 @@ class TestHoldTap(unittest.TestCase):
) )
def test_holdtap_chain(self): def test_holdtap_chain(self):
t_after = self.t_after
keyboard = KeyboardTest( keyboard = KeyboardTest(
[HoldTap()], [HoldTap()],
[ [
[ [
KC.N0, KC.N0,
KC.HT(KC.N1, KC.LCTL), KC.HT(KC.N1, KC.LCTL, tap_time=50),
KC.HT(KC.N2, KC.LSFT, tap_interrupted=True), KC.HT(KC.N2, KC.LSFT, tap_interrupted=True, tap_time=50),
KC.HT( KC.HT(
KC.N3, KC.N3,
KC.LALT, KC.LALT,
prefer_hold=False, prefer_hold=False,
tap_interrupted=True, tap_interrupted=True,
tap_time=50,
), ),
], ],
], ],
debug_enabled=False, debug_enabled=False,
) )
# t_within = 40
t_after = 60
keyboard.test( keyboard.test(
'chained 0', 'chained 0',
@@ -172,7 +156,7 @@ class TestHoldTap(unittest.TestCase):
'chained 1', 'chained 1',
[(2, True), (1, True), (0, True), (0, False), (1, False), (2, False)], [(2, True), (1, True), (0, True), (0, False), (1, False), (2, False)],
[ [
{KC.LSFT}, {KC.LCTL},
{KC.LCTL, KC.LSFT}, {KC.LCTL, KC.LSFT},
{KC.LCTL, KC.LSFT, KC.N0}, {KC.LCTL, KC.LSFT, KC.N0},
{KC.LCTL, KC.LSFT}, {KC.LCTL, KC.LSFT},
@@ -224,7 +208,7 @@ class TestHoldTap(unittest.TestCase):
'chained 5', 'chained 5',
[(3, True), (1, True), (0, True), (0, False), (1, False), (3, False)], [(3, True), (1, True), (0, True), (0, False), (1, False), (3, False)],
[ [
{KC.N3}, {KC.LCTL},
{KC.LCTL, KC.N3}, {KC.LCTL, KC.N3},
{KC.LCTL, KC.N3, KC.N0}, {KC.LCTL, KC.N3, KC.N0},
{KC.LCTL, KC.N3}, {KC.LCTL, KC.N3},
@@ -291,21 +275,21 @@ class TestHoldTap(unittest.TestCase):
# TODO test TT # TODO test TT
def test_holdtap_repeat(self): def test_holdtap_repeat(self):
t_within = self.t_within
t_after = self.t_after
keyboard = KeyboardTest( keyboard = KeyboardTest(
[HoldTap()], [HoldTap()],
[ [
[ [
KC.HT(KC.A, KC.B, repeat=HoldTapRepeat.ALL), KC.HT(KC.A, KC.B, repeat=HoldTapRepeat.ALL, tap_time=50),
KC.HT(KC.A, KC.B, repeat=HoldTapRepeat.TAP), KC.HT(KC.A, KC.B, repeat=HoldTapRepeat.TAP, tap_time=50),
KC.HT(KC.A, KC.B, repeat=HoldTapRepeat.HOLD), KC.HT(KC.A, KC.B, repeat=HoldTapRepeat.HOLD, tap_time=50),
] ]
], ],
debug_enabled=False, debug_enabled=False,
) )
t_within = 40
t_after = 60
keyboard.test( keyboard.test(
'repeat tap', 'repeat tap',
[ [
@@ -317,6 +301,7 @@ class TestHoldTap(unittest.TestCase):
(0, False), (0, False),
(0, True), (0, True),
(0, False), (0, False),
t_after,
], ],
[{KC.A}, {}, {KC.A}, {}, {KC.A}, {}], [{KC.A}, {}, {KC.A}, {}, {KC.A}, {}],
) )
@@ -332,6 +317,7 @@ class TestHoldTap(unittest.TestCase):
(0, False), (0, False),
(0, True), (0, True),
(0, False), (0, False),
t_after,
], ],
[{KC.B}, {}, {KC.B}, {}, {KC.B}, {}], [{KC.B}, {}, {KC.B}, {}, {KC.B}, {}],
) )
@@ -348,6 +334,7 @@ class TestHoldTap(unittest.TestCase):
t_after, t_after,
(0, True), (0, True),
(0, False), (0, False),
t_after,
], ],
[{KC.A}, {}, {KC.B}, {}, {KC.A}, {}], [{KC.A}, {}, {KC.B}, {}, {KC.A}, {}],
) )

View File

@@ -123,10 +123,12 @@ class TestKeys_dot(unittest.TestCase):
assert primary_key is secondary_key assert primary_key is secondary_key
def test_invalid_key_upper(self): def test_invalid_key_upper(self):
assert KC.INVALID_KEY == KC.NO with self.assertRaises(ValueError):
KC.INVALID_KEY
def test_invalid_key_lower(self): def test_invalid_key_lower(self):
assert KC.invalid_key == KC.NO with self.assertRaises(ValueError):
KC.invalid_key
def test_custom_key(self): def test_custom_key(self):
created = make_key( created = make_key(
@@ -166,10 +168,12 @@ class TestKeys_index(unittest.TestCase):
assert upper_key is lower_key assert upper_key is lower_key
def test_invalid_key_upper(self): def test_invalid_key_upper(self):
assert KC.INVALID_KEY == KC.NO with self.assertRaises(ValueError):
KC['NOT_A_VALID_KEY']
def test_invalid_key_lower(self): def test_invalid_key_lower(self):
assert KC.invalid_key == KC.NO with self.assertRaises(ValueError):
KC['not_a_valid_key']
def test_custom_key(self): def test_custom_key(self):
created = make_key( created = make_key(
@@ -214,10 +218,10 @@ class TestKeys_get(unittest.TestCase):
assert primary_key is secondary_key assert primary_key is secondary_key
def test_invalid_key_upper(self): def test_invalid_key_upper(self):
assert KC.get('INVALID_KEY') is KC.NO assert KC.get('INVALID_KEY') is None
def test_invalid_key_lower(self): def test_invalid_key_lower(self):
assert KC.get('not_a_valid_key') is KC.NO assert KC.get('not_a_valid_key') is None
def test_custom_key(self): def test_custom_key(self):
created = make_key( created = make_key(

View File

@@ -10,13 +10,8 @@ class TestLayers(unittest.TestCase):
self.kb = KeyboardTest( self.kb = KeyboardTest(
[Layers()], [Layers()],
[ [
[ [KC.N0, KC.LM(1, KC.LCTL)],
KC.N0, [KC.A, KC.B],
KC.LM(1, KC.LCTL),
KC.LT(1, KC.N2, tap_interrupted=True, prefer_hold=True),
KC.LT(1, KC.N3, tap_interrupted=False, prefer_hold=True),
],
[KC.A, KC.B, KC.C, KC.D],
], ],
debug_enabled=False, debug_enabled=False,
) )
@@ -28,25 +23,6 @@ class TestLayers(unittest.TestCase):
[{KC.LCTL}, {KC.LCTL, KC.A}, {KC.A}, {}], [{KC.LCTL}, {KC.LCTL, KC.A}, {KC.A}, {}],
) )
def test_layertap(self):
self.kb.test(
'Layertap roll',
[(2, True), (0, True), (2, False), (0, False)],
[{KC.N2}, {KC.N0, KC.N2}, {KC.N0}, {}],
)
self.kb.test(
'Layertap tap interrupted',
[(2, True), (0, True), 200, (0, False), (2, False)],
[{KC.A}, {}],
)
self.kb.test(
'Layertap tap interrupted by holdtap',
[(3, True), (2, True), (2, False), (3, False)],
[{KC.C}, {}],
)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@@ -6,30 +6,25 @@ from kmk.modules.oneshot import OneShot
from tests.keyboard_test import KeyboardTest from tests.keyboard_test import KeyboardTest
class TestOneshot(unittest.TestCase): class TestHoldTap(unittest.TestCase):
def test_oneshot(self): def test_oneshot(self):
t_within = 2 * KeyboardTest.loop_delay_ms
t_after = 7 * KeyboardTest.loop_delay_ms
timeout = (t_after + t_within) // 2
# overide default timeouts
OneShot.tap_time = timeout
keyboard = KeyboardTest( keyboard = KeyboardTest(
[Layers(), OneShot()], [Layers(), OneShot()],
[ [
[ [
KC.OS(KC.MO(1)), KC.OS(KC.MO(1), tap_time=50),
KC.MO(1), KC.MO(1),
KC.C, KC.C,
KC.D, KC.D,
KC.OS(KC.E), KC.OS(KC.E, tap_time=50),
KC.OS(KC.F), KC.OS(KC.F, tap_time=50),
], ],
[KC.N0, KC.N1, KC.N2, KC.N3, KC.OS(KC.LSFT), KC.TRNS], [KC.N0, KC.N1, KC.N2, KC.N3, KC.OS(KC.LSFT, tap_time=50), KC.TRNS],
], ],
debug_enabled=False, debug_enabled=False,
) )
t_within = 40
t_after = 60
keyboard.test( keyboard.test(
'OS timed out', 'OS timed out',

View File

@@ -37,8 +37,11 @@ class TestStickyMod(unittest.TestCase):
[ [
(4, True), (4, True),
(4, False), (4, False),
100,
(4, True), (4, True),
200,
(4, False), (4, False),
100,
(1, True), (1, True),
(1, False), (1, False),
], ],
@@ -58,19 +61,26 @@ class TestStickyMod(unittest.TestCase):
(1, True), (1, True),
(1, False), (1, False),
(2, True), (2, True),
200,
(0, True), (0, True),
50,
(0, False), (0, False),
50,
(0, True), (0, True),
50,
(0, False), (0, False),
(1, True), (1, True),
(1, False), (1, False),
50,
(1, True), (1, True),
(1, False), (1, False),
(0, True), (0, True),
50,
(0, False), (0, False),
(3, True), (3, True),
(3, False), (3, False),
(2, False), (2, False),
100,
(4, True), (4, True),
(4, False), (4, False),
(1, True), (1, True),

View File

@@ -7,7 +7,6 @@ from tests.keyboard_test import KeyboardTest
class TestStringSubstitution(unittest.TestCase): class TestStringSubstitution(unittest.TestCase):
def setUp(self) -> None: def setUp(self) -> None:
self.delay = KeyboardTest.loop_delay_ms
self.symbols = '`-=[]\\;\',./~!@#$%^&*()_+{}|:\"<>?' self.symbols = '`-=[]\\;\',./~!@#$%^&*()_+{}|:\"<>?'
self.everything = ALL_NUMBERS + ALL_ALPHAS + ALL_ALPHAS.lower() + self.symbols self.everything = ALL_NUMBERS + ALL_ALPHAS + ALL_ALPHAS.lower() + self.symbols
self.test_dictionary = { self.test_dictionary = {
@@ -47,21 +46,21 @@ class TestStringSubstitution(unittest.TestCase):
# that results in a corresponding match, as that key is never sent # that results in a corresponding match, as that key is never sent
self.keyboard.test( self.keyboard.test(
'multi-character key, single-character value', 'multi-character key, single-character value',
[(0, True), (0, False), (0, True), (0, False), self.delay], [(0, True), (0, False), (0, True), (0, False), 50],
[{KC.A}, {}, {KC.BACKSPACE}, {}, {KC.B}, {}], [{KC.A}, {}, {KC.BACKSPACE}, {}, {KC.B}, {}],
) )
# note: the pressed key is never sent here, as the event is # note: the pressed key is never sent here, as the event is
# intercepted and the replacement is sent instead # intercepted and the replacement is sent instead
self.keyboard.test( self.keyboard.test(
'multi-character value, single-character key', 'multi-character value, single-character key',
[(1, True), (1, False), self.delay], [(1, True), (1, False), 50],
[{KC.A}, {}, {KC.A}, {}], [{KC.A}, {}, {KC.A}, {}],
) )
# modifiers are force-released if there's a match, # modifiers are force-released if there's a match,
# so the keyup event for them isn't sent # so the keyup event for them isn't sent
self.keyboard.test( self.keyboard.test(
'shifted alphanumeric or symbol in key and/or value', 'shifted alphanumeric or symbol in key and/or value',
[(3, True), (2, True), (2, False), (3, False), self.delay], [(3, True), (2, True), (2, False), (3, False), 50],
[{KC.LSHIFT}, {KC.LSHIFT, KC.N2}, {}], [{KC.LSHIFT}, {KC.LSHIFT, KC.N2}, {}],
) )
self.keyboard.test( self.keyboard.test(
@@ -75,7 +74,7 @@ class TestStringSubstitution(unittest.TestCase):
(5, False), (5, False),
(5, True), (5, True),
(5, False), (5, False),
self.delay, 10,
], ],
[ [
{KC.D}, {KC.D},
@@ -94,7 +93,7 @@ class TestStringSubstitution(unittest.TestCase):
) )
self.keyboard.test( self.keyboard.test(
'the presence of non-shift modifiers prevents a multi-character match', 'the presence of non-shift modifiers prevents a multi-character match',
[(4, True), (0, True), (0, False), (0, True), (0, False), (4, False)], [(4, True), (0, True), (0, False), (0, True), (0, False), (4, False), 50],
[ [
{KC.LCTRL}, {KC.LCTRL},
{KC.LCTRL, KC.A}, {KC.LCTRL, KC.A},
@@ -106,7 +105,7 @@ class TestStringSubstitution(unittest.TestCase):
) )
self.keyboard.test( self.keyboard.test(
'the presence of non-shift modifiers prevents a single-character match', 'the presence of non-shift modifiers prevents a single-character match',
[(4, True), (1, True), (1, False), (4, False)], [(4, True), (1, True), (1, False), (4, False), 50],
[ [
{KC.LCTRL}, {KC.LCTRL},
{KC.LCTRL, KC.B}, {KC.LCTRL, KC.B},
@@ -116,7 +115,7 @@ class TestStringSubstitution(unittest.TestCase):
) )
self.keyboard.test( self.keyboard.test(
'the presence of non-shift modifiers resets current potential matches', 'the presence of non-shift modifiers resets current potential matches',
[(0, True), (0, False), (4, True), (0, True), (0, False), (4, False)], [(0, True), (0, False), (4, True), (0, True), (0, False), (4, False), 50],
[ [
{KC.A}, {KC.A},
{}, {},
@@ -129,15 +128,7 @@ class TestStringSubstitution(unittest.TestCase):
self.keyboard.test( self.keyboard.test(
'match found and replaced when there are preceding characters', 'match found and replaced when there are preceding characters',
[ [(5, True), (5, False), (0, True), (0, False), (0, True), (0, False), 50],
(5, True),
(5, False),
(0, True),
(0, False),
(0, True),
(0, False),
self.delay,
],
[ [
{KC.C}, {KC.C},
{}, {},
@@ -151,15 +142,7 @@ class TestStringSubstitution(unittest.TestCase):
) )
self.keyboard.test( self.keyboard.test(
'match found and replaced when there are trailing characters, and the trailing characters are sent', 'match found and replaced when there are trailing characters, and the trailing characters are sent',
[ [(0, True), (0, False), (0, True), (0, False), (5, True), (5, False), 50],
(0, True),
(0, False),
(0, True),
(0, False),
(5, True),
(5, False),
self.delay,
],
[ [
{KC.A}, {KC.A},
{}, {},
@@ -173,7 +156,7 @@ class TestStringSubstitution(unittest.TestCase):
) )
self.keyboard.test( self.keyboard.test(
'no match', 'no match',
[(0, True), (0, False), (2, True), (2, False)], [(0, True), (0, False), (2, True), (2, False), 50],
[ [
{KC.A}, {KC.A},
{}, {},
@@ -200,7 +183,7 @@ class TestStringSubstitution(unittest.TestCase):
(6, False), (6, False),
(0, True), (0, True),
(0, False), (0, False),
10 * self.delay, 50,
], ],
[ [
{KC.D}, {KC.D},
@@ -258,7 +241,7 @@ class TestStringSubstitution(unittest.TestCase):
# send the unreachable match "cccc" after matching "ccc" # send the unreachable match "cccc" after matching "ccc"
(5, True), (5, True),
(5, False), (5, False),
self.delay, 10,
], ],
[ [
{KC.C}, {KC.C},
@@ -289,7 +272,7 @@ class TestStringSubstitution(unittest.TestCase):
(0, True), (0, True),
(0, False), (0, False),
(7, False), (7, False),
self.delay, 10,
], ],
[ [
{KC.RSHIFT}, {KC.RSHIFT},
@@ -320,6 +303,7 @@ class TestStringSubstitution(unittest.TestCase):
(0, False), (0, False),
(4, False), (4, False),
(8, False), (8, False),
10,
], ],
[ [
{KC.RALT}, {KC.RALT},
@@ -341,6 +325,7 @@ class TestStringSubstitution(unittest.TestCase):
(1, False), (1, False),
(3, False), (3, False),
(8, False), (8, False),
10,
], ],
[ [
{KC.RALT}, {KC.RALT},

View File

@@ -9,39 +9,36 @@ from tests.keyboard_test import KeyboardTest
class TestTapDance(unittest.TestCase): class TestTapDance(unittest.TestCase):
def setUp(self): def setUp(self):
self.t_within = 2 * KeyboardTest.loop_delay_ms
self.t_after = 10 * KeyboardTest.loop_delay_ms
tap_time = (self.t_after + self.t_within) // 4 * 3
TapDance.tap_time = tap_time
self.keyboard = KeyboardTest( self.keyboard = KeyboardTest(
[Layers(), HoldTap(), TapDance()], [Layers(), HoldTap(), TapDance()],
[ [
[ [
KC.TD(KC.N0, KC.N1), KC.TD(KC.N0, KC.N1, tap_time=50),
KC.TD( KC.TD(
KC.HT(KC.N1, KC.A), KC.HT(KC.N1, KC.A, tap_time=50),
KC.HT(KC.N2, KC.B, tap_time=2 * tap_time), KC.HT(KC.N2, KC.B, tap_time=100),
), ),
KC.TD(KC.HT(KC.X, KC.Y), KC.X, tap_time=0), KC.TD(KC.HT(KC.X, KC.Y, tap_time=50), KC.X, tap_time=0),
KC.TD(KC.LT(1, KC.N3), KC.X, tap_time=0), KC.TD(KC.LT(1, KC.N3, tap_time=50), KC.X, tap_time=0),
KC.N4, KC.N4,
], ],
[KC.N9, KC.N8, KC.N7, KC.N6, KC.N5], [KC.N9, KC.N8, KC.N7, KC.N6, KC.N5],
], ],
debug_enabled=False, debug_enabled=False,
) )
self.t_within = 40
self.t_after = 60
def test_normal_key(self): def test_normal_key(self):
keyboard = self.keyboard keyboard = self.keyboard
t_within = self.t_within t_within = self.t_within
t_after = self.t_after
keyboard.test('Tap x1', [(0, True), (0, False)], [{KC.N0}, {}]) keyboard.test('Tap x1', [(0, True), (0, False), t_after], [{KC.N0}, {}])
keyboard.test( keyboard.test(
'Tap x2', 'Tap x2',
[(0, True), (0, False), t_within, (0, True), (0, False)], [(0, True), (0, False), t_within, (0, True), (0, False), t_after],
[{KC.N1}, {}], [{KC.N1}, {}],
) )
@@ -54,6 +51,7 @@ class TestTapDance(unittest.TestCase):
(0, False), (0, False),
(0, True), (0, True),
(0, False), (0, False),
t_after,
], ],
[{KC.N1}, {}, {KC.N0}, {}], [{KC.N1}, {}, {KC.N0}, {}],
) )
@@ -95,11 +93,11 @@ class TestTapDance(unittest.TestCase):
t_within = self.t_within t_within = self.t_within
t_after = self.t_after t_after = self.t_after
keyboard.test('Tap x1', [(1, True), (1, False)], [{KC.N1}, {}]) keyboard.test('Tap x1', [(1, True), (1, False), t_after], [{KC.N1}, {}])
keyboard.test( keyboard.test(
'Tap x2', 'Tap x2',
[(1, True), (1, False), t_within, (1, True), (1, False)], [(1, True), (1, False), t_within, (1, True), (1, False), 2 * t_after],
[{KC.N2}, {}], [{KC.N2}, {}],
) )
@@ -133,7 +131,7 @@ class TestTapDance(unittest.TestCase):
keyboard.test( keyboard.test(
'', '',
[(0, True), (0, False), t_within, (1, True), (1, False)], [(0, True), (0, False), t_within, (1, True), (1, False), t_after],
[{KC.N0}, {}, {KC.N1}, {}], [{KC.N0}, {}, {KC.N1}, {}],
) )
@@ -147,6 +145,7 @@ class TestTapDance(unittest.TestCase):
(2, False), (2, False),
t_after, t_after,
(0, False), (0, False),
t_after,
], ],
[{KC.N1}, {KC.N1, KC.X}, {KC.N1}, {}], [{KC.N1}, {KC.N1, KC.X}, {KC.N1}, {}],
) )
@@ -161,6 +160,7 @@ class TestTapDance(unittest.TestCase):
(0, False), (0, False),
t_after, t_after,
(2, False), (2, False),
t_after,
], ],
[{KC.X}, {KC.X, KC.N0}, {KC.X}, {}], [{KC.X}, {KC.X, KC.N0}, {KC.X}, {}],
) )
@@ -172,7 +172,7 @@ class TestTapDance(unittest.TestCase):
keyboard.test( keyboard.test(
'', '',
[(3, True), (3, False), t_within, (1, True), (1, False)], [(3, True), (3, False), t_within, (1, True), (1, False), t_after],
[{KC.N3}, {}, {KC.N1}, {}], [{KC.N3}, {}, {KC.N1}, {}],
) )