Compare commits

..

8 Commits

Author SHA1 Message Date
xs5871
cbfcd34fae
Move trackball id check to during_bootup 2023-02-24 19:33:20 +00:00
xs5871
1d53d3a8da
Implement PixelBuf interface for pimoroni trackball 2023-02-24 19:33:20 +00:00
xs5871
0e804ffd54
Use rotation matrix instead of trig for angle correction 2023-02-24 19:33:20 +00:00
xs5871
0cc308c055
Read trackball only if state changed 2023-02-24 19:33:20 +00:00
xs5871
c575fa396a
Fix trackball switch handling 2023-02-24 19:33:20 +00:00
xs5871
a83e833d10
Remove infinite blocking loop on I2C error 2023-02-24 19:33:20 +00:00
xs5871
e9af3e542a
Use debug() for error messages 2023-02-24 19:33:20 +00:00
xs5871
16d319359f
Make trackball constants const() 2023-02-24 19:33:19 +00:00
80 changed files with 738 additions and 1582 deletions

View File

@ -1,27 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG] Title"
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] Title"
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
[CircuitPython](https://github.com/adafruit/circuitpython).
## Support
For asynchronous support and chatter about KMK, [join our Zulip
community](https://kmkfw.zulipchat.com)!
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.
community](https://kmkfw.zulipchat.com)! 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
> 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),),)
knob.modules.append(encoder_handler)
print('ANAVI Knob 1')
rgb_ext = RGB(
pixel_pin=board.NEOPIXEL,
num_pixels=1,

View File

@ -12,15 +12,15 @@ from kmk.extensions.peg_rgb_matrix import Rgb_matrix
from kmk.handlers.sequences import send_string
from kmk.hid import HIDModes
from kmk.keys import KC
from kmk.modules.holdtap import HoldTap
from kmk.modules.layers import Layers
from kmk.modules.modtap import ModTap
from kmk.modules.split import Split, SplitSide, SplitType
keyboard = KMKKeyboard()
holdtap = HoldTap()
modtap = ModTap()
layers_ext = Layers()
keyboard.modules.append(layers_ext)
keyboard.modules.append(holdtap)
keyboard.modules.append(modtap)
oled_ext = Oled(
OledData(

View File

@ -12,7 +12,7 @@ Retailers (USA)
Extensions enabled by default
- [Layers](/docs/en/layers.md) Need more keys than switches? Use layers.
- [BLE_Split](/docs/en/split_keyboards.md) Connects halves without wires
- [HoldTap](/docs/en/holdtap.md) Allows mod keys to act as different keys when tapped.
- [ModTap](/docs/en/modtap.md) Allows mod keys to act as different keys when tapped.
Common Extensions
- [Split](/docs/en/split_keyboards.md) Connects halves using a wire

View File

@ -3,16 +3,16 @@ import board
from kb import KMKKeyboard
from kmk.keys import KC
from kmk.modules.holdtap import HoldTap
from kmk.modules.layers import Layers
from kmk.modules.modtap import ModTap
from kmk.scanners import DiodeOrientation
keyboard = KMKKeyboard()
keyboard.modules.append(Layers())
holdtap = HoldTap()
keyboard.modules.append(holdtap)
modtap = ModTap()
keyboard.modules.append(modtap)
NONE = KC.NO
@ -25,9 +25,9 @@ CAD = KC.LCTL(KC.LALT(KC.DEL))
ZSFT = KC.HT(KC.Z, KC.LSFT, prefer_hold=True, tap_interrupted=False, tap_time=3000)
SLSHSFT = KC.HT(KC.SLSH, KC.LSFT, prefer_hold=True, tap_interrupted=False, tap_time=3000)
ALCTL = KC.HT(KC.A, KC.LCTRL, prefer_hold=False, tap_interrupted=False, tap_time=150)
ZSFT = KC.MT(KC.Z, KC.LSFT, prefer_hold=True, tap_interrupted=False, tap_time=3000)
SLSHSFT = KC.MT(KC.SLSH, KC.LSFT, prefer_hold=True, tap_interrupted=False, tap_time=3000)
ALCTL = KC.MT(KC.A, KC.LCTRL, prefer_hold=False, tap_interrupted=False, tap_time=150)
# flake8: noqa: E261
keyboard.keymap = [

View File

@ -3,12 +3,13 @@
import board
from storage import getmount
from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
from kmk.quickpin.pro_micro.kb2040 import pinout as pins
from kmk.scanners.keypad import KeysScanner
from storage import getmount
# GPIO to key mapping - each line is a new row.
# fmt: off
_KEY_CFG_LEFT = [

View File

@ -3,12 +3,11 @@
# https://github.com/qmk/qmk_firmware/tree/master/keyboards/ferris/keymaps/default
import board
from kb import KMKKeyboard
from kmk.keys import KC
from kmk.modules.holdtap import HoldTap
from kmk.modules.layers import Layers
from kmk.modules.modtap import ModTap
from kmk.modules.mouse_keys import MouseKeys
from kmk.modules.split import Split, SplitSide
@ -28,7 +27,7 @@ split = Split(
)
layers_ext = Layers()
holdtap = HoldTap()
mod_tap = ModTap()
mouse_key = MouseKeys()
@ -40,12 +39,12 @@ XXXXXXX = KC.NO
# Mod-taps
A_SFT = KC.HT(KC.A, KC.LSFT)
SCLN_SFT = KC.HT(KC.SCLN, KC.LSFT)
X_CTL = KC.HT(KC.X, KC.LCTRL)
C_ALT = KC.HT(KC.C, KC.LALT)
COM_ALT = KC.HT(KC.COMM, KC.LALT)
DOT_CTL = KC.HT(KC.DOT, KC.LCTRL)
A_SFT = KC.MT(KC.A, KC.LSFT)
SCLN_SFT = KC.MT(KC.SCLN, KC.LSFT)
X_CTL = KC.MT(KC.X, KC.LCTRL)
C_ALT = KC.MT(KC.C, KC.LALT)
COM_ALT = KC.MT(KC.COMM, KC.LALT)
DOT_CTL = KC.MT(KC.DOT, KC.LCTRL)
CTL_ALT = KC.LCTRL(KC.LALT)

View File

@ -6,8 +6,8 @@ from kmk.extensions.media_keys import MediaKeys
from kmk.extensions.rgb import RGB
from kmk.keys import KC
from kmk.modules.encoder import EncoderHandler
from kmk.modules.holdtap import HoldTap
from kmk.modules.layers import Layers
from kmk.modules.modtap import ModTap
from kmk.modules.mouse_keys import MouseKeys
keyboard = KMKKeyboard()
@ -21,13 +21,13 @@ XXXXXXX = KC.NO
# Adding extensions
rgb = RGB(pixel_pin=keyboard.rgb_pixel_pin, num_pixels=keyboard.rgb_num_pixels, val_limit=50, hue_default=190, sat_default=100, val_default=5)
holdtap = HoldTap()
modtap = ModTap()
layers = Layers()
media_keys = MediaKeys()
encoder_handler = EncoderHandler()
keyboard.modules = [layers, holdtap] #, encoder_handler]
keyboard.modules = [layers, modtap] #, encoder_handler]
keyboard.modules.append(MouseKeys())
keyboard.extensions = [rgb, media_keys]
@ -46,14 +46,14 @@ MEDIA_BSPC = KC.LT(LYR_MEDIA, KC.BSPC)
MOUSE_M = KC.LT(LYR_MOUSE, KC.M)
# HOMEROW MODS
LCTL_A = KC.HT(KC.A, KC.LCTRL)
LGUI_R = KC.HT(KC.R, KC.LGUI)
LALT_S = KC.HT(KC.S, KC.LALT)
LSFT_T = KC.HT(KC.T, KC.LSFT)
RSFT_N = KC.HT(KC.N, KC.RSFT)
RALT_E = KC.HT(KC.E, KC.RALT)
RGUI_I = KC.HT(KC.I, KC.RGUI)
RCTL_O = KC.HT(KC.O, KC.RCTRL)
LCTL_A = KC.MT(KC.A, KC.LCTRL)
LGUI_R = KC.MT(KC.R, KC.LGUI)
LALT_S = KC.MT(KC.S, KC.LALT)
LSFT_T = KC.MT(KC.T, KC.LSFT)
RSFT_N = KC.MT(KC.N, KC.RSFT)
RALT_E = KC.MT(KC.E, KC.RALT)
RGUI_I = KC.MT(KC.I, KC.RGUI)
RCTL_O = KC.MT(KC.O, KC.RCTRL)
# OTHER SHORTCUTS
BRWSR_LFT = KC.LCTRL(KC.LSFT(KC.TAB))

View File

@ -8,7 +8,7 @@ Hardware Availability: [Gherkin project on 40% Keyboards](http://www.40percent.c
Extensions enabled by default
- [Layers](/docs/en/layers.md) Need more keys than switches? Use layers.
- [HoldTap](/docs/en/holdtap.md) Allows mod keys to act as different keys when tapped.
- [ModTap](/docs/en/modtap.md) Allows mod keys to act as different keys when tapped.
- [LED](/docs/en/led.md) Light your keys up
Common Extensions

View File

@ -2,17 +2,17 @@ from kb import KMKKeyboard
from kmk.extensions.led import LED
from kmk.keys import KC
from kmk.modules.holdtap import HoldTap
from kmk.modules.layers import Layers
from kmk.modules.modtap import ModTap
keyboard = KMKKeyboard()
holdtap = HoldTap()
modtap = ModTap()
layers_ext = Layers()
led = LED()
keyboard.extensions = [led]
keyboard.modules = [layers_ext, holdtap]
keyboard.modules = [layers_ext, modtap]
# Cleaner key names
_______ = KC.TRNS

View File

@ -2,7 +2,6 @@ from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
from kmk.quickpin.pro_micro.sparkfun_promicro_rp2040 import pinout as pins
from kmk.scanners import DiodeOrientation
class KMKKeyboard(_KMKKeyboard):
col_pins = (
pins[19],

View File

@ -31,7 +31,7 @@ It has the following modules/extensions enabled:
- [Split](/docs/en/split_keyboards.md) Connects halves using a wire
- [Layers](/docs/en/layers.md) Do you need more keys than switches? Use
layers.
- [HoldTap](/docs/en/holdtap.md) Enable press/hold double binding of keys
- [ModTap](/docs/en/modtap.md) Enable press/hold double binding of keys
- [MediaKeys](/docs/en/media_keys.md) Common media controls
Also uncomment right section to enable samples of following:

View File

@ -4,15 +4,15 @@ from kmk.extensions.media_keys import MediaKeys
from kmk.extensions.rgb import RGB, AnimationModes
from kmk.keys import KC
from kmk.modules.encoder import EncoderHandler
from kmk.modules.holdtap import HoldTap
from kmk.modules.layers import Layers
from kmk.modules.modtap import ModTap
from kmk.modules.split import Split, SplitType
keyboard = KMKKeyboard()
keyboard.debug_enabled = True
keyboard.modules.append(Layers())
keyboard.modules.append(HoldTap())
keyboard.modules.append(ModTap())
keyboard.extensions.append(MediaKeys())
# Using drive names (KYRIAL, KYRIAR) to recognize sides; use split_side arg if you're not doing it
@ -33,10 +33,10 @@ keyboard.extensions.append(rgb_ext)
# Edit your layout below
# Currently, that's a default QMK Kyria Layout - https://config.qmk.fm/#/splitkb/kyria/rev1/LAYOUT
ESC_LCTL = KC.HT(KC.ESC, KC.LCTL)
QUOTE_RCTL = KC.HT(KC.QUOTE, KC.RCTL)
ENT_LALT = KC.HT(KC.ENT, KC.LALT)
MINUS_RCTL = KC.HT(KC.MINUS, KC.RCTL)
ESC_LCTL = KC.MT(KC.ESC, KC.LCTL)
QUOTE_RCTL = KC.MT(KC.QUOTE, KC.RCTL)
ENT_LALT = KC.MT(KC.ENT, KC.LALT)
MINUS_RCTL = KC.MT(KC.MINUS, KC.RCTL)
keyboard.keymap = [
[
KC.TAB, KC.Q, KC.W, KC.E, KC.R, KC.T, KC.Y, KC.U, KC.I, KC.O, KC.P, KC.BSPC,

View File

@ -8,8 +8,8 @@ from kb import KMKKeyboard
from kmk.extensions.media_keys import MediaKeys
from kmk.extensions.RGB import RGB, AnimationModes
from kmk.keys import KC
from kmk.modules.holdtap import HoldTap
from kmk.modules.layers import Layers
from kmk.modules.modtap import ModTap
from kmk.modules.split import Split, SplitSide, SplitType
led = digitalio.DigitalInOut(board.GP25)
@ -20,7 +20,7 @@ keyboard = KMKKeyboard()
keyboard.tap_time = 100
layers_ext = Layers()
holdtap_ext = HoldTap()
modtap_ext = ModTap()
# TODO Comment one of these on each side
split_side = SplitSide.LEFT
@ -43,7 +43,7 @@ rgb_ext = RGB(
animation_mode=AnimationModes.BREATHING_RAINBOW
)
keyboard.modules = [layers_ext, holdtap_ext, split]
keyboard.modules = [layers_ext, modtap_ext, split]
keyboard.extensions.append(MediaKeys())
keyboard.extensions.append(rgb_ext)
@ -61,9 +61,9 @@ if split_side == SplitSide.LEFT:
LOWER = KC.MO(1)
RAISE = KC.MO(2)
ADJUST = KC.MO(3)
CT_TAB = KC.HT(KC.TAB, KC.LCTRL)
CT_QUOT = KC.HT(KC.QUOT, KC.LCTRL)
SF_MINS = KC.HT(KC.MINS, KC.LSHIFT)
CT_TAB = KC.MT(KC.TAB, KC.LCTRL)
CT_QUOT = KC.MT(KC.QUOT, KC.LCTRL)
SF_MINS = KC.MT(KC.MINS, KC.LSHIFT)
SG_PSCR = KC.LSFT(KC.LGUI(KC.PSCR))
SF_PSCR = KC.LSFT(KC.PSCR)
CG_RGHT = KC.LCTRL(KC.LGUI(KC.RGHT))

View File

@ -17,7 +17,7 @@ Retailers (USA)
Extensions enabled by default
- [Layers](/docs/en/layers.md) Need more keys than switches? Use layers.
- [RGB](/docs/en/rgb.md) Light it up
- [HoldTap](/docs/en/holdtap.md) Allows mod keys to act as different keys when tapped.
- [ModTap](/docs/en/modtap.md) Allows mod keys to act as different keys when tapped.
Common Extensions
- [Power](/docs/en/power.md) Powersaving features for battery life

View File

@ -2,18 +2,18 @@ from kb import KMKKeyboard
from kmk.extensions.rgb import RGB
from kmk.keys import KC
from kmk.modules.holdtap import HoldTap
from kmk.modules.layers import Layers
from kmk.modules.modtap import ModTap
keyboard = KMKKeyboard()
# Adding extensions
rgb = RGB(pixel_pin=keyboard.rgb_pixel_pin, num_pixels=keyboard.rgb_num_pixels, val_limit=100, hue_default=190, sat_default=100, val_default=5)
holdtap = HoldTap()
modtap = ModTap()
layers_ext = Layers()
keyboard.modules = [layers_ext, holdtap]
keyboard.modules = [layers_ext, modtap]
keyboard.extensions = [rgb]
# Cleaner key names

View File

@ -18,7 +18,7 @@ Retailers (USA)
Extensions enabled by default
- [Layers](/docs/en/layers.md) Need more keys than switches? Use layers.
- [RGB](/docs/en/rgb.md) Light it up
- [HoldTap](/docs/en/holdtap.md) Allows mod keys to act as different keys when tapped.
- [ModTap](/docs/en/modtap.md) Allows mod keys to act as different keys when tapped.
Common Extensions
- [Power](/docs/en/power.md) Powersaving features for battery life

View File

@ -2,18 +2,18 @@ from kb import KMKKeyboard
from kmk.extensions.rgb import RGB
from kmk.keys import KC
from kmk.modules.holdtap import HoldTap
from kmk.modules.layers import Layers
from kmk.modules.modtap import ModTap
keyboard = KMKKeyboard()
# Adding extensions
rgb = RGB(pixel_pin=keyboard.rgb_pixel_pin, num_pixels=keyboard.rgb_num_pixels, val_limit=100, hue_default=190, sat_default=100, val_default=5)
holdtap = HoldTap()
modtap = ModTap()
layers_ext = Layers()
keyboard.modules = [layers_ext, holdtap]
keyboard.modules = [layers_ext, modtap]
keyboard.extensions = [rgb]
# Cleaner key names
@ -23,8 +23,8 @@ XXXXXXX = KC.NO
LOWER = KC.MO(1)
RAISE = KC.MO(2)
ADJUST = KC.LT(3, KC.SPC)
RSFT_ENT = KC.HT(KC.ENT, KC.RSFT)
RSFT_SPC = KC.HT(KC.SPC, KC.RSFT)
RSFT_ENT = KC.MT(KC.ENT, KC.RSFT)
RSFT_SPC = KC.MT(KC.SPC, KC.RSFT)
RGB_TOG = KC.RGB_TOG
RGB_HUI = KC.RGB_HUI

View File

@ -14,7 +14,7 @@ Retailers (USA)
Extensions enabled by default
- [Layers](/docs/en/layers.md) Need more keys than switches? Use layers.
- [BLE_Split](/docs/en/split_keyboards.md) Connects halves without wires
- [HoldTap](/docs/en/holdtap.md) Allows mod keys to act as different keys when tapped.
- [ModTap](/docs/en/modtap.md) Allows mod keys to act as different keys when tapped.
Common Extensions
- [Split](/docs/en/split_keyboards.md) Connects halves using a wire

View File

@ -12,15 +12,15 @@ from kmk.extensions.peg_rgb_matrix import Rgb_matrix
from kmk.handlers.sequences import send_string
from kmk.hid import HIDModes
from kmk.keys import KC
from kmk.modules.holdtap import HoldTap
from kmk.modules.layers import Layers
from kmk.modules.modtap import ModTap
from kmk.modules.split import Split, SplitSide, SplitType
keyboard = KMKKeyboard()
holdtap = HoldTap()
modtap = ModTap()
layers_ext = Layers()
keyboard.modules.append(layers_ext)
keyboard.modules.append(holdtap)
keyboard.modules.append(modtap)
oled_ext = Oled(
OledData(

View File

@ -2,7 +2,7 @@
> Life was like a box of chocolates. You never know what you're gonna get.
KMK is a keyboard focused layer that sits on top of [CircuitPython](https://circuitpython.org/). As such, it should work with most [boards that support CircuitPython](https://circuitpython.org/downloads). KMK requires CircuitPython version 7.0 or above.
Known working and recommended devices can be found in the [list of officially supported microcontrollers](Officially_Supported_Microcontrollers.md)
Known working and recommended devices can be found [here](Officially_Supported_Microcontrollers.md)
## TL;DR Quick start guide
@ -50,21 +50,19 @@ if __name__ == '__main__':
> This is your last chance. After this, there is no turning back. You take the blue pill—the story ends, you wake up in your bed and believe whatever you want to believe. You take the red pill—you stay in Wonderland, and I show you how deep the rabbit hole goes. Remember: all I'm offering is the truth. Nothing more.
### You're extremely lucky and you have a fully supported keyboard
If your keyboard and microcontroller are officially supported, simply visit the page for your files, and dropping them on the root of the "flash drive".
Those pages can be found in the repositories [boards folder](https://github.com/KMKfw/kmk_firmware/tree/master/boards).
You will need the `kb.py` and `main.py`. If you need more detailed instructions on how to customize the configuration settings and key mappings, please refer to the [config and keymap](config_and_keymap.md) documentation.
If your keyboard and microcontroller are officially supported, simply visit the page for your files, and dropping them on the root of the "flash drive". Those pages can be found [here](https://github.com/KMKfw/kmk_firmware/tree/master/boards). You will need the `kb.py` and `main.py`. More advanced instructions can be found [here](config_and_keymap.md).
### You've got another, maybe DIY, board and want to customize KMK for it
First, be sure to understand how your device work, and particularly its specific matrix configuration. You can have a look at [how key matrices work](http://pcbheaven.com/wikipages/How_Key_Matrices_Works/) or read the [guide](https://docs.qmk.fm/#/hand_wire) provided by the QMK team for handwired keyboards
First, be sure to understand how your device work, and particularly its specific matrix configuration. You can have a look [here](http://pcbheaven.com/wikipages/How_Key_Matrices_Works/) or read the [guide](https://docs.qmk.fm/#/hand_wire) provided by the QMK team for handwired keyboards
Once you've got the gist of it:
- To start customizing your `code.py`/`main.py` file, please refer to the [config and keymap](config_and_keymap.md) and [keys](keys.md) files respectively, which provide detailed instructions on how to modify the configuration settings and key mappings.
- You can have a look [here](config_and_keymap.md) and [here](keys.md) to start customizing your code.py / main.py file
- There's a [reference](keycodes.md) of the available keycodes
- [International](international.md) extension adds keys for non US layouts and [Media Keys](media_keys.md) adds keys for ... media
And to go even further:
- [Sequences](sequences.md) are used for sending multiple keystrokes in a single action
- [Layers](layers.md) can transform the whole way your keyboard is behaving with a single touch
- [HoldTap](holdtap.md) allow you to customize the way a key behaves whether it is tapped or hold, and [TapDance](tapdance.md) depending on the number of times it is pressed
- [ModTap](modtap.md) allow you to customize the way a key behaves whether it is tapped or hold, and [TapDance](tapdance.md) depending on the number of times it is pressed
Want to have fun features such as RGB, split keyboards and more? Check out what builtin [modules](modules.md) and [extensions](extensions.md) can do!
You can also get ideas from the various [user examples](https://github.com/KMKfw/kmk_firmware/tree/master/user_keymaps) that we provide and dig into our [documentation](README.md).

View File

@ -25,7 +25,7 @@
- [Combos](combos.md): Adds chords and sequences
- [Layers](layers.md): Adds layer support (Fn key) to allow many more keys to be put on your keyboard
- [HoldTap](holdtap.md): Adds support for augmented modifier keys to act as one key when tapped, and modifier when held.
- [ModTap](modtap.md): Adds support for augmented modifier keys to act as one key when tapped, and modifier when held.
- [Mouse keys](mouse_keys.md): Adds mouse keycodes
- [OneShot](oneshot.md): Adds support for oneshot/sticky keys.
- [Power](power.md): Power saving features. This is mostly useful when on battery power.

View File

@ -3,8 +3,8 @@ Bluetooth connections help clean up the wire mess!
## CircuitPython
If not running KMKPython, this does require the adafruit_ble library from Adafruit.
This can be downloaded from the
[Adafruit CircuitPython BLE repository](https://github.com/adafruit/Adafruit_CircuitPython_BLE/tree/master/adafruit_ble).
This can be downloaded
[here](https://github.com/adafruit/Adafruit_CircuitPython_BLE/tree/master/adafruit_ble).
It is part of the [Adafruit CircuitPython Bundle](https://github.com/adafruit/Adafruit_CircuitPython_Bundle).
Simply put this in the "root" of your CircuitPython device. If unsure, it's the folder with main.py in it, and should be the first folder you see when you open the device.

View File

@ -1,6 +1,6 @@
# CapsWord
The CapsWord module functions similar to caps lock but will deactivate automatically when its encounters a key that breaks the word or after inactivity timeout.
By default it will not deactivate CapsWord on numbers, alphabets, underscore, modifiers, minus, backspace and other keys like HoldTap, Layers, etc.
By default it will not deactivate CapsWord on numbers, alphabets, underscore, modifiers, minus, backspace and other keys like ModTap, Layers, etc.
Add it to your keyboard's modules list with:
```python

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

@ -40,8 +40,8 @@ and a willingness to write them up, it's a good idea to familiarize yourself wit
the docs. Documentation should be informative but concise.
### Styling
Docs are written and rendered in GitHub Markdown.
Check out this comprehensive [guide to basic writing and formatting syntax](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) from GitHub's documentation.
Docs are written and rendered in GitHub Markdown. A comprehensive guide to GitHub's
Markdown can be found [here](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax).
In particular, KMK's docs should include a title, demarcated with `#`, and subheadings
should be demarcated with `##`, `###`, and so on. Headings should be short and specific.

View File

@ -23,7 +23,7 @@ Here is all you need to use this module in your `main.py` / `code.py` file.
```python
from kmk.modules.encoder import EncoderHandler
encoder_handler = EncoderHandler()
keyboard.modules = [layers, holdtap, encoder_handler]
keyboard.modules = [layers, modtap, encoder_handler]
```
2. Define the pins for each encoder: `pin_a`, `pin_b` for rotations, `pin_button` for the switch in the encoder. Set switch to `None` if the encoder's button is handled differently (as a part of matrix for example) or not at all. If you want to invert the direction of the encoder, set the 4th (optional) parameter `is_inverted` to `True`. 5th parameter is [encoder divisor](#encoder-resolution) (optional), it can be either `2` or `4`.

View File

@ -24,8 +24,9 @@ make MOUNTPOINT=/media/CIRCUITPY USER_KEYMAP=user_keymaps/nameofyourkeymap.py BO
Check to see if your drive may have mounted elsewhere with a GUI tool or other
automounter. Most of these tools will mount your device under `/media`, probably
as `/media/CIRCUITPY`. If it's not mounted, you can read up on how to [mount a
drive manually](https://wiki.archlinux.org/index.php/File_systems#Mount_a_file_system).
as `/media/CIRCUITPY`. If it's not mounted, you can read up on how to mount a
drive manually
[here](https://wiki.archlinux.org/index.php/File_systems#Mount_a_file_system).
For example,

View File

@ -1,6 +1,6 @@
# Handwire keyboards
This guide will not talk about the physical wiring. Check out our
[recommended microcontrollers](Officially_Supported_Microcontrollers.md) and
follow this amazing [hand wiring guide](https://docs.qmk.fm/#/hand_wire). That
follow the amazing guide for that [here](https://docs.qmk.fm/#/hand_wire). That
guide can be followed until you are setting up the firmware. After wiring the
keyboard, you can refer to our porting guide [here](porting_to_kmk.md)

View File

@ -188,7 +188,6 @@
| `KC.RESET` | Restarts the keyboard |
| `KC.RELOAD`, `KC.RLD` | Reloads the keyboard software, preserving any serial connections |
| `KC.DEBUG` | Toggle `debug_enabled`, which enables log spew to serial console |
| `KC.ANY` | Any key between `A and `/` |
| `KC.GESC` | Escape when tapped, <code>&#96;</code> when pressed with Shift or GUI |
| `KC.BKDL` | Backspace when tapped, Delete when pressed with GUI |
| `KC.UC_MODE_NOOP` | Sets UnicodeMode to NOOP |

View File

@ -15,7 +15,7 @@ the box.
### CircuitPython
CircuitPython can be installed by following this guide using the guide
for [installing circuit python](https://learn.adafruit.com/welcome-to-circuitpython/installing-circuitpython).
[here](https://learn.adafruit.com/welcome-to-circuitpython/installing-circuitpython).
It's recommended to run the latest stable version that is at least 5.0 or higher.
Beta versions may work, but expect limited support.
#### Notable differences include

View File

@ -21,7 +21,7 @@ keyboard.modules.append(Layers())
## Custom HoldTap Behavior
`KC.TT` and `KC.LT` use the same heuristic to determine taps and holds as
HoldTap. Check out the [HoldTap doc](holdtap.md) to find out more.
ModTap. Check out the [ModTap doc](modtap.md) to find out more.
## Working with Layers
When starting out, care should be taken when working with layers, since it's possible to lock
@ -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
- 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
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
@ -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
layers with `KC.DF()` the same way that you would treat using `KC.TO()`
## Example Code
For our example, let's take a simple 3x3 macropad with two layers as follows:

View File

@ -1,39 +1,39 @@
# HoldTap Keycodes
Enabling HoldTap will give you access to the following keycodes and can simply be
# ModTap Keycodes
Enabling ModTap will give you access to the following keycodes and can simply be
added to the modules list.
```python
from kmk.modules.holdtap import HoldTap
holdtap = HoldTap()
from kmk.modules.modtap import ModTap
modtap = ModTap()
# optional: set a custom tap timeout in ms
# holdtap.tap_time = 300
keyboard.modules.append(holdtap)
# modtap.tap_time = 300
keyboard.modules.append(modtap)
```
## Keycodes
|New Keycode | Description |
|---------------------------------------------------------|-----------------------------------------------------------------|
|`LCTL = KC.HT(KC.SOMETHING, KC.LCTRL)` |`LCTRL` if held `kc` if tapped |
|`LSFT = KC.HT(KC.SOMETHING, KC.LSFT)` |`LSHIFT` if held `kc` if tapped |
|`LALT = KC.HT(KC.SOMETHING, KC.LALT)` |`LALT` if held `kc` if tapped |
|`LGUI = KC.HT(KC.SOMETHING, KC.LGUI)` |`LGUI` if held `kc` if tapped |
|`RCTL = KC.HT(KC.SOMETHING, KC.RCTRL)` |`RCTRL` if held `kc` if tapped |
|`RSFT = KC.HT(KC.SOMETHING, KC.RSFT)` |`RSHIFT` if held `kc` if tapped |
|`RALT = KC.HT(KC.SOMETHING, KC.RALT)` |`RALT` if held `kc` if tapped |
|`RGUI = KC.HT(KC.SOMETHING, KC.RGUI)` |`RGUI` if held `kc` if tapped |
|`SGUI = KC.HT(KC.SOMETHING, KC.LSHFT(KC.LGUI))` |`LSHIFT` and `LGUI` if held `kc` if tapped |
|`LCA = KC.HT(KC.SOMETHING, KC.LCTRL(KC.LALT))` |`LCTRL` and `LALT` if held `kc` if tapped |
|`LCAG = KC.HT(KC.SOMETHING, KC.LCTRL(KC.LALT(KC.LGUI)))` |`LCTRL` and `LALT` and `LGUI` if held `kc` if tapped |
|`MEH = KC.HT(KC.SOMETHING, KC.LCTRL(KC.LSFT(KC.LALT)))` |`CTRL` and `LSHIFT` and `LALT` if held `kc` if tapped |
|`HYPR = KC.HT(KC.SOMETHING, KC.HYPR)` |`LCTRL` and `LSHIFT` and `LALT` and `LGUI` if held `kc` if tapped|
|`LCTL = KC.MT(KC.SOMETHING, KC.LCTRL)` |`LCTRL` if held `kc` if tapped |
|`LSFT = KC.MT(KC.SOMETHING, KC.LSFT)` |`LSHIFT` if held `kc` if tapped |
|`LALT = KC.MT(KC.SOMETHING, KC.LALT)` |`LALT` if held `kc` if tapped |
|`LGUI = KC.MT(KC.SOMETHING, KC.LGUI)` |`LGUI` if held `kc` if tapped |
|`RCTL = KC.MT(KC.SOMETHING, KC.RCTRL)` |`RCTRL` if held `kc` if tapped |
|`RSFT = KC.MT(KC.SOMETHING, KC.RSFT)` |`RSHIFT` if held `kc` if tapped |
|`RALT = KC.MT(KC.SOMETHING, KC.RALT)` |`RALT` if held `kc` if tapped |
|`RGUI = KC.MT(KC.SOMETHING, KC.RGUI)` |`RGUI` if held `kc` if tapped |
|`SGUI = KC.MT(KC.SOMETHING, KC.LSHFT(KC.LGUI))` |`LSHIFT` and `LGUI` if held `kc` if tapped |
|`LCA = KC.MT(KC.SOMETHING, KC.LCTRL(KC.LALT))` |`LCTRL` and `LALT` if held `kc` if tapped |
|`LCAG = KC.MT(KC.SOMETHING, KC.LCTRL(KC.LALT(KC.LGUI)))` |`LCTRL` and `LALT` and `LGUI` if held `kc` if tapped |
|`MEH = KC.MT(KC.SOMETHING, KC.LCTRL(KC.LSFT(KC.LALT)))` |`CTRL` and `LSHIFT` and `LALT` if held `kc` if tapped |
|`HYPR = KC.MT(KC.SOMETHING, KC.HYPR)` |`LCTRL` and `LSHIFT` and `LALT` and `LGUI` if held `kc` if tapped|
## Custom HoldTap Behavior
The full HoldTap signature is as follows:
The full ModTap signature is as follows:
```python
KC.HT(KC.TAP, KC.HOLD, prefer_hold=True, tap_interrupted=False, tap_time=None, repeat=HoldTapRepeat.NONE)
KC.MT(KC.TAP, KC.HOLD, prefer_hold=True, tap_interrupted=False, tap_time=None, repeat=HoldTapRepeat.NONE)
```
* `prefer_hold`: decides which keycode the HoldTap key resolves to when another
* `prefer_hold`: decides which keycode the ModTap key resolves to when another
key is pressed before the timeout finishes. When `True` the hold keycode is
chosen, the tap keycode when `False`.
* `tap_interrupted`: decides if the timeout will interrupt at the first other
@ -48,4 +48,4 @@ KC.HT(KC.TAP, KC.HOLD, prefer_hold=True, tap_interrupted=False, tap_time=None, r
* `NONE`: no repeat action (default), everything works as expected.
The `HoldTapRepeat` enum must be imported from `kmk.modules.holdtap`.
Each of these parameters can be set for every HoldTap key individually.
Each of these parameters can be set for every ModTap key individually.

View File

@ -10,7 +10,7 @@ modules are
- [Combos](combos.md): Adds chords and sequences
- [Layers](layers.md): Adds layer support (Fn key) to allow many more keys to be
put on your keyboard.
- [HoldTap](holdtap.md): Adds support for augmented modifier keys to act as one key
- [ModTap](modtap.md): Adds support for augmented modifier keys to act as one key
when tapped, and modifier when held.
- [Mouse keys](mouse_keys.md): Adds mouse keycodes.
- [OneShot](oneshot.md): Adds support for oneshot/sticky keys.

View File

@ -35,12 +35,12 @@ KC.OS(
)
```
## OneShot Modifier Combinations
The OneShot keys can be chained. In this example if you press `OS_LCTL` and then `OS_LSFT` followed by `KC.TAB`, the output will be `ctrl+shift+tab`.
The OneShot module by default cannot apply two OneShot modifiers to another key. To get around this you can use the [Combos](combos.md) module. Below is a minimal example that allows for multiple OneShot modifiers to apply to the next key pressed. In this example you can tap either of the OneShot keys then tap the other and finally tap `p` and that will send `ctrl+shift+p`.
```python
from kmk.modules.combos import Chord, Combos
from kmk.modules.oneshot import OneShot
oneshot = OneShot()
@ -48,8 +48,58 @@ keyboard.modules.append(oneshot)
OS_LCTL = KC.OS(KC.LCTL, tap_time=None)
OS_LSFT = KC.OS(KC.LSFT, tap_time=None)
OS_LCTL_LSFT = KC.OS(KC.LCTL(OS_LSFT), tap_time=None)
keyboard.keymap = [[OS_LSFT, OS_LCTL, KC.TAB]]
combos = Combos()
keyboard.modules.append(combos)
combos.combos = [
Chord((OS_LCTL, OS_LSFT), OS_LCTL_LSFT, timeout=1000),
]
keyboard.keymap = [[OS_LSFT, OS_LCTL, KC.P]]
```
Below is the complete list of OneShot and Chords you need to allow any combination of modifiers (left modifiers only).
> <details>
> <summary>Long code chunk (click to load)</summary>
>
> ```python
> OS_LCTL = KC.OS(KC.LCTL, tap_time=None)
> OS_LSFT = KC.OS(KC.LSFT, tap_time=None)
> OS_LGUI = KC.OS(KC.LGUI, tap_time=None)
> OS_LALT = KC.OS(KC.LALT, tap_time=None)
>
> OS_LCTL_LSFT = KC.OS(KC.LCTL(OS_LSFT), tap_time=None)
> OS_LCTL_LALT = KC.OS(KC.LCTL(OS_LALT), tap_time=None)
> OS_LCTL_LGUI = KC.OS(KC.LCTL(OS_LGUI), tap_time=None)
> OS_LSFT_LALT = KC.OS(KC.LSFT(OS_LALT), tap_time=None)
> OS_LSFT_LGUI = KC.OS(KC.LSFT(OS_LGUI), tap_time=None)
> OS_LALT_LGUI = KC.OS(KC.LALT(OS_LGUI), tap_time=None)
>
> OS_LCTL_LSFT_LGUI = KC.OS(KC.LCTL(KC.LSFT(OS_LGUI)), tap_time=None)
> OS_LCTL_LSFT_LALT = KC.OS(KC.LCTL(KC.LSFT(OS_LALT)), tap_time=None)
> OS_LCTL_LALT_LGUI = KC.OS(KC.LCTL(KC.LALT(OS_LGUI)), tap_time=None)
> OS_LSFT_LALT_LGUI = KC.OS(KC.LSFT(KC.LALT(OS_LGUI)), tap_time=None)
>
> OS_LCTL_LSFT_LALT_LGUI = KC.OS(KC.LCTL(KC.LSFT(KC.LALT(OS_LGUI))), tap_time=None)
>
> combos.combos = [
> Chord((OS_LCTL, OS_LSFT), OS_LCTL_LSFT, timeout=1000),
> Chord((OS_LCTL, OS_LALT), OS_LCTL_LALT, timeout=1000),
> Chord((OS_LCTL, OS_LGUI), OS_LCTL_LGUI, timeout=1000),
> Chord((OS_LSFT, OS_LALT), OS_LSFT_LALT, timeout=1000),
> Chord((OS_LSFT, OS_LGUI), OS_LSFT_LGUI, timeout=1000),
> Chord((OS_LALT, OS_LGUI), OS_LALT_LGUI, timeout=1000),
>
> Chord((OS_LCTL, OS_LSFT, OS_LGUI), OS_LCTL_LSFT_LGUI, timeout=1000),
> Chord((OS_LCTL, OS_LSFT, OS_LALT), OS_LCTL_LSFT_LALT, timeout=1000),
> Chord((OS_LCTL, OS_LALT, OS_LGUI), OS_LCTL_LALT_LGUI, timeout=1000),
> Chord((OS_LSFT, OS_LALT, OS_LGUI), OS_LSFT_LALT_LGUI, timeout=1000),
>
> Chord((OS_LCTL, OS_LSFT, OS_LALT, OS_LGUI), OS_LCTL_LSFT_LALT_LGUI, timeout=1000),
> ]
> ```
>
> </details>

View File

@ -17,7 +17,7 @@ To use this you need to make some changes to your kb.py as well as you main.py I
You need these frozen into your circuitpython or in a lib folder at the root of your drive.
* [Adafruit_CircuitPython_DisplayIO_SSD1306](https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_SSD1306)
* [Adafruit_CircuitPython_Display_Text](https://github.com/adafruit/Adafruit_CircuitPython_Display_Text)
* [Download .mpy versions from Adafruit_CircuitPython_Bundle](https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases/download/20220415/adafruit-circuitpython-bundle-7.x-mpy-20220415.zip)
* [Download .mpy versions from here](https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases/download/20220415/adafruit-circuitpython-bundle-7.x-mpy-20220415.zip)
## kb.py

View File

@ -30,7 +30,7 @@ Currently this extension does not support changing LEDs at runtime, as a result
The following libraries must be frozen in your CircuitPython distribution or in a 'lib' folder at the root of your drive.
* [Adafruit_CircuitPython_NeoPixel](https://github.com/adafruit/Adafruit_CircuitPython_NeoPixel)
* [Download .mpy versions from Adafruit_CircuitPython_Bundle](https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases/download/20220415/adafruit-circuitpython-bundle-7.x-mpy-20220415.zip)
* [Download .mpy versions from here](https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases/download/20220415/adafruit-circuitpython-bundle-7.x-mpy-20220415.zip)
## Required Changes to main.py and kb.py

View File

@ -126,4 +126,4 @@ if __name__ == '__main__':
```
## More information
More information on keymaps can be found in the [config and keymap](config_and_keymap.md) documentation.
More information on keymaps can be found [here](config_and_keymap.md)

View File

@ -2,7 +2,7 @@
Split keyboards are mostly the same as unsplit. Wired UART is fully supported,
and testing of Bluetooth splits, though we don't currently offer support for this.
Notice that this Split module must be added after the HoldTap module to the keyboard.modules.
Notice that this Split module must be added after the ModTap module to the keyboard.modules.
## Drive names
As you will have two circuitpython drives to update regularly, it is adviced to rename them to make

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.
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
> longer officially supported, please do not use them!

View File

@ -46,7 +46,7 @@ EXAMPLE_TD = KC.TD(
# Tap once for "a"
KC.A,
# Tap twice for "b", or tap and hold for "left control"
KC.HT(KC.B, KC.LCTL, prefer_hold=False),
KC.MT(KC.B, KC.LCTL, prefer_hold=False),
# Tap three times to send a raw string via macro
send_string('macros in a tap dance? I think yes'),
# Tap four times to toggle layer index 1, tap 3 times and hold for 3s to

View File

@ -80,7 +80,7 @@ QMK チームが提供している手配線キーボード用の[ガイド](http
- [シーケンス](sequences.md) 一つのアクションで複数のキーストロークを送信するために使用します。
- [レイヤー](layers.md)でタッチ一つでキーボードの全体の動きを変えることができます。
- [モドタップ](holdtap.md) でキーの押し/長押しの動作を設定し、何回押されたかによって[タップダンス](tapdance.md)を設定します。
- [モドタップ](modtap.md) でキーの押し/長押しの動作を設定し、何回押されたかによって[タップダンス](tapdance.md)を設定します。
RGB や分裂型などの機能を楽しめたい場合は、ビルトイン[モジュール](modules.md)と[拡張機能](extensions.md)を見てみてください!

View File

@ -101,7 +101,7 @@ E para ir mais além:
só.
- [Camadas](layers.md) podem transformar totalmente como seu teclado age com um
simples toque.
- [HoldTap](holdtap.md) te permite customizar a maneira que uma tecla age quando é
- [ModTap](modtap.md) te permite customizar a maneira que uma tecla age quando é
pressionada ou "segurada"; e o
- [TapDance](tapdance.md) dependendo do número de vezes que ela é pressionada.

View File

@ -1,27 +1,27 @@
# Keycodes HoldTap
# Keycodes ModTap
Habilitar o HoldTap (adicionando-o à lista de módulos) te dará acesso aos
Habilitar o ModTap (adicionando-o à lista de módulos) te dará acesso aos
keycodes abaixo:
```python
from kmk.modules.holdtap import HoldTap
keyboard.modules.append(HoldTap())
from kmk.modules.modtap import ModTap
keyboard.modules.append(ModTap())
```
## Keycodes
| Novo Keycode | Descrição |
|--------------------------------------------------------|-----------------------------------------------------------------|
| LCTL = KC.HT(KC.SOMETHING, KC.LCTRL) | `LCTRL` se segurado `kc` se tocado |
| LSFT = KC.HT(KC.SOMETHING, KC.LSFT) | `LSHIFT` se segurado `kc` se tocado |
| LALT = KC.HT(KC.SOMETHING, KC.LALT) | `LALT` se segurado `kc` se tocado |
| LGUI = KC.HT(KC.SOMETHING, KC.LGUI) | `LGUI` se segurado `kc` se tocado |
| RCTL = KC.HT(KC.SOMETHING, KC.RCTRL) | `RCTRL` se segurado `kc` se tocado |
| RSFT = KC.HT(KC.SOMETHING, KC.RSFT) | `RSHIFT` se segurado `kc` se tocado |
| RALT = KC.HT(KC.SOMETHING, KC.RALT) | `RALT` se segurado `kc` se tocado |
| RGUI = KC.HT(KC.SOMETHING, KC.RGUI) | `RGUI` se segurado `kc` se tocado |
| SGUI = KC.HT(KC.SOMETHING, KC.LSHFT(KC.LGUI)) | `LSHIFT` e `LGUI` se segurado `kc` se tocado |
| LCA = KC.HT(KC.SOMETHING, KC.LCTRL(KC.LALT)) | `LCTRL` e `LALT` se segurado `kc` se tocado |
| LCAG = KC.HT(KC.SOMETHING, KC.LCTRL(KC.LALT(KC.LGUI))) | `LCTRL` e `LALT` e `LGUI` se segurado `kc` se tocado |
| MEH = KC.HT(KC.SOMETHING, KC.LCTRL(KC.LSFT(KC.LALT))) | `CTRL` e `LSHIFT` e `LALT` se segurado `kc` se tocado |
| HYPR = KC.HT(KC.SOMETHING, KC.HYPR) | `LCTRL` e `LSHIFT` e `LALT` e `LGUI` se segurado `kc` if tapped |
| LCTL = KC.MT(KC.SOMETHING, KC.LCTRL) | `LCTRL` se segurado `kc` se tocado |
| LSFT = KC.MT(KC.SOMETHING, KC.LSFT) | `LSHIFT` se segurado `kc` se tocado |
| LALT = KC.MT(KC.SOMETHING, KC.LALT) | `LALT` se segurado `kc` se tocado |
| LGUI = KC.MT(KC.SOMETHING, KC.LGUI) | `LGUI` se segurado `kc` se tocado |
| RCTL = KC.MT(KC.SOMETHING, KC.RCTRL) | `RCTRL` se segurado `kc` se tocado |
| RSFT = KC.MT(KC.SOMETHING, KC.RSFT) | `RSHIFT` se segurado `kc` se tocado |
| RALT = KC.MT(KC.SOMETHING, KC.RALT) | `RALT` se segurado `kc` se tocado |
| RGUI = KC.MT(KC.SOMETHING, KC.RGUI) | `RGUI` se segurado `kc` se tocado |
| SGUI = KC.MT(KC.SOMETHING, KC.LSHFT(KC.LGUI)) | `LSHIFT` e `LGUI` se segurado `kc` se tocado |
| LCA = KC.MT(KC.SOMETHING, KC.LCTRL(KC.LALT)) | `LCTRL` e `LALT` se segurado `kc` se tocado |
| LCAG = KC.MT(KC.SOMETHING, KC.LCTRL(KC.LALT(KC.LGUI))) | `LCTRL` e `LALT` e `LGUI` se segurado `kc` se tocado |
| MEH = KC.MT(KC.SOMETHING, KC.LCTRL(KC.LSFT(KC.LALT))) | `CTRL` e `LSHIFT` e `LALT` se segurado `kc` se tocado |
| HYPR = KC.MT(KC.SOMETHING, KC.HYPR) | `LCTRL` e `LSHIFT` e `LALT` e `LGUI` se segurado `kc` if tapped |

View File

@ -12,7 +12,7 @@ módulos oferecidos correntemente são;
- [Layers](layers.md): Acrescenta suporte a camadas (Tecla Fn) para permitir
colocar bem mais teclas no seu teclado.
- [HoldTap](holdtap.md): Acrescenta suporte para teclas modificadoras que agem
- [ModTap](modtap.md): Acrescenta suporte para teclas modificadoras que agem
como teclas comuns ao serem tocadas, mas como modificadores quando seguradas.
- [Power](power.md): Economia de energia. Este é mais útil quando usando baterias.
- [Split](split_keyboards.md): Teclados repartidos em dois. Tão ergonômicos!

View File

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

View File

@ -1,272 +0,0 @@
import busio
from supervisor import ticks_ms
import adafruit_displayio_ssd1306
import displayio
import terminalio
from adafruit_display_text import label
from kmk.extensions import Extension
from kmk.handlers.stock import passthrough as handler_passthrough
from kmk.keys import make_key
from kmk.kmktime import PeriodicTimer, ticks_diff
from kmk.modules.split import Split, SplitSide
from kmk.utils import clamp
displayio.release_displays()
class TextEntry:
def __init__(
self,
text='',
x=0,
y=0,
x_anchor='L',
y_anchor='T',
direction='LTR',
line_spacing=0.75,
inverted=False,
layer=None,
side=None,
):
self.text = text
self.direction = direction
self.line_spacing = line_spacing
self.inverted = inverted
self.layer = layer
self.color = 0xFFFFFF
self.background_color = 0x000000
self.x_anchor = 0.0
self.y_anchor = 0.0
if x_anchor == 'L':
self.x_anchor = 0.0
x = x + 1
if x_anchor == 'M':
self.x_anchor = 0.5
if x_anchor == 'R':
self.x_anchor = 1.0
if y_anchor == 'T':
self.y_anchor = 0.0
if y_anchor == 'M':
self.y_anchor = 0.5
if y_anchor == 'B':
self.y_anchor = 1.0
self.anchor_point = (self.x_anchor, self.y_anchor)
self.anchored_position = (x, y)
if inverted:
self.color = 0x000000
self.background_color = 0xFFFFFF
self.side = side
if side == 'L':
self.side = SplitSide.LEFT
if side == 'R':
self.side = SplitSide.RIGHT
class ImageEntry:
def __init__(self, x=0, y=0, image='', layer=None, side=None):
self.x = x
self.y = y
self.image = displayio.OnDiskBitmap(image)
self.layer = layer
self.side = side
if side == 'L':
self.side = SplitSide.LEFT
if side == 'R':
self.side = SplitSide.RIGHT
class Oled(Extension):
def __init__(
self,
i2c=None,
sda=None,
scl=None,
device_address=0x3C,
entries=[],
width=128,
height=32,
flip: bool = False,
flip_left: bool = False,
flip_right: bool = False,
brightness=0.8,
brightness_step=0.1,
dim_time=20,
dim_target=0.1,
off_time=60,
powersave_dim_time=10,
powersave_dim_target=0.1,
powersave_off_time=30,
):
self.device_address = device_address
self.flip = flip
self.flip_left = flip_left
self.flip_right = flip_right
self.entries = entries
self.width = width
self.height = height
self.prev_layer = None
self.brightness = brightness
self.brightness_step = brightness_step
self.timer_start = ticks_ms()
self.powersave = False
self.dim_time_ms = dim_time * 1000
self.dim_target = dim_target
self.off_time_ms = off_time * 1000
self.powersavedim_time_ms = powersave_dim_time * 1000
self.powersave_dim_target = powersave_dim_target
self.powersave_off_time_ms = powersave_off_time * 1000
self.dim_period = PeriodicTimer(50)
self.split_side = None
# i2c initialization
self.i2c = i2c
if self.i2c is None:
self.i2c = busio.I2C(scl, sda)
make_key(
names=('OLED_BRI',),
on_press=self.oled_brightness_increase,
on_release=handler_passthrough,
)
make_key(
names=('OLED_BRD',),
on_press=self.oled_brightness_decrease,
on_release=handler_passthrough,
)
def render(self, layer):
splash = displayio.Group()
for entry in self.entries:
if entry.layer != layer and entry.layer is not None:
continue
if isinstance(entry, TextEntry):
splash.append(
label.Label(
terminalio.FONT,
text=entry.text,
color=entry.color,
background_color=entry.background_color,
anchor_point=entry.anchor_point,
anchored_position=entry.anchored_position,
label_direction=entry.direction,
line_spacing=entry.line_spacing,
padding_left=1,
)
)
elif isinstance(entry, ImageEntry):
splash.append(
displayio.TileGrid(
entry.image,
pixel_shader=entry.image.pixel_shader,
x=entry.x,
y=entry.y,
)
)
self.display.show(splash)
def on_runtime_enable(self, sandbox):
return
def on_runtime_disable(self, sandbox):
return
def during_bootup(self, keyboard):
for module in keyboard.modules:
if isinstance(module, Split):
self.split_side = module.split_side
if self.split_side == SplitSide.LEFT:
self.flip = self.flip_left
elif self.split_side == SplitSide.RIGHT:
self.flip = self.flip_right
for idx, entry in enumerate(self.entries):
if entry.side != self.split_side and entry.side is not None:
del self.entries[idx]
self.display = adafruit_displayio_ssd1306.SSD1306(
displayio.I2CDisplay(self.i2c, device_address=self.device_address),
width=self.width,
height=self.height,
rotation=180 if self.flip else 0,
brightness=self.brightness,
)
def before_matrix_scan(self, sandbox):
if self.dim_period.tick():
self.dim()
if sandbox.active_layers[0] != self.prev_layer:
self.prev_layer = sandbox.active_layers[0]
self.render(sandbox.active_layers[0])
def after_matrix_scan(self, sandbox):
if sandbox.matrix_update or sandbox.secondary_matrix_update:
self.timer_start = ticks_ms()
def before_hid_send(self, sandbox):
return
def after_hid_send(self, sandbox):
return
def on_powersave_enable(self, sandbox):
self.powersave = True
def on_powersave_disable(self, sandbox):
self.powersave = False
def deinit(self, sandbox):
displayio.release_displays()
self.i2c.deinit()
def oled_brightness_increase(self):
self.display.brightness = clamp(
self.display.brightness + self.brightness_step, 0, 1
)
self.brightness = self.display.brightness # Save current brightness
def oled_brightness_decrease(self):
self.display.brightness = clamp(
self.display.brightness - self.brightness_step, 0, 1
)
self.brightness = self.display.brightness # Save current brightness
def dim(self):
if self.powersave:
if (
self.powersave_off_time_ms
and ticks_diff(ticks_ms(), self.timer_start)
> self.powersave_off_time_ms
):
self.display.sleep()
elif (
self.powersave_dim_time_ms
and ticks_diff(ticks_ms(), self.timer_start)
> self.powersave_dim_time_ms
):
self.display.brightness = self.powersave_dim_target
else:
self.display.brightness = self.brightness
self.display.wake()
elif (
self.off_time_ms
and ticks_diff(ticks_ms(), self.timer_start) > self.off_time_ms
):
self.display.sleep()
elif (
self.dim_time_ms
and ticks_diff(ticks_ms(), self.timer_start) > self.dim_time_ms
):
self.display.brightness = self.dim_target
else:
self.display.brightness = self.brightness
self.display.wake()

View File

@ -4,7 +4,7 @@ from math import e, exp, pi, sin
from kmk.extensions import Extension
from kmk.handlers.stock import passthrough as handler_passthrough
from kmk.keys import make_key
from kmk.scheduler import create_task
from kmk.kmktime import PeriodicTimer
from kmk.utils import Debug, clamp
debug = Debug(__name__)
@ -90,10 +90,10 @@ class RGB(Extension):
self,
pixel_pin,
num_pixels=0,
rgb_order=(1, 0, 2), # GRB WS2812
val_limit=255,
hue_default=0,
sat_default=255,
rgb_order=(1, 0, 2), # GRB WS2812
val_default=255,
hue_step=4,
sat_step=13,
@ -109,9 +109,32 @@ class RGB(Extension):
pixels=None,
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.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.sat_step = sat_step
self.val_step = val_step
@ -130,11 +153,8 @@ class RGB(Extension):
self.reverse_animation = reverse_animation
self.user_animation = user_animation
self.disable_auto_write = disable_auto_write
self.pixels = pixels
self.refresh_rate = refresh_rate
self.rgbw = bool(len(rgb_order) == 4)
self._substep = 0
make_key(
@ -207,29 +227,7 @@ class RGB(Extension):
return
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._task = create_task(self.animate, period_ms=(1000 // self.refresh_rate))
self._timer = PeriodicTimer(1000 // self.refresh_rate)
def before_matrix_scan(self, sandbox):
return
@ -241,7 +239,7 @@ class RGB(Extension):
return
def after_hid_send(self, sandbox):
pass
self.animate()
def on_powersave_enable(self, sandbox):
return
@ -249,10 +247,6 @@ class RGB(Extension):
def on_powersave_disable(self, sandbox):
self._do_update()
def deinit(self, sandbox):
for pixel in self.pixels:
pixel.deinit()
def set_hsv(self, hue, sat, val, index):
'''
Takes HSV values and displays it on a single LED/Neopixel
@ -435,7 +429,7 @@ class RGB(Extension):
if self.animation_mode is AnimationModes.STATIC_STANDBY:
return
if self.enable:
if self.enable and self._timer.tick():
self._animation_step()
if self.animation_mode == AnimationModes.BREATHING:
self.effect_breathing()

View File

@ -137,10 +137,3 @@ def ble_disconnect(key, keyboard, *args, **kwargs):
keyboard._hid_helper.clear_bonds()
return keyboard
def any_pressed(key, keyboard, *args, **kwargs):
from random import randint
key.code = randint(4, 56)
default_pressed(key, keyboard, *args, **kwargs)

View File

@ -370,7 +370,6 @@ def maybe_make_firmware_key(candidate: str) -> Optional[Key]:
((('HID_SWITCH', 'HID'), handlers.hid_switch)),
((('RELOAD', 'RLD'), handlers.reload)),
((('RESET',), handlers.reset)),
((('ANY',), handlers.any_pressed)),
)
for names, handler in keys:
@ -476,9 +475,7 @@ class KeyAttrDict:
break
if not maybe_key:
if debug.enabled:
debug(f'Invalid key: {name}')
return KC.NO
raise ValueError(f'Invalid key: {name}')
if debug.enabled:
debug(f'{name}: {maybe_key}')

View File

@ -1,33 +1,28 @@
try:
from typing import Callable, Optional
from typing import Callable, Optional, Tuple
except ImportError:
pass
from supervisor import ticks_ms
from collections import namedtuple
from keypad import Event as KeyEvent
from kmk.consts import UnicodeMode
from kmk.hid import BLEHID, USBHID, AbstractHID, HIDModes
from kmk.keys import KC, Key
from kmk.kmktime import ticks_add, ticks_diff
from kmk.modules import Module
from kmk.scanners.keypad import MatrixScanner
from kmk.scheduler import Task, cancel_task, create_task, get_due_task
from kmk.utils import Debug
debug = Debug('kmk.keyboard')
debug = Debug(__name__)
KeyBufferFrame = namedtuple(
'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:
matrix_update = None
secondary_matrix_update = None
@ -64,6 +59,7 @@ class KMKKeyboard:
matrix_update = None
secondary_matrix_update = None
matrix_update_queue = []
state_changed = False
_trigger_powersave_enable = False
_trigger_powersave_disable = False
i2c_deinit_count = 0
@ -79,24 +75,47 @@ class KMKKeyboard:
_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:
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:
if not self._hid_send_enabled:
return
if debug.enabled:
if self.keys_pressed:
debug('keys_pressed=', self.keys_pressed)
if self.axes:
debug('axes=', self.axes)
if self.axes and debug.enabled:
debug(f'axes={self.axes}')
self._hid_helper.create_report(self.keys_pressed, self.axes)
try:
self._hid_helper.send()
except Exception as err:
debug_error(self._hid_helper, 'send', err)
except KeyError as e:
if debug.enabled:
debug(f'HidNotFound(HIDReportType={e})')
self.hid_pending = False
@ -106,32 +125,35 @@ class KMKKeyboard:
def _handle_matrix_report(self, kevent: KeyEvent) -> None:
if kevent is not None:
self._on_matrix_changed(kevent)
self.state_changed = True
def _find_key_in_map(self, int_coord: int) -> Key:
try:
idx = self.coord_mapping.index(int_coord)
except ValueError:
if debug.enabled:
debug('no such int_coord: ', int_coord)
debug(f'CoordMappingNotFound(ic={int_coord})')
return None
for layer in self.active_layers:
try:
key = self.keymap[layer][idx]
layer_key = self.keymap[layer][idx]
except IndexError:
key = None
layer_key = None
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
return key
return layer_key
def _on_matrix_changed(self, kevent: KeyEvent) -> None:
int_coord = kevent.key_number
is_pressed = kevent.pressed
if debug.enabled:
debug(f'MatrixChange(ic={int_coord}, pressed={is_pressed})')
key = None
if not is_pressed:
@ -139,16 +161,18 @@ class KMKKeyboard:
key = self._coordkeys_pressed[int_coord]
except KeyError:
if debug.enabled:
debug('release w/o press: ', int_coord)
debug(f'KeyNotPressed(ic={int_coord})')
if key is None:
key = self._find_key_in_map(int_coord)
if key is None:
return
if key is None:
if debug.enabled:
debug(f'MatrixUndefinedCoordinate(ic={int_coord})')
return self
if debug.enabled:
debug(kevent, ': ', key)
debug(f'KeyResolution(key={key})')
self.pre_process_key(key, is_pressed, int_coord)
@ -173,7 +197,7 @@ class KMKKeyboard:
key = ksf.key
# 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)
# Resume the processing of the key event and update the HID report
@ -214,7 +238,8 @@ class KMKKeyboard:
if key is None:
break
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 is_pressed:
@ -224,20 +249,18 @@ class KMKKeyboard:
del self._coordkeys_pressed[int_coord]
except KeyError:
if debug.enabled:
debug('release w/o press:', int_coord)
if debug.enabled:
debug('coordkeys_pressed=', self._coordkeys_pressed)
debug(f'ReleaseKeyError(ic={int_coord})')
if key:
self.process_key(key, is_pressed, int_coord)
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:
if is_pressed:
key.on_press(self, int_coord)
key.on_press(self, coord_int)
else:
key.on_release(self, int_coord)
key.on_release(self, coord_int)
def resume_process_key(
self,
@ -245,9 +268,8 @@ class KMKKeyboard:
key: Key,
is_pressed: bool,
int_coord: Optional[int] = None,
reprocess: Optional[bool] = False,
) -> None:
index = self.modules.index(module) + (0 if reprocess else 1)
index = self.modules.index(module) + 1
ksf = KeyBufferFrame(
key=key, is_pressed=is_pressed, int_coord=int_coord, index=index
)
@ -264,17 +286,60 @@ class KMKKeyboard:
def tap_key(self, keycode: Key) -> None:
self.add_key(keycode)
# On the next cycle, we'll remove the key.
self.set_timeout(0, lambda: self.remove_key(keycode))
self.set_timeout(False, lambda: self.remove_key(keycode))
def set_timeout(self, after_ticks: int, callback: Callable[[None], None]) -> [Task]:
return create_task(callback, after_ms=after_ticks)
def set_timeout(
self, after_ticks: int, callback: Callable[[None], None]
) -> Tuple[int, int]:
# We allow passing False as an implicit "run this on the next process timeouts cycle"
if after_ticks is False:
after_ticks = 0
if after_ticks == 0 and self._processing_timeouts:
after_ticks += 1
timeout_key = ticks_add(ticks_ms(), after_ticks)
if timeout_key not in self._timeouts:
self._timeouts[timeout_key] = []
idx = len(self._timeouts[timeout_key])
self._timeouts[timeout_key].append(callback)
return (timeout_key, idx)
def cancel_timeout(self, timeout_key: int) -> None:
cancel_task(timeout_key)
try:
self._timeouts[timeout_key[0]][timeout_key[1]] = None
except (KeyError, IndexError):
if debug.enabled:
debug(f'no such timeout: {timeout_key}')
def _process_timeouts(self) -> None:
for task in get_due_task():
task()
if not self._timeouts:
return
# Copy timeout keys to a temporary list to allow sorting.
# Prevent net timeouts set during handling from running on the current
# cycle by setting a flag `_processing_timeouts`.
current_time = ticks_ms()
timeout_keys = []
self._processing_timeouts = True
for k in self._timeouts.keys():
if ticks_diff(k, current_time) <= 0:
timeout_keys.append(k)
if timeout_keys and debug.enabled:
debug('processing timeouts')
for k in sorted(timeout_keys):
for callback in self._timeouts[k]:
if callback:
callback()
del self._timeouts[k]
self._processing_timeouts = False
def _init_sanity_check(self) -> None:
'''
@ -321,15 +386,10 @@ class KMKKeyboard:
self._hid_helper = self._hid_helper(**self._go_args)
self._hid_send_enabled = True
if debug.enabled:
debug('hid=', self._hid_helper)
def _deinit_hid(self) -> None:
self._hid_helper.clear_all()
self._hid_helper.send()
def _init_matrix(self) -> None:
if self.matrix is None:
if debug.enabled:
debug('Initialising default matrix scanner.')
self.matrix = MatrixScanner(
column_pins=self.col_pins,
row_pins=self.row_pins,
@ -345,134 +405,101 @@ class KMKKeyboard:
except TypeError:
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:
for module in self.modules:
try:
module.before_matrix_scan(self)
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:
try:
ext.before_matrix_scan(self.sandbox)
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:
for module in self.modules:
try:
module.after_matrix_scan(self)
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:
try:
ext.after_matrix_scan(self.sandbox)
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:
for module in self.modules:
try:
module.before_hid_send(self)
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:
try:
ext.before_hid_send(self.sandbox)
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:
for module in self.modules:
try:
module.after_hid_send(self)
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:
try:
ext.after_hid_send(self.sandbox)
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:
for module in self.modules:
try:
module.on_powersave_enable(self)
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:
try:
ext.on_powersave_enable(self.sandbox)
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:
for module in self.modules:
try:
module.on_powersave_disable(self)
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:
try:
ext.on_powersave_disable(self.sandbox)
except Exception as err:
debug_error(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)
if debug.enabled:
debug(f'Error in {ext}.powersave_disable: {err}')
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)
try:
while True:
self._main_loop()
finally:
debug('Unexpected error: cleaning up')
self._deinit_hid()
self.deinit()
while True:
self._main_loop()
def _init(
self,
@ -484,22 +511,29 @@ class KMKKeyboard:
self.hid_type = hid_type
self.secondary_hid_type = secondary_hid_type
if debug.enabled:
debug('Initialising ', self)
debug('unicode_mode=', self.unicode_mode)
self._init_sanity_check()
self._init_hid()
self._init_matrix()
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:
import gc
gc.collect()
debug('mem_info used:', gc.mem_alloc(), ' free:', gc.mem_free())
debug(f'init: {self}')
def _main_loop(self) -> None:
self.state_changed = False
self.sandbox.active_layers = self.active_layers.copy()
self.before_matrix_scan()
@ -537,6 +571,7 @@ class KMKKeyboard:
if self.hid_pending:
self._send_hid()
self.state_changed = True
self.after_hid_send()
@ -545,3 +580,6 @@ class KMKKeyboard:
if self._trigger_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):
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.kmk_keyboard import KMKKeyboard
from kmk.modules import Module
from kmk.utils import Debug
debug = Debug(__name__)
class _ComboState:
@ -217,7 +214,7 @@ class Combos(Module):
combo.insert(key, int_coord)
combo._state = _ComboState.MATCHING
key = None
key = combo.result
break
else:
@ -304,14 +301,10 @@ class Combos(Module):
keyboard.resume_process_key(self, key, is_pressed, int_coord)
def activate(self, keyboard, combo):
if debug.enabled:
debug('activate', combo)
combo.result.on_press(keyboard)
combo._state = _ComboState.ACTIVE
def deactivate(self, keyboard, combo):
if debug.enabled:
debug('deactivate', combo)
combo.result.on_release(keyboard)
combo._state = _ComboState.IDLE

View File

@ -54,7 +54,7 @@ class HoldTap(Module):
def __init__(self):
self.key_buffer = []
self.key_states = {}
if KC.get('HT') == KC.NO:
if not KC.get('HT'):
make_argumented_key(
validator=HoldTapKeyMeta,
names=('HT',),
@ -83,11 +83,6 @@ class HoldTap(Module):
if state.activated != ActivationType.PRESSED:
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.
if (is_pressed and not key.meta.tap_interrupted) or (
not is_pressed and key.meta.tap_interrupted and self.key_buffer
@ -98,12 +93,15 @@ class HoldTap(Module):
self.ht_activate_on_interrupt(
key, keyboard, *state.args, **state.kwargs
)
append_buffer = True
send_buffer = True
# if interrupt on release: store interrupting keys until one of them
# 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
# 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))
current_key = None
if send_buffer:
elif send_buffer:
self.send_key_buffer(keyboard)
keyboard.resume_process_key(self, current_key, is_pressed, int_coord)
current_key = None
return current_key
@ -221,11 +221,8 @@ class HoldTap(Module):
if not self.key_buffer:
return
reprocess = False
for (int_coord, key, is_pressed) in self.key_buffer:
keyboard.resume_process_key(self, key, is_pressed, int_coord, reprocess)
if isinstance(key.meta, HoldTapKeyMeta):
reprocess = True
keyboard.resume_process_key(self, key, is_pressed, int_coord)
self.key_buffer.clear()

View File

@ -36,15 +36,9 @@ class LayerKeyMeta:
class Layers(HoldTap):
'''Gives access to the keys used to enable the layer system'''
_active_combo = None
def __init__(
self,
combo_layers=None,
):
def __init__(self):
# Layers
super().__init__()
self.combo_layers = combo_layers
make_argumented_key(
validator=layer_key_validator,
names=('MO',),
@ -52,7 +46,9 @@ class Layers(HoldTap):
on_release=self._mo_released,
)
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(
validator=layer_key_validator,
@ -61,10 +57,14 @@ class Layers(HoldTap):
on_release=self._lm_released,
)
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(
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(
validator=layer_key_validator_lt,
@ -83,102 +83,67 @@ class Layers(HoldTap):
'''
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):
'''
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):
self.deactivate_layer(keyboard, key.meta.layer)
@staticmethod
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):
'''
As MO(layer) but with mod active
'''
keyboard.hid_pending = True
keyboard.keys_pressed.add(key.meta.kc)
self.activate_layer(keyboard, key.meta.layer)
# Sets the timer start and acts like MO otherwise
keyboard.add_key(key.meta.kc)
self._mo_pressed(key, keyboard, *args, **kwargs)
def _lm_released(self, key, keyboard, *args, **kwargs):
'''
As MO(layer) but with mod active
'''
keyboard.hid_pending = True
keyboard.keys_pressed.discard(key.meta.kc)
self.deactivate_layer(keyboard, key.meta.layer)
keyboard.remove_key(key.meta.kc)
self._mo_released(key, keyboard, *args, **kwargs)
def _tg_pressed(self, key, keyboard, *args, **kwargs):
'''
Toggles the layer (enables it if not active, and vise versa)
'''
# See mo_released for implementation details around this
if key.meta.layer in keyboard.active_layers:
self.deactivate_layer(keyboard, key.meta.layer)
else:
self.activate_layer(keyboard, key.meta.layer)
try:
del_idx = keyboard.active_layers.index(key.meta.layer)
del keyboard.active_layers[del_idx]
except ValueError:
keyboard.active_layers.insert(0, key.meta.layer)
def _to_pressed(self, key, keyboard, *args, **kwargs):
'''
Activates layer and deactivates all other layers
'''
self._active_combo = None
keyboard.active_layers.clear()
keyboard.active_layers.insert(0, key.meta.layer)
def _print_debug(self, keyboard):
# debug(f'__getitem__ {key}')
if debug.enabled:
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

@ -2,7 +2,6 @@ from kmk.keys import make_argumented_key
from kmk.modules.holdtap import HoldTap, HoldTapKeyMeta
# Deprecation Notice: The `ModTap` class serves as an alias for `HoldTap` and will be removed in a future update. Please use `HoldTap` instead.
class ModTap(HoldTap):
def __init__(self):
super().__init__()

View File

@ -1,14 +1,9 @@
from kmk.keys import make_argumented_key
from kmk.modules.holdtap import ActivationType, HoldTap, HoldTapKeyMeta
from kmk.modules.layers import LayerKeyMeta
from kmk.utils import Debug
debug = Debug(__name__)
class OneShotKeyMeta(HoldTapKeyMeta):
def __init__(self, kc, tap_time=None):
super().__init__(tap=kc, hold=kc, prefer_hold=False, tap_time=tap_time)
def oneshot_validator(kc, tap_time=None):
return HoldTapKeyMeta(tap=kc, hold=kc, prefer_hold=False, tap_time=tap_time)
class OneShot(HoldTap):
@ -17,49 +12,30 @@ class OneShot(HoldTap):
def __init__(self):
super().__init__()
make_argumented_key(
validator=OneShotKeyMeta,
validator=oneshot_validator,
names=('OS', 'ONESHOT'),
on_press=self.osk_pressed,
on_release=self.osk_released,
)
def process_key(self, keyboard, current_key, is_pressed, int_coord):
'''Release os key after interrupting non-os keyup, or reset timeout and
stack multiple os keys.'''
send_buffer = False
'''Release os key after interrupting keyup.'''
for key, state in self.key_states.items():
if key == current_key:
continue
if (isinstance(current_key.meta, OneShotKeyMeta)) or (
isinstance(current_key.meta, LayerKeyMeta)
):
keyboard.cancel_timeout(state.timeout_key)
if key.meta.tap_time is None:
tap_time = self.tap_time
else:
tap_time = key.meta.tap_time
state.timeout_key = keyboard.set_timeout(
tap_time,
lambda k=key: self.on_tap_time_expired(k, keyboard),
)
continue
if state.activated == ActivationType.PRESSED and is_pressed:
state.activated = ActivationType.HOLD_TIMEOUT
elif state.activated == ActivationType.RELEASED and is_pressed:
state.activated = ActivationType.INTERRUPTED
elif state.activated == ActivationType.INTERRUPTED:
if is_pressed:
send_buffer = True
self.key_buffer.insert(0, (None, key, False))
if send_buffer:
self.key_buffer.append((int_coord, current_key, is_pressed))
current_key = None
self.send_key_buffer(keyboard)
keyboard.remove_key(key.meta.tap)
self.key_buffer.append((int_coord, current_key, is_pressed))
keyboard.set_timeout(False, lambda: self.send_key_buffer(keyboard))
current_key = None
else:
self.ht_released(key, keyboard)
return current_key
@ -75,8 +51,8 @@ class OneShot(HoldTap):
try:
state = self.key_states[key]
except KeyError:
if debug.enabled:
debug(f'OneShot.osk_released: no such key {key}')
if keyboard.debug_enabled:
print(f'OneShot.osk_released: no such key {key}')
return keyboard
if state.activated == ActivationType.PRESSED:

View File

@ -6,45 +6,48 @@ from micropython import const
import math
import struct
from adafruit_pixelbuf import PixelBuf
from kmk.keys import AX, KC, make_argumented_key, make_key
from kmk.kmktime import PeriodicTimer
from kmk.modules import Module
from kmk.utils import Debug
I2C_ADDRESS = 0x0A
I2C_ADDRESS_ALTERNATIVE = 0x0B
_I2C_ADDRESS = const(0x0A)
_I2C_ADDRESS_ALTERNATIVE = const(0x0B)
CHIP_ID = 0xBA11
VERSION = 1
_CHIP_ID = const(0xBA11)
_VERSION = const(1)
REG_LED_RED = 0x00
REG_LED_GRN = 0x01
REG_LED_BLU = 0x02
REG_LED_WHT = 0x03
_REG_LED_RED = const(0x00)
_REG_LED_GRN = const(0x01)
_REG_LED_BLU = const(0x02)
_REG_LED_WHT = const(0x03)
REG_LEFT = 0x04
REG_RIGHT = 0x05
REG_UP = 0x06
REG_DOWN = 0x07
REG_SWITCH = 0x08
MSK_SWITCH_STATE = 0b10000000
_REG_LEFT = const(0x04)
_REG_RIGHT = const(0x05)
_REG_UP = const(0x06)
_REG_DOWN = const(0x07)
_REG_SWITCH = const(0x08)
_MSK_SWITCH_STATE = const(0b10000000)
REG_USER_FLASH = 0xD0
REG_FLASH_PAGE = 0xF0
REG_INT = 0xF9
MSK_INT_TRIGGERED = 0b00000001
MSK_INT_OUT_EN = 0b00000010
REG_CHIP_ID_L = 0xFA
RED_CHIP_ID_H = 0xFB
REG_VERSION = 0xFC
REG_I2C_ADDR = 0xFD
REG_CTRL = 0xFE
MSK_CTRL_SLEEP = 0b00000001
MSK_CTRL_RESET = 0b00000010
MSK_CTRL_FREAD = 0b00000100
MSK_CTRL_FWRITE = 0b00001000
_REG_USER_FLASH = const(0xD0)
_REG_FLASH_PAGE = const(0xF0)
_REG_INT = const(0xF9)
_MSK_INT_TRIGGERED = const(0b00000001)
_MSK_INT_OUT_EN = const(0b00000010)
_REG_CHIP_ID_L = const(0xFA)
_REG_CHIP_ID_H = const(0xFB)
_REG_VERSION = const(0xFC)
_REG_I2C_ADDR = const(0xFD)
_REG_CTRL = const(0xFE)
_MSK_CTRL_SLEEP = const(0b00000001)
_MSK_CTRL_RESET = const(0b00000010)
_MSK_CTRL_FREAD = const(0b00000100)
_MSK_CTRL_FWRITE = const(0b00001000)
ANGLE_OFFSET = 0
debug = Debug(__name__)
class TrackballHandlerKeyMeta:
@ -82,13 +85,8 @@ class PointingHandler(TrackballHandler):
if y:
AX.Y.move(keyboard, y)
if switch == 1: # Button pressed
keyboard.pre_process_key(KC.MB_LMB, is_pressed=True)
if not state and trackball.previous_state is True: # Button released
keyboard.pre_process_key(KC.MB_LMB, is_pressed=False)
trackball.previous_state = state
if switch == 1: # Button changed state
keyboard.pre_process_key(KC.MB_LMB, is_pressed=state)
class ScrollHandler(TrackballHandler):
@ -102,13 +100,8 @@ class ScrollHandler(TrackballHandler):
if y != 0:
AX.W.move(keyboard, y)
if switch == 1: # Button pressed
keyboard.pre_process_key(KC.MB_LMB, is_pressed=True)
if not state and trackball.previous_state is True: # Button released
keyboard.pre_process_key(KC.MB_LMB, is_pressed=False)
trackball.previous_state = state
if switch == 1: # Button changed state
keyboard.pre_process_key(KC.MB_LMB, is_pressed=state)
class KeyHandler(TrackballHandler):
@ -155,8 +148,8 @@ class Trackball(Module):
self,
i2c,
mode=TrackballMode.MOUSE_MODE,
address=I2C_ADDRESS,
angle_offset=ANGLE_OFFSET,
address=_I2C_ADDRESS,
angle_offset=0,
handlers=None,
):
self.angle_offset = angle_offset
@ -168,17 +161,10 @@ class Trackball(Module):
self._i2c_bus = i2c
self.mode = mode
self.previous_state = False # click state
self.handlers = handlers
self.current_handler = self.handlers[0]
self.polling_interval = 20
chip_id = struct.unpack('<H', bytearray(self._i2c_rdwr([REG_CHIP_ID_L], 2)))[0]
if chip_id != CHIP_ID:
raise RuntimeError(
f'Invalid chip ID: 0x{chip_id:04X}, expected 0x{CHIP_ID:04X}'
)
make_key(
names=('TB_MODE', 'TB_NEXT_HANDLER', 'TB_N'),
on_press=self._tb_handler_next_press,
@ -191,8 +177,17 @@ class Trackball(Module):
)
def during_bootup(self, keyboard):
chip_id = struct.unpack('<H', bytearray(self._i2c_rdwr([_REG_CHIP_ID_L], 2)))[0]
if chip_id != _CHIP_ID:
raise RuntimeError(
f'Invalid chip ID: 0x{chip_id:04X}, expected 0x{_CHIP_ID:04X}'
)
self._timer = PeriodicTimer(self.polling_interval)
a = math.pi * self.angle_offset / 180
self.rot = [[math.cos(a), math.sin(a)], [-math.sin(a), math.cos(a)]]
def before_matrix_scan(self, keyboard):
'''
Return value will be injected as an extra matrix update
@ -200,14 +195,15 @@ class Trackball(Module):
if not self._timer.tick():
return
if not (self._i2c_rdwr([_REG_INT], 1)[0] & _MSK_INT_TRIGGERED):
return
up, down, left, right, switch, state = self._read_raw_state()
x, y = self._calculate_movement(right - left, down - up)
self.current_handler.handle(keyboard, self, x, y, switch, state)
return
def after_matrix_scan(self, keyboard):
return
@ -225,23 +221,23 @@ class Trackball(Module):
def set_rgbw(self, r, g, b, w):
'''Set all LED brightness as RGBW.'''
self._i2c_rdwr([REG_LED_RED, r, g, b, w])
self._i2c_rdwr([_REG_LED_RED, r, g, b, w])
def set_red(self, value):
'''Set brightness of trackball red LED.'''
self._i2c_rdwr([REG_LED_RED, value & 0xFF])
self._i2c_rdwr([_REG_LED_RED, value & 0xFF])
def set_green(self, value):
'''Set brightness of trackball green LED.'''
self._i2c_rdwr([REG_LED_GRN, value & 0xFF])
self._i2c_rdwr([_REG_LED_GRN, value & 0xFF])
def set_blue(self, value):
'''Set brightness of trackball blue LED.'''
self._i2c_rdwr([REG_LED_BLU, value & 0xFF])
self._i2c_rdwr([_REG_LED_BLU, value & 0xFF])
def set_white(self, value):
'''Set brightness of trackball white LED.'''
self._i2c_rdwr([REG_LED_WHT, value & 0xFF])
self._i2c_rdwr([_REG_LED_WHT, value & 0xFF])
def activate_handler(self, handler):
if isinstance(handler, TrackballHandler):
@ -250,7 +246,8 @@ class Trackball(Module):
try:
self.current_handler = self.handlers[handler]
except KeyError:
print(f'no handler found with id {handler}')
if debug.enabled:
debug(f'no handler found with id {handler}')
def next_handler(self):
next_index = self.handlers.index(self.current_handler) + 1
@ -260,17 +257,17 @@ class Trackball(Module):
def _read_raw_state(self):
'''Read up, down, left, right and switch data from trackball.'''
left, right, up, down, switch = self._i2c_rdwr([REG_LEFT], 5)
switch, switch_state = (
switch & ~MSK_SWITCH_STATE,
(switch & MSK_SWITCH_STATE) > 0,
left, right, up, down, switch = self._i2c_rdwr([_REG_LEFT], 5)
switch_changed, switch_state = (
switch & ~_MSK_SWITCH_STATE,
(switch & _MSK_SWITCH_STATE) > 0,
)
return up, down, left, right, switch, switch_state
return up, down, left, right, switch_changed, switch_state
def _i2c_rdwr(self, data, length=0):
'''Write and optionally read I2C data.'''
while not self._i2c_bus.try_lock():
pass
if not self._i2c_bus.try_lock():
return
try:
if length > 0:
@ -298,17 +295,24 @@ class Trackball(Module):
if raw_x == 0 and raw_y == 0:
return 0, 0
var_accel = 1
power = 2.5
scale = math.sqrt(raw_x**2 + raw_y**2)
x = (self.rot[0][0] * raw_x + self.rot[0][1] * raw_y) * scale
y = (self.rot[1][0] * raw_x + self.rot[1][1] * raw_y) * scale
angle_rad = math.atan2(raw_y, raw_x) + self.angle_offset
vector_length = math.sqrt(pow(raw_x, 2) + pow(raw_y, 2))
vector_length = pow(vector_length * var_accel, power)
x = math.floor(vector_length * math.cos(angle_rad))
y = math.floor(vector_length * math.sin(angle_rad))
return int(x), int(y)
limit = 127 # hid size limit
x_clamped = max(min(limit, x), -limit)
y_clamped = max(min(limit, y), -limit)
return x_clamped, y_clamped
class TrackballPixel(PixelBuf):
'''PixelBuf interface for the Trackball RGBW LED'''
def __init__(self, trackball, **kwargs):
self.trackball = trackball
kwargs['byteorder'] = 'RGBW'
super().__init__(1, **kwargs)
def deinit(self):
super().deinit()
self.trackball.set_rgbw(0, 0, 0, 0)
def _transmit(self, b):
self.trackball.set_rgbw(b[0], b[1], b[2], b[3])

View File

@ -103,9 +103,9 @@ class Power(Module):
'''
Sleeps longer and longer to save power the more time in between updates.
'''
if check_deadline(ticks_ms(), self._powersave_start, 60000):
if check_deadline(ticks_ms(), self._powersave_start) <= 60000:
sleep(8 / 1000)
elif check_deadline(ticks_ms(), self._powersave_start, 240000) is False:
elif check_deadline(ticks_ms(), self._powersave_start) >= 240000:
sleep(180 / 1000)
return
@ -123,7 +123,7 @@ class Power(Module):
return
def usb_rescan_timer(self):
return bool(check_deadline(ticks_ms(), self._usb_last_scan, 5000) is False)
return bool(check_deadline(ticks_ms(), self._usb_last_scan) > 5000)
def usb_time_reset(self):
self._usb_last_scan = ticks_ms()

View File

@ -42,11 +42,12 @@ class Phrase:
self._characters: list[Character] = []
self._index: int = 0
for char in string:
key_code = KC[char]
if key_code == KC.NO:
try:
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}')
shifted = char.isupper() or key_code.has_modifiers == {2}
self._characters.append(Character(key_code, shifted))
def next_character(self) -> None:
'''Increment the current index for this phrase'''

View File

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

View File

@ -1,67 +0,0 @@
'''
Here we're abusing _asyncios TaskQueue to implement a very simple priority
queue task scheduler.
Despite documentation, Circuitpython doesn't usually ship with a min-heap
module; it does however implement a pairing-heap for `TaskQueue` in native code.
'''
try:
from typing import Callable
except ImportError:
pass
from supervisor import ticks_ms
from _asyncio import Task, TaskQueue
from kmk.kmktime import ticks_add, ticks_diff
_task_queue = TaskQueue()
class PeriodicTaskMeta:
def __init__(self, func: Callable[[None], None], period: int) -> None:
self._task = Task(self.call)
self._coro = func
self.period = period
def call(self) -> None:
self._coro()
after_ms = ticks_add(self._task.ph_key, self.period)
_task_queue.push_sorted(self._task, after_ms)
def create_task(
func: Callable[[None], None],
*,
after_ms: int = 0,
period_ms: int = 0,
) -> [Task, PeriodicTaskMeta]:
if period_ms:
r = PeriodicTaskMeta(func, period_ms)
t = r._task
else:
t = r = Task(func)
if after_ms:
after_ms = ticks_add(ticks_ms(), after_ms)
_task_queue.push_sorted(t, after_ms)
else:
_task_queue.push_head(t)
return r
def get_due_task() -> [Callable, None]:
while True:
t = _task_queue.peek()
if not t or ticks_diff(t.ph_key, ticks_ms()) > 0:
break
_task_queue.pop_head()
yield t.coro
def cancel_task(t: [Task, PeriodicTaskMeta]) -> None:
if isinstance(t, PeriodicTaskMeta):
t = t._task
_task_queue.remove(t)

View File

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

View File

@ -6,7 +6,6 @@ from kmk.keys import KC, ModifierKey
from kmk.kmk_keyboard import KMKKeyboard
from kmk.scanners import DiodeOrientation
from kmk.scanners.digitalio import MatrixScanner
from kmk.scheduler import _task_queue
class DigitalInOut(Mock):
@ -24,8 +23,6 @@ def code2name(code):
class KeyboardTest:
loop_delay_ms = 2
def __init__(
self,
modules,
@ -77,14 +74,7 @@ class KeyboardTest:
is_pressed = e[1]
self.pins[key_pos].value = is_pressed
self.do_main_loop()
# wait up to 10s for delayed actions to resolve, if there are any
timeout = time.time_ns() + 10 * 1_000_000_000
while timeout > time.time_ns():
self.do_main_loop()
if not _task_queue.peek() and not self.keyboard._resume_buffer:
break
assert timeout > time.time_ns(), 'infinite loop detected'
self.keyboard._main_loop()
matching = True
for i in range(max(len(hid_reports), len(assert_reports))):
@ -131,4 +121,4 @@ class KeyboardTest:
def do_main_loop(self):
self.keyboard._main_loop()
time.sleep(self.loop_delay_ms / 1000)
time.sleep(0.002)

View File

@ -9,10 +9,6 @@ class KeyEvent:
self.pressed = pressed
def ticks_ms():
return (time.time_ns() // 1_000_000) % (1 << 29)
def init_circuit_python_modules_mocks():
sys.modules['usb_hid'] = Mock()
sys.modules['digitalio'] = Mock()
@ -30,8 +26,4 @@ def init_circuit_python_modules_mocks():
sys.modules['micropython'].const = lambda x: x
sys.modules['supervisor'] = Mock()
sys.modules['supervisor'].ticks_ms = ticks_ms
from . import task
sys.modules['_asyncio'] = task
sys.modules['supervisor'].ticks_ms = lambda: time.time_ns() // 1_000_000

View File

@ -1,196 +0,0 @@
# MicroPython uasyncio module
# MIT license; Copyright (c) 2019-2020 Damien P. George
# This file contains the core TaskQueue based on a pairing heap, and the core Task class.
# They can optionally be replaced by C implementations.
# This file is a modified version, based on the extmod in Circuitpython, for
# unit testing in KMK only.
from supervisor import ticks_ms
from kmk.kmktime import ticks_diff
cur_task = None
__task_queue = None
class CancelledError(BaseException):
pass
# pairing-heap meld of 2 heaps; O(1)
def ph_meld(h1, h2):
if h1 is None:
return h2
if h2 is None:
return h1
lt = ticks_diff(h1.ph_key, h2.ph_key) < 0
if lt:
if h1.ph_child is None:
h1.ph_child = h2
else:
h1.ph_child_last.ph_next = h2
h1.ph_child_last = h2
h2.ph_next = None
h2.ph_rightmost_parent = h1
return h1
else:
h1.ph_next = h2.ph_child
h2.ph_child = h1
if h1.ph_next is None:
h2.ph_child_last = h1
h1.ph_rightmost_parent = h2
return h2
# pairing-heap pairing operation; amortised O(log N)
def ph_pairing(child):
heap = None
while child is not None:
n1 = child
child = child.ph_next
n1.ph_next = None
if child is not None:
n2 = child
child = child.ph_next
n2.ph_next = None
n1 = ph_meld(n1, n2)
heap = ph_meld(heap, n1)
return heap
# pairing-heap delete of a node; stable, amortised O(log N)
def ph_delete(heap, node):
if node is heap:
child = heap.ph_child
node.ph_child = None
return ph_pairing(child)
# Find parent of node
parent = node
while parent.ph_next is not None:
parent = parent.ph_next
parent = parent.ph_rightmost_parent
if parent is None or parent.ph_child is None:
return heap
# Replace node with pairing of its children
if node is parent.ph_child and node.ph_child is None:
parent.ph_child = node.ph_next
node.ph_next = None
return heap
elif node is parent.ph_child:
child = node.ph_child
next = node.ph_next
node.ph_child = None
node.ph_next = None
node = ph_pairing(child)
parent.ph_child = node
else:
n = parent.ph_child
while node is not n.ph_next:
n = n.ph_next
if not n:
return heap
child = node.ph_child
next = node.ph_next
node.ph_child = None
node.ph_next = None
node = ph_pairing(child)
if node is None:
node = n
else:
n.ph_next = node
node.ph_next = next
if next is None:
node.ph_rightmost_parent = parent
parent.ph_child_last = node
return heap
# TaskQueue class based on the above pairing-heap functions.
class TaskQueue:
def __init__(self):
self.heap = None
def peek(self):
return self.heap
def push_sorted(self, v, key):
v.data = None
v.ph_key = key
v.ph_child = None
v.ph_next = None
self.heap = ph_meld(v, self.heap)
def push_head(self, v):
self.push_sorted(v, ticks_ms())
def pop_head(self):
v = self.heap
self.heap = ph_pairing(v.ph_child)
# v.ph_child = None
return v
def remove(self, v):
self.heap = ph_delete(self.heap, v)
# Task class representing a coroutine, can be waited on and cancelled.
class Task:
def __init__(self, coro, globals=None):
self.coro = coro # Coroutine of this Task
self.data = None # General data for queue it is waiting on
self.state = True # None, False, True or a TaskQueue instance
self.ph_key = 0 # Pairing heap
self.ph_child = None # Paring heap
self.ph_child_last = None # Paring heap
self.ph_next = None # Paring heap
self.ph_rightmost_parent = None # Paring heap
def __await__(self):
if not self.state:
# Task finished, signal that is has been await'ed on.
self.state = False
elif self.state is True:
# Allocated head of linked list of Tasks waiting on completion of this task.
self.state = TaskQueue()
return self
def __next__(self):
if not self.state:
if self.data is None:
# Task finished but has already been sent to the loop's exception handler.
raise StopIteration
else:
# Task finished, raise return value to caller so it can continue.
raise self.data
else:
# Put calling task on waiting queue.
self.state.push_head(cur_task)
# Set calling task's data to this task that it waits on, to double-link it.
cur_task.data = self
def done(self):
return not self.state
def cancel(self):
# Check if task is already finished.
if not self.state:
return False
# Can't cancel self (not supported yet).
if self is cur_task:
raise RuntimeError("can't cancel self")
# If Task waits on another task then forward the cancel to the one it's waiting on.
while isinstance(self.data, Task):
self = self.data
# Reschedule Task as a cancelled task.
if hasattr(self.data, 'remove'):
# Not on the main running queue, remove the task from the queue it's on.
self.data.remove(self)
__task_queue.push_head(self)
elif ticks_diff(self.ph_key, ticks_ms()) > 0:
# On the main running queue but scheduled in the future, so bring it forward to now.
__task_queue.remove(self)
__task_queue.push_head(self)
self.data = CancelledError
return True

View File

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

View File

@ -1,36 +1,28 @@
import unittest
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 tests.keyboard_test import KeyboardTest
class TestCombo(unittest.TestCase):
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()
layers = Layers()
KCMO = KC.MO(1)
combos.combos = [
Chord((KC.A, KC.B, KC.C), KC.Y),
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.F, KC.G), KC.Z, timeout=3 * timeout),
Sequence((KC.N1, KC.N2, KC.N3), KC.Y),
Sequence((KC.N1, KC.N2), KC.X),
Sequence((KC.N3, KC.N4), KC.Z, timeout=2 * timeout),
Sequence((KC.N1, KC.N1, KC.N1), KC.W),
Sequence((KC.N3, KC.N2, KC.N1), KC.Y, fast_reset=False),
Sequence((KC.LEADER, KC.N1), KC.V),
Chord((KC.F, KC.G), KC.Z, timeout=130),
Sequence((KC.N1, KC.N2, KC.N3), KC.Y, timeout=50),
Sequence((KC.N1, KC.N2), KC.X, timeout=50),
Sequence((KC.N3, KC.N4), KC.Z, timeout=100),
Sequence((KC.N1, KC.N1, KC.N1), KC.W, timeout=50),
Sequence((KC.N3, KC.N2, KC.N1), KC.Y, timeout=50, fast_reset=False),
Sequence((KC.LEADER, KC.N1), KC.V, timeout=50),
]
self.keyboard = KeyboardTest(
[combos, layers],
@ -41,6 +33,9 @@ class TestCombo(unittest.TestCase):
debug_enabled=False,
)
self.t_within = 40
self.t_after = 60
def test_chord(self):
keyboard = self.keyboard
t_within = self.t_within

View File

@ -1,121 +1,103 @@
import unittest
from kmk.keys import KC
from kmk.modules.holdtap import HoldTap, HoldTapRepeat
from kmk.modules.holdtap import HoldTapRepeat
from kmk.modules.layers import Layers
from kmk.modules.modtap import ModTap
from kmk.modules.oneshot import OneShot
from tests.keyboard_test import KeyboardTest
class TestHoldTap(unittest.TestCase):
def setUp(self):
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):
t_within = self.t_within
t_after = self.t_after
keyboard = KeyboardTest(
[Layers(), HoldTap()],
[Layers(), ModTap(), OneShot()],
[
[
KC.HT(KC.A, KC.LCTL),
KC.LT(1, KC.B),
KC.C,
KC.D,
],
[KC.N1, KC.N2, KC.N3, KC.N4],
[KC.MT(KC.A, KC.LCTL), KC.LT(1, KC.B), KC.C, KC.D, KC.OS(KC.E)],
[KC.N1, KC.N2, KC.N3, KC.N4, KC.N5],
],
debug_enabled=False,
)
keyboard.test(
'HT tap behaviour', [(0, True), t_within, (0, False)], [{KC.A}, {}]
)
keyboard.test('MT tap behaviour', [(0, True), 100, (0, False)], [{KC.A}, {}])
keyboard.test(
'HT hold behaviour', [(0, True), t_after, (0, False)], [{KC.LCTL}, {}]
'MT hold behaviour', [(0, True), 350, (0, False)], [{KC.LCTL}, {}]
)
# TODO test multiple mods being held
# HT
# MT
keyboard.test(
'HT within tap time sequential -> tap behavior',
[(0, True), t_within, (0, False), (3, True), (3, False)],
'MT within tap time sequential -> tap behavior',
[(0, True), 100, (0, False), (3, True), (3, False)],
[{KC.A}, {}, {KC.D}, {}],
)
keyboard.test(
'HT within tap time rolling -> hold behavior',
[(0, True), t_within, (3, True), t_after, (0, False), (3, False)],
'MT within tap time rolling -> hold behavior',
[(0, True), 100, (3, True), 250, (0, False), (3, False)],
[{KC.LCTL}, {KC.LCTL, KC.D}, {KC.D}, {}],
)
keyboard.test(
'HT within tap time nested -> hold behavior',
[(0, True), t_within, (3, True), (3, False), t_after, (0, False)],
'MT within tap time nested -> hold behavior',
[(0, True), 100, (3, True), (3, False), 250, (0, False)],
[{KC.LCTL}, {KC.LCTL, KC.D}, {KC.LCTL}, {}],
)
keyboard.test(
'HT after tap time sequential -> hold behavior',
[(0, True), t_after, (0, False), (3, True), (3, False)],
'MT after tap time sequential -> hold behavior',
[(0, True), 350, (0, False), (3, True), (3, False)],
[{KC.LCTL}, {}, {KC.D}, {}],
)
keyboard.test(
'HT after tap time rolling -> hold behavior',
[(0, True), t_after, (3, True), (0, False), (3, False)],
'MT after tap time rolling -> hold behavior',
[(0, True), 350, (3, True), (0, False), (3, False)],
[{KC.LCTL}, {KC.LCTL, KC.D}, {KC.D}, {}],
)
keyboard.test(
'HT after tap time nested -> hold behavior',
[(0, True), t_after, (3, True), (3, False), (0, False)],
'MT after tap time nested -> hold behavior',
[(0, True), 350, (3, True), (3, False), (0, False)],
[{KC.LCTL}, {KC.LCTL, KC.D}, {KC.LCTL}, {}],
)
# LT
keyboard.test(
'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}, {}],
)
keyboard.test(
'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}, {}],
)
keyboard.test(
'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}, {}],
)
keyboard.test(
'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}, {}],
)
keyboard.test(
'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}, {}],
)
keyboard.test(
'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}, {}],
)
@ -123,9 +105,9 @@ class TestHoldTap(unittest.TestCase):
'LT after tap time nested -> hold behavior',
[
(0, True),
t_after,
350,
(1, True),
t_after,
350,
(3, True),
(3, False),
(1, False),
@ -135,25 +117,26 @@ class TestHoldTap(unittest.TestCase):
)
def test_holdtap_chain(self):
t_after = self.t_after
keyboard = KeyboardTest(
[HoldTap()],
[ModTap()],
[
[
KC.N0,
KC.HT(KC.N1, KC.LCTL),
KC.HT(KC.N2, KC.LSFT, tap_interrupted=True),
KC.HT(
KC.MT(KC.N1, KC.LCTL, tap_time=50),
KC.MT(KC.N2, KC.LSFT, tap_interrupted=True, tap_time=50),
KC.MT(
KC.N3,
KC.LALT,
prefer_hold=False,
tap_interrupted=True,
tap_time=50,
),
],
],
debug_enabled=False,
)
# t_within = 40
t_after = 60
keyboard.test(
'chained 0',
@ -172,7 +155,7 @@ class TestHoldTap(unittest.TestCase):
'chained 1',
[(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.N0},
{KC.LCTL, KC.LSFT},
@ -224,7 +207,7 @@ class TestHoldTap(unittest.TestCase):
'chained 5',
[(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.N0},
{KC.LCTL, KC.N3},
@ -291,21 +274,21 @@ class TestHoldTap(unittest.TestCase):
# TODO test TT
def test_holdtap_repeat(self):
t_within = self.t_within
t_after = self.t_after
keyboard = KeyboardTest(
[HoldTap()],
[ModTap()],
[
[
KC.HT(KC.A, KC.B, repeat=HoldTapRepeat.ALL),
KC.HT(KC.A, KC.B, repeat=HoldTapRepeat.TAP),
KC.HT(KC.A, KC.B, repeat=HoldTapRepeat.HOLD),
KC.MT(KC.A, KC.B, repeat=HoldTapRepeat.ALL, tap_time=50),
KC.MT(KC.A, KC.B, repeat=HoldTapRepeat.TAP, tap_time=50),
KC.MT(KC.A, KC.B, repeat=HoldTapRepeat.HOLD, tap_time=50),
]
],
debug_enabled=False,
)
t_within = 40
t_after = 60
keyboard.test(
'repeat tap',
[
@ -317,6 +300,7 @@ class TestHoldTap(unittest.TestCase):
(0, False),
(0, True),
(0, False),
t_after,
],
[{KC.A}, {}, {KC.A}, {}, {KC.A}, {}],
)
@ -332,6 +316,7 @@ class TestHoldTap(unittest.TestCase):
(0, False),
(0, True),
(0, False),
t_after,
],
[{KC.B}, {}, {KC.B}, {}, {KC.B}, {}],
)
@ -348,6 +333,7 @@ class TestHoldTap(unittest.TestCase):
t_after,
(0, True),
(0, False),
t_after,
],
[{KC.A}, {}, {KC.B}, {}, {KC.A}, {}],
)
@ -363,3 +349,76 @@ class TestHoldTap(unittest.TestCase):
[(2, True), (2, False), (2, True), t_after, (2, False)],
[{KC.A}, {}, {KC.B}, {}],
)
def test_oneshot(self):
keyboard = KeyboardTest(
[Layers(), ModTap(), OneShot()],
[
[
KC.MT(KC.A, KC.LCTL),
KC.LT(1, KC.B),
KC.C,
KC.D,
KC.OS(KC.E, tap_time=50),
],
[KC.N1, KC.N2, KC.N3, KC.N4, KC.N5],
],
debug_enabled=False,
)
t_within = 40
t_after = 60
# OS
keyboard.test(
'OS timed out',
[(4, True), (4, False), t_after],
[{KC.E}, {}],
)
keyboard.test(
'OS interrupt within tap time',
[(4, True), (4, False), t_within, (3, True), (3, False)],
[{KC.E}, {KC.D, KC.E}, {KC.E}, {}],
)
keyboard.test(
'OS interrupt, multiple within tap time',
[(4, True), (4, False), (3, True), (3, False), (2, True), (2, False)],
[{KC.E}, {KC.D, KC.E}, {KC.E}, {}, {KC.C}, {}],
)
keyboard.test(
'OS interrupt, multiple interleaved',
[(4, True), (4, False), (3, True), (2, True), (2, False), (3, False)],
[{KC.E}, {KC.D, KC.E}, {KC.D}, {KC.C, KC.D}, {KC.D}, {}],
)
keyboard.test(
'OS interrupt, multiple interleaved',
[(4, True), (4, False), (3, True), (2, True), (3, False), (2, False)],
[{KC.E}, {KC.D, KC.E}, {KC.D}, {KC.C, KC.D}, {KC.C}, {}],
)
keyboard.test(
'OS interrupt within tap time, hold',
[(4, True), (3, True), (4, False), t_after, (3, False)],
[{KC.E}, {KC.D, KC.E}, {KC.D}, {}],
)
keyboard.test(
'OS hold with multiple interrupt keys',
[
(4, True),
t_within,
(3, True),
(3, False),
(2, True),
(2, False),
(4, False),
],
[{KC.E}, {KC.D, KC.E}, {KC.E}, {KC.C, KC.E}, {KC.E}, {}],
)
if __name__ == '__main__':
unittest.main()

View File

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

View File

@ -10,13 +10,8 @@ class TestLayers(unittest.TestCase):
self.kb = KeyboardTest(
[Layers()],
[
[
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],
[KC.N0, KC.LM(1, KC.LCTL)],
[KC.A, KC.B],
],
debug_enabled=False,
)
@ -28,25 +23,6 @@ class TestLayers(unittest.TestCase):
[{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__':
unittest.main()

View File

@ -1,152 +0,0 @@
import unittest
from kmk.keys import KC
from kmk.modules.layers import Layers
from kmk.modules.oneshot import OneShot
from tests.keyboard_test import KeyboardTest
class TestOneshot(unittest.TestCase):
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(
[Layers(), OneShot()],
[
[
KC.OS(KC.MO(1)),
KC.MO(1),
KC.C,
KC.D,
KC.OS(KC.E),
KC.OS(KC.F),
],
[KC.N0, KC.N1, KC.N2, KC.N3, KC.OS(KC.LSFT), KC.TRNS],
],
debug_enabled=False,
)
keyboard.test(
'OS timed out',
[(4, True), (4, False), t_after],
[{KC.E}, {}],
)
keyboard.test(
'OS interrupt within tap time',
[(4, True), (4, False), t_within, (3, True), (3, False)],
[{KC.E}, {KC.D, KC.E}, {KC.E}, {}],
)
keyboard.test(
'OS interrupt, multiple within tap time',
[(4, True), (4, False), (3, True), (3, False), (2, True), (2, False)],
[{KC.E}, {KC.D, KC.E}, {KC.E}, {}, {KC.C}, {}],
)
keyboard.test(
'OS interrupt, multiple interleaved',
[(4, True), (4, False), (3, True), (2, True), (2, False), (3, False)],
[{KC.E}, {KC.D, KC.E}, {KC.D}, {KC.C, KC.D}, {KC.D}, {}],
)
keyboard.test(
'OS interrupt, multiple interleaved',
[(4, True), (4, False), (3, True), (2, True), (3, False), (2, False)],
[{KC.E}, {KC.D, KC.E}, {KC.D}, {KC.C, KC.D}, {KC.C}, {}],
)
keyboard.test(
'OS interrupt within tap time, hold',
[(4, True), (3, True), (4, False), t_after, (3, False)],
[{KC.E}, {KC.D, KC.E}, {KC.D}, {}],
)
keyboard.test(
'OS interrupt within tap time, hold',
[(4, True), (4, False), (3, True), t_after, (3, False)],
[{KC.E}, {KC.D, KC.E}, {KC.E}, {}],
)
keyboard.test(
'OS hold with multiple interrupt keys',
[
(4, True),
t_within,
(3, True),
(3, False),
(2, True),
(2, False),
(4, False),
],
[{KC.E}, {KC.D, KC.E}, {KC.E}, {KC.C, KC.E}, {KC.E}, {}],
)
keyboard.test(
'OS stacking within timeout reset',
[
(4, True),
(4, False),
t_within,
(5, True),
(5, False),
t_within,
(3, True),
(3, False),
],
[{KC.E}, {KC.E, KC.F}, {KC.E, KC.F, KC.D}, {KC.E, KC.F}, {KC.E}, {}],
)
keyboard.test(
'OS stacking timed out',
[
(4, True),
(4, False),
(5, True),
(5, False),
t_after,
(3, True),
(3, False),
],
[{KC.E}, {KC.E, KC.F}, {KC.E}, {}, {KC.D}, {}],
)
keyboard.test(
'OS stacking with OS-layer',
[
(0, True),
(0, False),
(4, True),
(4, False),
(1, True),
(1, False),
],
[{KC.LSFT}, {KC.LSFT, KC.N1}, {KC.LSFT}, {}],
)
keyboard.test(
'OS stacking with layer change',
[
(1, True),
(4, True),
(4, False),
(1, False),
(4, True),
(4, False),
(2, True),
(2, False),
],
[
{KC.LSFT},
{KC.LSFT, KC.E},
{KC.LSFT, KC.E, KC.C},
{KC.LSFT, KC.E},
{KC.LSFT},
{},
],
)

View File

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

View File

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

View File

@ -9,39 +9,36 @@ from tests.keyboard_test import KeyboardTest
class TestTapDance(unittest.TestCase):
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(
[Layers(), HoldTap(), TapDance()],
[
[
KC.TD(KC.N0, KC.N1),
KC.TD(KC.N0, KC.N1, tap_time=50),
KC.TD(
KC.HT(KC.N1, KC.A),
KC.HT(KC.N2, KC.B, tap_time=2 * tap_time),
KC.HT(KC.N1, KC.A, tap_time=50),
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.LT(1, KC.N3), 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, tap_time=50), KC.X, tap_time=0),
KC.N4,
],
[KC.N9, KC.N8, KC.N7, KC.N6, KC.N5],
],
debug_enabled=False,
)
self.t_within = 40
self.t_after = 60
def test_normal_key(self):
keyboard = self.keyboard
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(
'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}, {}],
)
@ -54,6 +51,7 @@ class TestTapDance(unittest.TestCase):
(0, False),
(0, True),
(0, False),
t_after,
],
[{KC.N1}, {}, {KC.N0}, {}],
)
@ -90,16 +88,16 @@ class TestTapDance(unittest.TestCase):
[{KC.N1}, {KC.N1, KC.N4}, {KC.N4}, {}],
)
def test_holdtap(self):
def test_modtap(self):
keyboard = self.keyboard
t_within = self.t_within
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(
'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}, {}],
)
@ -133,7 +131,7 @@ class TestTapDance(unittest.TestCase):
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}, {}],
)
@ -147,6 +145,7 @@ class TestTapDance(unittest.TestCase):
(2, False),
t_after,
(0, False),
t_after,
],
[{KC.N1}, {KC.N1, KC.X}, {KC.N1}, {}],
)
@ -161,6 +160,7 @@ class TestTapDance(unittest.TestCase):
(0, False),
t_after,
(2, False),
t_after,
],
[{KC.X}, {KC.X, KC.N0}, {KC.X}, {}],
)
@ -172,7 +172,7 @@ class TestTapDance(unittest.TestCase):
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}, {}],
)
@ -199,12 +199,12 @@ class TestTapDance(unittest.TestCase):
[{KC.A}, {}, {KC.N5}, {}],
)
def test_holdtap_repeat(self):
def test_modtap_repeat(self):
keyboard = self.keyboard
t_after = self.t_after
keyboard.test(
'HoldTap repeat',
'ModTap repeat',
[
(2, True),
(2, False),

View File

@ -2,8 +2,8 @@ from kb import KMKKeyboard
from kmk.extensions.rgb import RGB
from kmk.keys import KC
from kmk.modules.holdtap import HoldTap
from kmk.modules.layers import Layers
from kmk.modules.modtap import ModTap
keyboard = KMKKeyboard()
@ -22,15 +22,15 @@ FN1 = 2
rgb_ext = RGB(pixel_pin=keyboard.rgb_pixel_pin, num_pixels=16)
layers_ext = Layers()
holdtap = HoldTap()
modtap = ModTap()
keyboard.modules = [layers_ext, holdtap]
keyboard.modules = [layers_ext, modtap]
keyboard.extensions = [rgb_ext]
_______ = KC.TRNS
XXXXXXX = KC.NO
HOME = KC.HT(KC.HOME, KC.LSFT)
END = KC.HT(KC.END, KC.RSFT)
HOME = KC.MT(KC.HOME, KC.LSFT)
END = KC.MT(KC.END, KC.RSFT)
LEFT_LAY = KC.LT(FN1, KC.LEFT)
SHFT_INS = KC.LSFT(KC.INS)
SPC = KC.LT(FN1, KC.SPC)

View File

@ -5,8 +5,8 @@ from kb import KMKKeyboard
from kmk.extensions.rgb import RGB
from kmk.handlers.sequences import send_string, simple_key_sequence
from kmk.keys import KC
from kmk.modules.holdtap import HoldTap
from kmk.modules.layers import Layers
from kmk.modules.modtap import ModTap
from kmk.modules.split import Split
keyboard = KMKKeyboard()
@ -15,11 +15,11 @@ keyboard = KMKKeyboard()
keyboard.tap_time = 150
layers = Layers()
holdtap = HoldTap()
modtap = ModTap()
rgb_ext = RGB(pixel_pin=keyboard.rgb_pixel_pin, num_pixels=27, val_limit=100, hue_default=190, sat_default=100, val_default=5)
split = Split()
keyboard.modules = [holdtap, layers, split]
keyboard.modules = [modtap, layers, split]
keyboard.extensions = [rgb_ext]
_______ = KC.TRNS

View File

@ -35,7 +35,7 @@ keyboard.keymap = [
# Default
KC.GESC, KC.Q, KC.W, KC.E, KC.R, KC.T, KC.Y, KC.U, KC.I, KC.O, KC.P, KC.BSPC,
KC.TAB, KC.A, KC.S, KC.D, KC.F, KC.G, KC.H, KC.J, KC.K, KC.L, KC.SCLN, KC.QUOT,
KC.LSFT, KC.Z, KC.X, KC.C, KC.V, KC.B, KC.N, KC.M, KC.COMM, KC.DOT, KC.SLSH, KC.HT(KC.BSLS, KC.LSFT),
KC.LSFT, KC.Z, KC.X, KC.C, KC.V, KC.B, KC.N, KC.M, KC.COMM, KC.DOT, KC.SLSH, KC.MT(KC.BSLS, KC.LSFT),
KC.LCTRL, KC.LGUI, KC.LALT, LOWER, KC.ENT, KC.SPC, KC.SPC, UP_HYP, KC.LEFT, KC.DOWN, KC.UP, KC.RGHT,
],
[