Compare commits
69 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
b84cd0bdab | ||
|
d15569611e | ||
|
3c4e064201 | ||
|
bc5fb9dc9e | ||
|
0992bfb962 | ||
|
f532a57e9a | ||
|
5448cb4479 | ||
|
23d7c2d670 | ||
|
20ba48b623 | ||
|
76e6feda6f | ||
|
3e13c8c321 | ||
|
ba06d3c8a5 | ||
|
26bf630608 | ||
|
878fe0deca | ||
|
adff02e88a | ||
|
55b3a3a9b1 | ||
|
3c796c16f8 | ||
|
b9c85c02e2 | ||
|
bff7584fe0 | ||
|
47fe859e11 | ||
|
fd700cff44 | ||
|
2ccad46e26 | ||
|
94c042fec5 | ||
|
013046b44d | ||
|
18687e5278 | ||
|
20be6e9072 | ||
|
e40fd90d5c | ||
|
301ce3c025 | ||
|
6532497bb2 | ||
|
deb941b196 | ||
|
88c3616b6a | ||
|
6ab4154ad5 | ||
|
db2082f1c6 | ||
|
317f6407d6 | ||
|
08c255b6e4 | ||
|
61cf527370 | ||
|
38acda77b4 | ||
|
7ef2c2c2d3 | ||
|
1f751d8374 | ||
|
d4fe745e71 | ||
|
1674ff4ed7 | ||
|
a90d569690 | ||
|
449aab7dcb | ||
|
dc9b5f4512 | ||
|
cbaddef9a6 | ||
|
804b5ec575 | ||
|
a28df47199 | ||
|
9e5d2c24e1 | ||
|
ca800331de | ||
|
1ca27dab58 | ||
|
fc8d5edd52 | ||
|
e84bbd0d75 | ||
|
69d47343e8 | ||
|
470f16c97f | ||
|
27f101d139 | ||
|
2852420ea8 | ||
|
5a38af6632 | ||
|
fa7d72b175 | ||
|
d7e9cfbc5b | ||
|
408f2f487e | ||
|
5af52addf8 | ||
|
705d3eb032 | ||
|
638d64af72 | ||
|
e7427ff53b | ||
|
62d6bf16df | ||
|
47c242a2c9 | ||
|
4d52afa086 | ||
|
1935f05ccb | ||
|
83110c5f80 |
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG] 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.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[Enhancement] 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.
|
103
Makefile
103
Makefile
@ -30,19 +30,19 @@ TIMESTAMP := $(shell date +%s)
|
||||
|
||||
all: copy-kmk copy-bootpy copy-keymap copy-board
|
||||
|
||||
compile: $(MPY_TARGET_DIR)/.mpy.compiled
|
||||
|
||||
$(MPY_TARGET_DIR)/.mpy.compiled: $(PY_KMK_TREE)
|
||||
.PHONY: compile compile-check
|
||||
compile: compile-check
|
||||
ifeq ($(MPY_CROSS),)
|
||||
compile-check:
|
||||
@echo "===> Could not find mpy-cross in PATH, exiting"
|
||||
@false
|
||||
endif
|
||||
else
|
||||
compile-check: $(PY_KMK_TREE:%.py=$(MPY_TARGET_DIR)/%.mpy)
|
||||
@echo "===> Compiling all py files to mpy with flags $(MPY_FLAGS)"
|
||||
@mkdir -p $(MPY_TARGET_DIR)
|
||||
@echo "KMK_RELEASE = '$(DIST_DESCRIBE)'" > $(MPY_SOURCES)/release_info.py
|
||||
@find $(MPY_SOURCES) -name "*.py" -exec sh -c 'mkdir -p $(MPY_TARGET_DIR)/$$(dirname {}) && $(MPY_CROSS) $(MPY_FLAGS) {} -o $(MPY_TARGET_DIR)/$$(dirname {})/$$(basename -s .py {}).mpy' \;
|
||||
@rm -rf $(MPY_SOURCES)/release_info.py
|
||||
@touch $(MPY_TARGET_DIR)/.mpy.compiled
|
||||
$(MPY_TARGET_DIR)/%.mpy: %.py
|
||||
@mkdir -p $(dir $@)
|
||||
@$(MPY_CROSS) $(MPY_FLAGS) $? -o $@
|
||||
endif
|
||||
|
||||
.devdeps: Pipfile.lock
|
||||
@echo "===> Installing dependencies with pipenv"
|
||||
@ -92,7 +92,7 @@ test: lint unit-tests
|
||||
|
||||
.PHONY: unit-tests
|
||||
unit-tests: devdeps
|
||||
@$(PIPENV) run python3 -m unittest
|
||||
@$(PIPENV) run python3 -m unittest $(TESTS)
|
||||
|
||||
reset-bootloader:
|
||||
@echo "===> Rebooting your board to bootloader (safe to ignore file not found errors)"
|
||||
@ -102,55 +102,44 @@ reset-board:
|
||||
@echo "===> Rebooting your board (safe to ignore file not found errors)"
|
||||
@-timeout -k 5s 10s $(PIPENV) run ampy -p /dev/ttyACM0 -d ${AMPY_DELAY} -b ${AMPY_BAUD} run util/reset.py
|
||||
|
||||
ifdef MOUNTPOINT
|
||||
$(MOUNTPOINT)/kmk/.copied: $(shell find kmk/ -name "*.py" | xargs -0)
|
||||
@echo "===> Copying KMK source folder"
|
||||
@rsync -rh kmk $(MOUNTPOINT)/
|
||||
@touch $(MOUNTPOINT)/kmk/.copied
|
||||
@sync
|
||||
|
||||
copy-kmk: $(MOUNTPOINT)/kmk/.copied
|
||||
else
|
||||
copy-kmk:
|
||||
echo "**** MOUNTPOINT must be defined (wherever your CIRCUITPY drive is mounted) ****" && exit 1
|
||||
endif
|
||||
|
||||
copy-board: $(MOUNTPOINT)/kb.py
|
||||
$(MOUNTPOINT)/kb.py: $(BOARD)
|
||||
@echo "===> Copying your board to kb.py"
|
||||
@rsync -rh $(BOARD) $@
|
||||
@sync
|
||||
|
||||
ifdef MOUNTPOINT
|
||||
$(MOUNTPOINT)/kmk/boot.py: boot.py
|
||||
@echo "===> Copying required boot.py"
|
||||
@rsync -rh boot.py $(MOUNTPOINT)/
|
||||
@sync
|
||||
|
||||
copy-bootpy: $(MOUNTPOINT)/kmk/boot.py
|
||||
else
|
||||
copy-bootpy:
|
||||
echo "**** MOUNTPOINT must be defined (wherever your CIRCUITPY drive is mounted) ****" && exit 1
|
||||
endif
|
||||
|
||||
ifdef MOUNTPOINT
|
||||
ifndef USER_KEYMAP
|
||||
$(MOUNTPOINT)/main.py:
|
||||
@echo "**** USER_KEYMAP must be defined (ex. USER_KEYMAP=user_keymaps/noop.py) ****" && exit 1
|
||||
else
|
||||
$(MOUNTPOINT)/main.py: $(USER_KEYMAP)
|
||||
@echo "===> Copying your keymap to main.py"
|
||||
@rsync -rh $(USER_KEYMAP) $@
|
||||
@sync
|
||||
endif # USER_KEYMAP
|
||||
|
||||
copy-keymap: $(MOUNTPOINT)/main.py
|
||||
else
|
||||
copy-keymap:
|
||||
echo "**** MOUNTPOINT must be defined (wherever your CIRCUITPY drive is mounted) ****" && exit 1
|
||||
|
||||
ifdef BOARD
|
||||
copy-board: $(MOUNTPOINT)/kb.py
|
||||
copy-board:
|
||||
@echo "===> Copying your board from $(BOARD) to $(MOUNTPOINT)"
|
||||
@rsync -rhu $(BOARD)/*.py $(MOUNTPOINT)/
|
||||
@sync
|
||||
else # BOARD
|
||||
copy-board:
|
||||
@echo "**** Missing BOARD argument ****" && exit 1
|
||||
endif # BOARD
|
||||
|
||||
endif # MOUNTPOINT
|
||||
copy-bootpy:
|
||||
@echo "===> Copying required boot.py"
|
||||
@rsync -rhu boot.py $(MOUNTPOINT)/boot.py
|
||||
@sync
|
||||
|
||||
copy-compiled:
|
||||
@echo "===> Copying compiled KMK folder"
|
||||
@rsync -rhu $(MPY_TARGET_DIR)/* $(MOUNTPOINT)/
|
||||
@sync
|
||||
|
||||
ifdef USER_KEYMAP
|
||||
copy-keymap:
|
||||
@echo "===> Copying your keymap to main.py"
|
||||
@rsync -rhu $(USER_KEYMAP) $(MOUNTPOINT)/main.py
|
||||
@sync
|
||||
else # USER_KEYMAP
|
||||
copy-keymap:
|
||||
@echo "**** Missing USER_KEYMAP argument ****" && exit 1
|
||||
endif # USER_KEYMAP
|
||||
|
||||
copy-kmk:
|
||||
@echo "===> Copying KMK source folder"
|
||||
@rsync -rhu kmk $(MOUNTPOINT)/
|
||||
@sync
|
||||
|
||||
else # MOUNTPOINT
|
||||
copy-board copy-bootpy copy-compiled copy-keymap copy-kmk:
|
||||
@echo "**** MOUNTPOINT must be defined (wherever your CIRCUITPY drive is mounted) ****" && exit 1
|
||||
endif # ifndef MOUNTPOINT
|
||||
|
2
Pipfile
2
Pipfile
@ -17,6 +17,6 @@ ipython = "*"
|
||||
isort = "*"
|
||||
neovim = "*"
|
||||
s3cmd = "*"
|
||||
black = "==21.6b0"
|
||||
black = "==22.3.0"
|
||||
flake8-quotes = "*"
|
||||
flake8-black = "*"
|
||||
|
426
Pipfile.lock
generated
426
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "0a04ec24d4aef6828e4f5eefa0a7d2c312f21f2b2f18c42c7004cdbe0c02bd53"
|
||||
"sha256": "8c1b0ad909dc0ac0fc8013756ca7db8b5420f51c25cc1c2d1612f62fe5e19843"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@ -23,12 +23,12 @@
|
||||
"index": "pypi",
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"appdirs": {
|
||||
"asttokens": {
|
||||
"hashes": [
|
||||
"sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
|
||||
"sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
|
||||
"sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3",
|
||||
"sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c"
|
||||
],
|
||||
"version": "==1.4.4"
|
||||
"version": "==2.2.1"
|
||||
},
|
||||
"backcall": {
|
||||
"hashes": [
|
||||
@ -39,11 +39,32 @@
|
||||
},
|
||||
"black": {
|
||||
"hashes": [
|
||||
"sha256:dc132348a88d103016726fe360cb9ede02cecf99b76e3660ce6c596be132ce04",
|
||||
"sha256:dfb8c5a069012b2ab1e972e7b908f5fb42b6bbabcba0a788b86dc05067c7d9c7"
|
||||
"sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b",
|
||||
"sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176",
|
||||
"sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09",
|
||||
"sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a",
|
||||
"sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015",
|
||||
"sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79",
|
||||
"sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb",
|
||||
"sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20",
|
||||
"sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464",
|
||||
"sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968",
|
||||
"sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82",
|
||||
"sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21",
|
||||
"sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0",
|
||||
"sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265",
|
||||
"sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b",
|
||||
"sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a",
|
||||
"sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72",
|
||||
"sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce",
|
||||
"sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0",
|
||||
"sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a",
|
||||
"sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163",
|
||||
"sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad",
|
||||
"sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==21.6b0"
|
||||
"version": "==22.3.0"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
@ -58,144 +79,152 @@
|
||||
"sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330",
|
||||
"sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"markers": "python_version < '3.11' and python_version >= '3.7'",
|
||||
"version": "==5.1.1"
|
||||
},
|
||||
"executing": {
|
||||
"hashes": [
|
||||
"sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc",
|
||||
"sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"
|
||||
],
|
||||
"version": "==1.2.0"
|
||||
},
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b",
|
||||
"sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"
|
||||
"sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7",
|
||||
"sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.9.2"
|
||||
"version": "==6.0.0"
|
||||
},
|
||||
"flake8-black": {
|
||||
"hashes": [
|
||||
"sha256:941514149cb8b489cb17a4bb1cf18d84375db3b34381bb018de83509437931a0",
|
||||
"sha256:f26651bc10db786c03f4093414f7c9ea982ed8a244cec323c984feeffdf4c118"
|
||||
"sha256:0dfbca3274777792a5bcb2af887a4cad72c72d0e86c94e08e3a3de151bb41c34",
|
||||
"sha256:fe8ea2eca98d8a504f22040d9117347f6b367458366952862ac3586e7d4eeaca"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.2.1"
|
||||
"version": "==0.3.6"
|
||||
},
|
||||
"flake8-commas": {
|
||||
"hashes": [
|
||||
"sha256:d3005899466f51380387df7151fb59afec666a0f4f4a2c6a8995b975de0f44b7",
|
||||
"sha256:ee2141a3495ef9789a3894ed8802d03eff1eaaf98ce6d8653a7c573ef101935e"
|
||||
"sha256:940441ab8ee544df564ae3b3f49f20462d75d5c7cac2463e0b27436e2050f263",
|
||||
"sha256:ebb96c31e01d0ef1d0685a21f3f0e2f8153a0381430e748bf0bbbb5d5b453d54"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.0"
|
||||
"version": "==2.1.0"
|
||||
},
|
||||
"flake8-comprehensions": {
|
||||
"hashes": [
|
||||
"sha256:b07aef3277623db32310aa241a1cec67212b53c1d18e767d7e26d4d83aa05bf7",
|
||||
"sha256:f24be9032587127f7a5bc6d066bf755b6e66834f694383adb8a673e229c1f559"
|
||||
"sha256:412052ac4a947f36b891143430fef4859705af11b2572fbb689f90d372cf26ab",
|
||||
"sha256:d763de3c74bc18a79c039a7ec732e0a1985b0c79309ceb51e56401ad0a2cd44e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.5.0"
|
||||
"version": "==3.10.1"
|
||||
},
|
||||
"flake8-isort": {
|
||||
"hashes": [
|
||||
"sha256:2b91300f4f1926b396c2c90185844eb1a3d5ec39ea6138832d119da0a208f4d9",
|
||||
"sha256:729cd6ef9ba3659512dee337687c05d79c78e1215fdf921ed67e5fe46cce2f3c"
|
||||
"sha256:537f453a660d7e903f602ecfa36136b140de279df58d02eb1b6a0c84e83c528c",
|
||||
"sha256:aa0cac02a62c7739e370ce6b9c31743edac904bae4b157274511fc8a19c75bbc"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.0.0"
|
||||
"version": "==6.0.0"
|
||||
},
|
||||
"flake8-quotes": {
|
||||
"hashes": [
|
||||
"sha256:3f1116e985ef437c130431ac92f9b3155f8f652fda7405ac22ffdfd7a9d1055e"
|
||||
"sha256:6e26892b632dacba517bf27219c459a8396dcfac0f5e8204904c5a4ba9b480e1"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.2.0"
|
||||
"version": "==3.3.2"
|
||||
},
|
||||
"greenlet": {
|
||||
"hashes": [
|
||||
"sha256:0109af1138afbfb8ae647e31a2b1ab030f58b21dd8528c27beaeb0093b7938a9",
|
||||
"sha256:0459d94f73265744fee4c2d5ec44c6f34aa8a31017e6e9de770f7bcf29710be9",
|
||||
"sha256:04957dc96669be041e0c260964cfef4c77287f07c40452e61abe19d647505581",
|
||||
"sha256:0722c9be0797f544a3ed212569ca3fe3d9d1a1b13942d10dd6f0e8601e484d26",
|
||||
"sha256:097e3dae69321e9100202fc62977f687454cd0ea147d0fd5a766e57450c569fd",
|
||||
"sha256:0b493db84d124805865adc587532ebad30efa68f79ad68f11b336e0a51ec86c2",
|
||||
"sha256:13ba6e8e326e2116c954074c994da14954982ba2795aebb881c07ac5d093a58a",
|
||||
"sha256:13ebf93c343dd8bd010cd98e617cb4c1c1f352a0cf2524c82d3814154116aa82",
|
||||
"sha256:1407fe45246632d0ffb7a3f4a520ba4e6051fc2cbd61ba1f806900c27f47706a",
|
||||
"sha256:1bf633a50cc93ed17e494015897361010fc08700d92676c87931d3ea464123ce",
|
||||
"sha256:2d0bac0385d2b43a7bd1d651621a4e0f1380abc63d6fb1012213a401cbd5bf8f",
|
||||
"sha256:3001d00eba6bbf084ae60ec7f4bb8ed375748f53aeaefaf2a37d9f0370558524",
|
||||
"sha256:356e4519d4dfa766d50ecc498544b44c0249b6de66426041d7f8b751de4d6b48",
|
||||
"sha256:38255a3f1e8942573b067510f9611fc9e38196077b0c8eb7a8c795e105f9ce77",
|
||||
"sha256:3d75b8d013086b08e801fbbb896f7d5c9e6ccd44f13a9241d2bf7c0df9eda928",
|
||||
"sha256:41b825d65f31e394b523c84db84f9383a2f7eefc13d987f308f4663794d2687e",
|
||||
"sha256:42e602564460da0e8ee67cb6d7236363ee5e131aa15943b6670e44e5c2ed0f67",
|
||||
"sha256:4aeaebcd91d9fee9aa768c1b39cb12214b30bf36d2b7370505a9f2165fedd8d9",
|
||||
"sha256:4c8b1c43e75c42a6cafcc71defa9e01ead39ae80bd733a2608b297412beede68",
|
||||
"sha256:4d37990425b4687ade27810e3b1a1c37825d242ebc275066cfee8cb6b8829ccd",
|
||||
"sha256:4f09b0010e55bec3239278f642a8a506b91034f03a4fb28289a7d448a67f1515",
|
||||
"sha256:505138d4fa69462447a562a7c2ef723c6025ba12ac04478bc1ce2fcc279a2db5",
|
||||
"sha256:5067920de254f1a2dee8d3d9d7e4e03718e8fd2d2d9db962c8c9fa781ae82a39",
|
||||
"sha256:56961cfca7da2fdd178f95ca407fa330c64f33289e1804b592a77d5593d9bd94",
|
||||
"sha256:5a8e05057fab2a365c81abc696cb753da7549d20266e8511eb6c9d9f72fe3e92",
|
||||
"sha256:659f167f419a4609bc0516fb18ea69ed39dbb25594934bd2dd4d0401660e8a1e",
|
||||
"sha256:662e8f7cad915ba75d8017b3e601afc01ef20deeeabf281bd00369de196d7726",
|
||||
"sha256:6f61d71bbc9b4a3de768371b210d906726535d6ca43506737682caa754b956cd",
|
||||
"sha256:72b00a8e7c25dcea5946692a2485b1a0c0661ed93ecfedfa9b6687bd89a24ef5",
|
||||
"sha256:811e1d37d60b47cb8126e0a929b58c046251f28117cb16fcd371eed61f66b764",
|
||||
"sha256:81b0ea3715bf6a848d6f7149d25bf018fd24554a4be01fcbbe3fdc78e890b955",
|
||||
"sha256:88c8d517e78acdf7df8a2134a3c4b964415b575d2840a2746ddb1cc6175f8608",
|
||||
"sha256:8dca09dedf1bd8684767bc736cc20c97c29bc0c04c413e3276e0962cd7aeb148",
|
||||
"sha256:974a39bdb8c90a85982cdb78a103a32e0b1be986d411303064b28a80611f6e51",
|
||||
"sha256:9e112e03d37987d7b90c1e98ba5e1b59e1645226d78d73282f45b326f7bddcb9",
|
||||
"sha256:9e9744c657d896c7b580455e739899e492a4a452e2dd4d2b3e459f6b244a638d",
|
||||
"sha256:9ed358312e63bf683b9ef22c8e442ef6c5c02973f0c2a939ec1d7b50c974015c",
|
||||
"sha256:9f2c221eecb7ead00b8e3ddb913c67f75cba078fd1d326053225a3f59d850d72",
|
||||
"sha256:a20d33124935d27b80e6fdacbd34205732660e0a1d35d8b10b3328179a2b51a1",
|
||||
"sha256:a4c0757db9bd08470ff8277791795e70d0bf035a011a528ee9a5ce9454b6cba2",
|
||||
"sha256:afe07421c969e259e9403c3bb658968702bc3b78ec0b6fde3ae1e73440529c23",
|
||||
"sha256:b1992ba9d4780d9af9726bbcef6a1db12d9ab1ccc35e5773685a24b7fb2758eb",
|
||||
"sha256:b23d2a46d53210b498e5b701a1913697671988f4bf8e10f935433f6e7c332fb6",
|
||||
"sha256:b5e83e4de81dcc9425598d9469a624826a0b1211380ac444c7c791d4a2137c19",
|
||||
"sha256:be35822f35f99dcc48152c9839d0171a06186f2d71ef76dc57fa556cc9bf6b45",
|
||||
"sha256:be9e0fb2ada7e5124f5282d6381903183ecc73ea019568d6d63d33f25b2a9000",
|
||||
"sha256:c140e7eb5ce47249668056edf3b7e9900c6a2e22fb0eaf0513f18a1b2c14e1da",
|
||||
"sha256:c6a08799e9e88052221adca55741bf106ec7ea0710bca635c208b751f0d5b617",
|
||||
"sha256:cb242fc2cda5a307a7698c93173d3627a2a90d00507bccf5bc228851e8304963",
|
||||
"sha256:cce1e90dd302f45716a7715517c6aa0468af0bf38e814ad4eab58e88fc09f7f7",
|
||||
"sha256:cd4ccc364cf75d1422e66e247e52a93da6a9b73cefa8cad696f3cbbb75af179d",
|
||||
"sha256:d21681f09e297a5adaa73060737e3aa1279a13ecdcfcc6ef66c292cb25125b2d",
|
||||
"sha256:d38ffd0e81ba8ef347d2be0772e899c289b59ff150ebbbbe05dc61b1246eb4e0",
|
||||
"sha256:d566b82e92ff2e09dd6342df7e0eb4ff6275a3f08db284888dcd98134dbd4243",
|
||||
"sha256:d5b0ff9878333823226d270417f24f4d06f235cb3e54d1103b71ea537a6a86ce",
|
||||
"sha256:d6ee1aa7ab36475035eb48c01efae87d37936a8173fc4d7b10bb02c2d75dd8f6",
|
||||
"sha256:db38f80540083ea33bdab614a9d28bcec4b54daa5aff1668d7827a9fc769ae0a",
|
||||
"sha256:ea688d11707d30e212e0110a1aac7f7f3f542a259235d396f88be68b649e47d1",
|
||||
"sha256:f6327b6907b4cb72f650a5b7b1be23a2aab395017aa6f1adb13069d66360eb3f",
|
||||
"sha256:fb412b7db83fe56847df9c47b6fe3f13911b06339c2aa02dcc09dce8bbf582cd"
|
||||
"sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a",
|
||||
"sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a",
|
||||
"sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43",
|
||||
"sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33",
|
||||
"sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8",
|
||||
"sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088",
|
||||
"sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca",
|
||||
"sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343",
|
||||
"sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645",
|
||||
"sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db",
|
||||
"sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df",
|
||||
"sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3",
|
||||
"sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86",
|
||||
"sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2",
|
||||
"sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a",
|
||||
"sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf",
|
||||
"sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7",
|
||||
"sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394",
|
||||
"sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40",
|
||||
"sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3",
|
||||
"sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6",
|
||||
"sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74",
|
||||
"sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0",
|
||||
"sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3",
|
||||
"sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91",
|
||||
"sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5",
|
||||
"sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9",
|
||||
"sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8",
|
||||
"sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b",
|
||||
"sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6",
|
||||
"sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb",
|
||||
"sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73",
|
||||
"sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b",
|
||||
"sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df",
|
||||
"sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9",
|
||||
"sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f",
|
||||
"sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0",
|
||||
"sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857",
|
||||
"sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a",
|
||||
"sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249",
|
||||
"sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30",
|
||||
"sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292",
|
||||
"sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b",
|
||||
"sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d",
|
||||
"sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b",
|
||||
"sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c",
|
||||
"sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca",
|
||||
"sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7",
|
||||
"sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75",
|
||||
"sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae",
|
||||
"sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b",
|
||||
"sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470",
|
||||
"sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564",
|
||||
"sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9",
|
||||
"sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099",
|
||||
"sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0",
|
||||
"sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5",
|
||||
"sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19",
|
||||
"sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1",
|
||||
"sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==2.0.1"
|
||||
"version": "==2.0.2"
|
||||
},
|
||||
"ipdb": {
|
||||
"hashes": [
|
||||
"sha256:951bd9a64731c444fd907a5ce268543020086a697f6be08f7cc2c9a752a278c5"
|
||||
"sha256:c23b6736f01fd4586cc2ecbebdf79a5eb454796853e1cd8f2ed3b7b91d4a3e93",
|
||||
"sha256:f74c2f741c18b909eaf89f19fde973f745ac721744aa1465888ce45813b63a9c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.13.9"
|
||||
"version": "==0.13.11"
|
||||
},
|
||||
"ipython": {
|
||||
"hashes": [
|
||||
"sha256:55df3e0bd0f94e715abd968bedd89d4e8a7bce4bf498fb123fed4f5398fea874",
|
||||
"sha256:b5548ec5329a4bcf054a5deed5099b0f9622eb9ea51aaa7104d215fece201d8c"
|
||||
"sha256:b13a1d6c1f5818bd388db53b7107d17454129a70de2b87481d555daede5eb49e",
|
||||
"sha256:b38c31e8fc7eff642fc7c597061fff462537cf2314e3225a19c906b7b0d8a345"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==7.31.1"
|
||||
"version": "==8.10.0"
|
||||
},
|
||||
"isort": {
|
||||
"hashes": [
|
||||
"sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6",
|
||||
"sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"
|
||||
"sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504",
|
||||
"sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.8.0"
|
||||
"version": "==5.12.0"
|
||||
},
|
||||
"jedi": {
|
||||
"hashes": [
|
||||
@ -215,10 +244,11 @@
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
||||
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
|
||||
"sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
|
||||
],
|
||||
"version": "==0.6.1"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.7.0"
|
||||
},
|
||||
"msgpack": {
|
||||
"hashes": [
|
||||
@ -279,10 +309,11 @@
|
||||
},
|
||||
"mypy-extensions": {
|
||||
"hashes": [
|
||||
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
|
||||
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
|
||||
"sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d",
|
||||
"sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"
|
||||
],
|
||||
"version": "==0.4.3"
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"neovim": {
|
||||
"hashes": [
|
||||
@ -301,11 +332,11 @@
|
||||
},
|
||||
"pathspec": {
|
||||
"hashes": [
|
||||
"sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6",
|
||||
"sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"
|
||||
"sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229",
|
||||
"sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==0.10.3"
|
||||
"version": "==0.11.0"
|
||||
},
|
||||
"pexpect": {
|
||||
"hashes": [
|
||||
@ -322,6 +353,14 @@
|
||||
],
|
||||
"version": "==0.7.5"
|
||||
},
|
||||
"platformdirs": {
|
||||
"hashes": [
|
||||
"sha256:8a1228abb1ef82d788f74139988b137e78692984ec7b08eaa6c65f1723af28f9",
|
||||
"sha256:b1d5eb14f221506f50d6604a561f4c5786d9e80355219694a1b244bcd96f4567"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"prompt-toolkit": {
|
||||
"hashes": [
|
||||
"sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63",
|
||||
@ -337,29 +376,36 @@
|
||||
],
|
||||
"version": "==0.7.0"
|
||||
},
|
||||
"pure-eval": {
|
||||
"hashes": [
|
||||
"sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350",
|
||||
"sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"
|
||||
],
|
||||
"version": "==0.2.2"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068",
|
||||
"sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"
|
||||
"sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053",
|
||||
"sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.7.0"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.10.0"
|
||||
},
|
||||
"pyflakes": {
|
||||
"hashes": [
|
||||
"sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3",
|
||||
"sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"
|
||||
"sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf",
|
||||
"sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.3.1"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.0.1"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1",
|
||||
"sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"
|
||||
"sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297",
|
||||
"sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.13.0"
|
||||
"version": "==2.14.0"
|
||||
},
|
||||
"pynvim": {
|
||||
"hashes": [
|
||||
@ -384,129 +430,27 @@
|
||||
},
|
||||
"python-dotenv": {
|
||||
"hashes": [
|
||||
"sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5",
|
||||
"sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"
|
||||
"sha256:1c93de8f636cde3ce377292818d0e440b6e45a82f215c3744979151fa8151c49",
|
||||
"sha256:41e12e0318bebc859fcc4d97d4db8d20ad21721a6aa5047dd59f090391cb549a"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==0.21.0"
|
||||
"version": "==0.21.1"
|
||||
},
|
||||
"python-magic": {
|
||||
"hashes": [
|
||||
"sha256:4fec8ee805fea30c07afccd1592c0f17977089895bdfaae5fec870a84e997626",
|
||||
"sha256:de800df9fb50f8ec5974761054a708af6e4246b03b4bdaee993f948947b0ebcf"
|
||||
"sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b",
|
||||
"sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.4.24"
|
||||
},
|
||||
"regex": {
|
||||
"hashes": [
|
||||
"sha256:052b670fafbe30966bbe5d025e90b2a491f85dfe5b2583a163b5e60a85a321ad",
|
||||
"sha256:0653d012b3bf45f194e5e6a41df9258811ac8fc395579fa82958a8b76286bea4",
|
||||
"sha256:0a069c8483466806ab94ea9068c34b200b8bfc66b6762f45a831c4baaa9e8cdd",
|
||||
"sha256:0cf0da36a212978be2c2e2e2d04bdff46f850108fccc1851332bcae51c8907cc",
|
||||
"sha256:131d4be09bea7ce2577f9623e415cab287a3c8e0624f778c1d955ec7c281bd4d",
|
||||
"sha256:144486e029793a733e43b2e37df16a16df4ceb62102636ff3db6033994711066",
|
||||
"sha256:1ddf14031a3882f684b8642cb74eea3af93a2be68893901b2b387c5fd92a03ec",
|
||||
"sha256:1eba476b1b242620c266edf6325b443a2e22b633217a9835a52d8da2b5c051f9",
|
||||
"sha256:20f61c9944f0be2dc2b75689ba409938c14876c19d02f7585af4460b6a21403e",
|
||||
"sha256:22960019a842777a9fa5134c2364efaed5fbf9610ddc5c904bd3a400973b0eb8",
|
||||
"sha256:22e7ebc231d28393dfdc19b185d97e14a0f178bedd78e85aad660e93b646604e",
|
||||
"sha256:23cbb932cc53a86ebde0fb72e7e645f9a5eec1a5af7aa9ce333e46286caef783",
|
||||
"sha256:29c04741b9ae13d1e94cf93fca257730b97ce6ea64cfe1eba11cf9ac4e85afb6",
|
||||
"sha256:2bde29cc44fa81c0a0c8686992c3080b37c488df167a371500b2a43ce9f026d1",
|
||||
"sha256:2cdc55ca07b4e70dda898d2ab7150ecf17c990076d3acd7a5f3b25cb23a69f1c",
|
||||
"sha256:370f6e97d02bf2dd20d7468ce4f38e173a124e769762d00beadec3bc2f4b3bc4",
|
||||
"sha256:395161bbdbd04a8333b9ff9763a05e9ceb4fe210e3c7690f5e68cedd3d65d8e1",
|
||||
"sha256:44136355e2f5e06bf6b23d337a75386371ba742ffa771440b85bed367c1318d1",
|
||||
"sha256:44a6c2f6374e0033873e9ed577a54a3602b4f609867794c1a3ebba65e4c93ee7",
|
||||
"sha256:4919899577ba37f505aaebdf6e7dc812d55e8f097331312db7f1aab18767cce8",
|
||||
"sha256:4b4b1fe58cd102d75ef0552cf17242705ce0759f9695334a56644ad2d83903fe",
|
||||
"sha256:4bdd56ee719a8f751cf5a593476a441c4e56c9b64dc1f0f30902858c4ef8771d",
|
||||
"sha256:4bf41b8b0a80708f7e0384519795e80dcb44d7199a35d52c15cc674d10b3081b",
|
||||
"sha256:4cac3405d8dda8bc6ed499557625585544dd5cbf32072dcc72b5a176cb1271c8",
|
||||
"sha256:4fe7fda2fe7c8890d454f2cbc91d6c01baf206fbc96d89a80241a02985118c0c",
|
||||
"sha256:50921c140561d3db2ab9f5b11c5184846cde686bb5a9dc64cae442926e86f3af",
|
||||
"sha256:5217c25229b6a85049416a5c1e6451e9060a1edcf988641e309dbe3ab26d3e49",
|
||||
"sha256:5352bea8a8f84b89d45ccc503f390a6be77917932b1c98c4cdc3565137acc714",
|
||||
"sha256:542e3e306d1669b25936b64917285cdffcd4f5c6f0247636fec037187bd93542",
|
||||
"sha256:543883e3496c8b6d58bd036c99486c3c8387c2fc01f7a342b760c1ea3158a318",
|
||||
"sha256:586b36ebda81e6c1a9c5a5d0bfdc236399ba6595e1397842fd4a45648c30f35e",
|
||||
"sha256:597f899f4ed42a38df7b0e46714880fb4e19a25c2f66e5c908805466721760f5",
|
||||
"sha256:5a260758454580f11dd8743fa98319bb046037dfab4f7828008909d0aa5292bc",
|
||||
"sha256:5aefb84a301327ad115e9d346c8e2760009131d9d4b4c6b213648d02e2abe144",
|
||||
"sha256:5e6a5567078b3eaed93558842346c9d678e116ab0135e22eb72db8325e90b453",
|
||||
"sha256:5ff525698de226c0ca743bfa71fc6b378cda2ddcf0d22d7c37b1cc925c9650a5",
|
||||
"sha256:61edbca89aa3f5ef7ecac8c23d975fe7261c12665f1d90a6b1af527bba86ce61",
|
||||
"sha256:659175b2144d199560d99a8d13b2228b85e6019b6e09e556209dfb8c37b78a11",
|
||||
"sha256:6a9a19bea8495bb419dc5d38c4519567781cd8d571c72efc6aa959473d10221a",
|
||||
"sha256:6b30bddd61d2a3261f025ad0f9ee2586988c6a00c780a2fb0a92cea2aa702c54",
|
||||
"sha256:6ffd55b5aedc6f25fd8d9f905c9376ca44fcf768673ffb9d160dd6f409bfda73",
|
||||
"sha256:702d8fc6f25bbf412ee706bd73019da5e44a8400861dfff7ff31eb5b4a1276dc",
|
||||
"sha256:74bcab50a13960f2a610cdcd066e25f1fd59e23b69637c92ad470784a51b1347",
|
||||
"sha256:75f591b2055523fc02a4bbe598aa867df9e953255f0b7f7715d2a36a9c30065c",
|
||||
"sha256:763b64853b0a8f4f9cfb41a76a4a85a9bcda7fdda5cb057016e7706fde928e66",
|
||||
"sha256:76c598ca73ec73a2f568e2a72ba46c3b6c8690ad9a07092b18e48ceb936e9f0c",
|
||||
"sha256:78d680ef3e4d405f36f0d6d1ea54e740366f061645930072d39bca16a10d8c93",
|
||||
"sha256:7b280948d00bd3973c1998f92e22aa3ecb76682e3a4255f33e1020bd32adf443",
|
||||
"sha256:7db345956ecce0c99b97b042b4ca7326feeec6b75facd8390af73b18e2650ffc",
|
||||
"sha256:7dbdce0c534bbf52274b94768b3498abdf675a691fec5f751b6057b3030f34c1",
|
||||
"sha256:7ef6b5942e6bfc5706301a18a62300c60db9af7f6368042227ccb7eeb22d0892",
|
||||
"sha256:7f5a3ffc731494f1a57bd91c47dc483a1e10048131ffb52d901bfe2beb6102e8",
|
||||
"sha256:8a45b6514861916c429e6059a55cf7db74670eaed2052a648e3e4d04f070e001",
|
||||
"sha256:8ad241da7fac963d7573cc67a064c57c58766b62a9a20c452ca1f21050868dfa",
|
||||
"sha256:8b0886885f7323beea6f552c28bff62cbe0983b9fbb94126531693ea6c5ebb90",
|
||||
"sha256:8ca88da1bd78990b536c4a7765f719803eb4f8f9971cc22d6ca965c10a7f2c4c",
|
||||
"sha256:8e0caeff18b96ea90fc0eb6e3bdb2b10ab5b01a95128dfeccb64a7238decf5f0",
|
||||
"sha256:957403a978e10fb3ca42572a23e6f7badff39aa1ce2f4ade68ee452dc6807692",
|
||||
"sha256:9af69f6746120998cd9c355e9c3c6aec7dff70d47247188feb4f829502be8ab4",
|
||||
"sha256:9c94f7cc91ab16b36ba5ce476f1904c91d6c92441f01cd61a8e2729442d6fcf5",
|
||||
"sha256:a37d51fa9a00d265cf73f3de3930fa9c41548177ba4f0faf76e61d512c774690",
|
||||
"sha256:a3a98921da9a1bf8457aeee6a551948a83601689e5ecdd736894ea9bbec77e83",
|
||||
"sha256:a3c1ebd4ed8e76e886507c9eddb1a891673686c813adf889b864a17fafcf6d66",
|
||||
"sha256:a5f9505efd574d1e5b4a76ac9dd92a12acb2b309551e9aa874c13c11caefbe4f",
|
||||
"sha256:a8ff454ef0bb061e37df03557afda9d785c905dab15584860f982e88be73015f",
|
||||
"sha256:a9d0b68ac1743964755ae2d89772c7e6fb0118acd4d0b7464eaf3921c6b49dd4",
|
||||
"sha256:aa62a07ac93b7cb6b7d0389d8ef57ffc321d78f60c037b19dfa78d6b17c928ee",
|
||||
"sha256:ac741bf78b9bb432e2d314439275235f41656e189856b11fb4e774d9f7246d81",
|
||||
"sha256:ae1e96785696b543394a4e3f15f3f225d44f3c55dafe3f206493031419fedf95",
|
||||
"sha256:b683e5fd7f74fb66e89a1ed16076dbab3f8e9f34c18b1979ded614fe10cdc4d9",
|
||||
"sha256:b7a8b43ee64ca8f4befa2bea4083f7c52c92864d8518244bfa6e88c751fa8fff",
|
||||
"sha256:b8e38472739028e5f2c3a4aded0ab7eadc447f0d84f310c7a8bb697ec417229e",
|
||||
"sha256:bfff48c7bd23c6e2aec6454aaf6edc44444b229e94743b34bdcdda2e35126cf5",
|
||||
"sha256:c14b63c9d7bab795d17392c7c1f9aaabbffd4cf4387725a0ac69109fb3b550c6",
|
||||
"sha256:c27cc1e4b197092e50ddbf0118c788d9977f3f8f35bfbbd3e76c1846a3443df7",
|
||||
"sha256:c28d3309ebd6d6b2cf82969b5179bed5fefe6142c70f354ece94324fa11bf6a1",
|
||||
"sha256:c670f4773f2f6f1957ff8a3962c7dd12e4be54d05839b216cb7fd70b5a1df394",
|
||||
"sha256:ce6910b56b700bea7be82c54ddf2e0ed792a577dfaa4a76b9af07d550af435c6",
|
||||
"sha256:d0213671691e341f6849bf33cd9fad21f7b1cb88b89e024f33370733fec58742",
|
||||
"sha256:d03fe67b2325cb3f09be029fd5da8df9e6974f0cde2c2ac6a79d2634e791dd57",
|
||||
"sha256:d0e5af9a9effb88535a472e19169e09ce750c3d442fb222254a276d77808620b",
|
||||
"sha256:d243b36fbf3d73c25e48014961e83c19c9cc92530516ce3c43050ea6276a2ab7",
|
||||
"sha256:d26166acf62f731f50bdd885b04b38828436d74e8e362bfcb8df221d868b5d9b",
|
||||
"sha256:d403d781b0e06d2922435ce3b8d2376579f0c217ae491e273bab8d092727d244",
|
||||
"sha256:d8716f82502997b3d0895d1c64c3b834181b1eaca28f3f6336a71777e437c2af",
|
||||
"sha256:e4f781ffedd17b0b834c8731b75cce2639d5a8afe961c1e58ee7f1f20b3af185",
|
||||
"sha256:e613a98ead2005c4ce037c7b061f2409a1a4e45099edb0ef3200ee26ed2a69a8",
|
||||
"sha256:ef4163770525257876f10e8ece1cf25b71468316f61451ded1a6f44273eedeb5"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2022.10.31"
|
||||
"version": "==0.4.27"
|
||||
},
|
||||
"s3cmd": {
|
||||
"hashes": [
|
||||
"sha256:49cd23d516b17974b22b611a95ce4d93fe326feaa07320bd1d234fed68cbccfa",
|
||||
"sha256:966b0a494a916fc3b4324de38f089c86c70ee90e8e1cae6d59102103a4c0cc03"
|
||||
"sha256:15330776e7ff993d8ae0ac213bf896f210719e9b91445f5f7626a8fa7e74e30b",
|
||||
"sha256:2204306742c33c24fbca02b78e059bacfc1bfc04af09c7e9866f267a11a9ddb2"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.1.0"
|
||||
},
|
||||
"setuptools": {
|
||||
"hashes": [
|
||||
"sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31",
|
||||
"sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==65.5.1"
|
||||
"version": "==2.3.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
@ -516,35 +460,35 @@
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"testfixtures": {
|
||||
"stack-data": {
|
||||
"hashes": [
|
||||
"sha256:02dae883f567f5b70fd3ad3c9eefb95912e78ac90be6c7444b5e2f46bf572c84",
|
||||
"sha256:7de200e24f50a4a5d6da7019fb1197aaf5abd475efb2ec2422fdcf2f2eb98c1d"
|
||||
"sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815",
|
||||
"sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8"
|
||||
],
|
||||
"version": "==6.18.5"
|
||||
"version": "==0.6.2"
|
||||
},
|
||||
"toml": {
|
||||
"tomli": {
|
||||
"hashes": [
|
||||
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
||||
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
|
||||
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.10.2"
|
||||
"markers": "python_version < '3.11'",
|
||||
"version": "==2.0.1"
|
||||
},
|
||||
"traitlets": {
|
||||
"hashes": [
|
||||
"sha256:6cc57d6dc28c85d5365961726ffd19b538739347749e13ebe34e03323a0e8f84",
|
||||
"sha256:c864831efa0ba6576d09b44884b34e41defc18c0d7e720b4a2d6698c842cab3e"
|
||||
"sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8",
|
||||
"sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==5.8.0"
|
||||
"version": "==5.9.0"
|
||||
},
|
||||
"wcwidth": {
|
||||
"hashes": [
|
||||
"sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784",
|
||||
"sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"
|
||||
"sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e",
|
||||
"sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"
|
||||
],
|
||||
"version": "==0.2.5"
|
||||
"version": "==0.2.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
README.md
11
README.md
@ -9,10 +9,15 @@ KMK is a feature-rich and beginner-friendly firmware for computer keyboards
|
||||
written and configured in
|
||||
[CircuitPython](https://github.com/adafruit/circuitpython).
|
||||
|
||||
## Support
|
||||
|
||||
For asynchronous support and chatter about KMK, [join our Zulip
|
||||
community](https://kmkfw.zulipchat.com)! In particular, swing by the Zulip chat
|
||||
*before* opening a GitHub Issue about configuration, documentation, etc.
|
||||
concerns.
|
||||
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.
|
||||
|
||||
> The former Matrix and Discord rooms once linked to in this README are no
|
||||
> longer officially supported, please do not use them!
|
||||
|
@ -15,12 +15,11 @@ knob.extensions.append(media_keys)
|
||||
|
||||
# Rotary encoder that also acts as a key
|
||||
encoder_handler = EncoderHandler()
|
||||
encoder_handler.divisor = 2
|
||||
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,
|
||||
|
@ -15,6 +15,7 @@ knob.extensions.append(media_keys)
|
||||
|
||||
# Rotary encoders that also acts as keys
|
||||
encoder_handler = EncoderHandler()
|
||||
encoder_handler.divisor = 2
|
||||
encoder_handler.pins = (
|
||||
(board.D1, board.D2, board.D0),
|
||||
(board.D9, board.D10, board.D3),
|
||||
|
@ -78,6 +78,7 @@ keyboard.keymap = [
|
||||
|
||||
# Rotary encoder that also acts as a key
|
||||
encoder_handler = EncoderHandler()
|
||||
encoder_handler.divisor = 2
|
||||
encoder_handler.pins = ((board.D8, board.D7, board.D9),)
|
||||
encoder_handler.map = (((KC.VOLD, KC.VOLU, KC.MUTE),),)
|
||||
keyboard.modules.append(encoder_handler)
|
||||
|
12
boards/anavi/macro-pad-12/README.md
Normal file
12
boards/anavi/macro-pad-12/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# ANAVI Macro Pad 12
|
||||
|
||||
ANAVI Macro Pad 12 is an open source, programmable mechanical keyboard with 9 hot-swappable mechanical switches, RGB WS2812B underlighting and yellow backlit a rotary encoder and [Seeed XIAO RP2040](https://www.seeedstudio.com/XIAO-RP2040-v1-0-p-5026.html).
|
||||
|
||||
ANAVI Macro Pad 12 has been designed with the cross platform and open source electronics design automation suite KiCad. All KiCad [files and schematics are available at GitHub](https://github.com/anavitechnology/anavi-macro-pad-12) under [Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)](https://creativecommons.org/licenses/by-sa/4.0/).
|
||||
|
||||
Extensions enabled by default:
|
||||
- [Encoder](/docs/en/encoder.md) Twist control for all the things
|
||||
- [LED](/docs/en/led.md) Light your keys up (for backlit)
|
||||
- [RGB](/docs/en/rgb.md) Light it up (for underlighting)
|
||||
- [MediaKeys](/docs/en/media_keys.md) Control volume and other media functions
|
||||
- [PEG_OLED](/docs/peg_oled_display.md) Show information on the mini OLED display
|
97
boards/anavi/macro-pad-12/code.py
Normal file
97
boards/anavi/macro-pad-12/code.py
Normal file
@ -0,0 +1,97 @@
|
||||
import board
|
||||
|
||||
from kmk.extensions.LED import LED
|
||||
from kmk.extensions.media_keys import MediaKeys
|
||||
from kmk.extensions.peg_oled_Display import (
|
||||
Oled,
|
||||
OledData,
|
||||
OledDisplayMode,
|
||||
OledReactionType,
|
||||
)
|
||||
from kmk.extensions.RGB import RGB, AnimationModes
|
||||
from kmk.keys import KC
|
||||
from kmk.kmk_keyboard import KMKKeyboard
|
||||
from kmk.scanners import DiodeOrientation
|
||||
|
||||
keyboard = KMKKeyboard()
|
||||
|
||||
# I2C pins for the mini OLED display
|
||||
keyboard.SCL = board.D5
|
||||
keyboard.SDA = board.D4
|
||||
|
||||
oled_ext = Oled(
|
||||
OledData(
|
||||
corner_one={0: OledReactionType.STATIC, 1: ['ANAVI Macro Pad 12']},
|
||||
corner_two={0: OledReactionType.STATIC, 1: [' ']},
|
||||
corner_three={0: OledReactionType.STATIC, 1: ['Open Source']},
|
||||
corner_four={0: OledReactionType.STATIC, 1: [' ']},
|
||||
),
|
||||
oWidth=128,
|
||||
oHeight=64,
|
||||
toDisplay=OledDisplayMode.TXT,
|
||||
flip=False,
|
||||
)
|
||||
keyboard.extensions.append(oled_ext)
|
||||
|
||||
led_ext = LED(
|
||||
led_pin=[
|
||||
board.D0,
|
||||
],
|
||||
brightness=100,
|
||||
brightness_step=5,
|
||||
brightness_limit=100,
|
||||
breathe_center=1.5,
|
||||
animation_mode=AnimationModes.STATIC,
|
||||
animation_speed=1,
|
||||
user_animation=None,
|
||||
val=100,
|
||||
)
|
||||
keyboard.extensions.append(led_ext)
|
||||
|
||||
# WS2812B LED strips on the back
|
||||
underglow = RGB(
|
||||
pixel_pin=board.D10,
|
||||
num_pixels=6,
|
||||
val_limit=100,
|
||||
val_default=25,
|
||||
animation_mode=AnimationModes.RAINBOW,
|
||||
)
|
||||
keyboard.extensions.append(underglow)
|
||||
|
||||
# Neopixel on XIAO RP2040
|
||||
frontglow = RGB(
|
||||
pixel_pin=board.NEOPIXEL,
|
||||
num_pixels=1,
|
||||
val_limit=100,
|
||||
val_default=25,
|
||||
animation_mode=AnimationModes.RAINBOW,
|
||||
)
|
||||
keyboard.extensions.append(frontglow)
|
||||
|
||||
keyboard.col_pins = (board.D6, board.D8, board.D9)
|
||||
keyboard.row_pins = (board.D1, board.D2, board.D3, board.D7)
|
||||
keyboard.diode_orientation = DiodeOrientation.COL2ROW
|
||||
|
||||
media_keys = MediaKeys()
|
||||
keyboard.extensions.append(media_keys)
|
||||
|
||||
# Matrix 4x3 keymap, 12 keys in total
|
||||
keyboard.keymap = [
|
||||
[
|
||||
KC.N1,
|
||||
KC.N2,
|
||||
KC.N3,
|
||||
KC.N4,
|
||||
KC.N5,
|
||||
KC.N6,
|
||||
KC.N7,
|
||||
KC.N8,
|
||||
KC.N9,
|
||||
KC.N0,
|
||||
KC.A,
|
||||
KC.B,
|
||||
]
|
||||
]
|
||||
|
||||
if __name__ == '__main__':
|
||||
keyboard.go()
|
@ -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()
|
||||
modtap = ModTap()
|
||||
holdtap = HoldTap()
|
||||
layers_ext = Layers()
|
||||
keyboard.modules.append(layers_ext)
|
||||
keyboard.modules.append(modtap)
|
||||
keyboard.modules.append(holdtap)
|
||||
|
||||
oled_ext = Oled(
|
||||
OledData(
|
||||
|
@ -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
|
||||
- [ModTap](/docs/en/modtap.md) Allows mod keys to act as different keys when tapped.
|
||||
- [HoldTap](/docs/en/holdtap.md) Allows mod keys to act as different keys when tapped.
|
||||
|
||||
Common Extensions
|
||||
- [Split](/docs/en/split_keyboards.md) Connects halves using a wire
|
||||
|
@ -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())
|
||||
|
||||
modtap = ModTap()
|
||||
keyboard.modules.append(modtap)
|
||||
holdtap = HoldTap()
|
||||
keyboard.modules.append(holdtap)
|
||||
|
||||
|
||||
NONE = KC.NO
|
||||
@ -25,9 +25,9 @@ CAD = KC.LCTL(KC.LALT(KC.DEL))
|
||||
|
||||
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
# flake8: noqa: E261
|
||||
keyboard.keymap = [
|
||||
|
@ -3,13 +3,12 @@
|
||||
|
||||
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 = [
|
||||
|
@ -3,11 +3,12 @@
|
||||
# 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
|
||||
|
||||
@ -27,7 +28,7 @@ split = Split(
|
||||
)
|
||||
|
||||
layers_ext = Layers()
|
||||
mod_tap = ModTap()
|
||||
holdtap = HoldTap()
|
||||
mouse_key = MouseKeys()
|
||||
|
||||
|
||||
@ -39,12 +40,12 @@ XXXXXXX = KC.NO
|
||||
|
||||
|
||||
# Mod-taps
|
||||
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)
|
||||
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)
|
||||
CTL_ALT = KC.LCTRL(KC.LALT)
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
||||
modtap = ModTap()
|
||||
holdtap = HoldTap()
|
||||
layers = Layers()
|
||||
media_keys = MediaKeys()
|
||||
|
||||
encoder_handler = EncoderHandler()
|
||||
|
||||
keyboard.modules = [layers, modtap] #, encoder_handler]
|
||||
keyboard.modules = [layers, holdtap] #, 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.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)
|
||||
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)
|
||||
|
||||
# OTHER SHORTCUTS
|
||||
BRWSR_LFT = KC.LCTRL(KC.LSFT(KC.TAB))
|
||||
|
@ -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.
|
||||
- [ModTap](/docs/en/modtap.md) Allows mod keys to act as different keys when tapped.
|
||||
- [HoldTap](/docs/en/holdtap.md) Allows mod keys to act as different keys when tapped.
|
||||
- [LED](/docs/en/led.md) Light your keys up
|
||||
|
||||
Common Extensions
|
||||
|
@ -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()
|
||||
|
||||
|
||||
modtap = ModTap()
|
||||
holdtap = HoldTap()
|
||||
layers_ext = Layers()
|
||||
led = LED()
|
||||
keyboard.extensions = [led]
|
||||
keyboard.modules = [layers_ext, modtap]
|
||||
keyboard.modules = [layers_ext, holdtap]
|
||||
|
||||
# Cleaner key names
|
||||
_______ = KC.TRNS
|
||||
|
@ -2,6 +2,7 @@ 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],
|
||||
|
@ -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.
|
||||
- [ModTap](/docs/en/modtap.md) Enable press/hold double binding of keys
|
||||
- [HoldTap](/docs/en/holdtap.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:
|
||||
|
@ -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(ModTap())
|
||||
keyboard.modules.append(HoldTap())
|
||||
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.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)
|
||||
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)
|
||||
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,
|
||||
|
@ -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()
|
||||
modtap_ext = ModTap()
|
||||
holdtap_ext = HoldTap()
|
||||
|
||||
# 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, modtap_ext, split]
|
||||
keyboard.modules = [layers_ext, holdtap_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.MT(KC.TAB, KC.LCTRL)
|
||||
CT_QUOT = KC.MT(KC.QUOT, KC.LCTRL)
|
||||
SF_MINS = KC.MT(KC.MINS, KC.LSHIFT)
|
||||
CT_TAB = KC.HT(KC.TAB, KC.LCTRL)
|
||||
CT_QUOT = KC.HT(KC.QUOT, KC.LCTRL)
|
||||
SF_MINS = KC.HT(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))
|
||||
|
@ -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
|
||||
- [ModTap](/docs/en/modtap.md) Allows mod keys to act as different keys when tapped.
|
||||
- [HoldTap](/docs/en/holdtap.md) Allows mod keys to act as different keys when tapped.
|
||||
|
||||
Common Extensions
|
||||
- [Power](/docs/en/power.md) Powersaving features for battery life
|
||||
|
@ -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)
|
||||
|
||||
modtap = ModTap()
|
||||
holdtap = HoldTap()
|
||||
layers_ext = Layers()
|
||||
|
||||
keyboard.modules = [layers_ext, modtap]
|
||||
keyboard.modules = [layers_ext, holdtap]
|
||||
keyboard.extensions = [rgb]
|
||||
|
||||
# Cleaner key names
|
||||
|
@ -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
|
||||
- [ModTap](/docs/en/modtap.md) Allows mod keys to act as different keys when tapped.
|
||||
- [HoldTap](/docs/en/holdtap.md) Allows mod keys to act as different keys when tapped.
|
||||
|
||||
Common Extensions
|
||||
- [Power](/docs/en/power.md) Powersaving features for battery life
|
||||
|
@ -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)
|
||||
|
||||
modtap = ModTap()
|
||||
holdtap = HoldTap()
|
||||
layers_ext = Layers()
|
||||
|
||||
keyboard.modules = [layers_ext, modtap]
|
||||
keyboard.modules = [layers_ext, holdtap]
|
||||
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.MT(KC.ENT, KC.RSFT)
|
||||
RSFT_SPC = KC.MT(KC.SPC, KC.RSFT)
|
||||
RSFT_ENT = KC.HT(KC.ENT, KC.RSFT)
|
||||
RSFT_SPC = KC.HT(KC.SPC, KC.RSFT)
|
||||
|
||||
RGB_TOG = KC.RGB_TOG
|
||||
RGB_HUI = KC.RGB_HUI
|
||||
|
@ -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
|
||||
- [ModTap](/docs/en/modtap.md) Allows mod keys to act as different keys when tapped.
|
||||
- [HoldTap](/docs/en/holdtap.md) Allows mod keys to act as different keys when tapped.
|
||||
|
||||
Common Extensions
|
||||
- [Split](/docs/en/split_keyboards.md) Connects halves using a wire
|
||||
|
@ -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()
|
||||
modtap = ModTap()
|
||||
holdtap = HoldTap()
|
||||
layers_ext = Layers()
|
||||
keyboard.modules.append(layers_ext)
|
||||
keyboard.modules.append(modtap)
|
||||
keyboard.modules.append(holdtap)
|
||||
|
||||
oled_ext = Oled(
|
||||
OledData(
|
||||
|
3
boot.py
Executable file → Normal file
3
boot.py
Executable file → Normal file
@ -1,3 +0,0 @@
|
||||
import supervisor
|
||||
|
||||
supervisor.set_next_stack_limit(4096 + 4096)
|
@ -2,9 +2,8 @@
|
||||
> 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 [here](Officially_Supported_Microcontrollers.md)
|
||||
Known working and recommended devices can be found in the [list of officially supported microcontrollers](Officially_Supported_Microcontrollers.md)
|
||||
|
||||
<br>
|
||||
|
||||
## TL;DR Quick start guide
|
||||
> To infinity and beyond!
|
||||
@ -13,7 +12,7 @@ Known working and recommended devices can be found [here](Officially_Supported_M
|
||||
3. Unzip it and copy the KMK folder and the boot.py file at the root of the USB drive corresponding to your board (often appearing as CIRCUITPY)
|
||||
4. Create a new *code.py* or *main.py* file in the same root directory (same level as boot.py) with the example content hereunder:
|
||||
|
||||
***IMPORTANT:*** adapt the GP0 / GP1 pins to your specific board ! <br>
|
||||
***IMPORTANT:*** adapt the GP0 / GP1 pins to your specific board !
|
||||
|
||||
```
|
||||
print("Starting")
|
||||
@ -45,39 +44,41 @@ if __name__ == '__main__':
|
||||
|
||||
6. If it prints the letter "a" (or a "Q" or ... depending on your keyboard layout), you're done!
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
## Now that you're up and running, you may want to go further...
|
||||
> 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 [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).
|
||||
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.
|
||||
|
||||
### 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 [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
|
||||
<br>Once you've got the gist of it:
|
||||
- You can have a look [here](config_and_keymap.md) and [here](keys.md) to start customizing your code.py / main.py file
|
||||
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
|
||||
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.
|
||||
- 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
|
||||
- [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
|
||||
- [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
|
||||
|
||||
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).
|
||||
|
||||
<br>
|
||||
|
||||
## Additional help and support
|
||||
> Roads? Where we're going we don't need roads.
|
||||
|
||||
In case you need it, debugging help can be found [here](debugging.md)
|
||||
|
||||
If you need support with KMK or just want to say hi, find us in
|
||||
[#kmkfw:klar.sh on Matrix](https://matrix.to/#/#kmkfw:klar.sh). This channel is
|
||||
bridged to Discord [here](https://discordapp.com/widget?id=493256121075761173&theme=dark)
|
||||
for convenience. If you ask for help in chat or open a bug report, if possible
|
||||
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.
|
||||
|
@ -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
|
||||
- [ModTap](modtap.md): Adds support for augmented modifier keys to act as one key when tapped, and modifier when held.
|
||||
- [HoldTap](holdtap.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.
|
||||
|
@ -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
|
||||
[here](https://github.com/adafruit/Adafruit_CircuitPython_BLE/tree/master/adafruit_ble).
|
||||
This can be downloaded from the
|
||||
[Adafruit CircuitPython BLE repository](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.
|
||||
|
||||
|
@ -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 ModTap, Layers, etc.
|
||||
By default it will not deactivate CapsWord on numbers, alphabets, underscore, modifiers, minus, backspace and other keys like HoldTap, Layers, etc.
|
||||
Add it to your keyboard's modules list with:
|
||||
|
||||
```python
|
||||
|
98
docs/en/combo_layers.md
Normal file
98
docs/en/combo_layers.md
Normal file
@ -0,0 +1,98 @@
|
||||
## Combo Layers
|
||||
|
||||
Combo Layers is when you hold down 2 or more KC.MO() or KC.LM() keys at a time, and it goes to a defined layer.
|
||||
|
||||
By default combo layers is not activated. You can activate combo layers by adding this to your `main.py` file.
|
||||
The combolayers NEEDS to be above the `keyboard.modules.append(Layers(combolayers))`
|
||||
|
||||
```python
|
||||
combo_layers = {
|
||||
(1, 2): 3,
|
||||
}
|
||||
keyboard.modules.append(Layers(combo_layers))
|
||||
```
|
||||
|
||||
In the above code, when layer 1 and 2 are held, layer 3 will activate. If you release 1 or 2 it will go to whatever key is still being held, if both are released it goes to the default (0) layer.
|
||||
You should also notice that if you already have the layers Module activated, you can just add combolayers into `(Layers())`
|
||||
|
||||
You can add more, and even add more than 2 layers at a time.
|
||||
|
||||
```python
|
||||
combo_layers = {
|
||||
(1, 2): 3,
|
||||
(1, 2, 3): 4,
|
||||
}
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
There can only be one combo layer active at a time and for overlapping matches
|
||||
the first matching combo in `combo_layers` takes precedence.
|
||||
Example:
|
||||
```python
|
||||
layers = Layers()
|
||||
layers.combo_layers = {
|
||||
(1, 2, 3): 8,
|
||||
(1, 2): 9,
|
||||
}
|
||||
keyboard.modules.append(Layers(combo_layers))
|
||||
```
|
||||
* If you activate layers 1 then 2, your active layer will be layer number 9.
|
||||
* If you activate layers 1 then 2, then 3, your active layer will be layer
|
||||
number 3 (because the layer combo `(1,2)` has been activated, but layer 3
|
||||
stacks on top).
|
||||
* deactivate 1: you're on layer 3
|
||||
* deactivate 2: you're on layer 3
|
||||
* deactivate 3: you're on layer 8
|
||||
* If you activate layers 3 then 1, then 2, your active layer will be layer
|
||||
number 8. Deativate layer
|
||||
* deactivate any of 1/2/3: you're on layer 0
|
||||
|
||||
|
||||
## Fully Working Example code
|
||||
|
||||
Below is an example of a fully working keypad that uses combo layers.
|
||||
|
||||
```python
|
||||
print("Starting")
|
||||
|
||||
import board
|
||||
|
||||
from kmk.kmk_keyboard import KMKKeyboard
|
||||
from kmk.keys import KC
|
||||
|
||||
combo_layers = {
|
||||
(1, 2): 3,
|
||||
keyboard.modules.append(Layers(combo_layers))
|
||||
|
||||
|
||||
keyboard = KMKKeyboard()
|
||||
|
||||
|
||||
keyboard.keymap = [
|
||||
[ #Default
|
||||
KC.A, KC.B KC.C KC.D,
|
||||
KC.E, KC.F KC.G KC.H,
|
||||
KC.MO(1), KC.J, KC.K, KC.MO(2),
|
||||
],
|
||||
[ #Layer 1
|
||||
KC.N1, KC.N2, KC.N3, KC.N4,
|
||||
KC.N5, KC.N6, KC.N7, KC.8,
|
||||
KC.MO(1), KC.N9, KC.N0, KC.MO(2),
|
||||
],
|
||||
[ #Layer 2
|
||||
KC.EXLM, KC.AT, KC.HASH, KC.DLR,
|
||||
KC.PERC, KC.CIRC, KC.AMPR, KC.ASTR,
|
||||
KC.MO(1), KC.LPRN, KC.RPRN, KC.MO(2),
|
||||
],
|
||||
[ #Layer 3
|
||||
KC.F1, KC.F2, KC.F3, KC.F4,
|
||||
KC.F5, KC.F6, KC.F7, KC.F8,
|
||||
KC.MO(1) KC.F9, KC.F10, KC.MO(2)
|
||||
]
|
||||
|
||||
]
|
||||
|
||||
if __name__ == '__main__':
|
||||
keyboard.go()
|
||||
```
|
@ -6,8 +6,7 @@ to make contributing as easy as possible for everyone while maintaining a consis
|
||||
## Contributing Code
|
||||
The following guidelines should ensure that any code contributed can be merged in as
|
||||
painlessly as possible. If you're unsure how to set up your development environment,
|
||||
feel free to join the chat, [#kmkfw:klar.sh on Matrix](https://matrix.to/#/#kmkfw:klar.sh).
|
||||
This channel is bridged to Discord [here](https://discord.gg/QBHUUpeGUd) for convenience.
|
||||
feel free to [join our Zulip community](https://kmkfw.zulipchat.com).
|
||||
|
||||
### Code Style
|
||||
|
||||
@ -28,7 +27,12 @@ to Black formatting as documented in `pyproject.toml`)
|
||||
Unit tests within the `tests` folder mock various CircuitPython modules to allow
|
||||
them to be executed in a desktop development environment.
|
||||
|
||||
Execute tests using the command `python -m unittest`.
|
||||
Execute tests using the command `make unit-tests`. The unit-tests target accepts
|
||||
an optional environment variable for specifying a subset of tests with python
|
||||
unittest syntax:
|
||||
```sh
|
||||
make unit-tests TESTS="tests.test_capsword tests.test_hold_tap"
|
||||
```
|
||||
|
||||
## Contributing Documentation
|
||||
While KMK welcomes documentation from anyone with and understanding of the issues
|
||||
@ -36,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. 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).
|
||||
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.
|
||||
|
||||
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.
|
||||
|
@ -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, modtap, encoder_handler]
|
||||
keyboard.modules = [layers, holdtap, 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`.
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Stringy Keymaps
|
||||
|
||||
Enables referring to keys by `'NAME'` rather than `KC.NAME`.
|
||||
Enables referring to keys by `'NAME'` rather than `KC.NAME`.\
|
||||
This extension allows for a seamless integration of both string-based key references and standard keycodes.
|
||||
|
||||
For example:
|
||||
|
||||
@ -13,6 +14,9 @@ from kmk.extensions.stringy_keymaps import StringyKeymaps
|
||||
# Indexed
|
||||
# keyboard.keymap = [[ KC['A'], KC['B'], KC['RESET'] ]]
|
||||
|
||||
# String names mixed with normal keycodes
|
||||
# keyboard.keymap = [[ 'A' , KC.B, KC.RESET ]]
|
||||
|
||||
# String names
|
||||
keyboard.keymap = [[ 'A' , 'B', 'RESET' ]]
|
||||
|
||||
@ -27,3 +31,5 @@ keyboard.extensions.append(stringyKeymaps)
|
||||
|
||||
It should be noted that these are **not** ASCII. The string is **not** what
|
||||
will be sent to the computer. The examples above have no functional difference.
|
||||
|
||||
When utilizing argumented keys, such as `KC.MO(layer)`, it's not possible to use a string like `'MO(layer)'` instead employ the standard notation of e.g. `KC.MO(1)` in your keymap.
|
@ -9,9 +9,7 @@ Linux).
|
||||
Given `make` and `rsync` are available on your system (in `$PATH`), the
|
||||
following will copy the `kmk` tree to your CircuitPython device, and will copy
|
||||
the file defined as `USER_KEYMAP` as your `main.py`. It will also copy our
|
||||
`boot.py`, which allocates a larger stack size (simply - more of the device's
|
||||
RAM will be available to KMK and your keyboard config) than CircuitPython's
|
||||
default. If any of these files exist on your CircuitPython device already, they
|
||||
`boot.py`. If any of these files exist on your CircuitPython device already, they
|
||||
will be overwritten without a prompt.
|
||||
|
||||
If you get permissions errors here, **don't run make as root or with sudo**. See
|
||||
@ -26,9 +24,8 @@ 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
|
||||
[here](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](https://wiki.archlinux.org/index.php/File_systems#Mount_a_file_system).
|
||||
|
||||
For example,
|
||||
|
||||
|
@ -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 the amazing guide for that [here](https://docs.qmk.fm/#/hand_wire). That
|
||||
follow this amazing [hand wiring guide](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)
|
||||
|
@ -1,39 +1,39 @@
|
||||
# ModTap Keycodes
|
||||
Enabling ModTap will give you access to the following keycodes and can simply be
|
||||
# HoldTap Keycodes
|
||||
Enabling HoldTap will give you access to the following keycodes and can simply be
|
||||
added to the modules list.
|
||||
|
||||
```python
|
||||
from kmk.modules.modtap import ModTap
|
||||
modtap = ModTap()
|
||||
from kmk.modules.holdtap import HoldTap
|
||||
holdtap = HoldTap()
|
||||
# optional: set a custom tap timeout in ms
|
||||
# modtap.tap_time = 300
|
||||
keyboard.modules.append(modtap)
|
||||
# holdtap.tap_time = 300
|
||||
keyboard.modules.append(holdtap)
|
||||
```
|
||||
|
||||
## Keycodes
|
||||
|
||||
|New Keycode | Description |
|
||||
|---------------------------------------------------------|-----------------------------------------------------------------|
|
||||
|`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|
|
||||
|`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|
|
||||
|
||||
## Custom HoldTap Behavior
|
||||
The full ModTap signature is as follows:
|
||||
The full HoldTap signature is as follows:
|
||||
```python
|
||||
KC.MT(KC.TAP, KC.HOLD, prefer_hold=True, tap_interrupted=False, tap_time=None, repeat=HoldTapRepeat.NONE)
|
||||
KC.HT(KC.TAP, KC.HOLD, prefer_hold=True, tap_interrupted=False, tap_time=None, repeat=HoldTapRepeat.NONE)
|
||||
```
|
||||
* `prefer_hold`: decides which keycode the ModTap key resolves to when another
|
||||
* `prefer_hold`: decides which keycode the HoldTap 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.MT(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 ModTap key individually.
|
||||
Each of these parameters can be set for every HoldTap key individually.
|
@ -188,6 +188,7 @@
|
||||
| `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>`</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 |
|
||||
|
@ -15,7 +15,7 @@ the box.
|
||||
|
||||
### CircuitPython
|
||||
CircuitPython can be installed by following this guide using the guide
|
||||
[here](https://learn.adafruit.com/welcome-to-circuitpython/installing-circuitpython).
|
||||
for [installing circuit python](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
|
||||
|
@ -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
|
||||
ModTap. Check out the [ModTap doc](modtap.md) to find out more.
|
||||
HoldTap. Check out the [HoldTap doc](holdtap.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,6 +33,11 @@ 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
|
||||
@ -40,6 +45,7 @@ different games). In this case, best practice is to have these layers be the low
|
||||
defined first in your keymap. These layers are mutually-exclusive, so treat changing default
|
||||
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:
|
||||
|
||||
@ -84,7 +90,7 @@ class Layers(_Layers):
|
||||
last_top_layer = 0
|
||||
hues = (4, 20, 69)
|
||||
|
||||
def after_hid_send(keyboard):
|
||||
def after_hid_send(self, keyboard):
|
||||
if keyboard.active_layers[0] != self.last_top_layer:
|
||||
self.last_top_layer = keyboard.active_layers[0]
|
||||
rgb.set_hsv_fill(self.hues[self.last_top_layer], 255, 255)
|
||||
|
@ -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.
|
||||
- [ModTap](modtap.md): Adds support for augmented modifier keys to act as one key
|
||||
- [HoldTap](holdtap.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.
|
||||
|
@ -15,6 +15,8 @@ keyboard.modules.append(MouseKeys())
|
||||
| `KC.MB_LMB` | Left mouse button |
|
||||
| `KC.MB_RMB` | Right mouse button |
|
||||
| `KC.MB_MMB` | Middle mouse button |
|
||||
| `KC.MB_BTN4` | mouse button 4 |
|
||||
| `KC.MB_BTN5` | mouse button 5 |
|
||||
| `KC.MW_UP` | Mouse wheel up |
|
||||
| `KC.MW_DOWN`, `KC.MW_DN` | Mouse wheel down |
|
||||
| `KC.MS_UP` | Move mouse cursor up |
|
||||
|
@ -35,12 +35,12 @@ KC.OS(
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
## OneShot Modifier Combinations
|
||||
|
||||
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`.
|
||||
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`.
|
||||
|
||||
```python
|
||||
from kmk.modules.combos import Chord, Combos
|
||||
from kmk.modules.oneshot import OneShot
|
||||
|
||||
oneshot = OneShot()
|
||||
@ -48,58 +48,8 @@ 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)
|
||||
|
||||
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]]
|
||||
keyboard.keymap = [[OS_LSFT, OS_LCTL, KC.TAB]]
|
||||
```
|
||||
|
||||
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>
|
||||
|
@ -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 here](https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases/download/20220415/adafruit-circuitpython-bundle-7.x-mpy-20220415.zip)
|
||||
* [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)
|
||||
|
||||
|
||||
## kb.py
|
||||
|
@ -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 here](https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases/download/20220415/adafruit-circuitpython-bundle-7.x-mpy-20220415.zip)
|
||||
* [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)
|
||||
|
||||
## Required Changes to main.py and kb.py
|
||||
|
||||
|
@ -126,4 +126,4 @@ if __name__ == '__main__':
|
||||
```
|
||||
|
||||
## More information
|
||||
More information on keymaps can be found [here](config_and_keymap.md)
|
||||
More information on keymaps can be found in the [config and keymap](config_and_keymap.md) documentation.
|
||||
|
Before Width: | Height: | Size: 400 KiB After Width: | Height: | Size: 400 KiB |
@ -15,7 +15,7 @@ microcontrollers with only a single line change and less mistakes.
|
||||
|
||||
## Pro micro footprint pinout
|
||||
|
||||
![pro micro footprint pins](../img/pro_micro_pinout.png)
|
||||
![pro micro footprint pins](pro_micro_pinout.png)
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -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 ModTap module to the keyboard.modules.
|
||||
Notice that this Split module must be added after the HoldTap 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
|
||||
|
@ -1,9 +1,12 @@
|
||||
# Support
|
||||
|
||||
If you need support with KMK or just want to say hi, find us in [#kmkfw:klar.sh
|
||||
on Matrix](https://matrix.to/#/#kmkfw:klar.sh). This channel is bridged to
|
||||
Discord [here](https://discordapp.com/widget?id=493256121075761173&theme=dark)
|
||||
for convenience.
|
||||
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.
|
||||
|
||||
> The former Matrix and Discord rooms once linked to in this README are no
|
||||
> longer officially supported, please do not use them!
|
||||
|
@ -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.MT(KC.B, KC.LCTL, prefer_hold=False),
|
||||
KC.HT(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
|
||||
|
@ -9,7 +9,6 @@ KMK は[CircuitPython](https://circuitpython.org/)の上に配置されるキー
|
||||
|
||||
CircuitPython の最適化バージョン(特定のボードの容量制限に対処した、プリインストールされた関連モジュールの選択が可能なバージョン)も提供しています。
|
||||
|
||||
<br>
|
||||
|
||||
## TL;DR クイックスタートガイド
|
||||
> To infinity and beyond!
|
||||
@ -25,7 +24,7 @@ CircuitPython の最適化バージョン(特定のボードの容量制限に
|
||||
|
||||
4. 同じルートディレクトリー(boot.py と同レベル)に新規で*code.py* または *main.py*のファイルを作成する。中身は以下の例とする。
|
||||
|
||||
***重要:*** GP0 / GP1 ピンを使用ボードに合わせて下さい!<br>
|
||||
***重要:*** GP0 / GP1 ピンを使用ボードに合わせて下さい
|
||||
|
||||
|
||||
```
|
||||
@ -56,8 +55,6 @@ if __name__ == '__main__':
|
||||
|
||||
2. "A"や"Q"(キーボードのレイアウトによって異なる)が表示されたら、完成!
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
## とりあえず一通り動くようになったので、もっとに先へ進みたい場合
|
||||
> 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.
|
||||
@ -72,7 +69,7 @@ if __name__ == '__main__':
|
||||
最初にデバイスの動作や具体的なマトリックス構成についてしっかり理解してください。
|
||||
QMK チームが提供している手配線キーボード用の[ガイド](https://docs.qmk.fm/#/hand_wire)と[ドキュメント](http://pcbheaven.com/wikipages/How_Key_Matrices_Works/) を確認できます。
|
||||
|
||||
<br>要旨をつかめてきたら:
|
||||
要旨をつかめてきたら:
|
||||
- [ここ](config_and_keymap.md) と [ここ](keys.md)を見て、code.py / main.py ファイルをカスタイマイズできます。
|
||||
|
||||
- 使用可能なキーコードの[リファレンス](keycodes.md)があります。
|
||||
@ -83,21 +80,19 @@ QMK チームが提供している手配線キーボード用の[ガイド](http
|
||||
- [シーケンス](sequences.md) 一つのアクションで複数のキーストロークを送信するために使用します。
|
||||
- [レイヤー](layers.md)でタッチ一つでキーボードの全体の動きを変えることができます。
|
||||
|
||||
- [モドタップ](modtap.md) でキーの押し/長押しの動作を設定し、何回押されたかによって[タップダンス](tapdance.md)を設定します。
|
||||
- [モドタップ](holdtap.md) でキーの押し/長押しの動作を設定し、何回押されたかによって[タップダンス](tapdance.md)を設定します。
|
||||
|
||||
RGB や分裂型などの機能を楽しめたい場合は、ビルトイン[モジュール](modules.md)と[拡張機能](extensions.md)を見てみてください!
|
||||
|
||||
私たちが提供する、いろんな [ユーザー事例](https://github.com/KMKfw/user_keymaps)や[ドキュメント](https://github.com/KMKfw/kmk_firmware/tree/master/docs)からアイデアを得ることもできます。
|
||||
|
||||
<br>
|
||||
|
||||
## ヘルプ/サポート
|
||||
> Roads? Where we're going we don't need roads.
|
||||
|
||||
デバッグについてのヘルプが必要な場合は[こちら](debugging.md)。
|
||||
|
||||
KMK についてサポートが必要な場合や、コミュニケーションをとりたい場合は[こちら](https://matrix.to/#/#kmkfw:klar.sh)。
|
||||
このチャネルは[Discord](https://discordapp.com/widget?id=493256121075761173&theme=dark) からも見ることができます。
|
||||
KMK についてサポートが必要な場合や、コミュニケーションをとりたい場合は[こちら](https://kmkfw.zulipchat.com)。
|
||||
|
||||
チャットで助けを求める場合やバグ レポートを開く場合は、可能であれば KMK
|
||||
のコピーが最新であることを確認してください。
|
||||
|
@ -13,7 +13,6 @@ Também fornecemos uma versão de CircuitPython otimizada para teclados
|
||||
(simplificada para lidar com os limites de certas placas e com a seleção dos
|
||||
módulos relevantes pré-instalados). Se você estiver se perguntando por que usar
|
||||
|
||||
<br>
|
||||
|
||||
## Guia Rápido
|
||||
> Ao Infinito e Além!
|
||||
@ -30,7 +29,7 @@ módulos relevantes pré-instalados). Se você estiver se perguntando por que us
|
||||
4. Crie um novo arquivo *code.py* ou *main.py* no mesmo diretório raiz (no
|
||||
mesmo nível de boot.py) com o exemplo contido abaixo:
|
||||
|
||||
***IMPORTANTE:*** adapte os pinos GP0 / GP1 para a tua placa específica! <br>
|
||||
***IMPORTANTE:*** adapte os pinos GP0 / GP1 para a tua placa específica!
|
||||
|
||||
```
|
||||
print("Starting")
|
||||
@ -61,7 +60,6 @@ if __name__ == '__main__':
|
||||
6. Se ela imprimir um "A" (ou um "Q" ou o que depender do teu layout de
|
||||
teclado), você conseguiu!
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
## Agora que tudo está no seu lugar, você pode querer ir além...
|
||||
@ -88,7 +86,7 @@ sua configuração matricial específica. Você pode observar
|
||||
[guia](https://docs.qmk.fm/#/hand_wire) feito pelo time da QMK para teclados
|
||||
artesanais.
|
||||
|
||||
<br>Uma vez que você compreendeu a essência da coisa:
|
||||
Uma vez que você compreendeu a essência da coisa:
|
||||
- Você pode dar uma olhada [aqui](config_and_keymap.md) e [aqui](keys.md) para
|
||||
começar a customizar seu arquivo code.py / main.py.
|
||||
- Eis uma [referência](keycodes.md) dos códigos de teclas (*keycodes*)
|
||||
@ -103,7 +101,7 @@ E para ir mais além:
|
||||
só.
|
||||
- [Camadas](layers.md) podem transformar totalmente como seu teclado age com um
|
||||
simples toque.
|
||||
- [ModTap](modtap.md) te permite customizar a maneira que uma tecla age quando é
|
||||
- [HoldTap](holdtap.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.
|
||||
|
||||
@ -115,7 +113,6 @@ Você também pode obter ideias dos vários [exemplos de
|
||||
usuários](https://github.com/KMKfw/user_keymaps) que fornecemos e fuce nossa
|
||||
[documentação](https://github.com/KMKfw/kmk_firmware/tree/master/docs).
|
||||
|
||||
<br>
|
||||
|
||||
## Ajuda e Suporte Adicionais
|
||||
> Estradas? Para onde vamos, estradas são desnecessárias.
|
||||
@ -123,8 +120,6 @@ usuários](https://github.com/KMKfw/user_keymaps) que fornecemos e fuce nossa
|
||||
Caso precise, ajuda para depuração pode ser encontrada [aqui](debugging.md).
|
||||
|
||||
Se você precisa de suporte com o KMK ou quer somente dizer oi, encontre-nos no
|
||||
canal [#kmkfw:klar.sh no Matrix](https://matrix.to/#/#kmkfw:klar.sh). Este canal
|
||||
tem uma ponte no Discord
|
||||
[aqui](https://discordapp.com/widget?id=493256121075761173&theme=dark) por
|
||||
conveniência. Se você precisa de ajuda ou pretende abrir um bug report, se
|
||||
canal [kmkfw no Zulip](https://kmkfw.zulipchat.com).
|
||||
Se você precisa de ajuda ou pretende abrir um bug report, se
|
||||
possível se possível, verifique se sua cópia do KMK está atualizada.
|
||||
|
@ -1,27 +1,27 @@
|
||||
# Keycodes ModTap
|
||||
# Keycodes HoldTap
|
||||
|
||||
Habilitar o ModTap (adicionando-o à lista de módulos) te dará acesso aos
|
||||
Habilitar o HoldTap (adicionando-o à lista de módulos) te dará acesso aos
|
||||
keycodes abaixo:
|
||||
|
||||
```python
|
||||
from kmk.modules.modtap import ModTap
|
||||
keyboard.modules.append(ModTap())
|
||||
from kmk.modules.holdtap import HoldTap
|
||||
keyboard.modules.append(HoldTap())
|
||||
```
|
||||
|
||||
## Keycodes
|
||||
|
||||
| Novo Keycode | Descrição |
|
||||
|--------------------------------------------------------|-----------------------------------------------------------------|
|
||||
| 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 |
|
||||
| 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 |
|
@ -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.
|
||||
- [ModTap](modtap.md): Acrescenta suporte para teclas modificadoras que agem
|
||||
- [HoldTap](holdtap.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!
|
||||
|
@ -1,10 +1,7 @@
|
||||
# Suporte
|
||||
|
||||
Se você precisa de suporte com o KMK ou quer somente dizer oi, encontre-nos no
|
||||
canal [#kmkfw:klar.sh no Matrix](https://matrix.to/#/#kmkfw:klar.sh). Este canal
|
||||
tem uma ponte no Discord
|
||||
[aqui](https://discordapp.com/widget?id=493256121075761173&theme=dark) por
|
||||
conveniência.
|
||||
canal [no Zulip](https://kmkfw.zulipchat.com).
|
||||
|
||||
Se você precisa de ajuda ou pretende abrir um bug report, se
|
||||
possível se possível, verifique se sua cópia do KMK está atualizada.
|
||||
|
@ -49,3 +49,6 @@ class Extension:
|
||||
|
||||
def on_powersave_disable(self, keyboard):
|
||||
raise NotImplementedError
|
||||
|
||||
def deinit(self, keyboard):
|
||||
pass
|
||||
|
272
kmk/extensions/oled.py
Normal file
272
kmk/extensions/oled.py
Normal file
@ -0,0 +1,272 @@
|
||||
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()
|
@ -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.kmktime import PeriodicTimer
|
||||
from kmk.scheduler import create_task
|
||||
from kmk.utils import Debug, clamp
|
||||
|
||||
debug = Debug(__name__)
|
||||
@ -59,7 +59,7 @@ def hsv_to_rgb(hue, sat, val):
|
||||
return (r >> 8), (g >> 8), (b >> 8)
|
||||
|
||||
|
||||
def hsv_to_rgbw(self, hue, sat, val):
|
||||
def hsv_to_rgbw(hue, sat, val):
|
||||
'''
|
||||
Converts HSV values, and returns a tuple of RGBW values
|
||||
:param hue:
|
||||
@ -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,32 +109,9 @@ class RGB(Extension):
|
||||
pixels=None,
|
||||
refresh_rate=60,
|
||||
):
|
||||
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.pixel_pin = pixel_pin
|
||||
self.num_pixels = num_pixels
|
||||
|
||||
# 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.rgb_order = rgb_order
|
||||
self.hue_step = hue_step
|
||||
self.sat_step = sat_step
|
||||
self.val_step = val_step
|
||||
@ -153,8 +130,11 @@ 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(
|
||||
@ -227,7 +207,29 @@ class RGB(Extension):
|
||||
return
|
||||
|
||||
def during_bootup(self, sandbox):
|
||||
self._timer = PeriodicTimer(1000 // self.refresh_rate)
|
||||
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))
|
||||
|
||||
def before_matrix_scan(self, sandbox):
|
||||
return
|
||||
@ -239,7 +241,7 @@ class RGB(Extension):
|
||||
return
|
||||
|
||||
def after_hid_send(self, sandbox):
|
||||
self.animate()
|
||||
pass
|
||||
|
||||
def on_powersave_enable(self, sandbox):
|
||||
return
|
||||
@ -247,6 +249,10 @@ 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
|
||||
@ -429,7 +435,7 @@ class RGB(Extension):
|
||||
if self.animation_mode is AnimationModes.STATIC_STANDBY:
|
||||
return
|
||||
|
||||
if self.enable and self._timer.tick():
|
||||
if self.enable:
|
||||
self._animation_step()
|
||||
if self.animation_mode == AnimationModes.BREATHING:
|
||||
self.effect_breathing()
|
||||
|
@ -127,3 +127,20 @@ def ble_refresh(key, keyboard, *args, **kwargs):
|
||||
keyboard._hid_helper.stop_advertising()
|
||||
keyboard._hid_helper.start_advertising()
|
||||
return keyboard
|
||||
|
||||
|
||||
def ble_disconnect(key, keyboard, *args, **kwargs):
|
||||
from kmk.hid import HIDModes
|
||||
|
||||
if keyboard.hid_type != HIDModes.BLE:
|
||||
return keyboard
|
||||
|
||||
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)
|
||||
|
113
kmk/hid.py
113
kmk/hid.py
@ -4,7 +4,8 @@ from micropython import const
|
||||
|
||||
from storage import getmount
|
||||
|
||||
from kmk.keys import FIRST_KMK_INTERNAL_KEY, ConsumerKey, ModifierKey
|
||||
from kmk.keys import FIRST_KMK_INTERNAL_KEY, ConsumerKey, ModifierKey, MouseKey
|
||||
from kmk.utils import clamp
|
||||
|
||||
try:
|
||||
from adafruit_ble import BLERadio
|
||||
@ -68,6 +69,14 @@ class AbstractHID:
|
||||
self.report_mods = memoryview(self._evt)[1:2]
|
||||
self.report_non_mods = memoryview(self._evt)[3:]
|
||||
|
||||
self._cc_report = bytearray(HID_REPORT_SIZES[HIDReportTypes.CONSUMER] + 1)
|
||||
self._cc_report[0] = HIDReportTypes.CONSUMER
|
||||
self._cc_pending = False
|
||||
|
||||
self._pd_report = bytearray(HID_REPORT_SIZES[HIDReportTypes.MOUSE] + 1)
|
||||
self._pd_report[0] = HIDReportTypes.MOUSE
|
||||
self._pd_pending = False
|
||||
|
||||
self.post_init()
|
||||
|
||||
def __repr__(self):
|
||||
@ -76,47 +85,27 @@ class AbstractHID:
|
||||
def post_init(self):
|
||||
pass
|
||||
|
||||
def create_report(self, keys_pressed):
|
||||
def create_report(self, keys_pressed, axes):
|
||||
self.clear_all()
|
||||
|
||||
consumer_key = None
|
||||
for key in keys_pressed:
|
||||
if isinstance(key, ConsumerKey):
|
||||
consumer_key = key
|
||||
break
|
||||
if key.code >= FIRST_KMK_INTERNAL_KEY:
|
||||
continue
|
||||
|
||||
reporting_device = self.report_device[0]
|
||||
needed_reporting_device = HIDReportTypes.KEYBOARD
|
||||
if isinstance(key, ModifierKey):
|
||||
self.add_modifier(key)
|
||||
elif isinstance(key, ConsumerKey):
|
||||
self.add_cc(key)
|
||||
elif isinstance(key, MouseKey):
|
||||
self.add_pd(key)
|
||||
else:
|
||||
self.add_key(key)
|
||||
if key.has_modifiers:
|
||||
for mod in key.has_modifiers:
|
||||
self.add_modifier(mod)
|
||||
|
||||
if consumer_key:
|
||||
needed_reporting_device = HIDReportTypes.CONSUMER
|
||||
|
||||
if reporting_device != needed_reporting_device:
|
||||
# If we are about to change reporting devices, release
|
||||
# all keys and close our proverbial tab on the existing
|
||||
# device, or keys will get stuck (mostly when releasing
|
||||
# media/consumer keys)
|
||||
self.send()
|
||||
|
||||
self.report_device[0] = needed_reporting_device
|
||||
|
||||
if consumer_key:
|
||||
self.add_key(consumer_key)
|
||||
else:
|
||||
for key in keys_pressed:
|
||||
if key.code >= FIRST_KMK_INTERNAL_KEY:
|
||||
continue
|
||||
|
||||
if isinstance(key, ModifierKey):
|
||||
self.add_modifier(key)
|
||||
else:
|
||||
self.add_key(key)
|
||||
|
||||
if key.has_modifiers:
|
||||
for mod in key.has_modifiers:
|
||||
self.add_modifier(mod)
|
||||
|
||||
return self
|
||||
for axis in axes:
|
||||
self.move_axis(axis)
|
||||
|
||||
def hid_send(self, evt):
|
||||
# Don't raise a NotImplementedError so this can serve as our "dummy" HID
|
||||
@ -131,12 +120,24 @@ class AbstractHID:
|
||||
self._prev_evt[:] = self._evt
|
||||
self.hid_send(self._evt)
|
||||
|
||||
if self._cc_pending:
|
||||
self.hid_send(self._cc_report)
|
||||
self._cc_pending = False
|
||||
|
||||
if self._pd_pending:
|
||||
self.hid_send(self._pd_report)
|
||||
self._pd_pending = False
|
||||
|
||||
return self
|
||||
|
||||
def clear_all(self):
|
||||
for idx, _ in enumerate(self.report_keys):
|
||||
self.report_keys[idx] = 0x00
|
||||
|
||||
self.remove_cc()
|
||||
self.remove_pd()
|
||||
self.clear_axis()
|
||||
|
||||
return self
|
||||
|
||||
def clear_non_modifiers(self):
|
||||
@ -175,9 +176,6 @@ class AbstractHID:
|
||||
|
||||
where_to_place = self.report_non_mods
|
||||
|
||||
if self.report_device[0] == HIDReportTypes.CONSUMER:
|
||||
where_to_place = self.report_keys
|
||||
|
||||
for idx, _ in enumerate(where_to_place):
|
||||
if where_to_place[idx] == 0x00:
|
||||
where_to_place[idx] = key.code
|
||||
@ -193,15 +191,44 @@ class AbstractHID:
|
||||
def remove_key(self, key):
|
||||
where_to_place = self.report_non_mods
|
||||
|
||||
if self.report_device[0] == HIDReportTypes.CONSUMER:
|
||||
where_to_place = self.report_keys
|
||||
|
||||
for idx, _ in enumerate(where_to_place):
|
||||
if where_to_place[idx] == key.code:
|
||||
where_to_place[idx] = 0x00
|
||||
|
||||
return self
|
||||
|
||||
def add_cc(self, cc):
|
||||
# Add (or write over) consumer control report. There can only be one CC
|
||||
# active at any time.
|
||||
memoryview(self._cc_report)[1:3] = cc.code.to_bytes(2, 'little')
|
||||
self._cc_pending = True
|
||||
|
||||
def remove_cc(self):
|
||||
# Remove consumer control report.
|
||||
report = memoryview(self._cc_report)[1:3]
|
||||
if report != b'\x00\x00':
|
||||
report[:] = b'\x00\x00'
|
||||
self._cc_pending = True
|
||||
|
||||
def add_pd(self, key):
|
||||
self._pd_report[1] |= key.code
|
||||
self._pd_pending = True
|
||||
|
||||
def remove_pd(self):
|
||||
if self._pd_report[1]:
|
||||
self._pd_pending = True
|
||||
self._pd_report[1] = 0x00
|
||||
|
||||
def move_axis(self, axis):
|
||||
delta = clamp(axis.delta, -127, 127)
|
||||
axis.delta -= delta
|
||||
self._pd_report[axis.code + 2] = 0xFF & delta
|
||||
self._pd_pending = True
|
||||
|
||||
def clear_axis(self):
|
||||
for idx in range(2, len(self._pd_report)):
|
||||
self._pd_report[idx] = 0x00
|
||||
|
||||
|
||||
class USBHID(AbstractHID):
|
||||
REPORT_BYTES = 9
|
||||
|
93
kmk/keys.py
93
kmk/keys.py
@ -20,6 +20,7 @@ class KeyType:
|
||||
SIMPLE = const(0)
|
||||
MODIFIER = const(1)
|
||||
CONSUMER = const(2)
|
||||
MOUSE = const(3)
|
||||
|
||||
|
||||
FIRST_KMK_INTERNAL_KEY = const(1000)
|
||||
@ -33,6 +34,29 @@ ALL_NUMBER_ALIASES = tuple(f'N{x}' for x in ALL_NUMBERS)
|
||||
debug = Debug(__name__)
|
||||
|
||||
|
||||
class Axis:
|
||||
def __init__(self, code: int) -> None:
|
||||
self.code = code
|
||||
self.delta = 0
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'Axis(code={self.code}, delta={self.delta})'
|
||||
|
||||
def move(self, keyboard: Keyboard, delta: int):
|
||||
self.delta += delta
|
||||
if self.delta:
|
||||
keyboard.axes.add(self)
|
||||
keyboard.hid_pending = True
|
||||
else:
|
||||
keyboard.axes.discard(self)
|
||||
|
||||
|
||||
class AX:
|
||||
W = Axis(2)
|
||||
X = Axis(0)
|
||||
Y = Axis(1)
|
||||
|
||||
|
||||
def maybe_make_key(
|
||||
code: Optional[int],
|
||||
names: Tuple[str, ...],
|
||||
@ -340,11 +364,13 @@ def maybe_make_unicode_key(candidate: str) -> Optional[Key]:
|
||||
def maybe_make_firmware_key(candidate: str) -> Optional[Key]:
|
||||
keys = (
|
||||
((('BLE_REFRESH',), handlers.ble_refresh)),
|
||||
((('BLE_DISCONNECT',), handlers.ble_disconnect)),
|
||||
((('BOOTLOADER',), handlers.bootloader)),
|
||||
((('DEBUG', 'DBG'), handlers.debug_pressed)),
|
||||
((('HID_SWITCH', 'HID'), handlers.hid_switch)),
|
||||
((('RELOAD', 'RLD'), handlers.reload)),
|
||||
((('RESET',), handlers.reset)),
|
||||
((('ANY',), handlers.any_pressed)),
|
||||
)
|
||||
|
||||
for names, handler in keys:
|
||||
@ -400,43 +426,64 @@ KEY_GENERATORS = (
|
||||
|
||||
|
||||
class KeyAttrDict:
|
||||
__cache = {}
|
||||
# Instead of relying on the uncontrollable availability of a big chunk of
|
||||
# contiguous memory for key caching, we can manually fragment the cache into
|
||||
# reasonably small partitions. The partition size is chosen from the magic
|
||||
# values of CPs hash allocation sizes.
|
||||
# (https://github.com/adafruit/circuitpython/blob/main/py/map.c, 2023-02)
|
||||
__partition_size = 37
|
||||
__cache = [{}]
|
||||
|
||||
def __iter__(self):
|
||||
return self.__cache.__iter__()
|
||||
for partition in self.__cache:
|
||||
for name in partition:
|
||||
yield name
|
||||
|
||||
def __setitem__(self, key: str, value: Key):
|
||||
self.__cache.__setitem__(key, value)
|
||||
def __setitem__(self, name: str, key: Key):
|
||||
# Overwrite existing reference.
|
||||
for partition in self.__cache:
|
||||
if name in partition:
|
||||
partition[name] = key
|
||||
return key
|
||||
|
||||
def __getattr__(self, key: Key):
|
||||
return self.__getitem__(key)
|
||||
# Insert new reference.
|
||||
if len(self.__cache[-1]) >= self.__partition_size:
|
||||
self.__cache.append({})
|
||||
self.__cache[-1][name] = key
|
||||
return key
|
||||
|
||||
def get(self, key: Key, default: Optional[Key] = None):
|
||||
def __getattr__(self, name: str):
|
||||
return self.__getitem__(name)
|
||||
|
||||
def get(self, name: str, default: Optional[Key] = None):
|
||||
try:
|
||||
return self.__getitem__(key)
|
||||
return self.__getitem__(name)
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
def clear(self):
|
||||
self.__cache.clear()
|
||||
self.__cache.append({})
|
||||
|
||||
def __getitem__(self, key: Key):
|
||||
try:
|
||||
return self.__cache[key]
|
||||
except KeyError:
|
||||
pass
|
||||
def __getitem__(self, name: str):
|
||||
for partition in self.__cache:
|
||||
if name in partition:
|
||||
return partition[name]
|
||||
|
||||
for func in KEY_GENERATORS:
|
||||
maybe_key = func(key)
|
||||
maybe_key = func(name)
|
||||
if maybe_key:
|
||||
break
|
||||
else:
|
||||
raise ValueError(f'Invalid key: {key}')
|
||||
|
||||
if not maybe_key:
|
||||
if debug.enabled:
|
||||
debug(f'Invalid key: {name}')
|
||||
return KC.NO
|
||||
|
||||
if debug.enabled:
|
||||
debug(f'{key}: {maybe_key}')
|
||||
debug(f'{name}: {maybe_key}')
|
||||
|
||||
return self.__cache[key]
|
||||
return maybe_key
|
||||
|
||||
|
||||
# Global state, will be filled in throughout this file, and
|
||||
@ -669,6 +716,10 @@ class ConsumerKey(Key):
|
||||
pass
|
||||
|
||||
|
||||
class MouseKey(Key):
|
||||
pass
|
||||
|
||||
|
||||
def make_key(
|
||||
code: Optional[int] = None,
|
||||
names: Tuple[str, ...] = tuple(), # NOQA
|
||||
@ -699,6 +750,8 @@ def make_key(
|
||||
constructor = ModifierKey
|
||||
elif type == KeyType.CONSUMER:
|
||||
constructor = ConsumerKey
|
||||
elif type == KeyType.MOUSE:
|
||||
constructor = MouseKey
|
||||
else:
|
||||
raise ValueError('Unrecognized key type')
|
||||
|
||||
@ -731,6 +784,10 @@ def make_consumer_key(*args, **kwargs) -> Key:
|
||||
return make_key(*args, **kwargs, type=KeyType.CONSUMER)
|
||||
|
||||
|
||||
def make_mouse_key(*args, **kwargs) -> Key:
|
||||
return make_key(*args, **kwargs, type=KeyType.MOUSE)
|
||||
|
||||
|
||||
# Argumented keys are implicitly internal, so auto-gen of code
|
||||
# is almost certainly the best plan here
|
||||
def make_argumented_key(
|
||||
|
@ -1,28 +1,33 @@
|
||||
try:
|
||||
from typing import Callable, Optional, Tuple
|
||||
from typing import Callable, Optional
|
||||
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(__name__)
|
||||
debug = Debug('kmk.keyboard')
|
||||
|
||||
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
|
||||
@ -49,6 +54,7 @@ class KMKKeyboard:
|
||||
#####
|
||||
# Internal State
|
||||
keys_pressed = set()
|
||||
axes = set()
|
||||
_coordkeys_pressed = {}
|
||||
hid_type = HIDModes.USB
|
||||
secondary_hid_type = None
|
||||
@ -58,7 +64,6 @@ 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
|
||||
@ -74,75 +79,59 @@ 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 ''.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' _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}')
|
||||
return self.__class__.__name__
|
||||
|
||||
def _send_hid(self) -> None:
|
||||
if self._hid_send_enabled:
|
||||
hid_report = self._hid_helper.create_report(self.keys_pressed)
|
||||
try:
|
||||
hid_report.send()
|
||||
except KeyError as e:
|
||||
if debug.enabled:
|
||||
debug(f'HidNotFound(HIDReportType={e})')
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
self.hid_pending = False
|
||||
|
||||
for axis in self.axes:
|
||||
axis.move(self, 0)
|
||||
|
||||
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(f'CoordMappingNotFound(ic={int_coord})')
|
||||
debug('no such int_coord: ', int_coord)
|
||||
|
||||
return None
|
||||
|
||||
for layer in self.active_layers:
|
||||
try:
|
||||
layer_key = self.keymap[layer][idx]
|
||||
key = self.keymap[layer][idx]
|
||||
except IndexError:
|
||||
layer_key = None
|
||||
key = None
|
||||
if debug.enabled:
|
||||
debug(f'KeymapIndexError(idx={idx}, layer={layer})')
|
||||
debug('keymap IndexError: idx=', idx, ' layer=', layer)
|
||||
|
||||
if not layer_key or layer_key == KC.TRNS:
|
||||
if not key or key == KC.TRNS:
|
||||
continue
|
||||
|
||||
return layer_key
|
||||
return 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:
|
||||
@ -150,18 +139,16 @@ class KMKKeyboard:
|
||||
key = self._coordkeys_pressed[int_coord]
|
||||
except KeyError:
|
||||
if debug.enabled:
|
||||
debug(f'KeyNotPressed(ic={int_coord})')
|
||||
debug('release w/o press: ', int_coord)
|
||||
|
||||
if key is None:
|
||||
key = self._find_key_in_map(int_coord)
|
||||
|
||||
if key is None:
|
||||
if debug.enabled:
|
||||
debug(f'MatrixUndefinedCoordinate(ic={int_coord})')
|
||||
return self
|
||||
if key is None:
|
||||
return
|
||||
|
||||
if debug.enabled:
|
||||
debug(f'KeyResolution(key={key})')
|
||||
debug(kevent, ': ', key)
|
||||
|
||||
self.pre_process_key(key, is_pressed, int_coord)
|
||||
|
||||
@ -186,7 +173,7 @@ class KMKKeyboard:
|
||||
key = ksf.key
|
||||
|
||||
# Handle any unaccounted-for layer shifts by looking up the key resolution again.
|
||||
if ksf.int_coord in self._coordkeys_pressed.keys():
|
||||
if ksf.int_coord is not None:
|
||||
key = self._find_key_in_map(ksf.int_coord)
|
||||
|
||||
# Resume the processing of the key event and update the HID report
|
||||
@ -227,8 +214,7 @@ class KMKKeyboard:
|
||||
if key is None:
|
||||
break
|
||||
except Exception as err:
|
||||
if debug.enabled:
|
||||
debug(f'Error in {module}.process_key: {err}')
|
||||
debug_error(module, 'process_key', err)
|
||||
|
||||
if int_coord is not None:
|
||||
if is_pressed:
|
||||
@ -238,18 +224,20 @@ class KMKKeyboard:
|
||||
del self._coordkeys_pressed[int_coord]
|
||||
except KeyError:
|
||||
if debug.enabled:
|
||||
debug(f'ReleaseKeyError(ic={int_coord})')
|
||||
debug('release w/o press:', int_coord)
|
||||
if debug.enabled:
|
||||
debug('coordkeys_pressed=', self._coordkeys_pressed)
|
||||
|
||||
if key:
|
||||
self.process_key(key, is_pressed, int_coord)
|
||||
|
||||
def process_key(
|
||||
self, key: Key, is_pressed: bool, coord_int: Optional[int] = None
|
||||
self, key: Key, is_pressed: bool, int_coord: Optional[int] = None
|
||||
) -> None:
|
||||
if is_pressed:
|
||||
key.on_press(self, coord_int)
|
||||
key.on_press(self, int_coord)
|
||||
else:
|
||||
key.on_release(self, coord_int)
|
||||
key.on_release(self, int_coord)
|
||||
|
||||
def resume_process_key(
|
||||
self,
|
||||
@ -257,8 +245,9 @@ class KMKKeyboard:
|
||||
key: Key,
|
||||
is_pressed: bool,
|
||||
int_coord: Optional[int] = None,
|
||||
reprocess: Optional[bool] = False,
|
||||
) -> None:
|
||||
index = self.modules.index(module) + 1
|
||||
index = self.modules.index(module) + (0 if reprocess else 1)
|
||||
ksf = KeyBufferFrame(
|
||||
key=key, is_pressed=is_pressed, int_coord=int_coord, index=index
|
||||
)
|
||||
@ -275,60 +264,17 @@ 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(False, lambda: self.remove_key(keycode))
|
||||
self.set_timeout(0, lambda: self.remove_key(keycode))
|
||||
|
||||
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 set_timeout(self, after_ticks: int, callback: Callable[[None], None]) -> [Task]:
|
||||
return create_task(callback, after_ms=after_ticks)
|
||||
|
||||
def cancel_timeout(self, timeout_key: int) -> None:
|
||||
try:
|
||||
self._timeouts[timeout_key[0]][timeout_key[1]] = None
|
||||
except (KeyError, IndexError):
|
||||
if debug.enabled:
|
||||
debug(f'no such timeout: {timeout_key}')
|
||||
cancel_task(timeout_key)
|
||||
|
||||
def _process_timeouts(self) -> None:
|
||||
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
|
||||
for task in get_due_task():
|
||||
task()
|
||||
|
||||
def _init_sanity_check(self) -> None:
|
||||
'''
|
||||
@ -375,10 +321,15 @@ 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,
|
||||
@ -394,101 +345,134 @@ 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:
|
||||
if debug.enabled:
|
||||
debug(f'Error in {module}.before_matrix_scan: {err}')
|
||||
debug_error(module, 'before_matrix_scan', err)
|
||||
|
||||
for ext in self.extensions:
|
||||
try:
|
||||
ext.before_matrix_scan(self.sandbox)
|
||||
except Exception as err:
|
||||
if debug.enabled:
|
||||
debug(f'Error in {ext}.before_matrix_scan: {err}')
|
||||
debug_error(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:
|
||||
if debug.enabled:
|
||||
debug(f'Error in {module}.after_matrix_scan: {err}')
|
||||
debug_error(module, 'after_matrix_scan', err)
|
||||
|
||||
for ext in self.extensions:
|
||||
try:
|
||||
ext.after_matrix_scan(self.sandbox)
|
||||
except Exception as err:
|
||||
if debug.enabled:
|
||||
debug(f'Error in {ext}.after_matrix_scan: {err}')
|
||||
debug_error(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:
|
||||
if debug.enabled:
|
||||
debug(f'Error in {module}.before_hid_send: {err}')
|
||||
debug_error(module, 'before_hid_send', err)
|
||||
|
||||
for ext in self.extensions:
|
||||
try:
|
||||
ext.before_hid_send(self.sandbox)
|
||||
except Exception as err:
|
||||
if debug.enabled:
|
||||
debug(
|
||||
f'Error in {ext}.before_hid_send: {err}',
|
||||
)
|
||||
debug_error(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:
|
||||
if debug.enabled:
|
||||
debug(f'Error in {module}.after_hid_send: {err}')
|
||||
debug_error(module, 'after_hid_send', err)
|
||||
|
||||
for ext in self.extensions:
|
||||
try:
|
||||
ext.after_hid_send(self.sandbox)
|
||||
except Exception as err:
|
||||
if debug.enabled:
|
||||
debug(f'Error in {ext}.after_hid_send: {err}')
|
||||
debug_error(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:
|
||||
if debug.enabled:
|
||||
debug(f'Error in {module}.on_powersave: {err}')
|
||||
debug_error(module, 'powersave_enable', err)
|
||||
|
||||
for ext in self.extensions:
|
||||
try:
|
||||
ext.on_powersave_enable(self.sandbox)
|
||||
except Exception as err:
|
||||
if debug.enabled:
|
||||
debug(f'Error in {ext}.powersave_enable: {err}')
|
||||
debug_error(ext, 'powersave_enable', err)
|
||||
|
||||
def powersave_disable(self) -> None:
|
||||
for module in self.modules:
|
||||
try:
|
||||
module.on_powersave_disable(self)
|
||||
except Exception as err:
|
||||
if debug.enabled:
|
||||
debug(f'Error in {module}.powersave_disable: {err}')
|
||||
debug_error(module, 'powersave_disable', err)
|
||||
|
||||
for ext in self.extensions:
|
||||
try:
|
||||
ext.on_powersave_disable(self.sandbox)
|
||||
except Exception as err:
|
||||
if debug.enabled:
|
||||
debug(f'Error in {ext}.powersave_disable: {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)
|
||||
|
||||
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)
|
||||
while True:
|
||||
self._main_loop()
|
||||
try:
|
||||
while True:
|
||||
self._main_loop()
|
||||
finally:
|
||||
debug('Unexpected error: cleaning up')
|
||||
self._deinit_hid()
|
||||
self.deinit()
|
||||
|
||||
def _init(
|
||||
self,
|
||||
@ -500,29 +484,22 @@ class KMKKeyboard:
|
||||
self.hid_type = hid_type
|
||||
self.secondary_hid_type = secondary_hid_type
|
||||
|
||||
self._init_sanity_check()
|
||||
if debug.enabled:
|
||||
debug('Initialising ', self)
|
||||
debug('unicode_mode=', self.unicode_mode)
|
||||
|
||||
self._init_hid()
|
||||
self._init_matrix()
|
||||
self._init_coord_mapping()
|
||||
|
||||
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}')
|
||||
self.during_bootup()
|
||||
|
||||
if debug.enabled:
|
||||
debug(f'init: {self}')
|
||||
import gc
|
||||
|
||||
gc.collect()
|
||||
debug('mem_info used:', gc.mem_alloc(), ' free:', gc.mem_free())
|
||||
|
||||
def _main_loop(self) -> None:
|
||||
self.state_changed = False
|
||||
self.sandbox.active_layers = self.active_layers.copy()
|
||||
|
||||
self.before_matrix_scan()
|
||||
@ -560,7 +537,6 @@ class KMKKeyboard:
|
||||
|
||||
if self.hid_pending:
|
||||
self._send_hid()
|
||||
self.state_changed = True
|
||||
|
||||
self.after_hid_send()
|
||||
|
||||
@ -569,6 +545,3 @@ class KMKKeyboard:
|
||||
|
||||
if self._trigger_powersave_disable:
|
||||
self.powersave_disable()
|
||||
|
||||
if self.state_changed:
|
||||
self._print_debug_cycle()
|
||||
|
@ -41,3 +41,6 @@ class Module:
|
||||
|
||||
def on_powersave_disable(self, keyboard):
|
||||
raise NotImplementedError
|
||||
|
||||
def deinit(self, keyboard):
|
||||
pass
|
||||
|
@ -4,9 +4,9 @@ import microcontroller
|
||||
|
||||
import time
|
||||
|
||||
from kmk.keys import AX
|
||||
from kmk.modules import Module
|
||||
from kmk.modules.adns9800_firmware import firmware
|
||||
from kmk.modules.mouse_keys import PointingDevice
|
||||
|
||||
|
||||
class REG:
|
||||
@ -70,7 +70,6 @@ class ADNS9800(Module):
|
||||
DIR_READ = 0x7F
|
||||
|
||||
def __init__(self, cs, sclk, miso, mosi, invert_x=False, invert_y=False):
|
||||
self.pointing_device = PointingDevice()
|
||||
self.cs = digitalio.DigitalInOut(cs)
|
||||
self.cs.direction = digitalio.Direction.OUTPUT
|
||||
self.spi = busio.SPI(clock=sclk, MOSI=mosi, MISO=miso)
|
||||
@ -203,27 +202,14 @@ class ADNS9800(Module):
|
||||
if self.invert_y:
|
||||
delta_y *= -1
|
||||
|
||||
if delta_x < 0:
|
||||
self.pointing_device.report_x[0] = (delta_x & 0xFF) | 0x80
|
||||
else:
|
||||
self.pointing_device.report_x[0] = delta_x & 0xFF
|
||||
if delta_x:
|
||||
AX.X.move(delta_x)
|
||||
|
||||
if delta_y < 0:
|
||||
self.pointing_device.report_y[0] = (delta_y & 0xFF) | 0x80
|
||||
else:
|
||||
self.pointing_device.report_y[0] = delta_y & 0xFF
|
||||
if delta_y:
|
||||
AX.Y.move(delta_y)
|
||||
|
||||
if keyboard.debug_enabled:
|
||||
print('Delta: ', delta_x, ' ', delta_y)
|
||||
self.pointing_device.hid_pending = True
|
||||
|
||||
if self.pointing_device.hid_pending:
|
||||
keyboard._hid_helper.hid_send(self.pointing_device._evt)
|
||||
self.pointing_device.hid_pending = False
|
||||
self.pointing_device.report_x[0] = 0
|
||||
self.pointing_device.report_y[0] = 0
|
||||
|
||||
return
|
||||
|
||||
def after_matrix_scan(self, keyboard):
|
||||
return
|
||||
|
@ -8,6 +8,9 @@ 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:
|
||||
@ -214,7 +217,7 @@ class Combos(Module):
|
||||
combo.insert(key, int_coord)
|
||||
combo._state = _ComboState.MATCHING
|
||||
|
||||
key = combo.result
|
||||
key = None
|
||||
break
|
||||
|
||||
else:
|
||||
@ -301,10 +304,14 @@ 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
|
||||
|
||||
|
@ -4,8 +4,8 @@ Extension handles usage of AS5013 by AMS
|
||||
|
||||
from supervisor import ticks_ms
|
||||
|
||||
from kmk.keys import AX
|
||||
from kmk.modules import Module
|
||||
from kmk.modules.mouse_keys import PointingDevice
|
||||
|
||||
I2C_ADDRESS = 0x40
|
||||
I2X_ALT_ADDRESS = 0x41
|
||||
@ -44,7 +44,6 @@ class Easypoint(Module):
|
||||
self._i2c_bus = i2c
|
||||
|
||||
# HID parameters
|
||||
self.pointing_device = PointingDevice()
|
||||
self.polling_interval = 20
|
||||
self.last_tick = ticks_ms()
|
||||
|
||||
@ -82,12 +81,8 @@ class Easypoint(Module):
|
||||
return
|
||||
else:
|
||||
# Set the X/Y from easypoint
|
||||
self.pointing_device.report_x[0] = x
|
||||
self.pointing_device.report_y[0] = y
|
||||
|
||||
self.pointing_device.hid_pending = x != 0 or y != 0
|
||||
|
||||
return
|
||||
AX.X.move(keyboard, x)
|
||||
AX.Y.move(keyboard, y)
|
||||
|
||||
def after_matrix_scan(self, keyboard):
|
||||
return
|
||||
@ -96,9 +91,6 @@ class Easypoint(Module):
|
||||
return
|
||||
|
||||
def after_hid_send(self, keyboard):
|
||||
if self.pointing_device.hid_pending:
|
||||
keyboard._hid_helper.hid_send(self.pointing_device._evt)
|
||||
self._clear_pending_hid()
|
||||
return
|
||||
|
||||
def on_powersave_enable(self, keyboard):
|
||||
@ -107,20 +99,13 @@ class Easypoint(Module):
|
||||
def on_powersave_disable(self, keyboard):
|
||||
return
|
||||
|
||||
def _clear_pending_hid(self):
|
||||
self.pointing_device.hid_pending = False
|
||||
self.pointing_device.report_x[0] = 0
|
||||
self.pointing_device.report_y[0] = 0
|
||||
self.pointing_device.report_w[0] = 0
|
||||
self.pointing_device.button_status[0] = 0
|
||||
|
||||
def _read_raw_state(self):
|
||||
'''Read data from AS5013'''
|
||||
x, y = self._i2c_rdwr([X], length=2)
|
||||
return x, y
|
||||
|
||||
def getSignedNumber(self, number, bitLength=8):
|
||||
mask = (2 ** bitLength) - 1
|
||||
mask = (2**bitLength) - 1
|
||||
if number & (1 << (bitLength - 1)):
|
||||
return number | ~mask
|
||||
else:
|
||||
|
@ -54,7 +54,7 @@ class HoldTap(Module):
|
||||
def __init__(self):
|
||||
self.key_buffer = []
|
||||
self.key_states = {}
|
||||
if not KC.get('HT'):
|
||||
if KC.get('HT') == KC.NO:
|
||||
make_argumented_key(
|
||||
validator=HoldTapKeyMeta,
|
||||
names=('HT',),
|
||||
@ -83,6 +83,11 @@ 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
|
||||
@ -93,15 +98,12 @@ 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
|
||||
and not isinstance(current_key.meta, HoldTapKeyMeta)
|
||||
):
|
||||
if key.meta.tap_interrupted and is_pressed:
|
||||
append_buffer = True
|
||||
|
||||
# apply changes with 'side-effects' on key_states or the loop behaviour
|
||||
@ -110,10 +112,8 @@ class HoldTap(Module):
|
||||
self.key_buffer.append((int_coord, current_key, is_pressed))
|
||||
current_key = None
|
||||
|
||||
elif send_buffer:
|
||||
if 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,8 +221,11 @@ 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)
|
||||
keyboard.resume_process_key(self, key, is_pressed, int_coord, reprocess)
|
||||
if isinstance(key.meta, HoldTapKeyMeta):
|
||||
reprocess = True
|
||||
|
||||
self.key_buffer.clear()
|
||||
|
||||
|
@ -36,9 +36,15 @@ class LayerKeyMeta:
|
||||
class Layers(HoldTap):
|
||||
'''Gives access to the keys used to enable the layer system'''
|
||||
|
||||
def __init__(self):
|
||||
_active_combo = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
combo_layers=None,
|
||||
):
|
||||
# Layers
|
||||
super().__init__()
|
||||
self.combo_layers = combo_layers
|
||||
make_argumented_key(
|
||||
validator=layer_key_validator,
|
||||
names=('MO',),
|
||||
@ -77,41 +83,24 @@ class Layers(HoldTap):
|
||||
'''
|
||||
Switches the default layer
|
||||
'''
|
||||
keyboard.active_layers[-1] = key.meta.layer
|
||||
self._print_debug(keyboard)
|
||||
self.activate_layer(keyboard, key.meta.layer, as_default=True)
|
||||
|
||||
def _mo_pressed(self, key, keyboard, *args, **kwargs):
|
||||
'''
|
||||
Momentarily activates layer, switches off when you let go
|
||||
'''
|
||||
keyboard.active_layers.insert(0, key.meta.layer)
|
||||
self._print_debug(keyboard)
|
||||
self.activate_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 _mo_released(self, key, keyboard, *args, **kwargs):
|
||||
self.deactivate_layer(keyboard, key.meta.layer)
|
||||
|
||||
def _lm_pressed(self, key, keyboard, *args, **kwargs):
|
||||
'''
|
||||
As MO(layer) but with mod active
|
||||
'''
|
||||
keyboard.hid_pending = True
|
||||
# Sets the timer start and acts like MO otherwise
|
||||
keyboard.keys_pressed.add(key.meta.kc)
|
||||
self._mo_pressed(key, keyboard, *args, **kwargs)
|
||||
self.activate_layer(keyboard, key.meta.layer)
|
||||
|
||||
def _lm_released(self, key, keyboard, *args, **kwargs):
|
||||
'''
|
||||
@ -119,27 +108,77 @@ class Layers(HoldTap):
|
||||
'''
|
||||
keyboard.hid_pending = True
|
||||
keyboard.keys_pressed.discard(key.meta.kc)
|
||||
self._mo_released(key, keyboard, *args, **kwargs)
|
||||
self.deactivate_layer(keyboard, key.meta.layer)
|
||||
|
||||
def _tg_pressed(self, key, keyboard, *args, **kwargs):
|
||||
'''
|
||||
Toggles the layer (enables it if not active, and vise versa)
|
||||
'''
|
||||
# See mo_released for implementation details around this
|
||||
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)
|
||||
if key.meta.layer in keyboard.active_layers:
|
||||
self.deactivate_layer(keyboard, key.meta.layer)
|
||||
else:
|
||||
self.activate_layer(keyboard, 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
|
||||
|
@ -2,6 +2,7 @@ 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__()
|
||||
|
@ -1,54 +1,40 @@
|
||||
from supervisor import ticks_ms
|
||||
|
||||
from kmk.hid import HID_REPORT_SIZES, HIDReportTypes
|
||||
from kmk.keys import make_key
|
||||
from kmk.keys import AX, make_key, make_mouse_key
|
||||
from kmk.kmktime import PeriodicTimer
|
||||
from kmk.modules import Module
|
||||
|
||||
|
||||
class PointingDevice:
|
||||
MB_LMB = 1
|
||||
MB_RMB = 2
|
||||
MB_MMB = 4
|
||||
_evt = bytearray(HID_REPORT_SIZES[HIDReportTypes.MOUSE] + 1)
|
||||
|
||||
def __init__(self):
|
||||
self.key_states = {}
|
||||
self.hid_pending = False
|
||||
self.report_device = memoryview(self._evt)[0:1]
|
||||
self.report_device[0] = HIDReportTypes.MOUSE
|
||||
self.button_status = memoryview(self._evt)[1:2]
|
||||
self.report_x = memoryview(self._evt)[2:3]
|
||||
self.report_y = memoryview(self._evt)[3:4]
|
||||
self.report_w = memoryview(self._evt)[4:]
|
||||
|
||||
|
||||
class MouseKeys(Module):
|
||||
def __init__(self):
|
||||
self.pointing_device = PointingDevice()
|
||||
self._nav_key_activated = 0
|
||||
self._up_activated = False
|
||||
self._down_activated = False
|
||||
self._left_activated = False
|
||||
self._right_activated = False
|
||||
self._mw_up_activated = False
|
||||
self._mw_down_activated = False
|
||||
self.max_speed = 10
|
||||
self.ac_interval = 100 # Delta ms to apply acceleration
|
||||
self._next_interval = 0 # Time for next tick interval
|
||||
self.acc_interval = 10 # Delta ms to apply acceleration
|
||||
self.move_step = 1
|
||||
|
||||
make_key(
|
||||
make_mouse_key(
|
||||
names=('MB_LMB',),
|
||||
on_press=self._mb_lmb_press,
|
||||
on_release=self._mb_lmb_release,
|
||||
code=1,
|
||||
)
|
||||
make_key(
|
||||
make_mouse_key(
|
||||
names=('MB_MMB',),
|
||||
on_press=self._mb_mmb_press,
|
||||
on_release=self._mb_mmb_release,
|
||||
code=4,
|
||||
)
|
||||
make_key(
|
||||
make_mouse_key(
|
||||
names=('MB_RMB',),
|
||||
on_press=self._mb_rmb_press,
|
||||
on_release=self._mb_rmb_release,
|
||||
code=2,
|
||||
)
|
||||
make_mouse_key(
|
||||
names=('MB_BTN4',),
|
||||
code=8,
|
||||
)
|
||||
make_mouse_key(
|
||||
names=('MB_BTN5',),
|
||||
code=16,
|
||||
)
|
||||
make_key(
|
||||
names=('MW_UP',),
|
||||
@ -94,38 +80,33 @@ class MouseKeys(Module):
|
||||
)
|
||||
|
||||
def during_bootup(self, keyboard):
|
||||
return
|
||||
|
||||
def matrix_detected_press(self, keyboard):
|
||||
return keyboard.matrix_update is None
|
||||
self._timer = PeriodicTimer(self.acc_interval)
|
||||
|
||||
def before_matrix_scan(self, keyboard):
|
||||
return
|
||||
|
||||
def after_matrix_scan(self, keyboard):
|
||||
if not self._timer.tick():
|
||||
return
|
||||
|
||||
if self._nav_key_activated:
|
||||
if self._next_interval <= ticks_ms():
|
||||
# print("hello: ")
|
||||
# print(ticks_ms())
|
||||
self._next_interval = ticks_ms() + self.ac_interval
|
||||
# print(self._next_interval)
|
||||
if self.move_step < self.max_speed:
|
||||
self.move_step = self.move_step + 1
|
||||
if self.move_step < self.max_speed:
|
||||
self.move_step = self.move_step + 1
|
||||
if self._right_activated:
|
||||
self.pointing_device.report_x[0] = self.move_step
|
||||
AX.X.move(keyboard, self.move_step)
|
||||
if self._left_activated:
|
||||
self.pointing_device.report_x[0] = 0xFF & (0 - self.move_step)
|
||||
AX.X.move(keyboard, -self.move_step)
|
||||
if self._up_activated:
|
||||
self.pointing_device.report_y[0] = 0xFF & (0 - self.move_step)
|
||||
AX.Y.move(keyboard, -self.move_step)
|
||||
if self._down_activated:
|
||||
self.pointing_device.report_y[0] = self.move_step
|
||||
self.pointing_device.hid_pending = True
|
||||
return
|
||||
AX.Y.move(keyboard, self.move_step)
|
||||
|
||||
if self._mw_up_activated:
|
||||
AX.W.move(keyboard, 1)
|
||||
if self._mw_down_activated:
|
||||
AX.W.move(keyboard, -1)
|
||||
|
||||
def before_hid_send(self, keyboard):
|
||||
if self.pointing_device.hid_pending and keyboard._hid_send_enabled:
|
||||
keyboard._hid_helper.hid_send(self.pointing_device._evt)
|
||||
self.pointing_device.hid_pending = False
|
||||
return
|
||||
|
||||
def after_hid_send(self, keyboard):
|
||||
@ -137,50 +118,21 @@ class MouseKeys(Module):
|
||||
def on_powersave_disable(self, keyboard):
|
||||
return
|
||||
|
||||
def _mb_lmb_press(self, key, keyboard, *args, **kwargs):
|
||||
self.pointing_device.button_status[0] |= self.pointing_device.MB_LMB
|
||||
self.pointing_device.hid_pending = True
|
||||
|
||||
def _mb_lmb_release(self, key, keyboard, *args, **kwargs):
|
||||
self.pointing_device.button_status[0] &= ~self.pointing_device.MB_LMB
|
||||
self.pointing_device.hid_pending = True
|
||||
|
||||
def _mb_mmb_press(self, key, keyboard, *args, **kwargs):
|
||||
self.pointing_device.button_status[0] |= self.pointing_device.MB_MMB
|
||||
self.pointing_device.hid_pending = True
|
||||
|
||||
def _mb_mmb_release(self, key, keyboard, *args, **kwargs):
|
||||
self.pointing_device.button_status[0] &= ~self.pointing_device.MB_MMB
|
||||
self.pointing_device.hid_pending = True
|
||||
|
||||
def _mb_rmb_press(self, key, keyboard, *args, **kwargs):
|
||||
self.pointing_device.button_status[0] |= self.pointing_device.MB_RMB
|
||||
self.pointing_device.hid_pending = True
|
||||
|
||||
def _mb_rmb_release(self, key, keyboard, *args, **kwargs):
|
||||
self.pointing_device.button_status[0] &= ~self.pointing_device.MB_RMB
|
||||
self.pointing_device.hid_pending = True
|
||||
|
||||
def _mw_up_press(self, key, keyboard, *args, **kwargs):
|
||||
self.pointing_device.report_w[0] = self.move_step
|
||||
self.pointing_device.hid_pending = True
|
||||
self._mw_up_activated = True
|
||||
|
||||
def _mw_up_release(self, key, keyboard, *args, **kwargs):
|
||||
self.pointing_device.report_w[0] = 0
|
||||
self.pointing_device.hid_pending = True
|
||||
self._mw_up_activated = False
|
||||
|
||||
def _mw_down_press(self, key, keyboard, *args, **kwargs):
|
||||
self.pointing_device.report_w[0] = 0xFF
|
||||
self.pointing_device.hid_pending = True
|
||||
self._mw_down_activated = True
|
||||
|
||||
def _mw_down_release(self, key, keyboard, *args, **kwargs):
|
||||
self.pointing_device.report_w[0] = 0
|
||||
self.pointing_device.hid_pending = True
|
||||
self._mw_down_activated = False
|
||||
|
||||
# Mouse movement
|
||||
def _reset_next_interval(self):
|
||||
if self._nav_key_activated == 1:
|
||||
self._next_interval = ticks_ms() + self.ac_interval
|
||||
self.move_step = 1
|
||||
|
||||
def _check_last(self):
|
||||
@ -191,56 +143,38 @@ class MouseKeys(Module):
|
||||
self._nav_key_activated += 1
|
||||
self._reset_next_interval()
|
||||
self._up_activated = True
|
||||
self.pointing_device.report_y[0] = 0xFF & (0 - self.move_step)
|
||||
self.pointing_device.hid_pending = True
|
||||
|
||||
def _ms_up_release(self, key, keyboard, *args, **kwargs):
|
||||
self._up_activated = False
|
||||
self._nav_key_activated -= 1
|
||||
self._check_last()
|
||||
self.pointing_device.report_y[0] = 0
|
||||
self.pointing_device.hid_pending = False
|
||||
|
||||
def _ms_down_press(self, key, keyboard, *args, **kwargs):
|
||||
self._nav_key_activated += 1
|
||||
self._reset_next_interval()
|
||||
self._down_activated = True
|
||||
# if not self.x_activated and not self.y_activated:
|
||||
# self.next_interval = ticks_ms() + self.ac_intervalle
|
||||
self.pointing_device.report_y[0] = self.move_step
|
||||
self.pointing_device.hid_pending = True
|
||||
|
||||
def _ms_down_release(self, key, keyboard, *args, **kwargs):
|
||||
self._down_activated = False
|
||||
self._nav_key_activated -= 1
|
||||
self._check_last()
|
||||
self.pointing_device.report_y[0] = 0
|
||||
self.pointing_device.hid_pending = False
|
||||
|
||||
def _ms_left_press(self, key, keyboard, *args, **kwargs):
|
||||
self._nav_key_activated += 1
|
||||
self._reset_next_interval()
|
||||
self._left_activated = True
|
||||
self.pointing_device.report_x[0] = 0xFF & (0 - self.move_step)
|
||||
self.pointing_device.hid_pending = True
|
||||
|
||||
def _ms_left_release(self, key, keyboard, *args, **kwargs):
|
||||
self._nav_key_activated -= 1
|
||||
self._left_activated = False
|
||||
self._check_last()
|
||||
self.pointing_device.report_x[0] = 0
|
||||
self.pointing_device.hid_pending = False
|
||||
|
||||
def _ms_right_press(self, key, keyboard, *args, **kwargs):
|
||||
self._nav_key_activated += 1
|
||||
self._reset_next_interval()
|
||||
self._right_activated = True
|
||||
self.pointing_device.report_x[0] = self.move_step
|
||||
self.pointing_device.hid_pending = True
|
||||
|
||||
def _ms_right_release(self, key, keyboard, *args, **kwargs):
|
||||
self._nav_key_activated -= 1
|
||||
self._right_activated = False
|
||||
self._check_last()
|
||||
self.pointing_device.report_x[0] = 0
|
||||
self.pointing_device.hid_pending = False
|
||||
|
@ -1,9 +1,14 @@
|
||||
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__)
|
||||
|
||||
|
||||
def oneshot_validator(kc, tap_time=None):
|
||||
return HoldTapKeyMeta(tap=kc, hold=kc, prefer_hold=False, tap_time=tap_time)
|
||||
class OneShotKeyMeta(HoldTapKeyMeta):
|
||||
def __init__(self, kc, tap_time=None):
|
||||
super().__init__(tap=kc, hold=kc, prefer_hold=False, tap_time=tap_time)
|
||||
|
||||
|
||||
class OneShot(HoldTap):
|
||||
@ -12,30 +17,49 @@ class OneShot(HoldTap):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
make_argumented_key(
|
||||
validator=oneshot_validator,
|
||||
validator=OneShotKeyMeta,
|
||||
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 keyup.'''
|
||||
'''Release os key after interrupting non-os keyup, or reset timeout and
|
||||
stack multiple os keys.'''
|
||||
send_buffer = False
|
||||
|
||||
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:
|
||||
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)
|
||||
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)
|
||||
|
||||
return current_key
|
||||
|
||||
@ -51,8 +75,8 @@ class OneShot(HoldTap):
|
||||
try:
|
||||
state = self.key_states[key]
|
||||
except KeyError:
|
||||
if keyboard.debug_enabled:
|
||||
print(f'OneShot.osk_released: no such key {key}')
|
||||
if debug.enabled:
|
||||
debug(f'OneShot.osk_released: no such key {key}')
|
||||
return keyboard
|
||||
|
||||
if state.activated == ActivationType.PRESSED:
|
||||
|
@ -7,10 +7,9 @@ from micropython import const
|
||||
import math
|
||||
import struct
|
||||
|
||||
from kmk.keys import make_argumented_key, make_key
|
||||
from kmk.keys import AX, KC, make_argumented_key, make_key
|
||||
from kmk.kmktime import PeriodicTimer
|
||||
from kmk.modules import Module
|
||||
from kmk.modules.mouse_keys import PointingDevice
|
||||
|
||||
I2C_ADDRESS = 0x0A
|
||||
I2C_ADDRESS_ALTERNATIVE = 0x0B
|
||||
@ -78,29 +77,16 @@ class TrackballHandler:
|
||||
|
||||
class PointingHandler(TrackballHandler):
|
||||
def handle(self, keyboard, trackball, x, y, switch, state):
|
||||
if x > 0:
|
||||
trackball.pointing_device.report_x[0] = x
|
||||
elif x < 0:
|
||||
trackball.pointing_device.report_x[0] = 0xFF & x
|
||||
if y > 0:
|
||||
trackball.pointing_device.report_y[0] = y
|
||||
elif y < 0:
|
||||
trackball.pointing_device.report_y[0] = 0xFF & y
|
||||
|
||||
if x != 0 or y != 0:
|
||||
trackball.pointing_device.hid_pending = True
|
||||
if x:
|
||||
AX.X.move(keyboard, x)
|
||||
if y:
|
||||
AX.Y.move(keyboard, y)
|
||||
|
||||
if switch == 1: # Button pressed
|
||||
trackball.pointing_device.button_status[
|
||||
0
|
||||
] |= trackball.pointing_device.MB_LMB
|
||||
trackball.pointing_device.hid_pending = True
|
||||
keyboard.pre_process_key(KC.MB_LMB, is_pressed=True)
|
||||
|
||||
if not state and trackball.previous_state is True: # Button released
|
||||
trackball.pointing_device.button_status[
|
||||
0
|
||||
] &= ~trackball.pointing_device.MB_LMB
|
||||
trackball.pointing_device.hid_pending = True
|
||||
keyboard.pre_process_key(KC.MB_LMB, is_pressed=False)
|
||||
|
||||
trackball.previous_state = state
|
||||
|
||||
@ -114,17 +100,13 @@ class ScrollHandler(TrackballHandler):
|
||||
y = -y
|
||||
|
||||
if y != 0:
|
||||
pointing_device = trackball.pointing_device
|
||||
pointing_device.report_w[0] = 0xFF & y
|
||||
pointing_device.hid_pending = True
|
||||
AX.W.move(keyboard, y)
|
||||
|
||||
if switch == 1: # Button pressed
|
||||
pointing_device.button_status[0] |= pointing_device.MB_LMB
|
||||
pointing_device.hid_pending = True
|
||||
keyboard.pre_process_key(KC.MB_LMB, is_pressed=True)
|
||||
|
||||
if not state and trackball.previous_state is True: # Button released
|
||||
pointing_device.button_status[0] &= ~pointing_device.MB_LMB
|
||||
pointing_device.hid_pending = True
|
||||
keyboard.pre_process_key(KC.MB_LMB, is_pressed=False)
|
||||
|
||||
trackball.previous_state = state
|
||||
|
||||
@ -185,7 +167,6 @@ class Trackball(Module):
|
||||
self._i2c_address = address
|
||||
self._i2c_bus = i2c
|
||||
|
||||
self.pointing_device = PointingDevice()
|
||||
self.mode = mode
|
||||
self.previous_state = False # click state
|
||||
self.handlers = handlers
|
||||
@ -234,9 +215,6 @@ class Trackball(Module):
|
||||
return
|
||||
|
||||
def after_hid_send(self, keyboard):
|
||||
if self.pointing_device.hid_pending:
|
||||
keyboard._hid_helper.hid_send(self.pointing_device._evt)
|
||||
self._clear_pending_hid()
|
||||
return
|
||||
|
||||
def on_powersave_enable(self, keyboard):
|
||||
@ -280,13 +258,6 @@ class Trackball(Module):
|
||||
next_index = 0
|
||||
self.activate_handler(next_index)
|
||||
|
||||
def _clear_pending_hid(self):
|
||||
self.pointing_device.hid_pending = False
|
||||
self.pointing_device.report_x[0] = 0
|
||||
self.pointing_device.report_y[0] = 0
|
||||
self.pointing_device.report_w[0] = 0
|
||||
self.pointing_device.button_status[0] = 0
|
||||
|
||||
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)
|
||||
|
@ -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:
|
||||
elif check_deadline(ticks_ms(), self._powersave_start, 240000) is False:
|
||||
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)
|
||||
return bool(check_deadline(ticks_ms(), self._usb_last_scan, 5000) is False)
|
||||
|
||||
def usb_time_reset(self):
|
||||
self._usb_last_scan = ticks_ms()
|
||||
|
@ -42,12 +42,11 @@ class Phrase:
|
||||
self._characters: list[Character] = []
|
||||
self._index: int = 0
|
||||
for char in string:
|
||||
try:
|
||||
key_code = KC[char]
|
||||
shifted = char.isupper() or key_code.has_modifiers == {2}
|
||||
self._characters.append(Character(key_code, shifted))
|
||||
except ValueError:
|
||||
key_code = KC[char]
|
||||
if key_code == KC.NO:
|
||||
raise ValueError(f'Invalid character in dictionary: {char}')
|
||||
shifted = char.isupper() or key_code.has_modifiers == {2}
|
||||
self._characters.append(Character(key_code, shifted))
|
||||
|
||||
def next_character(self) -> None:
|
||||
'''Increment the current index for this phrase'''
|
||||
|
@ -17,7 +17,7 @@ class TapDanceKeyMeta:
|
||||
ht_key = KC.HT(
|
||||
tap=key,
|
||||
hold=key,
|
||||
prefer_hold=False,
|
||||
prefer_hold=True,
|
||||
tap_interrupted=False,
|
||||
tap_time=self.tap_time,
|
||||
)
|
||||
|
67
kmk/scheduler.py
Normal file
67
kmk/scheduler.py
Normal file
@ -0,0 +1,67 @@
|
||||
'''
|
||||
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)
|
13
kmk/utils.py
13
kmk/utils.py
@ -1,3 +1,8 @@
|
||||
try:
|
||||
from typing import Optional
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from supervisor import ticks_ms
|
||||
|
||||
|
||||
@ -16,8 +21,12 @@ class Debug:
|
||||
def __init__(self, name: str = __name__):
|
||||
self.name = name
|
||||
|
||||
def __call__(self, message: str) -> None:
|
||||
print(f'{ticks_ms()} {self.name}: {message}')
|
||||
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='')
|
||||
|
||||
@property
|
||||
def enabled(self) -> bool:
|
||||
|
@ -23,6 +23,7 @@ per-file-ignores =
|
||||
# your imports in whatever order you want
|
||||
user_keymaps/**/*.py: E131,F401,E501,E241,E131,BLK100,I003
|
||||
boards/**/main.py: E131,F401,E501,E241,E131,BLK100,I003
|
||||
boards/**/kb.py: E131,F401,E501,E241,E131,BLK100,I003
|
||||
tests/test_data/keymaps/**/*.py: F401,E501
|
||||
|
||||
[isort]
|
||||
|
@ -6,6 +6,7 @@ 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):
|
||||
@ -23,6 +24,8 @@ def code2name(code):
|
||||
|
||||
|
||||
class KeyboardTest:
|
||||
loop_delay_ms = 2
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
modules,
|
||||
@ -74,7 +77,14 @@ class KeyboardTest:
|
||||
is_pressed = e[1]
|
||||
self.pins[key_pos].value = is_pressed
|
||||
self.do_main_loop()
|
||||
self.keyboard._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'
|
||||
|
||||
matching = True
|
||||
for i in range(max(len(hid_reports), len(assert_reports))):
|
||||
@ -121,4 +131,4 @@ class KeyboardTest:
|
||||
|
||||
def do_main_loop(self):
|
||||
self.keyboard._main_loop()
|
||||
time.sleep(0.002)
|
||||
time.sleep(self.loop_delay_ms / 1000)
|
||||
|
@ -9,6 +9,10 @@ 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()
|
||||
@ -26,4 +30,8 @@ def init_circuit_python_modules_mocks():
|
||||
sys.modules['micropython'].const = lambda x: x
|
||||
|
||||
sys.modules['supervisor'] = Mock()
|
||||
sys.modules['supervisor'].ticks_ms = lambda: time.time_ns() // 1_000_000
|
||||
sys.modules['supervisor'].ticks_ms = ticks_ms
|
||||
|
||||
from . import task
|
||||
|
||||
sys.modules['_asyncio'] = task
|
||||
|
196
tests/task.py
Normal file
196
tests/task.py
Normal file
@ -0,0 +1,196 @@
|
||||
# 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
|
@ -10,7 +10,7 @@ from tests.keyboard_test import KeyboardTest
|
||||
class TestCapsWord(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.kb = KeyboardTest(
|
||||
[CapsWord()],
|
||||
[CapsWord(timeout=2 * KeyboardTest.loop_delay_ms)],
|
||||
[
|
||||
[KC.CW, KC.A, KC.Z, KC.N1, KC.N0, KC.SPC],
|
||||
],
|
||||
|
@ -1,28 +1,36 @@
|
||||
import unittest
|
||||
|
||||
from kmk.keys import KC
|
||||
from kmk.modules.combos import Chord, Combos, Sequence
|
||||
from kmk.modules.combos import Chord, Combo, Combos, Sequence
|
||||
from kmk.modules.layers import Layers
|
||||
from 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=80),
|
||||
Chord((KC.C, KC.D), KC.Z, timeout=2 * timeout),
|
||||
Chord((KC.C, KCMO), KC.Z),
|
||||
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),
|
||||
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),
|
||||
]
|
||||
self.keyboard = KeyboardTest(
|
||||
[combos, layers],
|
||||
@ -33,9 +41,6 @@ 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
|
||||
|
@ -1,103 +1,121 @@
|
||||
import unittest
|
||||
|
||||
from kmk.keys import KC
|
||||
from kmk.modules.holdtap import HoldTapRepeat
|
||||
from kmk.modules.holdtap import HoldTap, 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(), ModTap(), OneShot()],
|
||||
[Layers(), HoldTap()],
|
||||
[
|
||||
[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],
|
||||
[
|
||||
KC.HT(KC.A, KC.LCTL),
|
||||
KC.LT(1, KC.B),
|
||||
KC.C,
|
||||
KC.D,
|
||||
],
|
||||
[KC.N1, KC.N2, KC.N3, KC.N4],
|
||||
],
|
||||
debug_enabled=False,
|
||||
)
|
||||
|
||||
keyboard.test('MT tap behaviour', [(0, True), 100, (0, False)], [{KC.A}, {}])
|
||||
keyboard.test(
|
||||
'HT tap behaviour', [(0, True), t_within, (0, False)], [{KC.A}, {}]
|
||||
)
|
||||
|
||||
keyboard.test(
|
||||
'MT hold behaviour', [(0, True), 350, (0, False)], [{KC.LCTL}, {}]
|
||||
'HT hold behaviour', [(0, True), t_after, (0, False)], [{KC.LCTL}, {}]
|
||||
)
|
||||
|
||||
# TODO test multiple mods being held
|
||||
|
||||
# MT
|
||||
# HT
|
||||
keyboard.test(
|
||||
'MT within tap time sequential -> tap behavior',
|
||||
[(0, True), 100, (0, False), (3, True), (3, False)],
|
||||
'HT within tap time sequential -> tap behavior',
|
||||
[(0, True), t_within, (0, False), (3, True), (3, False)],
|
||||
[{KC.A}, {}, {KC.D}, {}],
|
||||
)
|
||||
|
||||
keyboard.test(
|
||||
'MT within tap time rolling -> hold behavior',
|
||||
[(0, True), 100, (3, True), 250, (0, False), (3, False)],
|
||||
'HT within tap time rolling -> hold behavior',
|
||||
[(0, True), t_within, (3, True), t_after, (0, False), (3, False)],
|
||||
[{KC.LCTL}, {KC.LCTL, KC.D}, {KC.D}, {}],
|
||||
)
|
||||
|
||||
keyboard.test(
|
||||
'MT within tap time nested -> hold behavior',
|
||||
[(0, True), 100, (3, True), (3, False), 250, (0, False)],
|
||||
'HT within tap time nested -> hold behavior',
|
||||
[(0, True), t_within, (3, True), (3, False), t_after, (0, False)],
|
||||
[{KC.LCTL}, {KC.LCTL, KC.D}, {KC.LCTL}, {}],
|
||||
)
|
||||
|
||||
keyboard.test(
|
||||
'MT after tap time sequential -> hold behavior',
|
||||
[(0, True), 350, (0, False), (3, True), (3, False)],
|
||||
'HT after tap time sequential -> hold behavior',
|
||||
[(0, True), t_after, (0, False), (3, True), (3, False)],
|
||||
[{KC.LCTL}, {}, {KC.D}, {}],
|
||||
)
|
||||
|
||||
keyboard.test(
|
||||
'MT after tap time rolling -> hold behavior',
|
||||
[(0, True), 350, (3, True), (0, False), (3, False)],
|
||||
'HT after tap time rolling -> hold behavior',
|
||||
[(0, True), t_after, (3, True), (0, False), (3, False)],
|
||||
[{KC.LCTL}, {KC.LCTL, KC.D}, {KC.D}, {}],
|
||||
)
|
||||
|
||||
keyboard.test(
|
||||
'MT after tap time nested -> hold behavior',
|
||||
[(0, True), 350, (3, True), (3, False), (0, False)],
|
||||
'HT after tap time nested -> hold behavior',
|
||||
[(0, True), t_after, (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), 100, (1, False), (3, True), (3, False)],
|
||||
[(1, True), t_within, (1, False), (3, True), (3, False)],
|
||||
[{KC.B}, {}, {KC.D}, {}],
|
||||
)
|
||||
|
||||
keyboard.test(
|
||||
'LT within tap time rolling -> tap behavior',
|
||||
[(1, True), 100, (3, True), 250, (1, False), (3, False)],
|
||||
[(1, True), t_within, (3, True), (1, False), (3, False)],
|
||||
[{KC.B}, {KC.B, KC.D}, {KC.D}, {}],
|
||||
)
|
||||
|
||||
keyboard.test(
|
||||
'LT within tap time nested -> tap behavior',
|
||||
[(1, True), 100, (3, True), (3, False), 250, (1, False)],
|
||||
[(1, True), t_within, (3, True), (3, False), (1, False)],
|
||||
[{KC.B}, {KC.B, KC.D}, {KC.B}, {}],
|
||||
)
|
||||
|
||||
keyboard.test(
|
||||
'LT after tap time sequential -> hold behavior',
|
||||
[(1, True), 350, (1, False), (3, True), (3, False)],
|
||||
[(1, True), t_after, (1, False), (3, True), (3, False)],
|
||||
[{KC.D}, {}],
|
||||
)
|
||||
|
||||
keyboard.test(
|
||||
'LT after tap time rolling -> hold behavior',
|
||||
[(1, True), 350, (3, True), (1, False), (3, False)],
|
||||
[(1, True), t_after, (3, True), (1, False), (3, False)],
|
||||
[{KC.N4}, {}],
|
||||
)
|
||||
|
||||
keyboard.test(
|
||||
'LT after tap time nested -> hold behavior',
|
||||
[(1, True), 350, (3, True), (3, False), (1, False)],
|
||||
[(1, True), t_after, (3, True), (3, False), (1, False)],
|
||||
[{KC.N4}, {}],
|
||||
)
|
||||
|
||||
@ -105,9 +123,9 @@ class TestHoldTap(unittest.TestCase):
|
||||
'LT after tap time nested -> hold behavior',
|
||||
[
|
||||
(0, True),
|
||||
350,
|
||||
t_after,
|
||||
(1, True),
|
||||
350,
|
||||
t_after,
|
||||
(3, True),
|
||||
(3, False),
|
||||
(1, False),
|
||||
@ -117,26 +135,25 @@ class TestHoldTap(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_holdtap_chain(self):
|
||||
t_after = self.t_after
|
||||
|
||||
keyboard = KeyboardTest(
|
||||
[ModTap()],
|
||||
[HoldTap()],
|
||||
[
|
||||
[
|
||||
KC.N0,
|
||||
KC.MT(KC.N1, KC.LCTL, tap_time=50),
|
||||
KC.MT(KC.N2, KC.LSFT, tap_interrupted=True, tap_time=50),
|
||||
KC.MT(
|
||||
KC.HT(KC.N1, KC.LCTL),
|
||||
KC.HT(KC.N2, KC.LSFT, tap_interrupted=True),
|
||||
KC.HT(
|
||||
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',
|
||||
@ -155,7 +172,7 @@ class TestHoldTap(unittest.TestCase):
|
||||
'chained 1',
|
||||
[(2, True), (1, True), (0, True), (0, False), (1, False), (2, False)],
|
||||
[
|
||||
{KC.LCTL},
|
||||
{KC.LSFT},
|
||||
{KC.LCTL, KC.LSFT},
|
||||
{KC.LCTL, KC.LSFT, KC.N0},
|
||||
{KC.LCTL, KC.LSFT},
|
||||
@ -207,7 +224,7 @@ class TestHoldTap(unittest.TestCase):
|
||||
'chained 5',
|
||||
[(3, True), (1, True), (0, True), (0, False), (1, False), (3, False)],
|
||||
[
|
||||
{KC.LCTL},
|
||||
{KC.N3},
|
||||
{KC.LCTL, KC.N3},
|
||||
{KC.LCTL, KC.N3, KC.N0},
|
||||
{KC.LCTL, KC.N3},
|
||||
@ -274,21 +291,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(
|
||||
[ModTap()],
|
||||
[HoldTap()],
|
||||
[
|
||||
[
|
||||
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),
|
||||
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),
|
||||
]
|
||||
],
|
||||
debug_enabled=False,
|
||||
)
|
||||
|
||||
t_within = 40
|
||||
t_after = 60
|
||||
|
||||
keyboard.test(
|
||||
'repeat tap',
|
||||
[
|
||||
@ -300,7 +317,6 @@ class TestHoldTap(unittest.TestCase):
|
||||
(0, False),
|
||||
(0, True),
|
||||
(0, False),
|
||||
t_after,
|
||||
],
|
||||
[{KC.A}, {}, {KC.A}, {}, {KC.A}, {}],
|
||||
)
|
||||
@ -316,7 +332,6 @@ class TestHoldTap(unittest.TestCase):
|
||||
(0, False),
|
||||
(0, True),
|
||||
(0, False),
|
||||
t_after,
|
||||
],
|
||||
[{KC.B}, {}, {KC.B}, {}, {KC.B}, {}],
|
||||
)
|
||||
@ -333,7 +348,6 @@ class TestHoldTap(unittest.TestCase):
|
||||
t_after,
|
||||
(0, True),
|
||||
(0, False),
|
||||
t_after,
|
||||
],
|
||||
[{KC.A}, {}, {KC.B}, {}, {KC.A}, {}],
|
||||
)
|
||||
@ -349,76 +363,3 @@ 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()
|
@ -123,12 +123,10 @@ class TestKeys_dot(unittest.TestCase):
|
||||
assert primary_key is secondary_key
|
||||
|
||||
def test_invalid_key_upper(self):
|
||||
with self.assertRaises(ValueError):
|
||||
KC.INVALID_KEY
|
||||
assert KC.INVALID_KEY == KC.NO
|
||||
|
||||
def test_invalid_key_lower(self):
|
||||
with self.assertRaises(ValueError):
|
||||
KC.invalid_key
|
||||
assert KC.invalid_key == KC.NO
|
||||
|
||||
def test_custom_key(self):
|
||||
created = make_key(
|
||||
@ -168,12 +166,10 @@ class TestKeys_index(unittest.TestCase):
|
||||
assert upper_key is lower_key
|
||||
|
||||
def test_invalid_key_upper(self):
|
||||
with self.assertRaises(ValueError):
|
||||
KC['NOT_A_VALID_KEY']
|
||||
assert KC.INVALID_KEY == KC.NO
|
||||
|
||||
def test_invalid_key_lower(self):
|
||||
with self.assertRaises(ValueError):
|
||||
KC['not_a_valid_key']
|
||||
assert KC.invalid_key == KC.NO
|
||||
|
||||
def test_custom_key(self):
|
||||
created = make_key(
|
||||
@ -218,10 +214,10 @@ class TestKeys_get(unittest.TestCase):
|
||||
assert primary_key is secondary_key
|
||||
|
||||
def test_invalid_key_upper(self):
|
||||
assert KC.get('INVALID_KEY') is None
|
||||
assert KC.get('INVALID_KEY') is KC.NO
|
||||
|
||||
def test_invalid_key_lower(self):
|
||||
assert KC.get('not_a_valid_key') is None
|
||||
assert KC.get('not_a_valid_key') is KC.NO
|
||||
|
||||
def test_custom_key(self):
|
||||
created = make_key(
|
||||
|
@ -10,8 +10,13 @@ class TestLayers(unittest.TestCase):
|
||||
self.kb = KeyboardTest(
|
||||
[Layers()],
|
||||
[
|
||||
[KC.N0, KC.LM(1, KC.LCTL)],
|
||||
[KC.A, KC.B],
|
||||
[
|
||||
KC.N0,
|
||||
KC.LM(1, KC.LCTL),
|
||||
KC.LT(1, KC.N2, tap_interrupted=True, prefer_hold=True),
|
||||
KC.LT(1, KC.N3, tap_interrupted=False, prefer_hold=True),
|
||||
],
|
||||
[KC.A, KC.B, KC.C, KC.D],
|
||||
],
|
||||
debug_enabled=False,
|
||||
)
|
||||
@ -23,6 +28,25 @@ 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()
|
||||
|
152
tests/test_oneshot.py
Normal file
152
tests/test_oneshot.py
Normal file
@ -0,0 +1,152 @@
|
||||
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},
|
||||
{},
|
||||
],
|
||||
)
|
@ -37,11 +37,8 @@ class TestStickyMod(unittest.TestCase):
|
||||
[
|
||||
(4, True),
|
||||
(4, False),
|
||||
100,
|
||||
(4, True),
|
||||
200,
|
||||
(4, False),
|
||||
100,
|
||||
(1, True),
|
||||
(1, False),
|
||||
],
|
||||
@ -61,26 +58,19 @@ 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),
|
||||
|
@ -7,6 +7,7 @@ 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 = {
|
||||
@ -46,21 +47,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), 50],
|
||||
[(0, True), (0, False), (0, True), (0, False), self.delay],
|
||||
[{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), 50],
|
||||
[(1, True), (1, False), self.delay],
|
||||
[{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), 50],
|
||||
[(3, True), (2, True), (2, False), (3, False), self.delay],
|
||||
[{KC.LSHIFT}, {KC.LSHIFT, KC.N2}, {}],
|
||||
)
|
||||
self.keyboard.test(
|
||||
@ -74,7 +75,7 @@ class TestStringSubstitution(unittest.TestCase):
|
||||
(5, False),
|
||||
(5, True),
|
||||
(5, False),
|
||||
10,
|
||||
self.delay,
|
||||
],
|
||||
[
|
||||
{KC.D},
|
||||
@ -93,7 +94,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), 50],
|
||||
[(4, True), (0, True), (0, False), (0, True), (0, False), (4, False)],
|
||||
[
|
||||
{KC.LCTRL},
|
||||
{KC.LCTRL, KC.A},
|
||||
@ -105,7 +106,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), 50],
|
||||
[(4, True), (1, True), (1, False), (4, False)],
|
||||
[
|
||||
{KC.LCTRL},
|
||||
{KC.LCTRL, KC.B},
|
||||
@ -115,7 +116,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), 50],
|
||||
[(0, True), (0, False), (4, True), (0, True), (0, False), (4, False)],
|
||||
[
|
||||
{KC.A},
|
||||
{},
|
||||
@ -128,7 +129,15 @@ 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), 50],
|
||||
[
|
||||
(5, True),
|
||||
(5, False),
|
||||
(0, True),
|
||||
(0, False),
|
||||
(0, True),
|
||||
(0, False),
|
||||
self.delay,
|
||||
],
|
||||
[
|
||||
{KC.C},
|
||||
{},
|
||||
@ -142,7 +151,15 @@ 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), 50],
|
||||
[
|
||||
(0, True),
|
||||
(0, False),
|
||||
(0, True),
|
||||
(0, False),
|
||||
(5, True),
|
||||
(5, False),
|
||||
self.delay,
|
||||
],
|
||||
[
|
||||
{KC.A},
|
||||
{},
|
||||
@ -156,7 +173,7 @@ class TestStringSubstitution(unittest.TestCase):
|
||||
)
|
||||
self.keyboard.test(
|
||||
'no match',
|
||||
[(0, True), (0, False), (2, True), (2, False), 50],
|
||||
[(0, True), (0, False), (2, True), (2, False)],
|
||||
[
|
||||
{KC.A},
|
||||
{},
|
||||
@ -183,7 +200,7 @@ class TestStringSubstitution(unittest.TestCase):
|
||||
(6, False),
|
||||
(0, True),
|
||||
(0, False),
|
||||
50,
|
||||
10 * self.delay,
|
||||
],
|
||||
[
|
||||
{KC.D},
|
||||
@ -241,7 +258,7 @@ class TestStringSubstitution(unittest.TestCase):
|
||||
# send the unreachable match "cccc" after matching "ccc"
|
||||
(5, True),
|
||||
(5, False),
|
||||
10,
|
||||
self.delay,
|
||||
],
|
||||
[
|
||||
{KC.C},
|
||||
@ -272,7 +289,7 @@ class TestStringSubstitution(unittest.TestCase):
|
||||
(0, True),
|
||||
(0, False),
|
||||
(7, False),
|
||||
10,
|
||||
self.delay,
|
||||
],
|
||||
[
|
||||
{KC.RSHIFT},
|
||||
@ -303,7 +320,6 @@ class TestStringSubstitution(unittest.TestCase):
|
||||
(0, False),
|
||||
(4, False),
|
||||
(8, False),
|
||||
10,
|
||||
],
|
||||
[
|
||||
{KC.RALT},
|
||||
@ -325,7 +341,6 @@ class TestStringSubstitution(unittest.TestCase):
|
||||
(1, False),
|
||||
(3, False),
|
||||
(8, False),
|
||||
10,
|
||||
],
|
||||
[
|
||||
{KC.RALT},
|
||||
|
@ -9,36 +9,39 @@ 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, tap_time=50),
|
||||
KC.TD(KC.N0, KC.N1),
|
||||
KC.TD(
|
||||
KC.HT(KC.N1, KC.A, tap_time=50),
|
||||
KC.HT(KC.N2, KC.B, tap_time=100),
|
||||
KC.HT(KC.N1, KC.A),
|
||||
KC.HT(KC.N2, KC.B, tap_time=2 * tap_time),
|
||||
),
|
||||
KC.TD(KC.HT(KC.X, KC.Y, tap_time=50), KC.X, tap_time=0),
|
||||
KC.TD(KC.LT(1, KC.N3, tap_time=50), KC.X, tap_time=0),
|
||||
KC.TD(KC.HT(KC.X, KC.Y), KC.X, tap_time=0),
|
||||
KC.TD(KC.LT(1, KC.N3), 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), t_after], [{KC.N0}, {}])
|
||||
keyboard.test('Tap x1', [(0, True), (0, False)], [{KC.N0}, {}])
|
||||
|
||||
keyboard.test(
|
||||
'Tap x2',
|
||||
[(0, True), (0, False), t_within, (0, True), (0, False), t_after],
|
||||
[(0, True), (0, False), t_within, (0, True), (0, False)],
|
||||
[{KC.N1}, {}],
|
||||
)
|
||||
|
||||
@ -51,7 +54,6 @@ class TestTapDance(unittest.TestCase):
|
||||
(0, False),
|
||||
(0, True),
|
||||
(0, False),
|
||||
t_after,
|
||||
],
|
||||
[{KC.N1}, {}, {KC.N0}, {}],
|
||||
)
|
||||
@ -88,16 +90,16 @@ class TestTapDance(unittest.TestCase):
|
||||
[{KC.N1}, {KC.N1, KC.N4}, {KC.N4}, {}],
|
||||
)
|
||||
|
||||
def test_modtap(self):
|
||||
def test_holdtap(self):
|
||||
keyboard = self.keyboard
|
||||
t_within = self.t_within
|
||||
t_after = self.t_after
|
||||
|
||||
keyboard.test('Tap x1', [(1, True), (1, False), t_after], [{KC.N1}, {}])
|
||||
keyboard.test('Tap x1', [(1, True), (1, False)], [{KC.N1}, {}])
|
||||
|
||||
keyboard.test(
|
||||
'Tap x2',
|
||||
[(1, True), (1, False), t_within, (1, True), (1, False), 2 * t_after],
|
||||
[(1, True), (1, False), t_within, (1, True), (1, False)],
|
||||
[{KC.N2}, {}],
|
||||
)
|
||||
|
||||
@ -131,7 +133,7 @@ class TestTapDance(unittest.TestCase):
|
||||
|
||||
keyboard.test(
|
||||
'',
|
||||
[(0, True), (0, False), t_within, (1, True), (1, False), t_after],
|
||||
[(0, True), (0, False), t_within, (1, True), (1, False)],
|
||||
[{KC.N0}, {}, {KC.N1}, {}],
|
||||
)
|
||||
|
||||
@ -145,7 +147,6 @@ class TestTapDance(unittest.TestCase):
|
||||
(2, False),
|
||||
t_after,
|
||||
(0, False),
|
||||
t_after,
|
||||
],
|
||||
[{KC.N1}, {KC.N1, KC.X}, {KC.N1}, {}],
|
||||
)
|
||||
@ -160,7 +161,6 @@ 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), t_after],
|
||||
[(3, True), (3, False), t_within, (1, True), (1, False)],
|
||||
[{KC.N3}, {}, {KC.N1}, {}],
|
||||
)
|
||||
|
||||
@ -199,12 +199,12 @@ class TestTapDance(unittest.TestCase):
|
||||
[{KC.A}, {}, {KC.N5}, {}],
|
||||
)
|
||||
|
||||
def test_modtap_repeat(self):
|
||||
def test_holdtap_repeat(self):
|
||||
keyboard = self.keyboard
|
||||
t_after = self.t_after
|
||||
|
||||
keyboard.test(
|
||||
'ModTap repeat',
|
||||
'HoldTap repeat',
|
||||
[
|
||||
(2, True),
|
||||
(2, False),
|
||||
|
@ -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()
|
||||
modtap = ModTap()
|
||||
holdtap = HoldTap()
|
||||
|
||||
keyboard.modules = [layers_ext, modtap]
|
||||
keyboard.modules = [layers_ext, holdtap]
|
||||
keyboard.extensions = [rgb_ext]
|
||||
|
||||
_______ = KC.TRNS
|
||||
XXXXXXX = KC.NO
|
||||
HOME = KC.MT(KC.HOME, KC.LSFT)
|
||||
END = KC.MT(KC.END, KC.RSFT)
|
||||
HOME = KC.HT(KC.HOME, KC.LSFT)
|
||||
END = KC.HT(KC.END, KC.RSFT)
|
||||
LEFT_LAY = KC.LT(FN1, KC.LEFT)
|
||||
SHFT_INS = KC.LSFT(KC.INS)
|
||||
SPC = KC.LT(FN1, KC.SPC)
|
||||
|
@ -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()
|
||||
modtap = ModTap()
|
||||
holdtap = HoldTap()
|
||||
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 = [modtap, layers, split]
|
||||
keyboard.modules = [holdtap, layers, split]
|
||||
keyboard.extensions = [rgb_ext]
|
||||
|
||||
_______ = KC.TRNS
|
||||
|
@ -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.MT(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.HT(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,
|
||||
],
|
||||
[
|
||||
|
Loading…
Reference in New Issue
Block a user