Compare commits
16 Commits
feature-bo
...
feature-gl
Author | SHA1 | Date | |
---|---|---|---|
|
ddaf9946eb | ||
|
5448cb4479 | ||
|
23d7c2d670 | ||
|
20ba48b623 | ||
|
76e6feda6f | ||
|
3e13c8c321 | ||
|
ba06d3c8a5 | ||
|
26bf630608 | ||
|
878fe0deca | ||
|
adff02e88a | ||
|
55b3a3a9b1 | ||
|
3c796c16f8 | ||
|
b9c85c02e2 | ||
|
bff7584fe0 | ||
|
47fe859e11 | ||
|
fd700cff44 |
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
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.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
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.
|
11
README.md
11
README.md
@@ -9,10 +9,15 @@ 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)! In particular, swing by the Zulip chat
|
community](https://kmkfw.zulipchat.com)!
|
||||||
*before* opening a GitHub Issue about configuration, documentation, etc.
|
|
||||||
concerns.
|
If you ask for help in chat or open a bug report, if possible
|
||||||
|
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!
|
||||||
|
@@ -20,8 +20,6 @@ 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,
|
||||||
|
98
docs/en/combo_layers.md
Normal file
98
docs/en/combo_layers.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
## 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()
|
||||||
|
```
|
@@ -33,6 +33,11 @@ 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
|
||||||
@@ -40,6 +45,7 @@ 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:
|
||||||
|
|
||||||
|
@@ -7,3 +7,6 @@ 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!
|
||||||
|
@@ -49,3 +49,6 @@ class Extension:
|
|||||||
|
|
||||||
def on_powersave_disable(self, keyboard):
|
def on_powersave_disable(self, keyboard):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def deinit(self, keyboard):
|
||||||
|
pass
|
||||||
|
@@ -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,32 +109,9 @@ class RGB(Extension):
|
|||||||
pixels=None,
|
pixels=None,
|
||||||
refresh_rate=60,
|
refresh_rate=60,
|
||||||
):
|
):
|
||||||
if pixels is None:
|
self.pixel_pin = pixel_pin
|
||||||
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
|
||||||
@@ -153,8 +130,11 @@ 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(
|
||||||
@@ -227,6 +207,28 @@ 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):
|
||||||
@@ -247,6 +249,10 @@ 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
|
||||||
|
@@ -475,7 +475,9 @@ class KeyAttrDict:
|
|||||||
break
|
break
|
||||||
|
|
||||||
if not maybe_key:
|
if not maybe_key:
|
||||||
raise ValueError(f'Invalid key: {name}')
|
if debug.enabled:
|
||||||
|
debug(f'Invalid key: {name}')
|
||||||
|
return KC.NO
|
||||||
|
|
||||||
if debug.enabled:
|
if debug.enabled:
|
||||||
debug(f'{name}: {maybe_key}')
|
debug(f'{name}: {maybe_key}')
|
||||||
|
@@ -16,13 +16,20 @@ 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(__name__)
|
debug = Debug('kmk.keyboard')
|
||||||
|
|
||||||
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
|
||||||
@@ -59,7 +66,6 @@ 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
|
||||||
@@ -75,47 +81,24 @@ 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 ''.join(
|
return self.__class__.__name__
|
||||||
[
|
|
||||||
'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 self.axes and debug.enabled:
|
if debug.enabled:
|
||||||
debug(f'axes={self.axes}')
|
if self.keys_pressed:
|
||||||
|
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 KeyError as e:
|
except Exception as err:
|
||||||
if debug.enabled:
|
debug_error(self._hid_helper, 'send', err)
|
||||||
debug(f'HidNotFound(HIDReportType={e})')
|
|
||||||
|
|
||||||
self.hid_pending = False
|
self.hid_pending = False
|
||||||
|
|
||||||
@@ -125,35 +108,32 @@ 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(f'CoordMappingNotFound(ic={int_coord})')
|
debug('no such int_coord: ', int_coord)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
for layer in self.active_layers:
|
for layer in self.active_layers:
|
||||||
try:
|
try:
|
||||||
layer_key = self.keymap[layer][idx]
|
key = self.keymap[layer][idx]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
layer_key = None
|
key = None
|
||||||
if debug.enabled:
|
if debug.enabled:
|
||||||
debug(f'KeymapIndexError(idx={idx}, layer={layer})')
|
debug('keymap IndexError: idx=', idx, ' layer=', layer)
|
||||||
|
|
||||||
if not layer_key or layer_key == KC.TRNS:
|
if not key or key == KC.TRNS:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return layer_key
|
return 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:
|
||||||
@@ -161,18 +141,16 @@ 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(f'KeyNotPressed(ic={int_coord})')
|
debug('release w/o press: ', 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:
|
||||||
if debug.enabled:
|
return
|
||||||
debug(f'MatrixUndefinedCoordinate(ic={int_coord})')
|
|
||||||
return self
|
|
||||||
|
|
||||||
if debug.enabled:
|
if debug.enabled:
|
||||||
debug(f'KeyResolution(key={key})')
|
debug(kevent, ': ', key)
|
||||||
|
|
||||||
self.pre_process_key(key, is_pressed, int_coord)
|
self.pre_process_key(key, is_pressed, int_coord)
|
||||||
|
|
||||||
@@ -197,7 +175,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 in self._coordkeys_pressed.keys():
|
if ksf.int_coord is not None:
|
||||||
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
|
||||||
@@ -238,8 +216,7 @@ class KMKKeyboard:
|
|||||||
if key is None:
|
if key is None:
|
||||||
break
|
break
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
if debug.enabled:
|
debug_error(module, 'process_key', err)
|
||||||
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:
|
||||||
@@ -249,18 +226,20 @@ 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(f'ReleaseKeyError(ic={int_coord})')
|
debug('release w/o press:', 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, coord_int: Optional[int] = None
|
self, key: Key, is_pressed: bool, int_coord: Optional[int] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
if is_pressed:
|
if is_pressed:
|
||||||
key.on_press(self, coord_int)
|
key.on_press(self, int_coord)
|
||||||
else:
|
else:
|
||||||
key.on_release(self, coord_int)
|
key.on_release(self, int_coord)
|
||||||
|
|
||||||
def resume_process_key(
|
def resume_process_key(
|
||||||
self,
|
self,
|
||||||
@@ -268,8 +247,9 @@ 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) + 1
|
index = self.modules.index(module) + (0 if reprocess else 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
|
||||||
)
|
)
|
||||||
@@ -386,14 +366,15 @@ 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,
|
||||||
@@ -409,96 +390,124 @@ 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:
|
||||||
if debug.enabled:
|
debug_error(module, 'before_matrix_scan', err)
|
||||||
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:
|
||||||
if debug.enabled:
|
debug_error(ext, 'before_matrix_scan', err)
|
||||||
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:
|
||||||
if debug.enabled:
|
debug_error(module, 'after_matrix_scan', err)
|
||||||
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:
|
||||||
if debug.enabled:
|
debug_error(ext, 'after_matrix_scan', err)
|
||||||
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:
|
||||||
if debug.enabled:
|
debug_error(module, 'before_hid_send', err)
|
||||||
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:
|
||||||
if debug.enabled:
|
debug_error(ext, 'before_hid_send', err)
|
||||||
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:
|
||||||
if debug.enabled:
|
debug_error(module, 'after_hid_send', err)
|
||||||
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:
|
||||||
if debug.enabled:
|
debug_error(ext, 'after_hid_send', err)
|
||||||
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:
|
||||||
if debug.enabled:
|
debug_error(module, 'powersave_enable', err)
|
||||||
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:
|
||||||
if debug.enabled:
|
debug_error(ext, 'powersave_enable', err)
|
||||||
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:
|
||||||
if debug.enabled:
|
debug_error(module, 'powersave_disable', err)
|
||||||
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:
|
||||||
if debug.enabled:
|
debug_error(ext, 'powersave_disable', err)
|
||||||
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)
|
||||||
@@ -508,6 +517,7 @@ 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,
|
||||||
@@ -519,29 +529,22 @@ 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
|
||||||
|
|
||||||
self._init_sanity_check()
|
if debug.enabled:
|
||||||
|
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:
|
||||||
debug(f'init: {self}')
|
import gc
|
||||||
|
|
||||||
|
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()
|
||||||
@@ -579,7 +582,6 @@ 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()
|
||||||
|
|
||||||
@@ -588,6 +590,3 @@ 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()
|
|
||||||
|
@@ -41,3 +41,6 @@ class Module:
|
|||||||
|
|
||||||
def on_powersave_disable(self, keyboard):
|
def on_powersave_disable(self, keyboard):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def deinit(self, keyboard):
|
||||||
|
pass
|
||||||
|
@@ -8,6 +8,9 @@ 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:
|
||||||
@@ -214,7 +217,7 @@ class Combos(Module):
|
|||||||
combo.insert(key, int_coord)
|
combo.insert(key, int_coord)
|
||||||
combo._state = _ComboState.MATCHING
|
combo._state = _ComboState.MATCHING
|
||||||
|
|
||||||
key = combo.result
|
key = None
|
||||||
break
|
break
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -301,10 +304,14 @@ 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
|
||||||
|
|
||||||
|
212
kmk/modules/glidepoint.py
Normal file
212
kmk/modules/glidepoint.py
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
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)
|
@@ -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 not KC.get('HT'):
|
if KC.get('HT') == KC.NO:
|
||||||
make_argumented_key(
|
make_argumented_key(
|
||||||
validator=HoldTapKeyMeta,
|
validator=HoldTapKeyMeta,
|
||||||
names=('HT',),
|
names=('HT',),
|
||||||
@@ -83,6 +83,11 @@ 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
|
||||||
@@ -93,15 +98,12 @@ 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 (
|
if key.meta.tap_interrupted and is_pressed:
|
||||||
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
|
||||||
@@ -110,10 +112,8 @@ 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
|
||||||
|
|
||||||
elif send_buffer:
|
if 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,8 +221,11 @@ 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)
|
keyboard.resume_process_key(self, key, is_pressed, int_coord, reprocess)
|
||||||
|
if isinstance(key.meta, HoldTapKeyMeta):
|
||||||
|
reprocess = True
|
||||||
|
|
||||||
self.key_buffer.clear()
|
self.key_buffer.clear()
|
||||||
|
|
||||||
|
@@ -36,9 +36,15 @@ 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'''
|
||||||
|
|
||||||
def __init__(self):
|
_active_combo = None
|
||||||
|
|
||||||
|
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',),
|
||||||
@@ -46,9 +52,7 @@ class Layers(HoldTap):
|
|||||||
on_release=self._mo_released,
|
on_release=self._mo_released,
|
||||||
)
|
)
|
||||||
make_argumented_key(
|
make_argumented_key(
|
||||||
validator=layer_key_validator,
|
validator=layer_key_validator, names=('DF',), on_press=self._df_pressed
|
||||||
names=('DF',),
|
|
||||||
on_press=self._df_pressed,
|
|
||||||
)
|
)
|
||||||
make_argumented_key(
|
make_argumented_key(
|
||||||
validator=layer_key_validator,
|
validator=layer_key_validator,
|
||||||
@@ -57,14 +61,10 @@ class Layers(HoldTap):
|
|||||||
on_release=self._lm_released,
|
on_release=self._lm_released,
|
||||||
)
|
)
|
||||||
make_argumented_key(
|
make_argumented_key(
|
||||||
validator=layer_key_validator,
|
validator=layer_key_validator, names=('TG',), on_press=self._tg_pressed
|
||||||
names=('TG',),
|
|
||||||
on_press=self._tg_pressed,
|
|
||||||
)
|
)
|
||||||
make_argumented_key(
|
make_argumented_key(
|
||||||
validator=layer_key_validator,
|
validator=layer_key_validator, names=('TO',), on_press=self._to_pressed
|
||||||
names=('TO',),
|
|
||||||
on_press=self._to_pressed,
|
|
||||||
)
|
)
|
||||||
make_argumented_key(
|
make_argumented_key(
|
||||||
validator=layer_key_validator_lt,
|
validator=layer_key_validator_lt,
|
||||||
@@ -83,67 +83,102 @@ class Layers(HoldTap):
|
|||||||
'''
|
'''
|
||||||
Switches the default layer
|
Switches the default layer
|
||||||
'''
|
'''
|
||||||
keyboard.active_layers[-1] = key.meta.layer
|
self.activate_layer(keyboard, key.meta.layer, as_default=True)
|
||||||
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
|
||||||
'''
|
'''
|
||||||
keyboard.active_layers.insert(0, key.meta.layer)
|
self.activate_layer(keyboard, key.meta.layer)
|
||||||
self._print_debug(keyboard)
|
|
||||||
|
|
||||||
@staticmethod
|
def _mo_released(self, key, keyboard, *args, **kwargs):
|
||||||
def _mo_released(key, keyboard, *args, **kwargs):
|
self.deactivate_layer(keyboard, key.meta.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(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
|
||||||
'''
|
'''
|
||||||
# Sets the timer start and acts like MO otherwise
|
keyboard.hid_pending = True
|
||||||
keyboard.add_key(key.meta.kc)
|
keyboard.keys_pressed.add(key.meta.kc)
|
||||||
self._mo_pressed(key, keyboard, *args, **kwargs)
|
self.activate_layer(keyboard, key.meta.layer)
|
||||||
|
|
||||||
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.remove_key(key.meta.kc)
|
keyboard.hid_pending = True
|
||||||
self._mo_released(key, keyboard, *args, **kwargs)
|
keyboard.keys_pressed.discard(key.meta.kc)
|
||||||
|
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
|
||||||
try:
|
if key.meta.layer in keyboard.active_layers:
|
||||||
del_idx = keyboard.active_layers.index(key.meta.layer)
|
self.deactivate_layer(keyboard, key.meta.layer)
|
||||||
del keyboard.active_layers[del_idx]
|
else:
|
||||||
except ValueError:
|
self.activate_layer(keyboard, key.meta.layer)
|
||||||
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
|
||||||
|
@@ -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, (0, key, False))
|
self.key_buffer.insert(0, (None, 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))
|
||||||
|
@@ -42,12 +42,11 @@ 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:
|
||||||
try:
|
|
||||||
key_code = KC[char]
|
key_code = KC[char]
|
||||||
|
if key_code == KC.NO:
|
||||||
|
raise ValueError(f'Invalid character in dictionary: {char}')
|
||||||
shifted = char.isupper() or key_code.has_modifiers == {2}
|
shifted = char.isupper() or key_code.has_modifiers == {2}
|
||||||
self._characters.append(Character(key_code, shifted))
|
self._characters.append(Character(key_code, shifted))
|
||||||
except ValueError:
|
|
||||||
raise ValueError(f'Invalid character in dictionary: {char}')
|
|
||||||
|
|
||||||
def next_character(self) -> None:
|
def next_character(self) -> None:
|
||||||
'''Increment the current index for this phrase'''
|
'''Increment the current index for this phrase'''
|
||||||
|
@@ -17,7 +17,7 @@ class TapDanceKeyMeta:
|
|||||||
ht_key = KC.HT(
|
ht_key = KC.HT(
|
||||||
tap=key,
|
tap=key,
|
||||||
hold=key,
|
hold=key,
|
||||||
prefer_hold=False,
|
prefer_hold=True,
|
||||||
tap_interrupted=False,
|
tap_interrupted=False,
|
||||||
tap_time=self.tap_time,
|
tap_time=self.tap_time,
|
||||||
)
|
)
|
||||||
|
13
kmk/utils.py
13
kmk/utils.py
@@ -1,3 +1,8 @@
|
|||||||
|
try:
|
||||||
|
from typing import Optional
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
from supervisor import ticks_ms
|
from supervisor import ticks_ms
|
||||||
|
|
||||||
|
|
||||||
@@ -16,8 +21,12 @@ 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) -> None:
|
def __call__(self, *message: str, name: Optional[str] = None) -> None:
|
||||||
print(f'{ticks_ms()} {self.name}: {message}')
|
if not name:
|
||||||
|
name = self.name
|
||||||
|
print(ticks_ms(), end=' ')
|
||||||
|
print(name, end=': ')
|
||||||
|
print(*message, sep='')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enabled(self) -> bool:
|
def enabled(self) -> bool:
|
||||||
|
@@ -23,6 +23,8 @@ def code2name(code):
|
|||||||
|
|
||||||
|
|
||||||
class KeyboardTest:
|
class KeyboardTest:
|
||||||
|
loop_delay_ms = 2
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
modules,
|
modules,
|
||||||
@@ -128,4 +130,4 @@ class KeyboardTest:
|
|||||||
|
|
||||||
def do_main_loop(self):
|
def do_main_loop(self):
|
||||||
self.keyboard._main_loop()
|
self.keyboard._main_loop()
|
||||||
time.sleep(0.002)
|
time.sleep(self.loop_delay_ms / 1000)
|
||||||
|
@@ -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()],
|
[CapsWord(timeout=2 * KeyboardTest.loop_delay_ms)],
|
||||||
[
|
[
|
||||||
[KC.CW, KC.A, KC.Z, KC.N1, KC.N0, KC.SPC],
|
[KC.CW, KC.A, KC.Z, KC.N1, KC.N0, KC.SPC],
|
||||||
],
|
],
|
||||||
|
@@ -1,28 +1,36 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from kmk.keys import KC
|
from kmk.keys import KC
|
||||||
from kmk.modules.combos import Chord, Combos, Sequence
|
from kmk.modules.combos import Chord, Combo, 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=80),
|
Chord((KC.C, KC.D), KC.Z, timeout=2 * timeout),
|
||||||
Chord((KC.C, KCMO), KC.Z),
|
Chord((KC.C, KCMO), KC.Z),
|
||||||
Chord((KC.F, KC.G), KC.Z, timeout=130),
|
Chord((KC.F, KC.G), KC.Z, timeout=3 * timeout),
|
||||||
Sequence((KC.N1, KC.N2, KC.N3), KC.Y, timeout=50),
|
Sequence((KC.N1, KC.N2, KC.N3), KC.Y),
|
||||||
Sequence((KC.N1, KC.N2), KC.X, timeout=50),
|
Sequence((KC.N1, KC.N2), KC.X),
|
||||||
Sequence((KC.N3, KC.N4), KC.Z, timeout=100),
|
Sequence((KC.N3, KC.N4), KC.Z, timeout=2 * timeout),
|
||||||
Sequence((KC.N1, KC.N1, KC.N1), KC.W, timeout=50),
|
Sequence((KC.N1, KC.N1, KC.N1), KC.W),
|
||||||
Sequence((KC.N3, KC.N2, KC.N1), KC.Y, timeout=50, fast_reset=False),
|
Sequence((KC.N3, KC.N2, KC.N1), KC.Y, fast_reset=False),
|
||||||
Sequence((KC.LEADER, KC.N1), KC.V, timeout=50),
|
Sequence((KC.LEADER, KC.N1), KC.V),
|
||||||
]
|
]
|
||||||
self.keyboard = KeyboardTest(
|
self.keyboard = KeyboardTest(
|
||||||
[combos, layers],
|
[combos, layers],
|
||||||
@@ -33,9 +41,6 @@ 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
|
||||||
|
@@ -10,20 +10,37 @@ 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('HT tap behaviour', [(0, True), 100, (0, False)], [{KC.A}, {}])
|
keyboard.test(
|
||||||
|
'HT tap behaviour', [(0, True), t_within, (0, False)], [{KC.A}, {}]
|
||||||
|
)
|
||||||
|
|
||||||
keyboard.test(
|
keyboard.test(
|
||||||
'HT hold behaviour', [(0, True), 350, (0, False)], [{KC.LCTL}, {}]
|
'HT hold behaviour', [(0, True), t_after, (0, False)], [{KC.LCTL}, {}]
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO test multiple mods being held
|
# TODO test multiple mods being held
|
||||||
@@ -31,74 +48,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), 100, (0, False), (3, True), (3, False)],
|
[(0, True), t_within, (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), 100, (3, True), 250, (0, False), (3, False)],
|
[(0, True), t_within, (3, True), t_after, (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), 100, (3, True), (3, False), 250, (0, False)],
|
[(0, True), t_within, (3, True), (3, False), t_after, (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), 350, (0, False), (3, True), (3, False)],
|
[(0, True), t_after, (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), 350, (3, True), (0, False), (3, False)],
|
[(0, True), t_after, (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), 350, (3, True), (3, False), (0, False)],
|
[(0, True), t_after, (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), 100, (1, False), (3, True), (3, False)],
|
[(1, True), t_within, (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), 100, (3, True), 250, (1, False), (3, False)],
|
[(1, True), t_within, (3, True), (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), 100, (3, True), (3, False), 250, (1, False)],
|
[(1, True), t_within, (3, True), (3, False), (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), 350, (1, False), (3, True), (3, False)],
|
[(1, True), t_after, (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), 350, (3, True), (1, False), (3, False)],
|
[(1, True), t_after, (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), 350, (3, True), (3, False), (1, False)],
|
[(1, True), t_after, (3, True), (3, False), (1, False)],
|
||||||
[{KC.N4}, {}],
|
[{KC.N4}, {}],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -106,9 +123,9 @@ class TestHoldTap(unittest.TestCase):
|
|||||||
'LT after tap time nested -> hold behavior',
|
'LT after tap time nested -> hold behavior',
|
||||||
[
|
[
|
||||||
(0, True),
|
(0, True),
|
||||||
350,
|
t_after,
|
||||||
(1, True),
|
(1, True),
|
||||||
350,
|
t_after,
|
||||||
(3, True),
|
(3, True),
|
||||||
(3, False),
|
(3, False),
|
||||||
(1, False),
|
(1, False),
|
||||||
@@ -118,26 +135,25 @@ 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, tap_time=50),
|
KC.HT(KC.N1, KC.LCTL),
|
||||||
KC.HT(KC.N2, KC.LSFT, tap_interrupted=True, tap_time=50),
|
KC.HT(KC.N2, KC.LSFT, tap_interrupted=True),
|
||||||
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',
|
||||||
@@ -156,7 +172,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.LCTL},
|
{KC.LSFT},
|
||||||
{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},
|
||||||
@@ -208,7 +224,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.LCTL},
|
{KC.N3},
|
||||||
{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},
|
||||||
@@ -275,21 +291,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, tap_time=50),
|
KC.HT(KC.A, KC.B, repeat=HoldTapRepeat.ALL),
|
||||||
KC.HT(KC.A, KC.B, repeat=HoldTapRepeat.TAP, tap_time=50),
|
KC.HT(KC.A, KC.B, repeat=HoldTapRepeat.TAP),
|
||||||
KC.HT(KC.A, KC.B, repeat=HoldTapRepeat.HOLD, tap_time=50),
|
KC.HT(KC.A, KC.B, repeat=HoldTapRepeat.HOLD),
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
debug_enabled=False,
|
debug_enabled=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
t_within = 40
|
|
||||||
t_after = 60
|
|
||||||
|
|
||||||
keyboard.test(
|
keyboard.test(
|
||||||
'repeat tap',
|
'repeat tap',
|
||||||
[
|
[
|
||||||
@@ -301,7 +317,6 @@ 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}, {}],
|
||||||
)
|
)
|
||||||
@@ -317,7 +332,6 @@ 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}, {}],
|
||||||
)
|
)
|
||||||
@@ -334,7 +348,6 @@ 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}, {}],
|
||||||
)
|
)
|
@@ -123,12 +123,10 @@ 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):
|
||||||
with self.assertRaises(ValueError):
|
assert KC.INVALID_KEY == KC.NO
|
||||||
KC.INVALID_KEY
|
|
||||||
|
|
||||||
def test_invalid_key_lower(self):
|
def test_invalid_key_lower(self):
|
||||||
with self.assertRaises(ValueError):
|
assert KC.invalid_key == KC.NO
|
||||||
KC.invalid_key
|
|
||||||
|
|
||||||
def test_custom_key(self):
|
def test_custom_key(self):
|
||||||
created = make_key(
|
created = make_key(
|
||||||
@@ -168,12 +166,10 @@ 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):
|
||||||
with self.assertRaises(ValueError):
|
assert KC.INVALID_KEY == KC.NO
|
||||||
KC['NOT_A_VALID_KEY']
|
|
||||||
|
|
||||||
def test_invalid_key_lower(self):
|
def test_invalid_key_lower(self):
|
||||||
with self.assertRaises(ValueError):
|
assert KC.invalid_key == KC.NO
|
||||||
KC['not_a_valid_key']
|
|
||||||
|
|
||||||
def test_custom_key(self):
|
def test_custom_key(self):
|
||||||
created = make_key(
|
created = make_key(
|
||||||
@@ -218,10 +214,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 None
|
assert KC.get('INVALID_KEY') is KC.NO
|
||||||
|
|
||||||
def test_invalid_key_lower(self):
|
def test_invalid_key_lower(self):
|
||||||
assert KC.get('not_a_valid_key') is None
|
assert KC.get('not_a_valid_key') is KC.NO
|
||||||
|
|
||||||
def test_custom_key(self):
|
def test_custom_key(self):
|
||||||
created = make_key(
|
created = make_key(
|
||||||
|
@@ -10,8 +10,13 @@ class TestLayers(unittest.TestCase):
|
|||||||
self.kb = KeyboardTest(
|
self.kb = KeyboardTest(
|
||||||
[Layers()],
|
[Layers()],
|
||||||
[
|
[
|
||||||
[KC.N0, KC.LM(1, KC.LCTL)],
|
[
|
||||||
[KC.A, KC.B],
|
KC.N0,
|
||||||
|
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,
|
||||||
)
|
)
|
||||||
@@ -23,6 +28,25 @@ 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()
|
||||||
|
@@ -6,25 +6,30 @@ from kmk.modules.oneshot import OneShot
|
|||||||
from tests.keyboard_test import KeyboardTest
|
from tests.keyboard_test import KeyboardTest
|
||||||
|
|
||||||
|
|
||||||
class TestHoldTap(unittest.TestCase):
|
class TestOneshot(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), tap_time=50),
|
KC.OS(KC.MO(1)),
|
||||||
KC.MO(1),
|
KC.MO(1),
|
||||||
KC.C,
|
KC.C,
|
||||||
KC.D,
|
KC.D,
|
||||||
KC.OS(KC.E, tap_time=50),
|
KC.OS(KC.E),
|
||||||
KC.OS(KC.F, tap_time=50),
|
KC.OS(KC.F),
|
||||||
],
|
],
|
||||||
[KC.N0, KC.N1, KC.N2, KC.N3, KC.OS(KC.LSFT, tap_time=50), KC.TRNS],
|
[KC.N0, KC.N1, KC.N2, KC.N3, KC.OS(KC.LSFT), KC.TRNS],
|
||||||
],
|
],
|
||||||
debug_enabled=False,
|
debug_enabled=False,
|
||||||
)
|
)
|
||||||
t_within = 40
|
|
||||||
t_after = 60
|
|
||||||
|
|
||||||
keyboard.test(
|
keyboard.test(
|
||||||
'OS timed out',
|
'OS timed out',
|
||||||
|
@@ -37,11 +37,8 @@ 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),
|
||||||
],
|
],
|
||||||
@@ -61,26 +58,19 @@ 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),
|
||||||
|
@@ -7,6 +7,7 @@ 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 = {
|
||||||
@@ -46,21 +47,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), 50],
|
[(0, True), (0, False), (0, True), (0, False), self.delay],
|
||||||
[{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), 50],
|
[(1, True), (1, False), self.delay],
|
||||||
[{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), 50],
|
[(3, True), (2, True), (2, False), (3, False), self.delay],
|
||||||
[{KC.LSHIFT}, {KC.LSHIFT, KC.N2}, {}],
|
[{KC.LSHIFT}, {KC.LSHIFT, KC.N2}, {}],
|
||||||
)
|
)
|
||||||
self.keyboard.test(
|
self.keyboard.test(
|
||||||
@@ -74,7 +75,7 @@ class TestStringSubstitution(unittest.TestCase):
|
|||||||
(5, False),
|
(5, False),
|
||||||
(5, True),
|
(5, True),
|
||||||
(5, False),
|
(5, False),
|
||||||
10,
|
self.delay,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{KC.D},
|
{KC.D},
|
||||||
@@ -93,7 +94,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), 50],
|
[(4, True), (0, True), (0, False), (0, True), (0, False), (4, False)],
|
||||||
[
|
[
|
||||||
{KC.LCTRL},
|
{KC.LCTRL},
|
||||||
{KC.LCTRL, KC.A},
|
{KC.LCTRL, KC.A},
|
||||||
@@ -105,7 +106,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), 50],
|
[(4, True), (1, True), (1, False), (4, False)],
|
||||||
[
|
[
|
||||||
{KC.LCTRL},
|
{KC.LCTRL},
|
||||||
{KC.LCTRL, KC.B},
|
{KC.LCTRL, KC.B},
|
||||||
@@ -115,7 +116,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), 50],
|
[(0, True), (0, False), (4, True), (0, True), (0, False), (4, False)],
|
||||||
[
|
[
|
||||||
{KC.A},
|
{KC.A},
|
||||||
{},
|
{},
|
||||||
@@ -128,7 +129,15 @@ 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},
|
||||||
{},
|
{},
|
||||||
@@ -142,7 +151,15 @@ 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},
|
||||||
{},
|
{},
|
||||||
@@ -156,7 +173,7 @@ class TestStringSubstitution(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
self.keyboard.test(
|
self.keyboard.test(
|
||||||
'no match',
|
'no match',
|
||||||
[(0, True), (0, False), (2, True), (2, False), 50],
|
[(0, True), (0, False), (2, True), (2, False)],
|
||||||
[
|
[
|
||||||
{KC.A},
|
{KC.A},
|
||||||
{},
|
{},
|
||||||
@@ -183,7 +200,7 @@ class TestStringSubstitution(unittest.TestCase):
|
|||||||
(6, False),
|
(6, False),
|
||||||
(0, True),
|
(0, True),
|
||||||
(0, False),
|
(0, False),
|
||||||
50,
|
10 * self.delay,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{KC.D},
|
{KC.D},
|
||||||
@@ -241,7 +258,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),
|
||||||
10,
|
self.delay,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{KC.C},
|
{KC.C},
|
||||||
@@ -272,7 +289,7 @@ class TestStringSubstitution(unittest.TestCase):
|
|||||||
(0, True),
|
(0, True),
|
||||||
(0, False),
|
(0, False),
|
||||||
(7, False),
|
(7, False),
|
||||||
10,
|
self.delay,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{KC.RSHIFT},
|
{KC.RSHIFT},
|
||||||
@@ -303,7 +320,6 @@ class TestStringSubstitution(unittest.TestCase):
|
|||||||
(0, False),
|
(0, False),
|
||||||
(4, False),
|
(4, False),
|
||||||
(8, False),
|
(8, False),
|
||||||
10,
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{KC.RALT},
|
{KC.RALT},
|
||||||
@@ -325,7 +341,6 @@ class TestStringSubstitution(unittest.TestCase):
|
|||||||
(1, False),
|
(1, False),
|
||||||
(3, False),
|
(3, False),
|
||||||
(8, False),
|
(8, False),
|
||||||
10,
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{KC.RALT},
|
{KC.RALT},
|
||||||
|
@@ -9,36 +9,39 @@ 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, tap_time=50),
|
KC.TD(KC.N0, KC.N1),
|
||||||
KC.TD(
|
KC.TD(
|
||||||
KC.HT(KC.N1, KC.A, tap_time=50),
|
KC.HT(KC.N1, KC.A),
|
||||||
KC.HT(KC.N2, KC.B, tap_time=100),
|
KC.HT(KC.N2, KC.B, tap_time=2 * tap_time),
|
||||||
),
|
),
|
||||||
KC.TD(KC.HT(KC.X, KC.Y, tap_time=50), KC.X, tap_time=0),
|
KC.TD(KC.HT(KC.X, KC.Y), KC.X, tap_time=0),
|
||||||
KC.TD(KC.LT(1, KC.N3, tap_time=50), KC.X, tap_time=0),
|
KC.TD(KC.LT(1, KC.N3), 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), t_after], [{KC.N0}, {}])
|
keyboard.test('Tap x1', [(0, True), (0, False)], [{KC.N0}, {}])
|
||||||
|
|
||||||
keyboard.test(
|
keyboard.test(
|
||||||
'Tap x2',
|
'Tap x2',
|
||||||
[(0, True), (0, False), t_within, (0, True), (0, False), t_after],
|
[(0, True), (0, False), t_within, (0, True), (0, False)],
|
||||||
[{KC.N1}, {}],
|
[{KC.N1}, {}],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,7 +54,6 @@ 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}, {}],
|
||||||
)
|
)
|
||||||
@@ -93,11 +95,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), t_after], [{KC.N1}, {}])
|
keyboard.test('Tap x1', [(1, True), (1, False)], [{KC.N1}, {}])
|
||||||
|
|
||||||
keyboard.test(
|
keyboard.test(
|
||||||
'Tap x2',
|
'Tap x2',
|
||||||
[(1, True), (1, False), t_within, (1, True), (1, False), 2 * t_after],
|
[(1, True), (1, False), t_within, (1, True), (1, False)],
|
||||||
[{KC.N2}, {}],
|
[{KC.N2}, {}],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -131,7 +133,7 @@ class TestTapDance(unittest.TestCase):
|
|||||||
|
|
||||||
keyboard.test(
|
keyboard.test(
|
||||||
'',
|
'',
|
||||||
[(0, True), (0, False), t_within, (1, True), (1, False), t_after],
|
[(0, True), (0, False), t_within, (1, True), (1, False)],
|
||||||
[{KC.N0}, {}, {KC.N1}, {}],
|
[{KC.N0}, {}, {KC.N1}, {}],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -145,7 +147,6 @@ 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}, {}],
|
||||||
)
|
)
|
||||||
@@ -160,7 +161,6 @@ 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), t_after],
|
[(3, True), (3, False), t_within, (1, True), (1, False)],
|
||||||
[{KC.N3}, {}, {KC.N1}, {}],
|
[{KC.N3}, {}, {KC.N1}, {}],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user