From da622df01f3dac1cb6259593995f32c65d2ef9f5 Mon Sep 17 00:00:00 2001 From: Christoph Cullmann Date: Tue, 27 May 2025 23:58:44 +0200 Subject: [PATCH] tap flow is now in QMK --- modules/getreuer/.gitignore | 3 - modules/getreuer/CODE_OF_CONDUCT.md | 94 ---- modules/getreuer/CONTRIBUTING.md | 30 -- modules/getreuer/LICENSE.txt | 202 ------- modules/getreuer/README.md | 103 ---- modules/getreuer/achordion/README.md | 31 -- modules/getreuer/achordion/achordion.c | 380 ------------- modules/getreuer/achordion/achordion.h | 167 ------ modules/getreuer/achordion/introspection.h | 17 - modules/getreuer/achordion/qmk_module.json | 5 - modules/getreuer/custom_shift_keys/README.md | 45 -- .../custom_shift_keys/custom_shift_keys.c | 94 ---- .../custom_shift_keys/custom_shift_keys.h | 62 --- .../custom_shift_keys/introspection.c | 37 -- .../custom_shift_keys/introspection.h | 17 - .../custom_shift_keys/qmk_module.json | 5 - modules/getreuer/doc/banner.jpg | Bin 47038 -> 0 bytes modules/getreuer/doc/voyager.jpg | Bin 63788 -> 0 bytes modules/getreuer/keycode_string/README.md | 40 -- .../getreuer/keycode_string/introspection.h | 18 - .../getreuer/keycode_string/keycode_string.c | 510 ------------------ .../getreuer/keycode_string/keycode_string.h | 134 ----- .../getreuer/keycode_string/qmk_module.json | 5 - modules/getreuer/mouse_turbo_click/README.md | 37 -- .../mouse_turbo_click/mouse_turbo_click.c | 121 ----- .../mouse_turbo_click/qmk_module.json | 15 - modules/getreuer/orbital_mouse/README.md | 56 -- .../getreuer/orbital_mouse/introspection.h | 17 - .../getreuer/orbital_mouse/orbital_mouse.c | 364 ------------- .../getreuer/orbital_mouse/orbital_mouse.h | 93 ---- .../getreuer/orbital_mouse/qmk_module.json | 23 - modules/getreuer/palettefx/README.md | 55 -- modules/getreuer/palettefx/introspection.h | 17 - modules/getreuer/palettefx/palettefx.c | 493 ----------------- modules/getreuer/palettefx/palettefx.h | 150 ------ modules/getreuer/palettefx/palettefx.inc | 345 ------------ .../palettefx/palettefx_default_config.h | 48 -- modules/getreuer/palettefx/qmk_module.json | 8 - modules/getreuer/palettefx/rules.mk | 15 - modules/getreuer/select_word/README.md | 68 --- modules/getreuer/select_word/introspection.h | 17 - modules/getreuer/select_word/qmk_module.json | 25 - modules/getreuer/select_word/select_word.c | 301 ----------- modules/getreuer/select_word/select_word.h | 91 ---- modules/getreuer/sentence_case/README.md | 60 --- .../getreuer/sentence_case/introspection.h | 17 - .../getreuer/sentence_case/qmk_module.json | 10 - .../getreuer/sentence_case/sentence_case.c | 380 ------------- .../getreuer/sentence_case/sentence_case.h | 206 ------- modules/getreuer/socd_cleaner/README.md | 79 --- modules/getreuer/socd_cleaner/introspection.c | 36 -- modules/getreuer/socd_cleaner/introspection.h | 17 - modules/getreuer/socd_cleaner/qmk_module.json | 10 - modules/getreuer/socd_cleaner/socd_cleaner.c | 116 ---- modules/getreuer/socd_cleaner/socd_cleaner.h | 83 --- modules/getreuer/tap_flow/README.md | 59 -- modules/getreuer/tap_flow/introspection.h | 17 - modules/getreuer/tap_flow/qmk_module.json | 25 - modules/getreuer/tap_flow/tap_flow.c | 216 -------- modules/getreuer/tap_flow/tap_flow.h | 124 ----- update.sh | 5 - 61 files changed, 5818 deletions(-) delete mode 100644 modules/getreuer/.gitignore delete mode 100644 modules/getreuer/CODE_OF_CONDUCT.md delete mode 100644 modules/getreuer/CONTRIBUTING.md delete mode 100644 modules/getreuer/LICENSE.txt delete mode 100644 modules/getreuer/README.md delete mode 100644 modules/getreuer/achordion/README.md delete mode 100644 modules/getreuer/achordion/achordion.c delete mode 100644 modules/getreuer/achordion/achordion.h delete mode 100644 modules/getreuer/achordion/introspection.h delete mode 100644 modules/getreuer/achordion/qmk_module.json delete mode 100644 modules/getreuer/custom_shift_keys/README.md delete mode 100644 modules/getreuer/custom_shift_keys/custom_shift_keys.c delete mode 100644 modules/getreuer/custom_shift_keys/custom_shift_keys.h delete mode 100644 modules/getreuer/custom_shift_keys/introspection.c delete mode 100644 modules/getreuer/custom_shift_keys/introspection.h delete mode 100644 modules/getreuer/custom_shift_keys/qmk_module.json delete mode 100644 modules/getreuer/doc/banner.jpg delete mode 100644 modules/getreuer/doc/voyager.jpg delete mode 100644 modules/getreuer/keycode_string/README.md delete mode 100644 modules/getreuer/keycode_string/introspection.h delete mode 100644 modules/getreuer/keycode_string/keycode_string.c delete mode 100644 modules/getreuer/keycode_string/keycode_string.h delete mode 100644 modules/getreuer/keycode_string/qmk_module.json delete mode 100644 modules/getreuer/mouse_turbo_click/README.md delete mode 100644 modules/getreuer/mouse_turbo_click/mouse_turbo_click.c delete mode 100644 modules/getreuer/mouse_turbo_click/qmk_module.json delete mode 100644 modules/getreuer/orbital_mouse/README.md delete mode 100644 modules/getreuer/orbital_mouse/introspection.h delete mode 100644 modules/getreuer/orbital_mouse/orbital_mouse.c delete mode 100644 modules/getreuer/orbital_mouse/orbital_mouse.h delete mode 100644 modules/getreuer/orbital_mouse/qmk_module.json delete mode 100644 modules/getreuer/palettefx/README.md delete mode 100644 modules/getreuer/palettefx/introspection.h delete mode 100644 modules/getreuer/palettefx/palettefx.c delete mode 100644 modules/getreuer/palettefx/palettefx.h delete mode 100644 modules/getreuer/palettefx/palettefx.inc delete mode 100644 modules/getreuer/palettefx/palettefx_default_config.h delete mode 100644 modules/getreuer/palettefx/qmk_module.json delete mode 100644 modules/getreuer/palettefx/rules.mk delete mode 100644 modules/getreuer/select_word/README.md delete mode 100644 modules/getreuer/select_word/introspection.h delete mode 100644 modules/getreuer/select_word/qmk_module.json delete mode 100644 modules/getreuer/select_word/select_word.c delete mode 100644 modules/getreuer/select_word/select_word.h delete mode 100644 modules/getreuer/sentence_case/README.md delete mode 100644 modules/getreuer/sentence_case/introspection.h delete mode 100644 modules/getreuer/sentence_case/qmk_module.json delete mode 100644 modules/getreuer/sentence_case/sentence_case.c delete mode 100644 modules/getreuer/sentence_case/sentence_case.h delete mode 100644 modules/getreuer/socd_cleaner/README.md delete mode 100644 modules/getreuer/socd_cleaner/introspection.c delete mode 100644 modules/getreuer/socd_cleaner/introspection.h delete mode 100644 modules/getreuer/socd_cleaner/qmk_module.json delete mode 100644 modules/getreuer/socd_cleaner/socd_cleaner.c delete mode 100644 modules/getreuer/socd_cleaner/socd_cleaner.h delete mode 100644 modules/getreuer/tap_flow/README.md delete mode 100644 modules/getreuer/tap_flow/introspection.h delete mode 100644 modules/getreuer/tap_flow/qmk_module.json delete mode 100644 modules/getreuer/tap_flow/tap_flow.c delete mode 100644 modules/getreuer/tap_flow/tap_flow.h delete mode 100755 update.sh diff --git a/modules/getreuer/.gitignore b/modules/getreuer/.gitignore deleted file mode 100644 index 344bdc0..0000000 --- a/modules/getreuer/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.bin -*.hex - diff --git a/modules/getreuer/CODE_OF_CONDUCT.md b/modules/getreuer/CODE_OF_CONDUCT.md deleted file mode 100644 index 0bd8761..0000000 --- a/modules/getreuer/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,94 +0,0 @@ -# Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of -experience, education, socio-economic status, nationality, personal appearance, -race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, or to ban temporarily or permanently any -contributor for other behaviors that they deem inappropriate, threatening, -offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -This Code of Conduct also applies outside the project spaces when the Project -Steward has a reasonable belief that an individual's behavior may have a -negative impact on the project or its community. - -## Conflict Resolution - -We do not believe that all conflict is bad; healthy debate and disagreement -often yield positive results. However, it is never okay to be disrespectful or -to engage in behavior that violates the project’s code of conduct. - -If you see someone violating the code of conduct, you are encouraged to address -the behavior directly with those involved. Many issues can be resolved quickly -and easily, and this gives people more control over the outcome of their -dispute. If you are unable to resolve the matter for any reason, or if the -behavior is threatening or harassing, report it. We are dedicated to providing -an environment where participants feel welcome and safe. - -Reports should be directed to *[PROJECT STEWARD NAME(s) AND EMAIL(s)]*, the -Project Steward(s) for *[PROJECT NAME]*. It is the Project Steward’s duty to -receive and address reported violations of the code of conduct. They will then -work with a committee consisting of representatives from the Open Source -Programs Office and the Google Open Source Strategy team. If for any reason you -are uncomfortable reaching out to the Project Steward, please email -opensource@google.com. - -We will investigate every complaint, but you may not receive a direct response. -We will use our discretion in determining when and how to follow up on reported -incidents, which may range from not taking action to permanent expulsion from -the project and project-sponsored spaces. We will notify the accused of the -report and provide them an opportunity to discuss it before any action is taken. -The identity of the reporter will be omitted from the details of the report -supplied to the accused. In potentially harmful situations, such as ongoing -harassment or threats to anyone's safety, we may take action without notice. - -## Attribution - -This Code of Conduct is adapted from the Contributor Covenant, version 1.4, -available at -https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - diff --git a/modules/getreuer/CONTRIBUTING.md b/modules/getreuer/CONTRIBUTING.md deleted file mode 100644 index 1d5ee2d..0000000 --- a/modules/getreuer/CONTRIBUTING.md +++ /dev/null @@ -1,30 +0,0 @@ -# How to Contribute - -We'd love to accept your patches and contributions to this project. There are -just a few small guidelines you need to follow. - -## Contributor License Agreement - -Contributions to this project must be accompanied by a Contributor License -Agreement (CLA). You (or your employer) retain the copyright to your -contribution; this simply gives us permission to use and redistribute your -contributions as part of the project. Head over to - to see your current agreements on file or -to sign a new one. - -You generally only need to submit a CLA once, so if you've already submitted one -(even if it was for a different project), you probably don't need to do it -again. - -## Code Reviews - -All submissions, including submissions by project members, require review. We -use GitHub pull requests for this purpose. Consult -[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more -information on using pull requests. - -## Community Guidelines - -This project follows -[Google's Open Source Community Guidelines](https://opensource.google/conduct/). - diff --git a/modules/getreuer/LICENSE.txt b/modules/getreuer/LICENSE.txt deleted file mode 100644 index d645695..0000000 --- a/modules/getreuer/LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/modules/getreuer/README.md b/modules/getreuer/README.md deleted file mode 100644 index e8d17e3..0000000 --- a/modules/getreuer/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# @getreuer's QMK community modules - -(This is not an officially supported Google product.) - -![](doc/banner.jpg) - -| Module | Description | -|-------------------------------------------|-------------------------------------------------------| -| [Achordion](./achordion/) | Customize the tap-hold decision. | -| [Custom Shift Keys](./custom_shift_keys/) | Customize what keycode is produced when shifted. | -| [Keycode String](./keycode_string/) | Format QMK keycodes as human-readable strings. | -| [Mouse Turbo Click](./mouse_turbo_click/) | Click the mouse rapidly. | -| [Orbital Mouse](./orbital_mouse/) | A polar approach to mouse key control. | -| [PaletteFx](./palettefx/) | Palette-based animated RGB matrix lighting effects. | -| [Select Word](./select_word/) | Convenient word and line selection. | -| [Sentence Case](./sentence_case/) | Automatically capitalize sentences. | -| [SOCD Cleaner](./socd_cleaner/) | SOCD filtering for fast gaming inputs. | -| [Tap Flow](./tap_flow/) | Disable HRMs during fast typing (Global Quick Tap). | - - -## What is this? - -This repo contains my community modules for [Quantum Mechanical Keyboard -(QMK)](https://docs.qmk.fm) firmware, used on custom keyboards like the ZSA -Voyager pictured above. I use most of these modules myself in [my QMK -keymap](https://github.com/getreuer/qmk-keymap). - - -## License - -This repo uses the Apache License 2.0 except where otherwise indicated. See the -[LICENSE file](LICENSE.txt) for details. - - -## How to install - -This repo makes use of [Community -Modules](https://getreuer.info/posts/keyboards/qmk-community-modules/index.html) -support added in QMK Firmware 0.28.0, released on 2025-02-27. [Update your QMK -set -up](https://docs.qmk.fm/newbs_git_using_your_master_branch#updating-your-master-branch) -to get the latest. If you have it, there will be a `modules` folder inside your -`qmk_firmware` folder. - -**Step 1. Download modules.** Run these shell commands to download the -modules, replacing `/path/to/qmk_firmware` with the path of your -"`qmk_firmware`" folder: - -```sh -cd /path/to/qmk_firmware -mkdir -p modules -git submodule add https://github.com/getreuer/qmk-modules.git modules/getreuer -git submodule update --init --recursive -``` - -Or if using [External -Userspace](https://docs.qmk.fm/newbs_external_userspace), replace the first -line with `cd /path/to/your/external/userspace`. - -Or if you don't want to use git, [download a .zip of this -repo](https://github.com/getreuer/qmk-modules/archive/refs/heads/main.zip) into -the `modules` folder. Unzip it, then rename the resulting `qmk-modules-main` -folder to `getreuer`. - -In any case, the installed directory structure is like this: - - - └── modules - └── getreuer - ├── achordion - ├── custom_shift_keys - ├── keycode_string -    └── ... - -**Step 2. Add modules to keymap.json.** Add a module to your keymap by writing a -file `keymap.json` in your keymap folder with the content - -```json -{ - "modules": ["getreuer/tap_flow"] -} -``` - -Or if a `keymap.json` already exists, merge the `"modules"` line into it. Add -multiple modules like: - -```json -{ - "modules": ["getreuer/tap_flow", "getreuer/sentence_case"] -} -``` - -Follow the modules' documentation for any further specific set up. - -**Step 3. Update the firmware.** Compile and flash the firmware as usual. If -there are build errors, try running `qmk clean` and compiling again for a clean -build. - - -## How to uninstall - -Remove the modules from `keymap.json` and delete the `modules/getreuer` folder. - diff --git a/modules/getreuer/achordion/README.md b/modules/getreuer/achordion/README.md deleted file mode 100644 index 18ee43c..0000000 --- a/modules/getreuer/achordion/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Achordion - - - - - - - -
Modulegetreuer/achordion
Version2025-03-07
MaintainerPascal Getreuer (@getreuer)
LicenseApache 2.0
Documentation -https://getreuer.info/posts/keyboards/achordion -
- -This is a community module adaptation of -[Achordion](https://getreuer.info/posts/keyboards/achordion), a customizable -"opposite hands" rule implementation for tap-hold keys. Achordion is the -predecessor of QMK core feature [Chordal -Hold](https://docs.qmk.fm/tap_hold#chordal-hold). - -Add the following to your `keymap.json` to use Achordion: - -```json -{ - "modules": ["getreuer/achordion"] -} -``` - -Optionally, Achordion can be customized through several callbacks and config -options. See the [Achordion -documentation](https://getreuer.info/posts/keyboards/achordion) for how to do -that and further details. - diff --git a/modules/getreuer/achordion/achordion.c b/modules/getreuer/achordion/achordion.c deleted file mode 100644 index 9aad8b2..0000000 --- a/modules/getreuer/achordion/achordion.c +++ /dev/null @@ -1,380 +0,0 @@ -// Copyright 2022-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file achordion.c - * @brief Achordion community module implementation - * - * For full documentation, see - * - */ - -#include "achordion.h" - -#pragma message \ - "Achordion has evolved into core QMK feature Chordal Hold! To use it, update your QMK set up and see https://docs.qmk.fm/tap_hold#chordal-hold" - -ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(1, 0, 0); - -// Copy of the `record` and `keycode` args for the current active tap-hold key. -static keyrecord_t tap_hold_record; -static uint16_t tap_hold_keycode = KC_NO; -// Timeout timer. When it expires, the key is considered held. -static uint16_t hold_timer = 0; -// Eagerly applied mods, if any. -static uint8_t eager_mods = 0; -// Flag to determine whether another key is pressed within the timeout. -static bool pressed_another_key_before_release = false; - -#ifdef ACHORDION_STREAK -// Timer for typing streak -static uint16_t streak_timer = 0; -#else -// When disabled, is_streak is never true -#define is_streak false -#endif - -// Achordion's current state. -enum { - // A tap-hold key is pressed, but hasn't yet been settled as tapped or held. - STATE_UNSETTLED, - // Achordion is inactive. - STATE_RELEASED, - // Active tap-hold key has been settled as tapped. - STATE_TAPPING, - // Active tap-hold key has been settled as held. - STATE_HOLDING, - // This state is set while calling `process_record()`, which will recursively - // call `process_achordion()`. This state is checked so that we don't process - // events generated by Achordion and potentially create an infinite loop. - STATE_RECURSING, -}; -static uint8_t achordion_state = STATE_RELEASED; - -#ifdef ACHORDION_STREAK -static void update_streak_timer(uint16_t keycode, keyrecord_t* record) { - if (achordion_streak_continue(keycode)) { - // We use 0 to represent an unset timer, so `| 1` to force a nonzero value. - streak_timer = record->event.time | 1; - } else { - streak_timer = 0; - } -} -#endif - -// Presses or releases eager_mods through process_action(), which skips the -// usual event handling pipeline. The action is considered as a mod-tap hold or -// release, with Retro Tapping if enabled. -static void process_eager_mods_action(void) { - action_t action; - action.code = ACTION_MODS_TAP_KEY( - eager_mods, QK_MOD_TAP_GET_TAP_KEYCODE(tap_hold_keycode)); - process_action(&tap_hold_record, action); -} - -// Calls `process_record()` with state set to RECURSING. -static void recursively_process_record(keyrecord_t* record, uint8_t state) { - achordion_state = STATE_RECURSING; -#if defined(POINTING_DEVICE_ENABLE) && defined(POINTING_DEVICE_AUTO_MOUSE_ENABLE) - int8_t mouse_key_tracker = get_auto_mouse_key_tracker(); -#endif - process_record(record); -#if defined(POINTING_DEVICE_ENABLE) && defined(POINTING_DEVICE_AUTO_MOUSE_ENABLE) - set_auto_mouse_key_tracker(mouse_key_tracker); -#endif - achordion_state = state; -} - -// Sends hold press event and settles the active tap-hold key as held. -static void settle_as_hold(void) { - if (eager_mods) { - // If eager mods are being applied, nothing needs to be done besides - // updating the state. - dprintln("Achordion: Settled eager mod as hold."); - achordion_state = STATE_HOLDING; - } else { - // Create hold press event. - dprintln("Achordion: Plumbing hold press."); - recursively_process_record(&tap_hold_record, STATE_HOLDING); - } -} - -// Sends tap press and release and settles the active tap-hold key as tapped. -static void settle_as_tap(void) { - if (eager_mods) { // Clear eager mods if set. -#if defined(RETRO_TAPPING) || defined(RETRO_TAPPING_PER_KEY) -#ifdef DUMMY_MOD_NEUTRALIZER_KEYCODE - neutralize_flashing_modifiers(get_mods()); -#endif // DUMMY_MOD_NEUTRALIZER_KEYCODE -#endif // defined(RETRO_TAPPING) || defined(RETRO_TAPPING_PER_KEY) - tap_hold_record.event.pressed = false; - // To avoid falsely triggering Retro Tapping, process eager mods release as - // a regular mods release rather than a mod-tap release. - action_t action; - action.code = ACTION_MODS(eager_mods); - process_action(&tap_hold_record, action); - eager_mods = 0; - } - - dprintln("Achordion: Plumbing tap press."); - tap_hold_record.event.pressed = true; - tap_hold_record.tap.count = 1; // Revise event as a tap. - tap_hold_record.tap.interrupted = true; - // Plumb tap press event. - recursively_process_record(&tap_hold_record, STATE_TAPPING); - - send_keyboard_report(); -#if TAP_CODE_DELAY > 0 - wait_ms(TAP_CODE_DELAY); -#endif // TAP_CODE_DELAY > 0 - - dprintln("Achordion: Plumbing tap release."); - tap_hold_record.event.pressed = false; - // Plumb tap release event. - recursively_process_record(&tap_hold_record, STATE_TAPPING); -} - -bool process_record_achordion(uint16_t keycode, keyrecord_t* record) { - // Don't process events that Achordion generated. - if (achordion_state == STATE_RECURSING) { - return true; - } - - // Determine whether the current event is for a mod-tap or layer-tap key. - const bool is_mt = IS_QK_MOD_TAP(keycode); - const bool is_tap_hold = is_mt || IS_QK_LAYER_TAP(keycode); - // Check that this is a normal key event, don't act on combos. - const bool is_key_event = IS_KEYEVENT(record->event); - - // Event while no tap-hold key is active. - if (achordion_state == STATE_RELEASED) { - if (is_tap_hold && record->tap.count == 0 && record->event.pressed && - is_key_event) { - // A tap-hold key is pressed and considered by QMK as "held". - const uint16_t timeout = achordion_timeout(keycode); - if (timeout > 0) { - achordion_state = STATE_UNSETTLED; - // Save info about this key. - tap_hold_keycode = keycode; - tap_hold_record = *record; - hold_timer = record->event.time + timeout; - pressed_another_key_before_release = false; - eager_mods = 0; - - if (is_mt) { // Apply mods immediately if they are "eager." - const uint8_t mod = mod_config(QK_MOD_TAP_GET_MODS(keycode)); - if ( -#if defined(CAPS_WORD_ENABLE) - // Since eager mods bypass normal event handling, Caps Word does - // not work as expected with eager Shift. So we don't apply Shift - // eagerly while Caps Word is on. - !(is_caps_word_on() && (mod & MOD_LSFT) != 0) && -#endif // defined(CAPS_WORD_ENABLE) - achordion_eager_mod(mod)) { - eager_mods = mod; - process_eager_mods_action(); - } - } - - dprintf("Achordion: Key 0x%04X pressed.%s\n", keycode, - eager_mods ? " Set eager mods." : ""); - return false; // Skip default handling. - } - } - -#ifdef ACHORDION_STREAK - update_streak_timer(keycode, record); -#endif - return true; // Otherwise, continue with default handling. - } else if (record->event.pressed && tap_hold_keycode != keycode) { - // Track whether another key was pressed while using a tap-hold key. - pressed_another_key_before_release = true; - } - - // Release of the active tap-hold key. - if (keycode == tap_hold_keycode && !record->event.pressed) { - if (eager_mods) { - dprintln("Achordion: Key released. Clearing eager mods."); - tap_hold_record.event.pressed = false; - process_eager_mods_action(); - } else if (achordion_state == STATE_HOLDING) { - dprintln("Achordion: Key released. Plumbing hold release."); - tap_hold_record.event.pressed = false; - // Plumb hold release event. - recursively_process_record(&tap_hold_record, STATE_RELEASED); - } else if (!pressed_another_key_before_release) { - // No other key was pressed between the press and release of the tap-hold - // key, plumb a hold press and then a release. - dprintln("Achordion: Key released. Plumbing hold press and release."); - recursively_process_record(&tap_hold_record, STATE_HOLDING); - tap_hold_record.event.pressed = false; - recursively_process_record(&tap_hold_record, STATE_RELEASED); - } else { - dprintln("Achordion: Key released."); - } - - achordion_state = STATE_RELEASED; - tap_hold_keycode = KC_NO; - return false; - } - - if (achordion_state == STATE_UNSETTLED && record->event.pressed) { -#ifdef ACHORDION_STREAK - const uint16_t s_timeout = - achordion_streak_chord_timeout(tap_hold_keycode, keycode); - const bool is_streak = - streak_timer && s_timeout && - !timer_expired(record->event.time, (streak_timer + s_timeout)); -#endif - - // Press event occurred on a key other than the active tap-hold key. - - // If the other key is *also* a tap-hold key and considered by QMK to be - // held, then we settle the active key as held. This way, things like - // chording multiple home row modifiers will work, but let's our logic - // consider simply a single tap-hold key as "active" at a time. - // - // Otherwise, we call `achordion_chord()` to determine whether to settle the - // tap-hold key as tapped vs. held. We implement the tap or hold by plumbing - // events back into the handling pipeline so that QMK features and other - // user code can see them. This is done by calling `process_record()`, which - // in turn calls most handlers including `process_record_user()`. - if (!is_streak && - (!is_key_event || (is_tap_hold && record->tap.count == 0) || - achordion_chord(tap_hold_keycode, &tap_hold_record, keycode, - record))) { - settle_as_hold(); - -#ifdef REPEAT_KEY_ENABLE - // Edge case involving LT + Repeat Key: in a sequence of "LT down, other - // down" where "other" is on the other layer in the same position as - // Repeat or Alternate Repeat, the repeated keycode is set instead of the - // the one on the switched-to layer. Here we correct that. - if (get_repeat_key_count() != 0 && IS_QK_LAYER_TAP(tap_hold_keycode)) { - record->keycode = KC_NO; // Forget the repeated keycode. - clear_weak_mods(); - } -#endif // REPEAT_KEY_ENABLE - } else { - settle_as_tap(); - -#ifdef ACHORDION_STREAK - update_streak_timer(keycode, record); - if (is_streak && is_key_event && is_tap_hold && record->tap.count == 0) { - // If we are in a streak and resolved the current tap-hold key as a tap - // consider the next tap-hold key as active to be resolved next. - update_streak_timer(tap_hold_keycode, &tap_hold_record); - const uint16_t timeout = achordion_timeout(keycode); - tap_hold_keycode = keycode; - tap_hold_record = *record; - hold_timer = record->event.time + timeout; - achordion_state = STATE_UNSETTLED; - pressed_another_key_before_release = false; - return false; - } -#endif - } - - recursively_process_record(record, achordion_state); // Re-process event. - return false; // Block the original event. - } - -#ifdef ACHORDION_STREAK - // update idle timer on regular keys event - update_streak_timer(keycode, record); -#endif - return true; -} - -void housekeeping_task_achordion(void) { - if (achordion_state == STATE_UNSETTLED && - timer_expired(timer_read(), hold_timer)) { - settle_as_hold(); // Timeout expired, settle the key as held. - } - -#ifdef ACHORDION_STREAK -#define MAX_STREAK_TIMEOUT 800 - if (streak_timer && - timer_expired(timer_read(), (streak_timer + MAX_STREAK_TIMEOUT))) { - streak_timer = 0; // Expired. - } -#endif -} - -// Returns true if `pos` on the left hand of the keyboard, false if right. -static bool on_left_hand(keypos_t pos) { -#ifdef SPLIT_KEYBOARD - return pos.row < MATRIX_ROWS / 2; -#else - return (MATRIX_COLS > MATRIX_ROWS) ? pos.col < MATRIX_COLS / 2 - : pos.row < MATRIX_ROWS / 2; -#endif -} - -bool achordion_opposite_hands(const keyrecord_t* tap_hold_record, - const keyrecord_t* other_record) { - return on_left_hand(tap_hold_record->event.key) != - on_left_hand(other_record->event.key); -} - -// By default, use the BILATERAL_COMBINATIONS rule to consider the tap-hold key -// "held" only when it and the other key are on opposite hands. -__attribute__((weak)) bool achordion_chord(uint16_t tap_hold_keycode, - keyrecord_t* tap_hold_record, - uint16_t other_keycode, - keyrecord_t* other_record) { - return achordion_opposite_hands(tap_hold_record, other_record); -} - -// By default, the timeout is 1000 ms for all keys. -__attribute__((weak)) uint16_t achordion_timeout(uint16_t tap_hold_keycode) { - return 1000; -} - -// By default, Shift and Ctrl mods are eager, and Alt and GUI are not. -__attribute__((weak)) bool achordion_eager_mod(uint8_t mod) { - return (mod & (MOD_LALT | MOD_LGUI)) == 0; -} - -#ifdef ACHORDION_STREAK -__attribute__((weak)) bool achordion_streak_continue(uint16_t keycode) { - // If any mods other than shift or AltGr are held, don't continue the streak - if (get_mods() & (MOD_MASK_CG | MOD_BIT_LALT)) return false; - // This function doesn't get called for holds, so convert to tap version of - // keycodes - if (IS_QK_MOD_TAP(keycode)) keycode = QK_MOD_TAP_GET_TAP_KEYCODE(keycode); - if (IS_QK_LAYER_TAP(keycode)) keycode = QK_LAYER_TAP_GET_TAP_KEYCODE(keycode); - // Regular letters and punctuation continue the streak. - if (keycode >= KC_A && keycode <= KC_Z) return true; - switch (keycode) { - case KC_DOT: - case KC_COMMA: - case KC_QUOTE: - case KC_SPACE: - return true; - } - // All other keys end the streak - return false; -} - -__attribute__((weak)) uint16_t achordion_streak_chord_timeout( - uint16_t tap_hold_keycode, uint16_t next_keycode) { - return achordion_streak_timeout(tap_hold_keycode); -} - -__attribute__((weak)) uint16_t -achordion_streak_timeout(uint16_t tap_hold_keycode) { - return 200; -} -#endif diff --git a/modules/getreuer/achordion/achordion.h b/modules/getreuer/achordion/achordion.h deleted file mode 100644 index 4b83907..0000000 --- a/modules/getreuer/achordion/achordion.h +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2022-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file achordion.h - * @brief Achordion community module: Customizing the tap-hold decision. - * - * Overview - * -------- - * - * This module customizes when tap-hold keys are considered held vs. tapped - * based on the next pressed key, like Manna Harbour's Bilateral Combinations or - * ZMK's positional hold. The library works on top of QMK's existing tap-hold - * implementation. You define mod-tap and layer-tap keys as usual and use - * Achordion to fine-tune the behavior. - * - * When QMK settles a tap-hold key as held, Achordion intercepts the event. - * Achordion then revises the event as a tap or passes it along as a hold: - * - * * Chord condition: On the next key press, a customizable `achordion_chord()` - * function is called, which takes the tap-hold key and the next key pressed - * as args. When the function returns true, the tap-hold key is settled as - * held, and otherwise as tapped. - * - * * Timeout: If no other key press occurs within a timeout, the tap-hold key - * is settled as held. This is customizable with `achordion_timeout()`. - * - * Achordion only changes the behavior when QMK considered the key held. It - * changes some would-be holds to taps, but no taps to holds. - * - * @note Some QMK features handle events before the point where Achordion can - * intercept them, particularly: Combos, Key Lock, and Dynamic Macros. It's - * still possible to use these features and Achordion in your keymap, but beware - * they might behave poorly when used simultaneously with tap-hold keys. - * - * - * For full documentation, see - * - */ - -#pragma once - -#include "quantum.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Optional callback to customize which key chords are considered "held". - * - * In your keymap.c, define the callback - * - * bool achordion_chord(uint16_t tap_hold_keycode, - * keyrecord_t* tap_hold_record, - * uint16_t other_keycode, - * keyrecord_t* other_record) { - * // Conditions... - * } - * - * This callback is called if while `tap_hold_keycode` is pressed, - * `other_keycode` is pressed. Return true if the tap-hold key should be - * considered held, or false to consider it tapped. - * - * @param tap_hold_keycode Keycode of the tap-hold key. - * @param tap_hold_record keyrecord_t from the tap-hold press event. - * @param other_keycode Keycode of the other key. - * @param other_record keyrecord_t from the other key's press event. - * @return True if the tap-hold key should be considered held. - */ -bool achordion_chord(uint16_t tap_hold_keycode, keyrecord_t* tap_hold_record, - uint16_t other_keycode, keyrecord_t* other_record); - -/** - * Optional callback to define a timeout duration per keycode. - * - * In your keymap.c, define the callback - * - * uint16_t achordion_timeout(uint16_t tap_hold_keycode) { - * // ... - * } - * - * The callback determines Achordion's timeout duration for `tap_hold_keycode` - * in units of milliseconds. The timeout be in the range 0 to 32767 ms (upper - * bound is due to 16-bit timer limitations). Use a timeout of 0 to bypass - * Achordion. - * - * @param tap_hold_keycode Keycode of the tap-hold key. - * @return Timeout duration in milliseconds in the range 0 to 32767. - */ -uint16_t achordion_timeout(uint16_t tap_hold_keycode); - -/** - * Optional callback defining which mods are "eagerly" applied. - * - * This callback defines which mods are "eagerly" applied while a mod-tap - * key is still being settled. This is helpful to reduce delay particularly when - * using mod-tap keys with an external mouse. - * - * Define this callback in your keymap.c. The default callback is eager for - * Shift and Ctrl, and not for Alt and GUI: - * - * bool achordion_eager_mod(uint8_t mod) { - * return (mod & (MOD_LALT | MOD_LGUI)) == 0; - * } - * - * @note `mod` should be compared with `MOD_` prefixed codes, not `KC_` codes, - * described at . - * - * @param mod Modifier `MOD_` code. - * @return True if the modifier should be eagerly applied. - */ -bool achordion_eager_mod(uint8_t mod); - -/** - * Returns true if the args come from keys on opposite hands. - * - * @param tap_hold_record keyrecord_t from the tap-hold key's event. - * @param other_record keyrecord_t from the other key's event. - * @return True if the keys are on opposite hands. - */ -bool achordion_opposite_hands(const keyrecord_t* tap_hold_record, - const keyrecord_t* other_record); - -/** - * Suppress tap-hold mods within a *typing streak* by defining - * ACHORDION_STREAK. This can help preventing accidental mod - * activation when performing a fast tapping sequence. - * This is inspired by - * https://sunaku.github.io/home-row-mods.html#typing-streaks - * - * Enable with: - * - * #define ACHORDION_STREAK - * - * Adjust the maximum time between key events before modifiers can be enabled - * by defining the following callback in your keymap.c: - * - * uint16_t achordion_streak_chord_timeout( - * uint16_t tap_hold_keycode, uint16_t next_keycode) { - * return 200; // Default of 200 ms. - * } - */ -#ifdef ACHORDION_STREAK -uint16_t achordion_streak_chord_timeout(uint16_t tap_hold_keycode, - uint16_t next_keycode); - -bool achordion_streak_continue(uint16_t keycode); - -/** @deprecated Use `achordion_streak_chord_timeout()` instead. */ -uint16_t achordion_streak_timeout(uint16_t tap_hold_keycode); -#endif - -#ifdef __cplusplus -} -#endif diff --git a/modules/getreuer/achordion/introspection.h b/modules/getreuer/achordion/introspection.h deleted file mode 100644 index 7dbbebe..0000000 --- a/modules/getreuer/achordion/introspection.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once -#include "achordion.h" - diff --git a/modules/getreuer/achordion/qmk_module.json b/modules/getreuer/achordion/qmk_module.json deleted file mode 100644 index 6e7adac..0000000 --- a/modules/getreuer/achordion/qmk_module.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "module_name": "Achordion", - "maintainer": "getreuer", - "url": "https://getreuer.info/posts/keyboards/achordion" -} diff --git a/modules/getreuer/custom_shift_keys/README.md b/modules/getreuer/custom_shift_keys/README.md deleted file mode 100644 index 1dcb597..0000000 --- a/modules/getreuer/custom_shift_keys/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Custom Shift Keys - - - - - - - -
Modulegetreuer/custom_shift_keys
Version2025-03-07
MaintainerPascal Getreuer (@getreuer)
LicenseApache 2.0
Documentation -https://getreuer.info/posts/keyboards/custom-shift-keys -
- -This is a community module adaptation of [Custom Shift -Keys](https://getreuer.info/posts/keyboards/custom-shift-keys), a light -alternative to QMK's [Key Overrides](https://docs.qmk.fm/features/key_overrides) -for customizing what keycode is produced when a key is shifted. - -Add the following to your `keymap.json` to use Custom Shift Keys: - -```json -{ - "modules": ["getreuer/custom_shift_keys"] -} -``` - -Then in your `keymap.c`, define how keys are shifted with the -`custom_shift_keys` array. Each row defines one key. The first keycode is the -keycode as it appears in your layout and determines what is typed normally. The -shifted_keycode is what you want the key to type when shifted. An example: - -```c -const custom_shift_key_t custom_shift_keys[] = { - {KC_DOT , KC_QUES}, // Shift . is ? - {KC_COMM, KC_EXLM}, // Shift , is ! - {KC_MINS, KC_EQL }, // Shift - is = - {KC_COLN, KC_SCLN}, // Shift : is ; -}; -``` - -For instance, the first row defines that when `KC_DOT` is pressed with Shift -held, keycode `KC_QUES` is sent. - -See the [Custom Shift Keys -documentation](https://getreuer.info/posts/keyboards/custom-shift-keys) for -configuration options and further details. diff --git a/modules/getreuer/custom_shift_keys/custom_shift_keys.c b/modules/getreuer/custom_shift_keys/custom_shift_keys.c deleted file mode 100644 index 4c7c88a..0000000 --- a/modules/getreuer/custom_shift_keys/custom_shift_keys.c +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2021-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file custom_shift_keys.c - * @brief Custom Shift Keys community module implementation - * - * For full documentation, see - * - */ - -#include "custom_shift_keys.h" - -ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(1, 0, 0); - -// Defined in introspection.c. -uint16_t custom_shift_keys_count(void); -const custom_shift_key_t* custom_shift_keys_get(uint16_t index); - -bool process_record_custom_shift_keys(uint16_t keycode, keyrecord_t *record) { - static uint16_t registered_keycode = KC_NO; - - // If a custom shift key is registered, then this event is either releasing - // it or manipulating another key at the same time. Either way, we release - // the currently registered key. - if (registered_keycode != KC_NO) { - unregister_code16(registered_keycode); - registered_keycode = KC_NO; - } - - if (record->event.pressed) { // Press event. - const uint8_t saved_mods = get_mods(); -#ifndef NO_ACTION_ONESHOT - const uint8_t mods = saved_mods | get_weak_mods() | get_oneshot_mods(); -#else - const uint8_t mods = saved_mods | get_weak_mods(); -#endif // NO_ACTION_ONESHOT -#if CUSTOM_SHIFT_KEYS_LAYER_MASK != 0 - const uint8_t layer = read_source_layers_cache(record->event.key); -#endif // CUSTOM_SHIFT_KEYS_LAYER_MASK - if ((mods & MOD_MASK_SHIFT) != 0 // Shift is held. -#if CUSTOM_SHIFT_KEYS_NEGMODS != 0 - // Nothing in CUSTOM_SHIFT_KEYS_NEGMODS is held. - && (mods & (CUSTOM_SHIFT_KEYS_NEGMODS)) == 0 -#endif // CUSTOM_SHIFT_KEYS_NEGMODS != 0 -#if CUSTOM_SHIFT_KEYS_LAYER_MASK != 0 - // Pressed key is on a layer appearing in the layer mask. - && ((1 << layer) & (CUSTOM_SHIFT_KEYS_LAYER_MASK)) != 0 -#endif // CUSTOM_SHIFT_KEYS_LAYER_MASK - ) { - // Continue default handling if this is a tap-hold key being held. - if ((IS_QK_MOD_TAP(keycode) || IS_QK_LAYER_TAP(keycode)) && - record->tap.count == 0) { - return true; - } - - // Search for a custom shift key whose keycode is `keycode`. - for (int i = 0; i < (int)custom_shift_keys_count(); ++i) { - const custom_shift_key_t* custom_shift_key = custom_shift_keys_get(i); - if (keycode == custom_shift_key->keycode) { - registered_keycode = custom_shift_key->shifted_keycode; - if (IS_QK_MODS(registered_keycode) && // Should keycode be shifted? - (QK_MODS_GET_MODS(registered_keycode) & MOD_LSFT) != 0) { - register_code16(registered_keycode); // If so, press it directly. - } else { - // Otherwise cancel shift mods, press the key, and restore mods. - del_weak_mods(MOD_MASK_SHIFT); -#ifndef NO_ACTION_ONESHOT - del_oneshot_mods(MOD_MASK_SHIFT); -#endif // NO_ACTION_ONESHOT - unregister_mods(MOD_MASK_SHIFT); - register_code16(registered_keycode); - set_mods(saved_mods); - } - return false; - } - } - } - } - - return true; // Continue with default handling. -} - diff --git a/modules/getreuer/custom_shift_keys/custom_shift_keys.h b/modules/getreuer/custom_shift_keys/custom_shift_keys.h deleted file mode 100644 index 4636e16..0000000 --- a/modules/getreuer/custom_shift_keys/custom_shift_keys.h +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2021-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file custom_shift_keys.h - * @brief Custom Shift Keys community module: customize how keys shift. - * - * This library implements custom shift keys, keys where you can customize what - * keycode is produced when shifted. In your keymap.c, define a table of custom - * shift keys like - * - * const custom_shift_key_t custom_shift_keys[] = { - * {KC_DOT , KC_QUES}, // Shift . is ? - * {KC_COMM, KC_EXLM}, // Shift , is ! - * {KC_MINS, KC_EQL }, // Shift - is = - * {KC_COLN, KC_SCLN}, // Shift : is ; - * }; - * - * Each row defines one key. The first field is the keycode as it appears in - * your layout and determines what is typed normally. The second entry is what - * you want the key to type when shifted. - * - * - * For full documentation, see - * - */ - -#pragma once - -#include "quantum.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Custom shift key entry. The `keycode` field is the keycode as it appears in - * your layout and determines what is typed normally. The `shifted_keycode` is - * what you want the key to type when shifted. - */ -typedef struct { - uint16_t keycode; - uint16_t shifted_keycode; -} custom_shift_key_t; - -/** Table of custom shift keys. */ -extern const custom_shift_key_t custom_shift_keys[]; - -#ifdef __cplusplus -} -#endif diff --git a/modules/getreuer/custom_shift_keys/introspection.c b/modules/getreuer/custom_shift_keys/introspection.c deleted file mode 100644 index 6835622..0000000 --- a/modules/getreuer/custom_shift_keys/introspection.c +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifdef COMMUNITY_MODULE_CUSTOM_SHIFT_KEYS_ENABLE - -uint16_t custom_shift_keys_count_raw(void) { - return ARRAY_SIZE(custom_shift_keys); -} - -__attribute__((weak)) uint16_t custom_shift_keys_count(void) { - return custom_shift_keys_count_raw(); -} - -const custom_shift_key_t* custom_shift_keys_get_raw(uint16_t index) { - if (index >= custom_shift_keys_count_raw()) { - return NULL; - } - return &custom_shift_keys[index]; -} - -__attribute__((weak)) const custom_shift_key_t* custom_shift_keys_get( - uint16_t index) { - return custom_shift_keys_get_raw(index); -} - -#endif // COMMUNITY_MODULE_CUSTOM_SHIFT_KEYS_ENABLE diff --git a/modules/getreuer/custom_shift_keys/introspection.h b/modules/getreuer/custom_shift_keys/introspection.h deleted file mode 100644 index fd09bb8..0000000 --- a/modules/getreuer/custom_shift_keys/introspection.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once -#include "custom_shift_keys.h" - diff --git a/modules/getreuer/custom_shift_keys/qmk_module.json b/modules/getreuer/custom_shift_keys/qmk_module.json deleted file mode 100644 index 47304e9..0000000 --- a/modules/getreuer/custom_shift_keys/qmk_module.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "module_name": "Custom Shift Keys", - "maintainer": "getreuer", - "url": "https://getreuer.info/posts/keyboards/custom-shift-keys" -} diff --git a/modules/getreuer/doc/banner.jpg b/modules/getreuer/doc/banner.jpg deleted file mode 100644 index 479bdad20c2a5e29474b4dc30b6f0235f05de23d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47038 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 F{{Vwq3Nioy diff --git a/modules/getreuer/doc/voyager.jpg b/modules/getreuer/doc/voyager.jpg deleted file mode 100644 index 19bebfd9d7b741ec7bd3a3d67a4964b97dcdf102..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63788 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 z zsv%Aw2cc$0fQTw)KqC{Gmk2F)=e(Pwm8@AD5jO{?oy{mA7F!{0*I#jIW&%}ew)~YJ zuv(77Fa;=G)UdP5(Qn9x*$1OM#}jU|yXXGQMol(-GA}%zyZapcy!$SR^_d~Gh#@`H zl~S^|VD43H&fI4ZqHAZUAaXfZ`=iHq`)SN{mm8!lF}k+r3%*rOIs8&3F$!I~3dobV@=%v>9JNJnEy zc+Fvi9bdc0dds7u;RC3?`QW4>x64NDLRI5sY5?!zWfBz78P<-02VLrwvENL~ltXjr z7{aOhRcEMGWwET*0?Qln1pZM+aifnAwnK;%5g?LEB~?BDMCE(#NCG(86dt4i{^{1& zxmWbui_BwKXNoUG0n4MAi0@{?HvO_sh#aC!G5aN{SUL+?2$!Xe)$*i?^C>U#W z1)LIvzft}LX#M)=8y2AEE()fih)BZmU=BU7jbG!p?)u}~vyvBV(Q*FkvSxFGm#M3{ z(&Yt*8W^`J)9@3kz|;7P7>90*lonAD```=xX`gx8^OkwWK`?`M^mU!0;x4m1>*nzfp(+)`X zF&n+6O0`v%Q!-5$^K)Gq2C2F2+rb+s)J>b~%Wvwd-efUeN*G~N&y2@AKsUg;aEiixna0rE8}iFt69H`#z=WoL20? zwJK&FclK>~c)?}xivAP)kH~$rp-3|JI(TD>luqpqfrI(BQl+T#Trc=0H<3F1^XZx- z>3Wdgf4TX>`NENmx8wC6h$qCqJ5$Ort6vH8TA%VDw~H&%Ni*DQ$qxZY@017%#K14{yvTzHW4p4%~kd1c^ ze7B=EPUIFLs9DlVb7{LJctY1H!CLkZRo<#$Xa8ulkXhU?bHOQ6g)lA}I8_)h!)E{Gm^yC)AUF`zWVeFG+TgIIqUTVznX3NxCyzf=)yb|sIX$(?tW zE(DESN1eC%IEbsV#hj;g9!vI?M-#i|YFAD9%H~o>+cA%6n^i0veDN)fSGNJOi7eefu^b60r9SW+(%K2l>-WfrW9L^S>f$45;Le17J29C0FcQX=;J5ip938v@b{pCs z;Sq88y#l{Dk`K|{i>!$OCTY-&8&TScc&o9?pYY-OJ>8d1QFbaX%$NcsN^7>~9+oP( zi?)%6A`OoKx}nxY>r8@AoHetBxmV<_ps}Kp!BZ?I3ID}N7duuv5r=O@kKjFYo%GXA zgfSKxJrpfVYEs0AYPU|Q1+ggd^(&&3BCsOj?aa+@>`qMA3$>TtDxv8beS!gYT=2>1 zd-;8M3@Kb;lf2eanuSAeAf4C|$0E@yY0~DlCkgIE6|dr&!nWpa7S zvNAG+060jUCvM>4&#P6K&4;lmC0#gFE+8tG2z3)%re4ehS+y6oxw)r`f7 zr70B9L4-iVqs}SvN2haVs0%Z@PZcJ6o4(jDGU0>X6^j^xgsE8iENd<@H{r$n@_m$T zvK`_TrC<=7l8Y*+eLoqCU1{~|2Fc+b)R;OL@B3dfZ4^mj;!i*6-OhA-`fvV~HZ*wz zTN6;|5|5d(E4N{(Rh9W4*Hj1dhW2w?ppvy{6(tCMDy>vK%XrC!(=&>tzOLXSnJ|ob zDLV$j=aDZ}{zY?a7A0BbZcke9_Wk_~EyI^G?E zq-Zh2+He}F(ohR+Y^nuw#%P1ixr$tJ{U;m$Qp=Ue4mP?QV7C92sD_#Yl;y&OQHm$8 zDXlD9`I3c=uH(y=y`+%ml0i7C@`MWJM!Rrnmz!JTv65x`)~s1ch01awZEVjOpxI>X z566|_RCZ-oc$JTJmBmWZso*Eea<)%e<#Zv_>4Z1nJ83+*MM~kCd&oo-FB+~(?S+LR zL{RVYa@OJQ3AwIL3AXdsJw(F~s=KLneAWQri_8_U{ zhKpoE0J=}e+{zFmp*oJ$)Ojh~fv${@(-7goCXGp1k4=z}kLm@4;e=Y1)w?cMj8jhN zvxY+<(H(})O(|Ki6&tmKw(~!8dixRKvh-_3$;fTDCQ&E(-kq6mNsC#i`-xF6Smls? ztJk5*YTW;MN3FQID`d#(Zcp1ux(=e_hlWlJ9Ztt|B9lF^e0pcM%VxOzv<3c6451-uGWBqCGVeM z8DV(Y5T8?m!`7opbVd`tW_$uPv@QQta+lSHMh;F&ndhViwx&*e`6XB^a9c=CK)jA9 zE!(AhphSQtikQvH(*RtpX7r%k8jt4;1g4sJxTsu0SDBTJGAh86SC?6GmS`5mbs>Q0{umR`jpxJL3nCnel%w^kjdVRB0@`BL*^Ry}f4I?drub~Ars zdP%AzBP~%BgWF%nwc~9E9HFON^NjO(& zoYN!D2FqG`W&{;7oRw76YX|ENLG+HQ_fTHfxYC;ih^KA3x3WC`WIytqyzB|ffZE)p6EOa(#f zYUt>-DrGuzhH?sjUzNyRGnbj}{}x=}JE>|}Cirnx`mHzK28nLH|JU3fM$t#Rkc8XR zv)qQ{_4y?6O12EThPHOz3}6(Q#0*{IdUR(>^{|^5xG&Za6GJpG(jW2A7%DuS#iBB; zn3CbUHTVr}HRE{SQ!(1;TbHV5))=F9TM%v#ff%Q@yd&+zg!hLtHvBbvu#?^IfHm){ z7&nobXon+gWju7cNuL!mp-ts@1da^rnS^iB2&x_HGElyzLnTeRbV}ROu5;yc@Laj3 z323=f=i#ttX3hqYJswkwH}>gP3dT*(9`8h)vB0vk&?B#uW4o)w>QbQhYg8_KleLn{ zSr$Z#OCjyMjcWCA74?ryhZ=U~v&E#6&*7q#b(`5=fRet3zgF-;+#r=tf#m2$)EkT3 zuq3d`S?MSHWuV}pLw;!k(sVTp{{sqyF11yF*Oyd1DZgiW~v^WRgmMkTIu}Zg8rH>t8C=_(opk!c;!cbg6z z5Lzmf>{YCqJ#J($)7g}7!Ly`pXY?AO*FZCFgCaZKe2B}jV%9k^6Z74Rbw_85AC%&L zGAauvZpkKLoP>87XVeDcGrYJEnqxU(ft3|8ObwzO3}dL)NAxY@FIJhZ4Tp%{h$+yS zRs#-9f)Y6To4;K~J72IRu=+@z%#j^`x{mDp@~{+muG;^6khQ*}rk)_E9GkCK;Tchj zd*}_aWH`-JqTxID=vGR~R!YXVEzvk<&EI#HzQ1K7&{_J=_DCL?IA?WNs2``RXQ^}& zN-*ndnw+qTm%>$@aF}5;QCyWr(<5!7sI{6sr_ui+wknk7F>-V^nMt6Gwm75GWGu^b zlQICJZa;Ur`~4YVQ)szjLeAsZVAKQWKq#Z00$nF|sqJe|^urayBO= z`eE9c(OEa1KB#-dMIOiGE&(^yXEhzV0AwmF_o_zk^3gI*G+kFRlE#K0TMcba<%38n zjw@Vyz?~`7Qtol~kaDC!Q!>hUWmD})GgC3@wWsF>^LQ2_oeb(V7alS_{RXWzP|uomch(o=uAIL5`SnTWe#JMOV#PIDwyD- z0YK4nW94m#M=RVDTP@AT>v&Ar&EaHFT+m@Dc_+?Dj;iLS;8R}IXijrUuBMTPP9Han zq&as^ySow`cd=zWp;+BzVPE@6n5ej}fF3l84pSjY|30tE`*zLVI-F=##&;C>DIjmq zeK()k{fQPW?onBw#Bs;+hq804$A}?RU>In9hxYM?m*qlqkuAS3Wwmq#h&em@F4P$u z<6-bw-{F$3(hZ|4WeghjCi`<~jJV~;bXv(NWHj)hGpxT~va@{B48)mwn1knsCqvB<(!F*xAvT1bm9!pezCN0djEVtepN9+s^Xx^Le|br2 zCq7If^4(jG>0dbBAB_2E3+=NGJ30Sue?|KKF0O?G_v_T?z5qva+IY#aMd?u*{~fq6I-ubuw)2BkpQ~= z16r}Q|sldlnmt+sgAH>iZd(|)di5j9(g{e~&W6Gv<3NBX0QT7BSP z2a6$Y*#QZg03R&IP;6PYIUk@_1><)KL%gc=I4n*%+-BzVqWOxdjLru=dATMg;`xGL zPu?Uu6t`KuDi!IO&?(*?&5@aV|8ySib-b^5)y|zn8pAX!f&fMH0^^wP*j0406u|5} z&+hL>QCit_P^VR{@%@;c>+P}C3<|y7wBoePseHUE!jVhp^AtWU8C$hCP4Mc6Nr3ZV zl0V(oCKi!HLsl)(JNzL2rP*%kx?G!+zN6^HDc=r%I#v$-7gi4KX?|s_!cX6hEqk0? z3{`$=5z9OLv8s8@&2XR-d(18j{-c1^Umq-7#gIwl%5)GV_T>OC)1g3rJv7J=TB>7RWM53Gh^GQ@^ST%j|I<6)Pgm(@%>_~TVT1bGF&IW zIpx+g6Y#prEAkUo;(KyllZwYEjEkL)XL(hu)Ge%7B@k)0MHlBRLtmJl=Ov;q7Z8zg zNr+05Bz7XhE9fx4YG~XLt2X6-;Vb`c1{zA0{>QC}O5zX?CqGIrX3E8MXqiiBmaZ<# zVX-Q8=%|2#M790`YFCP|2zBHFY9ZGfV1+0>WAR9lS%^b5j!J$$wQ^ml;8wFJ>#(Eny>%Un4eP1Xf{RS|NZQRm5R zwF5F2AC_jWoF4@H=0TaSt4hs=&01eblBALmTjHvR$b)?E6!1KdGe4T@mCyI3GAj$~ z*8KwO4*OlM9RYam_DR}Dj*O;5c32YcHY0F*sJH(DivCsTYWLllRlTuXxO0~>eD%B> z*R7dN*}q%}Xr_WnOL2?Rj?OREHjn-_dZHR6BL73-<{+yW&mMO1(?)~Ro0NNIlgPxd z6<1j;*?-~fNNN!tHO5b7s);FDJCxP}h8x%a`k7pz+l9K}6LCAc&g9ZkIzdVX)=KXH z-K3zZHnxa`>ohZL+U)S+%=PmPyFiJd!YtUyi+P6_o z@!w(^Y3V$d<*N0|M(XR(ccb`Yp6wv+w!?57ES-|bCKs~y$ZZ1kmRFLa?Cp?6^_%>M zc${r3$3l2B+W|CPEuk-MH4Gx!Xsg~dqbANB3x_@WVaq<5 za+wgx(#D93B}#?S?JQiJ>e~)HDCLjLcj|9FA>Hc__9s(`1^hjqb(ce_FMdmQq%He2 zGFlEyGH(?$4N1*y?BAC5oeMvWT&@^8evowem&(+c()y;|sZ}z(G=Snqq1G%;wAVF= zh&$hmoL7OK+%dTP_%Y+F>$1UWnM-j7PW2%xp>HG>)~*6J8B&~xQNqE}?`j9p8qT)c zvrT`9nxkMsY?Eb0)GJPolaO^k%5Jewz__XMGOmwY`lc;V$_VBfXHpQFg45*VUq(G; zWGewfdGPz7X*EIW3iJDdh$xY)O$?sRC}ebqPE^Ugq7!@b!zBp^?>Te|Q|o$+9a^LQ z=o+o02T(SwJgt^9boD!8<438q{?28+>!~Vf63&#M9+@*oXwG{+%+$7F^wcze?cDWw zQqlZlGY{nRmM>ywe!={i#j~-JmmnFB8uNp)IjBD~!1kjJhk7UjBEc(H*_4enJ}(cc zmHYOuQ@ zpKwtc`cCl?`&3n*%|u3@Ct9-@RR6h9ND3J~z58UvM7cjYbz8 z&<*~SSuH?wfoLbc{1?EfHwEBmmjg`cZ17q-oIUf>%EZARE=K?4@6PYbB)s%Kgm zNbup#{?uVqn7aR(IHt#9lU6M9eTcN19C9y6eybk!=Y)8naBEK!6PH%5>Mf7DT%6yT zO6x6ql~tuM=NlwZ=%q3{sUK4BH?uZdqOVd~9Lc7t^B6NyF6z9t)P9I_?vkPri7QoF z+$==e=ag!94rLK<>q35E_dw9*ZgMDOa`JWtCUH7Nm>)89k9s7VP`cfD8FSKk@EVAB zdFqw6H0#|PC7Zu~O(h|No{v>%ovryGT2N0xNn!G+3>^8AsPN*5-q*eOIQd(oF+%|2 zC2I>MZkMukBb>BcDbTdRb%+edp#WM3ta(_EY~QRPs8Pekcr10-*3^l`6>86b+Rj_0 z+L}w_eBKaUCKj$yF{YEVz7@Kf(iADME;)+vpuzX(pixg0Ig+S1qgOT>Xm&@vr2Kho zTkCZd_sO;-YI-#Qt-vw5JC#v#U{V~nTyJ$ut}v!JQ&}9fG$YWxz{NpBDNJz-zZsDr z*TnkV-6OhZw&e38ZSa_T?Vm)Ga;nL|!;-qaQAr_(@BY;GHV6nVjZ^J6eNELxcbUK% zTBBKy74=1@3V*gaIbM1>rltmqssn|O%S!-B8=Z71v4cwG&&|-3jLb5%F^>OeELXjB zW%u!IL*wES<5mGMb?-wYQfN|6lc8j!gpKifYl&m9MfFoqlvz(~lhHQsbe14cZ{DH0 zbA&r2IjHq7V7|XCDH}n=rku-!gENRxEDexTLctQnJ=3BCc9Csca;Rv=&$&xomug1U zURhbBQp#m7{6pWQ9ts2=KhrVM3iPsUETWHvmVlQt9Rm#ZH)IZp ztj0a&Iu#|tH3dr49mA3vd|UR)o-V211!*Q{e}2q~EYIB^e>||uK{@p=KPgN&>dlFj zrTBe!nsC%GcIG?lprm2B!B%~s>H04r!NSaUq&n*x%=?D}Q9qe1TQ1PPgmi2pXUCFJ#&e1?tfh z&FWRhDhNLRU`bfZtlUWb$1(B{zO1{lskWoPfMb!!V>Dd`Szl5=)s_45j%zxGzX0D7 z2de%`4=RUWiG#j{P&PlEWs4pIHD+d#s_>ry5_}GVG+F(TOzp<#f+vG)A`~{A|D7L> zy(U8ErtrfYA&Bg)WkTWAkF@tDRc&pVURDA_CNMdb#$fi9K3Mf4>J=3|U7e=8+Jox( zFa7y`6P(ooX%(mFl@6sB z4=p;fgWXc2RJ2qyp|a2qD;4nXn(-)YJI`z(c?{jBTU3+USN|R*Giu5h z3F{;TQIFwuE33BIU}QMc6e$%<8X#y+%GCce__^2L$K_e^4{6^oG@Ff1c3PIyW-FM~ z(LUKBn|3-egp`H@TW)p{4cyE6DjNJyUA&Co8xSC)rb6>Pj zd5Q~eN-Xk`=jK$QcRilw;(HxnW4h zXTPfU6E5To$@=cJkL6_nDCz|KVa{ZFIdu!K)&{ef;%GvOQq)WuBS>#9tqK3e&)^;CF7C9@m0t^i2wFcM46Twe1AmFwJoC>M0iu z8>~|GEgft$lBL@(A4?K8$L``=vcI)y>u2HhpPD|A7H|i#p=?yEbMI;3z_l3$<91*d zoPHJs+k1V`-kStllmCFUtTtRac27FSSs2lDm7=MQP`)DlCUsckVTIUYdyt&!f5wCe z$(wLF-O#G45DKSNPV?H1+sdHR*}?gU6mQKg&m;K{{;RSX&FSWoF~wpzw=T?z+MAkF zR~<|vhzwrYK(PQgXhz|88~BesJiX2noACw*Bd3n01e%DpuB1+~c5YKp4X`e`;I zCnn9BM!O+m;6m`kcDS#8~9PhoKDTgQq3b~MB^b##^lM4 z91M~ML;0Tl^!z+eHmWs~C)(*BEU^Ra1oW32mjGV#do4f9`U zeQrQ*uj8)BI+6+nX!d>yU&J5qqawzY*njmge(*T07h&Y!cBlO~L5MQH= zYAa}HUv>5vYfMqO1|tkdLSJlteR$vb0%0#>ovZ_$je4*8-6DGWEFHoz=fSuiO$d8;mIr9Ysl7YaK$#AgQ-vvbNo6{b2^4 z4x+V-QuL^C%2M#Ktv;jMM2_NC(~r_6MQsVO(6pp?0bYjc@(P@t6){zG8eN*(Xv7P} zLwz9fC7Gx`QmNy>YA(aiYBRJLVT_KE6cK+f2^rq$|g;|i{Ete{{u5yvq{ z3B$UIQYI`h7>sSjtxhbm-y>}!D4#?qFk>bih}dIRDA;bB7WdBitP`0V?5h~yc~z8M zhR;u7TadX*Lb;Q~I(al!=+-q_D_laMb=3x*FCn$J%`LyxRu8w~Lf>+CGT&I;u z@~nPXnNF`_g-DWtxmA;BJ2-8iZUzbQRpkNxVZJHvd&+$UCoEn^<(99v=Y3od{AQd> zXUc{JhLrnxQ75MCR|>|&j5u>sHPU*zXwr1n*G@kg3))jtFtCNSr8PW*vvBxfsUAX| zktUWbSu&Y*G?5x{w^7+PJc$s3&{7~freyXG+)Co~s0^~(Ba74KGt?G*LDZ6--XuFa zT~FRqP0}%@T)hqiSGW_ZP}^&d{{Ut6x>S^|2L;{>2G>;Szu|ptVN{4*G}1zzDX>=# zG@4i9CV|iq_FS$WRmY=?W7)1vI`;~e8$(Y9r?p{QE%=r%2Ax*qw2vSb6NzCUgNk9L z`&35G`wG^Qa6w2rh|#toB2pSC={7miqLl%gX6qihu8a>mjkt$A7aIf|d6!V4TX0VR z?4I=g&8g(AB*M5%M*2Wp$6vKsuy@)ugJMfcRrxH&PVu%2Z<>LG*;+toLN0ze5WBWE zT=j#gwhnwfOy+P54h3u$;0q^}_{{m{awIpKeV276o0-E~HNM7<*Xq+7FskX%ZDBJu zxS0SBQWn21cCcJgE0n6r*w0FTS8;*ay!Dz!lSY`zM4LJ)6u12O5n6!K2P@CQNh29b zUsEl-sBlRgA0-S%XaL&QP&UrtWqSps=R9KJ%TVzA zC)K!Jgaz`Pv9DR?=DMouL7LJ~c2Z7EPRDT(#3;HOiymyaEXqQYusg8^Y_)R7y3rPi zrgBxLoYt{)y?<#`t~9)rHiEt^&tEn-#0^~T%QD<{OU^v?9)r|chBD^M&btd@x3j)s!8okkE5>ZH6%ql8Z<-zc~KEtZJnE*GqHFb^RbbF z8~$)Zk+v!W=fQHh#AEQe0VYEbWJZ1z+x{QhX-N)F($#vA$S$f?RBkKkrV zzr}5HFos}A(%QpQDXH3?qLLL#QYve7y^GGN_o7rzVIU{7wa(7xU;(YO+$$Wav2Y|T zz_a@htkJ(Ak8m@gaxk@VojYQaxoXin5Jk4*n`;649^@Y`E>ceBTZsRv6~U>OmbChk@znE^>;_3H5Ti!nZufB z}75pdhKGGF_Zc zB8k1WMwF^f{D`8jW7u)DM%cC*TIDRqmO9EpgD}(v#uEg1Tru`yC+NZ_$Pt|wrkntC zDWx62R4D}~bwP1{mqu6YY0-yIwZ7^_D+wM2TO$p7SYR+Q$PxC(>!Tg=OaHA*-KDD7-=jcl_chu;~fr~;qoaPQT7XTudF1YX(&+!vJ&S>ZM13V zihof+j<_<=M#@957yAYvtI&-U{-PT=nBX4)w|CzX)T7`Q<$!RaS>I)`C0OhkDjVaK z^6=NpP*5KiEyLxC1*>AX^=M>`n_&s@fs_HhbkaNkkTY~0^S5DMF~|{BzAmOA^two= z7f%>quWTrU1|=W>fTxE1+{A(pPBO;Fu!x{`a;g^l7%+wz&wKuXZ5k(`G7 zsHz2hHhPO7Zb)V4*4kT*3AinCqfu#I5=`N8ZfY^s!qRu;%_X6ga@%$m5$ak*Mxrwo z5eo%9gr+2h*9sU)kZ@JJt%xusHThx~-Cvi)A>6XGn46Bs0HB%q%ifBwB?fH`{3;eCI7%C47s#FW!Z+VyF~-LG=L`&0=}0f3mx>8d z!HVOEDPSd}B$6Vla-rL{d6Jo^1M?GWR(DX717Wa!85`$w6+7p37wpZ3eBhl7Za}eQ z`b_A=Zlz7D#LB4L;w4x35F%A!d-V2vPWMnFD#4{e9tUd3+hG}u zF_^&_j3|tsdqzIF82VZ~c;t81be=nIsOun{Qwtu;4uQMOQ-yjsH**)jRnExWpq#!QK%n*M;TJ)URk-QCL#A3ZRtu3#g9uFkICD* zMr_#JLYFEt{H{Wp+~xy_-#^(pYe&}C)xCeJxH)GYMV_Xrf|~bKKR=BgbK@2Eihs0x zDf>-2l1T!gL&*5TFjllS+*hGKgjDBjcN=sL<8-s&U}X5)qBEke7+Bxgh!hTXn_!AZ zq_r6X!AmFCBY#|}`Yo^Mra&G!Y)0Iw`C);}FR<;0pn*L@$|Fxu@!8mVP=_yKG03t} z?nATVNQ{LS(xq~@{YuZ`5a<3yvOwP+P)EDR-4%9bBTGZABa!xr#;2|$$w6w(3)n8+ z&!pN~W}2)vJC}*VMjR2u@;cx)DnJ@GaS`&gY5?K2oxgog_M08qk@p&ej(QW_rl z*ciDHk_l0~Y%7%?DtrYS6!_fCVC}lQb#6kCjmb;sl!3c0viDU_IJb&`1rnFG#7kJk zOCN7bAb8cBNr6&chhIK)>q%>!V-_pZ-Y6`v)OOO_XAWvAWlBhw8mi4~xE@6wp1jnl zWy;5)>8=~}-nF{acKViSnPyJg3Ot zU`}(Wc+P*apwho?-V(j`)#=2?fIV`=PonH(;dVFCR{sF1lh5HsG26j|zCQupL(3XF zjudmDmk1lJvY##?ag?rHC4_j#9zZ807u*|-b;EVfdD~idI*h!y@}xyWsVP!}(T2&T ztaek2l`Qxh4F?{AUahER{{W4N(5+3UYfT-=*lEgAk<6`UsAHt2rX75Dr^sqMItlmm zzNU!JZ{IRSZ2(t0`&b@pFdI zbOgGk*POYfak)vE4lffNpwX4o8hH`arO1@#lF=5Lp}^uahJ&Wlu$w|VD*P3-=LLLC zw!s20^dm41z%tr%k-x4+&K4L@@?ydsNRI^iF`Pehw$~qGtm?4l;3<||IAYCC6iglnD<86#wsl_r003+htVHyTmC3|S_tYT!r<^?%UJwZ0>lp?iH)Dgsqu?_W7g8&{@7;FCv01G1I9>cVJDhW zR-SHMU8bYmg)P~&lWJu4@>qstJQIjYTS8ESi`e1VWd%Z>TXG!LR_oJruBJOEdH1>1 zK~gB;2bZF0-IHe$&EXx_sc3Cj_Vd`tK{jOO6lJxarQMudyOt+a8Z2sBxuAl&yl*A>sz* z^&ca+lk6@;wGEU^k2t4f40220PT68R#3?FD(_?9CZsShasAI|INx;Z$L=GQlD3|CKR#j}ptZl86a`xaMyDJrE zdnpMeX4_-r+po_|TFp2tW_v5tFE-*(;w|i*UnpI*gIvXExp~B;O?<5L?RL(O?k34( zsJGtE+k7?_C?l9zOCw@tOe)1HQjYlCxg&mL&UH8}UkSp_{)Byxol`CG~ky!VHG{0$!5R42kpIsJn zqKxRL3n@4lMsyLK1bz1H9PQiA-MlfI&egmyn)@&q^SbNeVyj~M3$BcD%H$u`R3)J& zYhHG_fwz>?sCk#ID~)uewi-}L=`Cz7JH^B#gXB?Kbpm0$N4!wS1}QngS+Qj4=}?1A z6XXc0P}yxpZLdmFM%$Hh55*`Rd6yl9k3FM*;`2*oDkTrNvW`Y##dM{|2rZ{})a0dN zR-^c+!H(~e+!(3B8aU3AL)R$V5`(vZ*$2-|;cR{OsKCNArjdau7!rPgcWmZ&>=k{2 zrFaTqWO-8(N9oeA#EU>rDh;6aS7I^Q6G8I1c025s=36uYEzGxCwn|*I1M}`B1Nrrd z9$h@x`wLf*twYfDZNrF;`*Ir#N7gM_3LBH;C8Wb*B}^Xa?Ll#boY5h-Z!M8){ux>hDi!6yXQzKpk*uql0b zB|Dwd=Xn13XF72<4*h(J@L4bK3c7KO!h?iI?QQBh_m zv&iDHct&Wh9%_0fX|*F_YDW7B>pq0p2hf!)ozhn=M{~5FY37;t{YrrfEl2>AIKE}%P8X(~fD-4Rjh8abM+D1Ycfmg2)(N)x-P;|830B$MTOG&}Eq3xBweh%A zj0%&5QnQ*ybH9PtKEujB`he#9jqs(P^C^Hy+@`#pfR7L8w7~Vs)-1N4LTe#hp(ZMN zgry9vAuaS3w!pIJ^rSzEOW5l!D2?r;J2Kkc_nLKa+$uve+IwWF4LJL#ZMr&^qc%eI zzNe3Dwjn~KsCv>`Be$~a{e0x1razb_Ogq(grfZ0LGdalmmeY7!VJzfr)Q? z4R4(gM)(^aT`+T|pT3Y789wu-d~eb|268NCO~8QQF8L1Q`!!xXE1%m&UT18L6NkbWjQ)!>*z?7~KNEZ~5^ z!x0B@f*MesM6S7|9ms&l&ego_k&9pr@o6L0zu~vbI`y?P$aR;UjU_=z_ot*S7O-<$ zYIH_uR94r*c1z7L#e4>6AR;?-wT-xeCFY>AwE|+mXl;x_PmDxC^q9`=_@$?|*!12w zhTPl8Wmu+P-|^``_E=HdV#CMOqarGjayn8{RG`8MPbrKx8}^%@35DYfH%G3`@GwPr zKEn@#frs|M(}MyIz#c_<&QpQ1Ms&R5o7)u0CmoJ9$Kq}Y$uN(cn_Q&>ml{#Isa9$| zFE*uXdkU0=2b)?^C7`zX2;3eSQXNOEYAX(>*Lqur$&pW@sVH?0xVU(;qxf}4rs48u zX&C2W7jkeyi}WE!!4fz)A;vrBWXGMmM!n)=qAIVahi&)Jz947j*t!^KL6=Nyp}>G$I1veMLAnSx%m zr=LY6$C?`{{E^U6Sy9t8sHchE$*hK}If`4%Bpy(h(a(+9XKX=f0CNd&)9o{0V4njM zE)#%qlN^laX+8>_Pxz(Bfc#8{_V8Sk;n^*v{nV8>{4}Xt?a7R-&KDTK;t!-aowmTg z^}C;2yl@xrcLZS)d~N+!Z!hoqqHvi53f%iroy93*+ML_k)($>UK?X=i<&q0YCwG~P zAu2<<>qw(d$xPTRxMDlZ7a1;ctrgG8tq+)*4~7;xlr=VGB~?u#HC8~ z8*>A19nNyd18f3LZ7(@xXDqA&M&#zwFm?X+c;_p)0)X@RX37|M*yDR{bHMfVbN>K9 z^8JX6V1pE$$*+b_pN@ik{8e_|O;uUnV@+G16J4JZ2=BMV#QH@{bfg3Ek;WS4FhON0 z^V_sNC57xZ4Z1eYMFk9TsVZUBv`d?ig8OOku%&wiemuCek&kWXB`261wyTXfhgg%S zbX?k1IVJ{}5S2L(NmD=)qnp3*`PNha`ItdD+^>(0t6LYQ`Lha`j%9m!&P7fg-TJBqAWuTp*2F+-p5jgelUTYB?ZomJiX2 zRqwNF@3Uupu*6DKl$j8iZMNM&X`P$ILJA%`29NA3S@XEAjt=BkMc;fdV*}vCYr!25 zM%mFx8PQ1bogKY&$Qxo&viHEiI3uL_&efCb#gBqOa}?xoKR&H`eHoDx=uS`g_sS!~ zb5>83xE18?+4c)V0-DZdcEYA{OJrejA4_>Hlg!-EPlGp@5rN51{th(}9ycZAje%GD zE~H|lHTL7}WA8F@L1gcbrJc@L=-Rn*jD{-UM6GBx+(k4pJE-xqPx{$16b2>3?-H_i z1aE;P;B5OLE(5SjZhOET!PFVCKKCOzztCE|ZPZZq9#(fer*qx5ef8p>VfeV2F-8ed za7>4STr+}HOzt9_>am?hbEwuAQNM!XP8Tsw93=TT;3N}{rCxFN_x7X|oyMZl)q1YG zvaqAzvBLQA=Wk+9&2wMX2x~Vigq~t(EJ9Kx&@<%cagRwOOjTA zfhhY=T|WAxdoPjby}tX0);)D!I2R*1q-OF!@w4YYpNs4GI1G&Dm7LOWxcI>p3GzBh z`YkKbFd1D!pc{{VfmNb}HaVw>@{DV5{R$k_6{CnW8T35wrtor+cb#+!S_ z?6mKVnrZJFHq*Sm5YvZ!(HM<^?8(6k{dRt~5LT~gfUWn=#mq2V-@$_B_?RpE5LbzS zy}XD7oG?GvW6B05GLxK+j3MH4k^D%Pw~kq!cMVEb1|{HPcs>g@S(J(BHd zQv&muUqNfa^ji{5VpYZ zTt;jawJ zjptDxT;n(MgsVR@=}B|xNOb!xxq-Jg#J1)oD#WDVP5^KW*yh>!X6yRqUIqUEzI2RK zhEwfQ+20K20mV%u5Qjsq56DGAfC+V`oSNG4lmw)1g*GuNMs*W`_cL{wySG^Et@E&k!LBQBDF;J!!~GgjlRd}`eFuZ=+CrF?2B2TJ(T$U0ZXgY}A5H3Wyv zt9R*IOi}ezZW9^S)Ffh~0Z7OtXo2QtnL8Bg(6uP0CYh;)I#Y^a(rBk$I2^{NJ#{EO zG=b4hMh-Dgs%eEP_2QJ{pw>GgmNU*NBOKI%vzi8vs-T=5EOGtC>3juH@_rynd9c{{YGU zm83r>`)c<+824)QyMcgMYj>?)6OK>z)yMw;-pyQ&ulCj2h~q-+NZtxEcq>_+^5m2p zjBucIQ;k#r4U86u!>SB*iA&y7QluNq&O z0qdwbmFnJ3gp!hB&aYbF^EG>DchLaz8g$1h(O%6u_G(q=qDbS{BAgZ3zqDgKVy1^6 z0kG_=sPoP$X!@#k=)kFS%)*(vO*?kYF+A=~Ds_rj>NtILiO^{UBK1);eTX%O>Znz?iCsbJV^Ldu+(RzsR=xoHEQReR76#~o@J<8q*7FE*) z4I?V>lBTr%n!4}Cxsm%Q4QSmVd;D9Pz@2gsYvkR8BY@9v(jKH$p`l|OLfPBB=`<{y z_Rc$=zE5b`hY5yvi1j#X(bv`|k5Ok%xa&<>PRIJOAXz&NgIg+p%~LOEQ>mt2f@(}f z0%w(v0*+H!9uS#3L+!;|Pi$O(#S|L2Vx(s|Wj?yNe%5K#xx$(%Jzd6|^EZuSu9S7s zEh6;_Md}qA^$Lx8g+!e~pkAR-DC(shLYo}|q&}*1+NN&ZGxp6E?X`Zktah5Q+B64f z_*Z}WRE_HJshIM4J{2kI2BmJusrHUE7^O!8eC!nvJk@}CX`>wn!k#|KpgIq{F7H=Ib?QIP37MS%q3lnzWm$t)}X$ zX}YQjhebm-QKT(2Q4Bhm3h^LEhC;}EmANjJf$%>BcEkmAk9us-KTDq zZwmFRcvd=(g-QVDsgU!LLNUrJSkMjjBmrM2ZJEdQcIS6~sPkF|>ge!Ebn60j4R))` zJsTPOHqiq8xizXCE2W&AWSJ#j3b(V-iJTv#N}-3Exi#|GEJ!mUg+d_nq#~@eo}Za` zcT$mdh+6*u!_5I|ybW=?T|cdmRkfsF!EFLUgP>KmdMe$=Hbo?ytecRNc4Vju!7q)3HhY<&+KF1xcbM`}SDg6hH>}i++BGblqf$%PQ%%~b9QnqXyKPd+ zIixjpFH0J@bJA+&Ue#Pl(~7uyxqhKjw_sJyJjJWUJV2t=+MMymDpYSYQBJC8iRPt@ z`G>}ek1+VtuVejaFJ_3#?~n+hy+)CGsxeV|>J0Sf*;aVz9u;YyJN+ury2V=&(hWmU znt_d4$?B-vtkh51H52xYM=xm9GIg4YUb9h4)@df|G?R7G%R%U{TXR|VSQ+`O#yHc;CkHVdL6+9|NL<4I!qKf%L1IWAa z!HM6hFl$2HT=7Y8;sxqmV^dn#ClnFuKWa^CO(mryC-~)Ft>IByXz0)O@8xzAHKuNE z&k8~WDb{jpWLC)?T5oRCia-IA)hsdnt<0772~xxFa66&09o+iDN-gj>6Ot9(b0tZL@>E z8rCyb64=Urth6RS$Vpn$vEB{{ZcxDep^;iL`pq2bq{J&k$C1WG81EB?8-nqJ%+VDh ztOcY7RVf2p8G;uBX1jrqOB1wHHu+*3e<|dURQGU-!bJ>iGsxTp2D2WV9hLH)?H#vS z-M0kBS%KM>$@mKeD@s_mN*DuSP!HQEU3+ z&1rotV$1zg$ojpBGAP^W1mpgKUbNX-QsYLTp1%_m2~ozhanX2**+A{ z*r^<9ju^Mi44yI%uo_eA@StcP%i}|>M~!}(YYl=TYNPd^|!-O9UnPuQrX>>7?w zMrkDM8jeoGQa__=S09Gn6>>*7$9NTby`-zv?MLHRgSQ`zUG+M9O@4gdvefah3(Dnu6-W9ZQ1e=atjfl;qjJ0MY< zs}bE+cDhP92xy*Z-6UPq>`d**uac-ipL5K2qj(IR^{ky(Nf)j5Qd(%}{{ZJnmh7f$s5}TM+6xeWqyx&Ak}>a9 zsJ2hEK+C`0ZSztCM0L{+)iE@}h&;+GPTPsUu!%(Tb|TI?Yvk_ecZBz?s68BcKDzlv z)@t9>*(%$Pv6ih9q_mSa_0sN+J93;-S=p!V%{XsThNg{~c00$NM;KkJn(~`jn6u@A z=LC_zwBomhiMVBu3^{2OgTGfT6pM{gq^TVuum1q^J7N7z=3i$dgUS@mqwm{bww#PP z;*Iy3jVY{(v4ZL|zOzQMeyX*&(Q_T=wQnb|XG>{?2@T#^0&#^lZp{yJ-Ok ze)Mm$thERK08?w_4>=8KXs(9{L*=jDQgSL%x!>4Nwy)nDSRi-Q@^zZHezQ}_+BGX4 zdTPZepeq%MD@vZaRH#85VAhqewf_F*IR5}qtvO?Ta~yi@6cUFaLB?__GuA3IQJPQ7 zQT|$t_6-5pHTq2-Q&R!j@u@v+<5D+URL$0E=NaQ;QiD=@>Bm7nG+6T*j&|mg#MBdX z$STrKms3!6nn?4^Pq5Sx6aCrnsIAxV+u@@C6%H*5$^)AuQ$>pem?9 z00VdcDTh^R0rzTE9%1pR)E~K8Qrgx9-n}YMOnt*zp1SKrXeZEa!-hVvC^>@W-bkcEGETU~SxpqmHr+!xRwtDRWvwL*@(t)EJCwf(ts8M=GM$Se z73mq+dBrQ;#F!CYAPp+76oCC|mK&%)eln*Vj5oonZ&K6%bS*%=OHmHW)`_;fOki$^ zHP`OA+AB)54ZbYy&R3aa6{7Df9S@Or4s|)hr8TyhfNz$f53;k)&qCPdI?JUlwzO2Vl zwRrKI)}1#cb#k`$(!bWN1Ej8hYGW-RSQnFgleVXx%H7Z;Hc?!;LLHp4*$6B}z9I>mR^0tNlJW^q*S#gdJQ&u2=5h| zSqS8fk9KL8bH>BjQ~5{iYU-~$lZ@5C$U8QV3b?@g*r}%Jc`8|ZZCiQHd%>uq>?gG= zP@l8;mFee4L$vzF2Fp*h+?#dqD^_qz;m} zx|L#)5qC0?Z?VfN)`ge0D@R;LcrZ1^UW-;Ou(1R6SIVnq+|ahByd<0q;0n4TYl&V) zV*q8#oa4!C(k|!*a>Od0P|b?Fh@K^N$62fBqZ{~FQ|+xHhaBEDrQn1e;_MeZ*=p0) zgz`$K-mNhK=4C$dtHhJAJ(Q`AU31(;M1h9!4I+Wjnn;H^G+^w#2zv&MK82`6c?2iM zpon>xt4P=l{3xqf(C6)__8JfmHolG3{^~2a=&Lx;0llv$?HZvCEzmbpF+C=9+QXxhS7Z3JUnR|x&^DsE`MQE7Bt@Y=59p-L#btbIy_^t5OhyBCw zRe|)M?W-5hO}BTkl@)W#9&uAx>HA^4rNnm>bjE~m zcEuu>hp6nRGara?$hYk-1*`= zQiOw`R_WOkB17_uF?xBZ#nE#mDwD}_#+i~!-`d=PA#d2B*bm-6 z{{Vr8+R?WcX%g#5VTGg$Idwsxom8MAjF!gA0EYU!)ue`x(eg3fZa1-kkTb!aI=y?# z1c7zsbFRC#PC_;YMJ_zI*F5mIMR0OA1FlDnD(a3{BtR&{^A>~qP%4qp;&D<(XaRU^C*NP#zL`NQ_t7V@fc@c+xD@EKdE~6gc zYSKZ@q<7RMYwhU=jB_RuvxJH@-a7zLDnZ#ba)TTDcc`Y6zRs&$Z8or;LN>_UgJoV_ zX&ofvFV7%KB1gofRpd)UNv45?}H2(lu`9Q#{Xsw|e$r9?1^);?~D6pn(gIl5L z6>lqaRiyoqR&#Wys}g!Dz*U(WY6IO$2Rl>7KNwcz(`e^Q$cS*n1v;)fCafX3v(lC( zR1dMEniHL9+kH+AOmvop&y^zJ%WgQOQNjE|rhBa$ypm*@ZN4Prsp6IyP&{%gm8qX2mj##zVsHv?2mhvCBY-EJ>07ZPI(iXf+B5tGIua*|B zKjy_E*UDWKeJ8_TDC`^VB!}IvlweCDmQ;=wCs&V{B>=oy%PL&e>9H< zH)tfxQzmzbQ^CqBcJ0&6*1i=a-D&XAl=MgNQa@%tf}?)KUj+v2C&Nrf;itn#CzTi2 zD$47oNb1CS@<&nHA^!D!f{mZ0{{U@$1GZ1QOJ&es;G+QM-G7%?vOy!J?R6wh%E3;> zOyNkW&Bf*9iwtsHIc~OWoC?yGce7l;(z-&0S}YuSK=2+4MI5-L4HOCkKk=Rlfux#g z6(dF8W@CT_3t+db%83-(&a9&(Fr$j{wNNuK;Lpt!lBDd+E?zQbLc<3eaa&2R>z^z? zg*N)lZzu!j`o{{Rjhkf*vkF^uTVxG1v#|)e+rwpPWQ}xf%f?X_Iin4nokHqPD@?)_ zcnzE+A5g11=q?Onb`e4zF#I~FJJmeK<~WR-jL{gGkZ+aa#YB2>QHq3~V^-4-KmF9Q z4tCG9)XXvbMw>X&vbK5OHF&#YQ~e;}6{QjjXct;ZOM(X=10<8c1xqfD(3{MqwPl@0 z+L?#dPv8|JSlNH|g#ey!?iBr1{xeW4C{A6l8RTv^W3`PAwG;sJMM#6qsja$W9yKkO zHlQBbn$Qj{?`=x~JWAdMwvHgp<4DAP_wlPoJq~KhPKeL8tu6*RG3}^l9&r^M-LpZ) zDccXD?m-9pvqu{97|G<4GAiH@RH;)SAcMXL)-|ea*UP=p=yGEmGDJUjjcRvo8#{SG z6PO(RwdCU!d#tPjD+${K9iWUFxF_-C@u^#)c|2-KkK(_LZvDAwt~+uXo^j6aS912v zTuq<6YWC!3ioQ_`Ste4^q|b&bHfI2DR-5`uq-D}1Co&K=D9GWfcP}&LiDkmB@B?n} zsD`To50t(%M!me4I5n%fK`e7etTmS?Lkx})Uni|BXSM`MJWgE6)&>X78X<;lQiNy8 z;5p@}h6v9MTv*-NPaJMq-Pr1GgCl=_q8?E4SQC|Q3I`)$tnB&6%zyZIJB8Zt7-ZOc zCv7ZvBOvEt2hB8N?VpCVJ7?jdqt7SVOucl%n0NbWx`6hzA5i$I^?dAA$5iS)>bj6~ zN*mg0RypGeeU$k1RH}Ikzi>1)B8E11D#UD2&qSBFl)eeh7kO8pQP#L{QmsD!01XVRU$lO6v1@n?R+PK3c@t6I-d2dR@e-s{ z7G@-~6Ou{VNhZF#;TN5&B)4^GVWXsm2AIwX5(OI$pi&7n^49+I=(?GjE2&p&4C=7g zIALEabh?G&xMSa15=|vMAfw%jkr9vsu(IlJ){nTeyMZ;@2_IisH?@(IexjT6irvP~ z-g?fgj*_l-{IwN$(89JcysfTw5HLU-CnA;Lu!WD&Ei;@C-+nn7nE}}F*%kvFPcIqb zTDb(D;)weyQPmOlRM_T7{u;SpKH?u`Ujfe=){$Oq%pZMgNx-T%%SK0ZhOTACGD|Fq zn!Juq39E#4<0P>E0MxIdHx{yZmZNGp7C8N=YDoD25fAdIqa*PD0GCol=;QcmNc#y! zGv+9wosmH`1&zTzjL^wjy=;BR$)#XFhHQ6Hl%8419Y(Ey(RY&S+}n+!-e%(BN2`Kb zG%{GRVlsnkN7242;d+Vo)zWqk#Zy9g)xC{E-1N*lnx?df{_9U7AI#Mh*YnDn?H#Pz z;xyg`$!;3?KXc`%BUr|3MLnI%VSbp%7(x#D7$j>~%A5Rww1&lA%XKWs3d<`NVym+o zXzc?BqO>gIP9n-;x{5OGBOR%R&KHy#QaH0)BD_MUl0~Dn4nA>&xlPJXzPrYb)Y(_} zO=yj*w(TpJmAJxZ0Q}N0isn4WgN!=wikZnzB;}2Ku{IiV)XK)vX;C9(U#Jirt*uhA z9Ond8%!{lAeQoFjBBt!7duHOcvDEGHY!H0vh3Ng(EN=-hv7XlwAeeC2u9P# znz?vb<(S8@n718Gq^~8_$w(2Lfzel2Ya~ooNZhn;tT(|2I5l^2qsWEM~&C&x-nK9#8Js7B%0zTvxPW0;~VtE7^NGc{`_)zEWIMG zJex66bBCG8}#@d$jSN z#2(Z&99^itl~!<{IxpnbkBhYz@~X!lb)ooHqU#Inb5I_M6nEyLA#y*ZDCl_`Q0A@U z)g<`>D4QIL#d24ktZu*?+J+!HvMR#&rK9+(M%|w|J}S}IeWm!TN8RUjqCsCXN$Uy5Npp}Or6HrMQmH`5i2-`CRsM*%u(&FP)Ms~2=$9-8`bv0RBp4Yat zZNay@g`$%qcsBD6PgQ&44|58&nW4FbWsQbN!;ICuy+qU!c1=XzNPpQ{ zD}n<|!(rxK^G;{Dl16Ezkn20K+mcNH7F5R1-df*6RP?Kb&w9vQ8;6ww5*634TEfF~$-bVF+E9IuR z{{WY6>+2xanOlpP2J5Mb3!PKHlAsFOvmn?jVH@CzAZ_EQoto8zsNqQ&8{<5pkC7^j zeCy@fHeZ%D-=@mA)>A{~mA6Yw&uO=QCO%U18fK@br}b0%slP2a?A63>fPZYQx0uE% z8*})Ap#$!eKJ`G&%<=qHYdPdyLVs09J-l(Lc9KyTTqw!=d8p-vQ3!@pag9k8KBFpv zN}8I_t%W=%q@QgyvDDgf9Y;+-lF2-p#0`UB;5A@Wa!XdEekXvaV`dy1w%=H&0tn;6 zN4@~1jdh3_AYfO@I4(PxgyV8(aKb2N9_8Zy-Sj;)V z9pqJ%y*j_l6>lIPcz-gZU^&$H$Z9jQXxaz9Xy!lda+U-P+#ITZ4>IqyJH#3pWZu>ADX8@o#Cu0!yAjXo&Mx2f$M4vLWr#4 z*iOXq-yLFvqZ~@Z-8FG2m!Z0#LKYl`?P6^!|UVJXzXPSIzDlcBxG|MLed7@oN*Db=B<)pkFew3 z#c1YER&)cto*;KN#A%l&qzqE3NpEt>%AhF62EkU)5JpB(O=;T;g0|von_~hh$Hsxb zeQMq?4nfsWHXdrWvYs?^BqDO&KqrYd)`8G>N_U;ZpVn*SzsbnY0mW!t7xX!NnOYL; z>bz*Bg65Hx;+I-5agvO3H>*Zm7@b2aqL%L4R(CqwTa)Lyv{d_>DGbsfMZx>nphUKk z;Ai&<*;+1X5!4V<3`Tgs+gc{`KiofsXxC4nw&>|2gfd6=&@OUvDn@&Is8VQ~BxPJ= zQnBZGJ}M3AB=MzxNd7ghIi{1wq@UBt<3{9uJN#95H%xvCYI5Iu$^QT@ksq3LAE`xJ z&&-oT-Nvl1`GN-@kkbWgqx(=0sS%;c8(8n3o8+2FrS775=e?OxmoNQdpRJHLg>=^a z!;OAO*M7DtfCX4feWY`Xdvg;yGGpxHU_7Ry+|i_BiJa9LI*pyu}xYA+x3=QkB#lFE5^L`?tlKp_(YCRLQpTGC z-r4m|LitjiTHPD*(yb(a#EGglkN*JF+!ZBv)C6Cv&s#;KtgJtKCj@m0v$>Kv6-h4ij{yzEfB`5O5My03K4tuVvQ+vlu2KHs&Zs zYX0d7&O%Q%c5;_BZKdy{w85Q{=K)FD2b6Z2y3z?GZHJBJZG;`6g&)~oAydfWvy?dK zry*E^R2_u!iWD%ecNtGU@uRhNj$>aY7f8c-f|I04B&mUoUo4H41H)|6v4%G}WkPlX zS1kj?LKjh-&}O27iJ-3O!1L*-a*X z@{e}3OA*Nj!nI{@Z>&I8nmGp3#0+~+Af$mpxdjR?slngEGdSr{R<^mu(x^vx6tRUH zPm>-7KJcN4i*9f(0K`eLcewZdeje)Dx!j@qqvzgTwAe_oB%M125L65deCNt5sGoVN zc-62q(dpvokW`U$$h9;otTC;wHc{4%4yQLGu!yKI!CFD}nH9E$G@$C!wxnf>rcS6QWUb;aOolY%Xux7Rny)8G^n9Cx1>59rq2CpKH%LDb ztsyQyxrvA8R;!ljvbH_Z)bSkfHG~V?1zm&yU8+~4UZ&7vX$!BT9RCk{>?kyQ%a?^!h}b4U<;}Oby-w>ZMA-4KzLeK z1Ff2`Ai4x_MDmRGnAMH3Jd1UOt&t7vrjF_=clmSv}?o-E{t65))wk})YSGX zgZ+eKxhn3(A%Zq}WG7H6D9I-Rk&9e9$8Qkd)E-1=DJ@Ie!~0e;k(peVbFp~MC8wr8 z4K1&wJ{24!Zx9`;?8rq8pn@vZ1en?m(bQ_zH!m0`5yrcE7d4`+3^ubEipw!ak-~-{ zMQEgoXkC&-VYOLK2&lwp@43s3gUX}zNMX}gXp3?Hni-RHiOTO1IK?=7i>aDfSOB22 zoSXqe_Ll4=L-%Xy%^2&VI{sv5@-hmH>agB5u>DCn#=}P$82N_D5P{EM(m>`ZM=I>0 z99K4yEU|(zgyRGe#MPm#EM>NgqZnD7$M2OM1JEb3L7ueo3uOd2!M+I=o!yt^2Q6832?@}Nf zW@X3gqL|~>$n|EH7Qo|^*p(C=iO;g4usrtP?x}5HMg%tF4x=|oCbYOn&};~p^=B0` z&A*Y9LF!*^Sw$Sz(u-K+Q?IHBEXO>;k=LUlpVVlRN6NS&B;?NhHs(}dt!mgu6feiEtkq_&e|ff( zS~kWj7@%pwyo5^erqaqPEncJE4Fr8$VRaO3Cwm|Y)rh+S6!B39o91vzsW_vOjT}II z?Wwe+sl(*XqGi*tX3Ujb+B^hSn%@Iqp17qf%&QBU%K8EB(h0i zcb3-v%3-Q{h!opOrOZ6CL6Gay8x!W#+ngFs9n6!pLysv9UfVhBch#hF5%!X3i0Ukc zqtzS34nND8=(s%R!lmmbu2DeW&ZoRZT)=sn59W%FHarI}{$900pZ@HBl~CIbM{D0? zLXrZmB~Xx=B2a|^{HN;qqa69pzWIPr4v}fV3!e^(2!!&8fDnp5H~8fB0zIIn@9HaCU%B z&?pWQw}1)<%X6v7H;2uqF3yZi>rHGXGn{de$@3G59LV_M8wF1YUMB#WxVPxpQ=I+4 z!_?H#Ft!z~^Roh^8qmw2DU|Q;}Cq8r(vXf?3Ld!#KjB-sh{t zl=3kfeMY7L@dhWpj85Oa#6gjD+T<>5JnwnSs7HxwaCWKt( zWM9=mM$QFCJ!-Y8e+@h+nKH-f;V=oU;aMZL=-NU)Kdf)i-Z7^ji+aGk!2 zWCS+n!M{dDdsf)d>>wUm(>g^#Ootb9W_8N$ zmnX^`gc1#D3yYVrEhJ)QF4!k0$|TmBwz9X>8>2}g1`WlkpQ!+M(P619!{>`B@mNZcG|)vPumBPQS-#1tN#EIv~W;%2>@gH8opjx zxZ;8aHKp3}>NBZ$t{kh|$+5HKsM)?M{^d@%16BYdB=Bccnvjr3?{0bPBCKG@I~{~4 z)^^pyYh;~Y*s$BkRMgXv$i+wG#X!gKli{mF=vU#ZPy0Ri6%r-P!*I4bxH|4P{L#1O zwX>P8B2;grAv(IdK^mk~#%rsoOp?AamNmYMvz3nUt+LEaj(Bov#9mul+$57*la0|- z?OM>{!p^}QMjKSh<@oh>05Pyv0Kl!WS}q3`w-3+0PAK7~#7Wb?LjXffrhGdubz;WVgv_n|b$RyrPaxHVc6m=V~dcN&3JMj+)#$g)$E?u=O==EnwshP0jG$iNI;yTx4>KME#GQgi00X_8SIR`QP&|V= z0L}MQkj#07MI?iJ>9c*7#}T&pUiIsCl)%8UuKq9u9$AG&AL6uC$z1OA*Vo zhX+@ZzE`RYX9WzKe9$Z9gk*mWv(}iFb_aRVMP8*!k!QA$sS@fHBU^*Y!KQX9Mi*Ux z$3dv06~+1zD-$Kxp>5oPYQVsl@M7Y#F67>8EEX40=GZU^<_f2;-mXZ zNlb0bDrSyYT_uV|I4a6=2%&04nI&Y3Ek|Sx*j4L9R~t&IWob zMOe!nl)Ib6^D@fqsvF4_qr?)UGDx{2dtp*35h0#Ecr{Qc$Uac0l(0K${=y530)Se| zq`T`3gOR)_%V~3MGe;`M<#l93gS=`D+Va8o8Rt(XO;*~jbzU;rtNFt1@{uqHArE= zn`@oCTi-tN_tEkY?z4LZ00N8;pX?`F9qNz?q&aNtCp&(29!R76J6OxP{wAxaqzW)H z4Fu04f2A15Ije~GPza0zl1Z)RAyl366j8vZ4b5A_Z&hz8-Cwv@*g={oEf(CHaVoPy zWzX9SR-Z@FXHjBMr4^VDDb-H!CWdjaPz0zrJekF8!iDKIX7d8e9#zV6ouD;nXX4v% z{{RJOqP)^K^6N!)Hs4xSikR}DLVeoNH<%;$B0uIiDT!Brmb9Ez2A>(G- zR}fg+!5*rux_Nx0$oV-Ze;A=H%&qmNB)cAWYQK#D{-zzSdE|U3=%|(yCvN7d#kpC8 zz?u{K74I^df?_z#Xg%Xr?6~8KFND>zHz-KQxmMD2BxBsDWa$a;R4vkp@YH9rqqtWC z8t0`$Egi0L2(O*j(D7-DI3K|!3MOg&Gg{{2`7DG6mJ3ZZ!3vn`>(gu zYR2HiY@kbjsbz8JQO^j+J_wpJRISe(F z)|j}lX_r<}85*t!?<)cgNM1=CySX_fiOD91?X28PoS<)tK19k0ts!$~I0biGd0HpI z-x*R3NY3uXnV6DEJLHp6YT93cYjeak&ye10P$LJDfasxK-)XHCf7W~}MPGVDS~ltB zwX~8{Lgli+Y&hZ@=8owjv3HO*)@C^**0AWMV?-{XF4U}E(0^eNp!! zAMkRu5&JX)VB_-k@tw8yi8Xy3a>(y1sXKh5f$A0Xi4^ZLLhQX_mUS#}p^zyje_&=5 z`=fs9ykzwUHE%O=3?FRu1E;xw?*IFZAmryr#NqJ_&`x&K+xQh8%rqlkd+;u){<%W@O zzL3e@mGYsF-?O*)Yvn9GHc;2fNC4v&FxSfKmHUuer@3D%^waVr`01F|8+YV{jC%>kDd< zr-1Q_yO!qWcP}i`oo8dlO$_A@DEU^?WqFR#M`I{8XKv+p-S<+*pAHAjBxuNYH`W;p z4$uuXniNbdQQ7$_I%TOeYA*q6XuC%yHr4b{LAmBx z7>_5D85?Wm9*C5G9Ht}hSIRvF5|h95y<^k`e6Fy$xM;kH)@YGQ;sCKE(`D_Pq2&X3 z*6|tnhCvu2vK1S3fbquX*lUD*^1EMI{{Zlpqjn=MgJb#YO2?ix>EZho-+$%?2+04itQgkTA zPY4E)TZd>PxY8YYiNd3ky~0CP>3}HS1e(&?$Cj?0`I$#}si#r~K_N(~%;b%dADZDW zwDrP5R#bN~qwFqa;8Dq3Cbl)hgxSB1@@QitTdt=t+}p?`ib1}tyT@&57P)9ttV^m9 z5FPv~*Vz~qtbU6ajR4_uzgDP6-8BFMkzd(O4tShMwFFJ9F{~JnI>4u0(^F>M@=o8M zgy2^I$5j$b9j3SN6my!M+g21<@V6kU?%@T}9N(FxETVCt#y- zelRn-KE9#kCv92FkKGR@MG5xrhMM4diCTitFY(6GH(p7kL2IV%gi~s%RaxM`^{x#x z#(N1jcv3eYm?q!qYPP&z?<8+yRlLKU+xe9gesC@NiKVyn2x`{Zx$VHM9T!64tpd~% z4> z#}WjMPPxL4noBLY;BoI7iXZ!*1uPuIzrjM^w;=Z^;2HioJStedB(*d<{{Y&{<5w_x zS$-bj59OYv3O`guMox>A3=bMI84ib;dCorj*dt9i&hB96&WspsGMkIZ#q zxQ|tj0L6TqN0K7uamEP6_EmBCR8~iI8aE*s~5MPCm@1w$itqPt*zMAl%2Y-zpQKJ z-Ho;5?Fk*kOc8hk17G-?qOr=EKb5Qh01#u9f8|6_b&&ObF5^ODlqR{t<8E2Nsc)sY zkmLyM=UvQmsI4pVi9du|mK|(@q0{%D-bJXrOR8$@XuC_Ba|E_hv~kNj#&VJ>YdP|A zK_pg~UriVC>q*3Utg>6LroIJzk`8?01?(GaWGRq3; z2^(WdUwydNxK1eH<{}j7-=U8r6Z7vV^_o6J3#xhBYk$G^P`g*xk%>I*wA7+CcU5OQ z$_c?e6{P-=rKFZEQkNU4l|I`#jfyCprj!PnP%t=`X{#XEP!8R-3n1ikQXuD({V7$5 zJL@Pl6tNiJagn6}E3}8n5%HV?O>^~9GZlT)3JEw(ya3cUJ!(HyxTL=vq2R}?B~Nj& zfdQkpAh7$gjM7`=oR)Y2Z1NK+$3%=yw`^%9|$LKhM{c)1ew$AFiv}AA^H{21jq+}3k`XkQ*Rs18G-A8e)BNw4x8qyJT z!aQpB1I38E8n008-Z98O%&B18#}oemGNwW1P3@Mh5dIS$_cc5|g#2}X3{ggfsDge|*zfbjE$HO`@1^g1Fn; z6()@6{{TrUSV{f{Vcy1(NaGI2)G=C&dBDeRMKuI1$Yx!jabe9_%ba9@V{CMZM|DLw z``V4_-Ksa_1sB;VsK;3p(Dtd2^AU=NIZ{Cn8Q$B#eqr@k8kG`I;I6p?1CMV2ua$Z^ zTUR7X$_8=98opL&AUOV$Uo7mop>+LBYe-sN#`_vIkV;g6w34*ih{=(v22RY>Qp)VW zC@;QAz#B1I5fa{V6iSnsR6hQz<9O5oNd#w}kMt7jNB3H|RCWB@)v+WyjJ-R=(d899 zVQv5uLo#c|_}(OOiYjNinWdLhYOb@KoC*NQgI+w0`9guzbpe1RV2y`+maQXf54E#t-PZMlx0ig4zWXU<4TJSXX}f!=B8EwB z>~|11AsITz8~4Db5L`s!Naa8jhFzfStsc@m@#5iQfnQd1*y~y@6)3ofx7{{TMnt!)sf9&^^fqZOrPw}#EuJ<0;2 zLRe=cws0!&UR&DT$qZBM^3@JAP^r~88!I3xoGwlPEn`Z6DWn$T_GwL$OH_5=Q&A@9}3jy%vred@WeAAtbxdb_Ylv*f>$8MU$f&Tz1FJzQ# zIfN8Nzav@RPrmt>5QN6CCtg4DxW@G!;1P+aWRk2<&FI_Rz4r*(N*I!Xo zfh#kAc>4P`e(fSQ-2VVZjNh6#su^?0gV{+s^bcriO>@f_&v2-5o*}D3L$`(V-Y9=Z zAp@GYMMkZvHA8Ta2F&nL2o&n!5q+YpCcM13Vk3w$sW=%sZ~y>Sg__QMYXxQmGV!Yn zf+`XiAu4mdUs?y$arbDQyTlw-{BzpC$lCjm6FE(buHP`iP5UViDc+;?PDe5M;CTW^ ztX)))q;C>v;QdKC2-tt%*peTlL(Jb;4r5#U#G`E19U=_Y+?NrJqt99c&M)j`sP=M8D*N>& zq) z8bZ7yCuKa1$@QrpraCM-F-pcpWd#2K2&Nd1$-tNC zz!khcf3!*H750lw&`uZTxRxOx)qO`RtT>K0$ILguYD^g8RbmcxzSd7&eYPf?A-I*_ zXqS0>k^LDJb=57v_L{qM{{T}stC{*P<*SR2@sfB|)z3xScva2O{{X9R3c0%IyJPID zo4@g-dnuUT+Y@nM(#|A1s*S0MTd!4F#A`;tdQXLD$T|t|ts5SG^=Rb-l0ZF`WWaJt zqOGR%t596}Ri(Heh&_~UeI*-?maT1BW6oQRb{gMPPliTMCyW!c(faAEIx0m=t4A!F z!zw`-WnsiY!L1)_Xe^>+M1vOFji`8)(eTmJy|_O~v1k#)(hlQ!Foh)~6@ z;V^lT1ppFf-QfV|$$g{LYR(sp_9{H99E=AD4PPcM&nlSMb%w1Lwu6MtDrcz+8jwth zF07I&6#%xvn`q@+Wqx6{zOyb+@i^W!vDB25aWd>bG1w|o9QN|8u{hsWLy=QnvfV|j z!CoM#AlU1+GrVdYDBP#qw6pk%nmr+BUm2vgdas#kdx|6&W zb-kjbm~+H^gw@`w0uPlNKkgRt(3 zdyQp0wL7MAxEuzj62}^X4nY+nH#n`# zdB&uObkxAi%cy~N@7E5h4rL!c`8XB8G0-TS$k{lq1p=Xm_E4gn+}n+~+p(M)w}YbQ z+xeAi52v<2=2fIU6Wf0>tl{*w-^{Bx_VG<^{K~b9rdw}pwQmUgYhnIvTSk8#+xfMk zlYf7Aw;ugkZg5K?jU^0nBL#NZujCW4_<3s*XgkRR$cO9M%KPpI?tbv7B4l+8j2snZ zJ3$3=u+^Qu2ORPbaMD?)J=CAeucI7q>NJdcGZ?KCG@EgdsgAO1L{5P4;wp;=ibqlF zRcaAWresZ#q?@dE;O{)yQP8z4msi(w85Nx6!o(W+fQAxlP@+2SN(v|YpjxJ-ygTAFKd!gcNpXY(yW(he4lwZQDuQvx{BLDhvd zXEP7Hi~5MI7dZojv>ww|tr*VSv;*{#YGogaqusA7@#6mgN`g*@L)_I3$3{ zjWa6iouNSKHK1>@P6n)H$6Dl8fWJB8g_L!*BsR)aIqCI;jfFqzgGrEvL6yGX@AZo^|q%9!;0lkReG(J@wq+sd& zU(s(@x<=k0pcJ1-Pjtlb>-inkc4e0GGr2Uy+zBJ{&@TQNh}F+6M9+1)2+}Y%o+`ex zm6|Zk9E&7nPZmI6X*6mr8k{7iHr&qWNDM~9wT}&2OBK{^cTn6UovaTCJarb_Z;@Llip68*$>$W5 zyG?m6IC$$I(;9k`q#aaL(A91uLZoCTgd9}t+=mspo_NsvDm!)C^@K_Yr;XDj;G7yl z4e&YmD=cKP^ByC} zYDFA=LmuKNXYOw5d&OG0Ct)wTl6y^4MP~#{m!3xoiU%7tG>luweJJP$ph7<}q?MXt8Bi|3 z80sSA(j;VX7h*Pf!!%-S43ZEY=VR6@cZZ3Cln0n<){(ejzvb$;>IR*kp9}hNR;qef z`)VyvH-OCvlb3LEI>8ljb1zU)NR#y`qDB@=B6=^NCsc;`u zE6SsPTT>#mwv}MVF6Tws>o-i3HECaz0hz-25LiMp)b)GvoVVH5zO_Zq0XmRYnY<1C z`}+u~nptFG(n#d9GZT_YHE4-$HVI~Z&0Fr(_KA=0PX7P}e5TNmbIR57>q4hC-uD&% z0En7Bas8Ysf6TA^MnjL?wjL(5tk+0>w6aC22dHeKH%OZ!e+Irq=-X?S+GZEGYITnJ zQ5jua*U6n7Du=-)Z6PK{9^0DGdI<>x2M|u|yrXoPg=sl~<4}XRR*<$L*$P7QS=u|M zc90rK$=hEj?jn$ZtdPZT7zdD~V!l{uEFLS2ewG80!COmb_tg-fM6-lfufL+kidx=*d?^tO2m zQ|X@3_8NUMeCP2M{{Wic@yJJe*VDHC^xyBOgx2>6QdngsRb@N}ex{cC!LntsbuBDw z#gFR}w3W_Ho>xTO2F^!+p#Cy#-XLFZ!b=D`0l1*Gn2lui604A=S z@n%$S@~cQpnBZ9ycSb7tix0G8gtuO%Ye-LkbF)!TNmUyv{oC)NKX|LP1Cj`baTRnu z5`1-ZJqr6CqzCYa^C)W$TVy}x8i);;7_|%fT8y0#x%Qefus{8?ino*eGa7>9Xx>{) z4zLYJa52A_h^^ImOi$rgsT4?jwv}X1I>iF885pAxg3Fzl2Y7tb#SZ7PRFpYcHs?7| zYf9)lI3mDQNH3oQp)2KHj&=Z$vFLl54_mrcnKDg)V~_s;C=nxh?=w}x2(Du(Alzae zN#DlG8`T>1145&DB$0xmASIZd00khH;{l_v3^hj)nAG-hz?cz0lMsprBCSbj+HZ!w zjnS-q+SK$?P;>WTkoVNmyITV93&pGCjq*9D5_61K%FC;J$rCc$#|b)zizOPqQ0aMj zkPq^zZ86@k#!+s_)!1I8(@*M!BsE3WjqejAknA;YsBX>Po+;$h^t694%@l~Auj30WfoOy>d6?w)f&{1>u zVP>f2^bO%q-1Cch4|1A{;B(r|@K)+M?PmBWzo3wNl<(+d9_=%qF~_o;1IiwXo?zJ& zcUZWvYhfGecgN4p$B0nEt!V3sp@e?cXyRs6`^*L_YMtZrmI_alSAQIpE4+C{nFs-y z3k4{|U>xs_gB3JmZZ^uJyw$`UfBvBbNuE4h#(ml?=~2DPk^Lh4HFQD#^bciTbZ+b6 zS26V5C$%bTfAMHNd!0{inc@0Mzpz!@9GH<$wy$6WZ{lSh+P1dc{{Z@!C0OR`Q{{Bm z%I5=uDWMYM1BAqD^_ky z@-EWV2G6HhzB|LwM?C%<5R>u z;p0-r*G(T#sXro~@M%H(2+zA#;~3BM5%2bD)jDBMhOOfCW5TVGR02zGV*m~FP)3$k z@{P?|f-cj5&A*ayKnIRSM#F0iso*>y{KBH$3vAS&HIMaauymS?E(elF1q@^7hsjUr zXMK^zIM~oKOv4gi$i7kJgdOGOraM~6*Rj}Raghq96;iwyFA;N?-chwbc5X#Cp_}CTbAdM&$g@(^W^u|ih%N% z!L1c(=s>L=oPs#x$E>Yr^MxBTxt{4oJj#q>lT?y58ZnKSCyO+o?KsN#iZU+G?9p45iEk=G?W!=>t1{M7IHsJZL#tz~h+ORFMK z%Npln#(r%JGwC?tmObU%BxnAy+JGSNQ*0w3VusPbQpAKDkmQYQZNR`a7EE`4v`m6O zW#t{c6=M>PM@IDuw2U9(hg-N+ov`ILtZEwp_pO*zS~L7RQPF_%TeV+V^9Y`~^ip zb1IJNxK*o@=8XH*JLiv5Ph^6bl<0#nPsAY|wFu*z$$lfGo zj=@N9*A)ZW=XCMr?!ER{~M{#(5?lyb3YM2mb(W zPjrHr<3Yfdt&Y9WioU)Fi9k?#ovQChvEnxjJCxGi^xYl3Xe!y?JS}?5j?OC)jw5$D zANrMbZl{*wWBzEXv?url(|1a0W4*&1nd@?D?&Fv+_kpX8Ab#wqzQJEiVas)c&UF#0 z&f-DKJCJ|u){236wdY`Q`6|NLIQpe-TO&NrPyl%MNn76(6c!tB&dLuuW}>%s{`GWl-3a*Yt`Fw~n|4GHb_f zz!FzuNPmcmxQau=i6ik9{{X2FAFia2wzZCiq;t$y;;7vAeXUmWA2m<5v~|M+#~S$7 zh?mhPjcCbFIwzic-5unOi%u`Yj{5mkGoM_bjE$DNk2;X0YUk-4M!(Rkx{tkQYtBWBV+9p z?7w+jdreNU=X+gQd&OMCstFq94l+4$M_7Tph|Sz6%$(86y~3xQ^vm38N9$ozw`tF| zq;uvsw!V;glV|BQmEH9&6p_M48`dgDc3ovs$2d}Rfky@QXNsAYRXK_Cv4vnvgUTFy zqJYmGZNSgY$R-UeQxm=lHuqIZqoXw`BOBO3?`ENr;JXlfrECr?IRc=PHU9vgA^tt1C2kv;0tqqi9D z6{Q)6Ey*3kRr+K90L#yLtGHX9X#8qxFYzf?Hb8%T8`)R3N3h-XabSGN>h8#L00+HV zM+5g#U0+sBUSsb|lSTEo_eaN2H8|=%+|=5`i%?M=kUND$hn<;wl+-+!61F{Y)w%S? z`E>#G-}`lKpT_?H+o%+NF+Sj^AoH1zaH#FOQq*vcX(yow^QNN@RENHv6XvyQQbc*h zTHBvBej2xnu%8V^qnbGW8rk@9d^B>BTt4Q1fljLLathlSJzUhlgTg*uz3g`Mfxt@UA-nEjK;YB<5-I{w6@i?t~CE9m_*%j{Yf z-{K`1^-#C}02MVQvHt+6nysym8Qt9XxT_m;xBEsu{aC}%pKH~eU2XAIynMuGzcp!BDmXe23XVR=r9n)+4&K8~N&E8n({20ndmgjepZT>Lo@TiIWkoM) z;QRFygPo`TV5FBE*B=?DLHC{g<5R#`4dPc+bCFTn1|^FanmL$)xGG2NfzJ5aaauS< zjfPod5wjjZ+s2k42MgUqe@LgZ<{-)XY8adD!zk}HXBp^W?W<%5kP4hzN4z(+obe*> zv8YTCMw*c;9LnpQ<;_Mgy9<4l6nAIeIr_Z^N7lyqKe{OlcI?S{YaFFZ1s}70rCXA%k-X@?lU8Uv=zWmYn&9%q z{ED$10zc$clXg@26=-`a{{Zpo*YYR-0LQCi=;QXnDgqDOgdhDXx0j(4*&SQm0C<#q z6%FC)9qCq=hopNJp}9QnJ{peXdEk4rvya4I3X);ZF!*Xxc^Y)~tpvv;>U)5t7(er| z?4>y6pV_EuPmao;WqhC=mH2Dr7pXoP`9=9-@U1+jo;d4FgUte->b@_{YARVzFVUm; z>Un(xBfYA2^Y;|)&0WRmqxK=G!RBy%%DG|82Zde9*h;)7%~4Mdq_2%;d=N35or4?I zdl@{4U-XezcD{U4CwZ%jKzRuR)@oSK-HqG1>Jq?k`D@dTLVL)qA2eY8iT$#-Nw-k8MORw20WttPC)p`HVY-T{YFo-ea+2AG;cj ziYqA^?RKOJqkw$-E+QK_c_v6HGfi{OfePdeexdem}(#wT|5U{$*wR!q=U zpB<~`DlbQgkZF9w*6Z}o;6uEaj0C1B{ zyIJW85*gzIK3nWZ0Piq0Xx)UXHay6+XvdjWty`&lYRSJZjaZY;SK_M?>h>D4TC-ZS z6==Oet$^qi9xEf6n0FCUziv-!P}U=au|4Lc)!D-!QCdRXM+fblkLyudN-ev5mt)ua zdfa4*E#5s~xZ5KQxp^^;nsmaT9zcoC&6{yM*O$w4lf{>r^px79HSJDtzYl z8nqvr;j3UO$}{+YR)~o6v}Uv{JyBMOhqE18MFrc!8s>FTts1`asT?F%pU17knsT@_ zOUBzIUQRJg860I#7^~B3Z+9_SB^V8{O9fJRSd3NdvfAGX0FGh|h|&DOWsW6I0xA;? z=tgU+*#Kc1x!NKFe1XGHB-2d#)6XF+@;TvRXnh{VTM}Kk*iY`pKHF8UNBhuW%7AybF+XuGWbx|IOI6!BvdQT(UH_ujb4M!()$Rj-~E5)RqV&^nU~gN zHFgoB_-`%OMI${|0m8s_FMIYwWw*a0Ua=p%K{#87)A8u<(9+%I#T0?Z^ zZ}wBl+|A(?Zs799=iL=_SIxy=f;x|NTumq6mPhZvqbNL@p7mW%%n}3btNDi^R2{(8 zzSPat)x@)f-u;VL-qi@J3rF z5TF}LC(Q#iBP71P>_tT%l?x zoqx0#$3V$76c`xWCQ#>@^=Lp32C5kcsEW3Y^S_j6&r@cS*?Ab8(10FD%lpewvvP<) zANrLVCO@>ok5M%?j_sdmsqDs1KC87|TXaVyTtZJGH;qddWcbvO`iFZ^iMuD?sNx*K zekw@G;5w>(`j`RyAg6PhWs{M?Z*4(u3kKHKk6t#XglUgP&iBQ&warayf=B*iUKN$RVWpn9A$5A*uO(uJ@K}S4< zdnl{NIwW(2ZE}u-0P_)F(ZALLj(9=ipmQEFc?~!0TX-~4DQaM*W!mZ%+ejrp3^g<{R(TXH>(;lFE!J?w!VC!yp_J`3qLD z2kgiLexXW>%(RGlb$!;PZiiAoFj|@+$i_c)%_I%hdyPV)n<9s1k-`?Y8~2785Cp2K)lEIiimsI}vfO1crg z4P~HmOiXw2#Cb&@ApKLtTQV?57_I7jxY-jP5&OUJ$OX-{+cb%sF^$00YS}~_@(&7* zG0}=s&J>YS93CcMM?nIa+wUCG0u4o+53;#6^rVkh${(~u^ME+3$N&Qxf-uYg-`DcT zs=T%X8p^B(UTQ678`{l^k5`XZ%B!awSw$YQhM?UK?=VA}wU4Ga{3-}Xm?mUrP4ik_iEz&bC&iYr@C*v@BZ3+edcrC^>Txx`(>>lgYL9r^*OC4 zsz1EXsTQ<|a!vv(6>fYsD&pm{yrT!kqnvrQE!gND)vN2Z^AP(hN!@gy9?H@Z9(5iS zarA(*g2#n&pz8Ed=NusX`v&_r5Uo{$5KLjWotK324jN)&Mmv~3wlpzq5!;=C&lfP^ zq&kbS2OlW{r;$N9Km0Q`PdQ4va5-$-;H%aQ7a3b3G6zLpKNfz5YW7e@`CB8luO~eA zMz1I4`Vp(C$2F0w>yyd!BUiTrlju!X_kZCYuV(48dc2qX3sv<00N|RmjNLX(T1m$} zqgIpu0QRj~P=AGET3$T=0Qk&3wWVP{^zY+ZUfZS@9p<#nr=o8f($;;9cbd|6JwN{d z)oCj)SH)UJ%hmB!?UQwURdZv~2aQjo&qXGWZc4JV_4^jAZ8{Opx+*BwX9c{THLcNY zFSODV#7dPQ^w8QX>kZ?^DM?29!k>8MSW(32Jg?#$WBv=Yw7HUX*0q;rwY%@Da*?+c zq%E<*kVReDcB<;fw^vqOdGBhuvgXzBaaT49c=sTE`oFXEKl_z+V))~LAM+}EWjt}L ziTWiKqz)sMWJ0mZPO_bC^C2driTUm2&#jFBe*!^JhUbmNTCwvQtz_v(HD>;e>dbmM zOE6&1&&6&=S+ zf})qD)`JdqT0(lN2{BN~*)lJx&KNNUX?A;{~vaaO(z7=ris-}JY?9&k|-p*Vd z@WIHzrqn-`xeb2J{{VE*%Q3(dbx{Cm=<)kNc}b zRU-g4Hs_M9nWTPFHAg`eWiH+(e;TuwwEi_~FI!c-gQbN-CvCiH342dg?m5Y*CH&O{ z-<#W3()F;bc{&-qY6<&q<50=zAI7cUwtQ;ZW5^x+Y6-_Pw~a@4+mptU@3$p=C~}67 z*V8*wQ(kqpZ#v2`Yja$iaJf{g1V6zj*1uS>EX_P&VhK+-29tWDP8H6{eBeaVmDB^!5 zf{tUfnvjfa7?gYlrI7PThrKFyJ$;>JP+NVp^??FKOL2Gi;sh@7Iv;@Mx4l(Ow;F2k8_HF$dr*) zi_?8qTEqwPT`W!JlcmC$c^vOQls^Nd7-iUYli1G+TaFe>FXE);2*q;>TFwRU7cKyM zE+lrZwBEDI-|nwU56(qpI2BtHP-hC)P6x3l6)wWkz zJ5QM~U^fhc%FA)_)hvLT^?%WHyRdkx^tbHq`37y}aBJ_n{y@n~*(wuBVyUht|4tzk ze@c05_1^izGlk{}UPxg=Et1)qBKMy^-s)!XukmnXxf_5xt(c>NM|;^@=F7RNBj|RS zs)hKEBEWOq3;w66b=Cd3pMl?H&SMLy+@<|TGup9V_-K6v(ZKr7^f)j)4zoqIGo(urpbj4qZejf`(_ zTdIDCm2s~XEC7Ek!|d_i0rO1f@u@Fb_rv23U)HoGet(axwjcvn!+c1^F?pHj)Av)t z95>@3wW`?zA5)W)YfpMjaeFzjJhfK_A2^QDe4@`aT=;xPKa_9z2fF6_UhGw*dpmchfDzb`!z=eI^~V!& z7m4<*`!~FpqoPM9@%*&ys(|#ON4wQJ#t?bm+}^zU z@mG;(W3NU>ZgI*IB2+VM*Bd1lCu z4*-3)1CE7#`czumx*9;3{xZV>`ft#7PGk4B~yZu;wH6rWhcw+Pky# zjpa#TN1<%Z{(hOe5HF@EeK$=LwuRvav>6;!3Lym)A~zJY2ruaiug9nY#Cx#@Oyxz2 zR3l3F&qx%6-}ff8X@^1bRiId&zv5;o(SC?4sL3588Oj~&R9W8U!x}lgc|VKIC6@xY zXPS_O=M*p&l zPMpPA)%2e$Ea*wox%dWa9xx!4kp~t@L(2(__hhL05AI>ALczCtQ>SK5OsM(43=?GX zT$1qHl&jL;$EGAIB@&y;8;P7~Q$=yT=}&qu%&-sJW#WGr`!xQ+t(%4x(q5(Y7CHt7 zbm8-04Vs1)Sw2Skuhdph>rl<7lkMUod`MEDl$s5&5BF8pVw{a53BI4Ios<51#3=iRT-eX9I#V_R!Q;OtwUPYs8ZT}sKSfLHyYH!+n8h4d7A(iLkV$?;ozH&LG|r=u z^Azx_{KKW8XtqT$l`FdJRz+(VU%j{X`i2i;)Out~r?YMP1Bu@*l=&WNa(fzkU&i^K z;hRB82RZAGy;5wVh`mkOgn~4Q{1A7LVsg}H^#RzT4T<%M3kmC--Sk(I1fdEIc}B@Z zW>`=Zw%pgC!On&k9Jw7qJEh91U=eAk#FyEV35Nnp09g!=Un3JNKPI6Vp_H^o#%OF6 zHXcdG=laEGA9miyiNx(1oR3rv7R0~nOwU?4(6#BZ962Il$PZDeNehpK_2}C)2YwDY zk<-0TmPTT;y@d8A@=Qi;tCeVYYqxpg_Ex?BBtPU`MNu$=WoASF^W@ z-@PQn5PRveRL>1A2d~hlLg}xJWqID&7z{Ld=2w1gevAdF!ODVQS;OE78|r6ePH{Wp z4H~OCcBxe~&e}+`( zfX~kExB%bU_PC&e)g)rJvg%dD!|wfK_{qwe+bazxN$4V3Gq#qeSdLI^A9QHjRDx^~ zd#))bo5jy0BDLD-mF01MoFe6${Ru^cPIbX;l&;;XLA%N9{HBM|JIxOxWm7N_o6$*@ ziw|VX4}=jrC-8o*>UxRAyx3o#*1qwv&-6L;e2Qq?3E*RB=wJMlHykT$69djl1Wl31 z{$j$eTLR3@k8ec72vbGincV%kdaNE;m!Zu1*32)zI2ZCZuG#)UEB@giPsXyBc0h8# z&^sB8Ti{ZEfQ>qS>p)w)gmzK&gLi$-30lCp45QT?H-(w~r0kIxzB$V+{8@#9C{1Kt z8nmfz zp>2+$-INGyr^p>poZDpY5OT9pec@+QT`clgkw%X1`I?}sI>)m8%(|^s$E>}!>~oWHbu9YyO-c8AyHoW%D3QEdugaa~qS5*CL@X_&>mt=ru^e)dw2_DMco z=C$nEkvK0_zV2^`4;sXF6B;m*1z@(^+f-{#!=K|bzIH!o#(UpClN1Gnx&G0DepO<6 zIN8~y+?=?Ydl1VpjV*4yy?*gv`}_9h5?c(0!Y)I7eY%8l0(wR-G9q< z>ZZO0f7AAG_NfZGpSy>ZI0HcV=O(JTBk?07qo4oaBw4mS1MgXN6TUA0syGtwDKcHf zdXk}7nf2^@%km>>NEQp~66#h7&+@zT#p14gI5~gfxtd?=2Jcj~-V9Id>f+wVHO!Ci zPSI3X3z0>3wo&-ok>(vPq_k6b*co<@8K@jWQVQv5Qw8IE=T|9vzCWi>IZL^`uSJ_T zZXNCU$miqs{R>Aa*)z-2)?A{vkleXJG)4H(7yOh1Y?2&_@!H5PkK?Fi-I827zm&ql zu{}f#_~l83JMD>)_aN#lM=^7Je;?xa6#7++-pgA^GR7vk0|><($Ek&Fw{s8V+?js4 z3L0xqic^j{zNexdRT9TmC%;~`;^rlu`Kp$y0!FG_51K;=&bs>y)>SVTkCV15Jxx~{ z3Vxw!${fcjlzqOr(kM}i%%MobFYkNQBMFHyR9LdtB@4l{&-uoAVvo{CDnGX%xC4K} zPfi5ujmr6HIY4Q=XQcs3aU^pLDMCDoow6zJ>S!TPEW|=VrHr>W`QSWE8P_FV8?r+@ zqnk{u>B0xiQ7qL4t>9RlckB5r`|~cI!Tuqa#VR!j%t=MVe|V=q_{4~|zG(P`h>(*U zuK((UNk^?)jc>d(WTtgVy*c8t^JUYa;pE8eF}A-*u$FmimU-ird8-tCkKZe4$UzxI zlWA^qk0m+6rXW8&D|D;tq}2=L4rr?zg|%ipk9Ut;Rk9neid9w~p>Nh@{&up!4!x@? z3&9Af%}lVnlS^e}q#W`%yK4C5mltJz?a`LqGR*?Kai8SObw74V=aE7%#LoH{526c; zonyy_LqLE9&%rjz;bD<;Fc}4p0QU|N8wZJ+>mx26jRY+>J~9=LB%PGH>;HtwA;80< zz(G|QTzP_-pg~y*8NX-3;$k}tKoR}vvL#Csyfc|RvY>x(&E3Uu#oG)7;@@+gG0ZFl zgrANkhzodaii^W<=WzWp=RV$N6(2I05QY>Z0~%ikry9C6Co<{*#I3`<2ks2+JW0;F zu;`D@#$$anfa&>Hm}+VKy6|-W215gF7O8Dc-$uvv&APvj4oFL|zANaK>Z$*1`*l}V zN;eubY)Z%d7lVq%$(A0hKVq|hkQ5&@_lQvs!dOW!3`k5$RNc*0Xgj=`00M!M^mwEf z(%A{t-#3$AROSD{?S6gkp%SBz#LSIx{fW->Xp)vb<11C;CqvGV`&WE;XJ(aRvIOi{ zLh*9?1KCk5(_7a?ZM1m|;C$FHG&oWF6R35dA&?N{!Jf8E=Hkv~qql$Lxb_5&iY-fe z;&F985BxnaOghkw#Yp)EBrZTc3{=B_eop~{xw3Oa zf?E{FgOJ#J`|49-&ofp%z4MCBGP4m~{V_?J{WYRmVtnf$<3%$eguDCiM`xCIN+)D` zIVoxTZQ5OS-;@h)&P}L>#sbly?xdq%pVFQeb?7mOrRG5_98%h`!OmW{q&%7tVuBef zT|@?ZzKk$Ekrv1^p`*ndCpk~fSjLi{>QjvM*nBT z6Va>7&wV}J+>?B;mNOlG1)~5hd$Wtr{r5hBQg#~pN^*>`)+>m1>S)DS<*LfT!On_q z@1aQ-ArIj9u7Sb)PiwtKqjpPZ-Ehk7i9n-~mCC6nxj?f3xiFCgw656X(~$oMNhjL{ zJ4T^%x?_1n1_5+)a~Z2jf9WXB{(YH&1>#O-^xYhKlh zHPJ*RrIr>2s-Nza`}zPz z%`s5d*uKS}C^a({gk~mQ*?py#ee+6Z?#MqZD04d+6XX(iwUt&n8dd(PGSy+{V$&Cn zpJxjlsubtB?5hNOgS6sX`W`}m;%XejTMmFN;(7lNCFP+#S5MCm8Gac(f@}VwSDuQS z{IX_I$*F&EYMQ zC^>KiFCWL*?LBLv*0c5JB@MO}{5mn2vyNhkx?!*V$j__^XUAv+{o;$2+EVm7y0>6G zj}LJr3T0Z$?mQ?S7v+OAMk`wCf1LS(Sy57~SdJ)NC45w0EPSXGyQVCAhdwKPM%Bx< znB=9Ax%yN|i7I`9hXyh6{weE9`JG7a3`CW_yJKeT^G=hHWE`xGTc%gAGFW3=OzZ0H z9mNXL;$|k8Dm7tp*M5wOf9`xU`FJp5iQ>qV?SS^>2$mk$FAnC9;o0n=ycL= zO3PoYYLey@3{Oe3R##yVl@IrC3!`NnlKjepJi zPKS<5E#$|-8P0+qN%(K!qV(c9LeG*}_ zJfNj_mF@Vpsz=PguQ=oHB9tu|HSlv?o||uXuX}q0f}Z}jw+K?DO)kad*t}Y^#`A?+ zV$k25^;4E7&JUTbcQK$0y)z~UG%JLtfs48d4Z zVwouE1!y9mo*Y{mUlZ}St7Pl>_3?a)xhn@T!RY|FxHx4V0A!I*vZ2>x5ngoB2kO&>BEu=$Rni5;nkzdpg;wLrn;8_ht9Qjg;lX>QxeKn?Q z%x2U-#v8Q1Wb|wGlqS#TPX07i!F>#uUMrv(cS!6X9aMfAG~qh0Rp22dISWGzR-du^ zW4)kv_Giq(*$e!McR4J%EV6Atq&!`^4O_78lTO1Pa`)!(-m9A!Yd#tjc@|#hIF+X$ zDJG*cE67I{J8a^WiusTPd{1F{~j8 zGgZO~65Y-!dfL<;x~OiaE6gs3^R+;e71NE?_MN6$S(Z71rL|$?b_F1tho?)iC=N{TqU_E`0Bm4(f3W>zl+%vYUZ!xo(o+#y6 z%L<;&716voMJ^7OvtPm=wDbTeobjUi$>U5p4EPYT>ouj5S`gpmnyP&I^${{@fsAWm zr@T{N%X5w#7ms+wWvU~&sb&MvQ29L_5@_S1DfG?vYk{xvhv`XZ9d7hS-VCmyB~xaW z8M1?>1txK?>tadlo1_eC;Shn*aT^PjnR4o$#p#(+&SFo?fC^XKCk{)20<&TJ_1UV)eX993$1q+_{|e)kfUlxjWx zXFfA7eWyzSK!;>2)W2`^At~F&pCJ(;-%bBEDE{*)fUO78ND|toc^zR^$NxU)V~`}!N$ed2@)1MHu+V@q5r zz0k>rIMm9fjjrkbRd>9DLhyFo$zOmxqo!><%47(pEjwm@LNzsI9bPjTTtAfsp`Eq%pHB|wBMdSPSl#fj_$+P~bL$C++7K47*2xgQjqfFCIVhQr@*ktC8X#4W?X(SNqbx;Ago%+LdE!C;s5LKdFk&tN+!tb7en+j@jcia zeQ>y?1holl_mJsRlIsI|uVCD8`o#_qSp{a?zd(Vo_)-ea77R{zxDr*tWjF}7O=w!~ zA4aex>SB)MyB5Sbns;wA?|-SIELK1F9q&%VBbr?Tvf@Uh>0q~^krP)DTs zFIPH`iW^r_qi*I?A`-2|Hjk8=8>Hvz|HMA3IGiLd;f&{9f^}MU90T{@&@I=&f)k#O zyu7qDwN(ZITP5V$<;eQ7=eh2fM2cvTE5>S^m~WwsXvte?V3q4I@*f;gih8veMx01awgwts$_X_6V{DK+-@hbuAu1XLlv`mqr=T7iyv}IiXCm!`nUyb>GjVLXGftPD zOeIf+7MC0eS^|s{ouD5BgN9>?Tt>R`M+zNz5eE&j{CK*tdr99oZQQtM@l9>Rk00|t z*;>VA3IoMwXfcZxJzmjq??ydh2=fH|ir?L+`M%3eCl!;oZDlv!-Db@mc_ZT}#)*<$QY+5O{w5<6@FwqI=xv zD3GgC{x&|>Hdp&TeaX9`2l~nO+y>({BS_o=4ZSjxtMRa}_A&iITgpHrlLY69@`9i1 zWH6_#9!#K{h%?<6j(&O);|qn0T$Tp-qz+@!_mVlA2Wm z=4maie`uw~EWnX=rA%au=vTshj_X$~!Ddr+Z9Xxc2f3ojt;|=(hupfb6Q&x<$s|2n zUXP$hHrkMTFH^SuN&O=&BZ@&L1gpC~Zs(j9?ajJ+Fm8F7rKmXghy}}Jqv#7bD^R9k zV&#+?k8200vZ)|;DkT{~3(#{#frQkZBeE=t7b*!cA+JYS)^=j2o!16R+G)R%RQ_Vw z7H5TBiU-wLXbBTQtoD+1GGjKzOl(+<^#LIJjmk)QQ};(Hy4fEE*zFujzSPvvWfL$M zO7wi=rjCn?OY=H#Ew`u2!XM`or@~^Qq@21G8IpPyH;Q97nhDhZ$x^fUHu(NM`uvs2 zzV{JmXWY(*h09tDll8aW#te0o-9uJ|h2SPK1&wQ?0LyCX(3!&#ok&i*G{`=`X3oNC zWK}6IjMIW$(LmaD21hk4g|ySeL|(GpQ>Y2Dfx9>YR_f?dza%_s`cDx_Iu67OyM=7ik-9*FIuM82g1XbJ=f{vtSb(N z%VkQ~z1XXZ7v<$w&^B{SvtMxRJ7lz)o6MVcB=T0YViLQ*buEJi`gfrvP#+%f;_&kq zZH-r{7eV$_f|`qgH$-Z(h#sF2T2fS>iI34IK$#0ObB_{7btC85*k%6Yhg&`q$G(>4 z4HHJ2A0|Auwteabg2j_l!D(61OtQ>O3KHU;dIOhsy$w(>U1+(=(oENIZ)z|l+FBy) z8yHO#hwfSQ1ANHhxIDN3i%r2bjAM}uSQxD%AkQJn_sZv=5+Xuh70lCH9Okpj#ln-k z@*U4kBih{(VoI2b2l`xh3gR8Ry6lb;+h8xhN zur@8SwSo)mSV(|Ebm28_Xs4ajAa>H(hG|bHB$sg|O`kS7+SJ0rto5Y2R;H@(y35f9 z)r=9-I^qaPkAHFL!*KiV}xa&y5*JjvYHYa*php9lKNxyC#o}8GKZW}%NPap@q zt##km3Z2QR?=AWBClk}FdLW3YAB!J}!drgATi>}D(utek62OMfV_ zy*7`dh@f{&mGQwIaz53CI8OF43wjB`22IPWpT?a5d>fxON@Kn_9MOZ;?IDNF8OWJm zZ|My{(Q>l1cEb5zBiMDNGyo!vv~783VdHyZHAj}qDX_Ye8^La$V6^{S!+7Z}mq0;L z!*i9DA0mUF=!Y8lCAz@RWjK}fmYP-7)2z)((8%)-Z9`#)EiUO5Yhr424>;+_Ox*M> zJHab^k$%q;#F_NrV^V6FQ&RX2o!p-8)-pB3ae;PHqhx1rGm1A$=f~KCxLbs7P*9JU zTii2IQd$U4eI@~}qvam48Ys(}faKS>)*>jHWMS4`_Dqst;-Q|d3QiI(jBe&8D`of>!5*%+$k>mwh< zwpYs9F0pKp7}Xb9d0R2d1Ca*z^~7DUE*afj0QXU(a~WMW{S2Po(uVdKw|rt=$sL1< zCu7ol#lx7q^s~<9NabEb&005Es-*nRnEh<(o4@#VxcHa47Tjp zq`vGqW`45^M5hb6ZQ?q(R@&rT#&DpwB5dtd*9TtSjvVUKvCZJn$wN5TOaW@CDvOdd zBYCFPp3Kl)&uddy2a<03W=v;|FPGw()lM*DGK9U#ksSnlMID=E_bB4unuE|OSI+~ z;tjjv+#8@^!*g$qU|k3iIq*}KiCs`M)JUDcdt94HY%y z`+tdsQ5w$&8~(u|8+%3vT$D&Fdk#vY+V5_!@RuO`+}bfV^$pOz%{J9h5#5A3h}EA8 z`(*>e^+i-GpQZ|*CMwSb&a{3PBjHNoM7`FN=He^cKbY;O?iQCeE?^K1%els=Jij=4 zbCpE_KM#;x!e@TE5F*=DgMJB9L`{2Y)NV#=@y)UIW4>Lpzf~+%|25AZsa*+k!;ty; ztl$jEuiozFQ!6FAy|pqtk#rv5{iP^-4@;tS8A7k-ib=0h$=1xbA!jZ}=LsMv9t1){ z%bH(%e!lWiv`Jh6@qRfM1fRM*BYAGtOzk*Kd4p3y(OmcKLe0)r)OuAAn~c5uV2FQ7 zanAT$tKwi@pXJow`7>N?P&9+teKS*5R-0>*OCRT)yaUrf36Xtg#2pNA5Y$@Lrx#~% zWzKb~bZj)mQ} z6_oQ(BRObVe^;#3ZZ*E>90OEmwVy&7>f>Qq9ATac>FLD}e~Bd;x7t-?arw>|qWuxw z`~hB4yiFl(kP@V&u(Fnnq@=6Ihy-waCe2!!byY}V+xr;1S}K00GDN4L9FE5kOE<>J zzQyIWPoGg%5J=XOS+yga?4b_)wfLD%54)|lBo%&`Y|Wja^R4`Nv$_lyTu!AF$OkHA z+x|y?MzcZ#cdKIVI~6opZ>LrG=CySM?a4W;jXd3p9-BSXtu3pP+jkiBHM4?##7)wG zCO#KV@a_udgV4{-g>Unyco znPe-PZ>9fHsoNYhbQ~;r*GO14H(0*lr9=`nedO!67H92gcaZ7zy>HMrCgg_>LPkMP zG~Bp-Ta6uStlqKVhn3NKG$_leFRKYU9OSA(4}NPb?T1>eNr1SlWhkcu77)1hht!>; z{(3~vid<)w18j4ad2Bw0uQvchum;WdM`A$%c|UE8P~SZHco zHHQGOcUq~)F^_p%HoDIo)iD8N*}0Be&35hzni~(_w|Dcq1vGJLKmF)4u5bcvmGZ=v z@Q_TR=`Fgg$77!Ru_hQ=@ukuzIUD&AI}A4nR;ng+SmirbDO-E$n>=bSkBw_^dL6jU z^cHrqHZ8DTZiZ2@G?P>~w1baO^M{*WBSwBbr2da6e%!>Q4IfC5xcs#tvGIG{A7L!IlWlA+nlGqXv;je;@j71VyN8L2BYl> z+S$&9YI(}(qWgYA^hkC4j4JBfBpKGsTv|6kXk+x7x-N~dP~)unzlI<}a;vp3zhjv5 zw}B>o2b_a-k`u$nZE=+aK0~Q56;O!tqRJ+4yuAN|m(x+L&PX-23BHdf08*m={oo{$ zBB@V`QWXA)Nq=T;w0?I%Z+i!#JMm}@2u(&XnL zVzeZQPj`(uCCLm(r-q zZS-LR_PD|{PsM7#qy>egHp4@OK80j{WNS=qV4I`0HFEJMUO}kkR#Di%Nl+Y*Cl+85 zupf}SBRiLOZ;4uq>A(JPE}F`EKiP6$WdVug?8^Zyh64w8NI@{>1C# zWg#rgyYpMwP2;C1;A?e%0{lmNILL{8SF!9+YwhOl239zNQcZag+o;o>p)kGnFKrbn zE?iC6*;JY(cZABO%_N;S;6;8=S`7uj@1b+Mc7NYK4BDjJ*O{98w$ejgc9&9IQT~qe zeKVy>H$i2JA9o$a*lL~){pQpLkY$qJ&?jxu%JkT&VsrWlg8k;>T4d=!xr%Z034hzMNZ&onkR9)I`(TVmTe}I5J@vGf4 z+AH&!*&JT#gtu9QG&3gkqWU0?Sxcm1iTVYv(oW&ECG#{zvMa-jXqfFOTf%)tHK42Dv34FOYKK&0BiB-)v{Xlr(M^)!R$-{L}xv=z)kW4MUIzj(W&Kv96x#>(r+WX`jmta!7R6qF*z|AQG?q&839HSFbm|s zK3kWH3Q}G(YSE}3bLFs=J8rHdE(ypM$YR5d30C1yhVO~)_#R~BJ+~`3i5tC*mVj`9 z7M{%`NZcQhIle%IIMVV9CH*9wS6B?0^D^TX5h_Pq(-DcJ)XP>{g7W ztl&L{1vlLEtY8d=q1H65;UP~(JR4;p@9VBPxv9?2#f8OHl6?sZNZu z6g-mftp&n^#xbO>k!MA(N{$A)gdZ&z5a-Ys@w24QM4R|fDOU80GWGLdl20tjPOSDS zZH@?=gI)7$F$Z#n)5yx?bPx)Hlg)x>_G|m(O)^yg*7KX7cYs&+meBV?GA~pLhB?*3 z+223VSTIQJY-gQY8^8(;u*UbnY3>l5LSdGMt3p4J?P;oV>3gB25xQb_toEd~4;Zwxc#5{uv!pYNwTD`P_v@nyPK>@?+T)* zsnD)H=Q^R3Vr;>>Sa7%-4({Kx`LYmU6@oC#frLkRhm45y4&fc@|2dHFuwf1)BFutJ z$b)&1b-3J;8bMS)dXQ#5xh2x_e711UhxA_lj|ce<9_B%UK#6p9pvH!XHm20xUTNvz zC<2_1b;^31i2Y+}z0yk~uv!HK%A_D*(^E)LLfHyVE^MO_3Xj0XrjZGcVv&7GV%7p5 zX~ggyI7PnXYnQ&N1IOxA^6_P!5P6nF_7B@Q(Wd-4Lobsfn*sIi%k(sNYC{lW2P|sRc&@@_ZRQOAib92b(%2bpu zy~f;0J6;SvxWUm=p~y_O7nlGsI#j9m7w_ke%bt$ZjgU&+TBFyr(Luj81a9d$l!p9x zWxplmU{L*<=%B+J!M0&Bo@x~=!JUkpKpj1qjbFmAXNNkUi-eP(_x`p@{zbtZ+>&S_ z3FIcYwNe+?l94pd!It)-pQK9xUHc}N0RL9x7b+&4M9!QjjLqOxc8te2_(mMmO2TS4 z$$K0cwgljd;IhE*mZtN^zePRf07?EQ?e)vZOqJ&vSqUkhm%5n`D!L<_G&~wA6F!MC z8S)z@%Epf#uh5@P*Kr;&y0#7OV3 znN@3`Zm|J-2{**=S6;B-ZXLic1Q>j064Z|10GXq&NH-4+DgiP zjhwUeNW5~GUh(v4zAuA;in7Sxw375;GiyB^E>3zOS=rr0u<>)M6?f0h7EW&0mxd5; zIRGzo>{6qQu7-n9X}n5XQDwQuuxiMIEa}jZ&%q~o`^Lp(wVyvxN6`GPkNN;&9cLc3 ziZK-GTC$Uh>O<{aZg(6TIs5IJbs?D$^FT#~eU+a-{o;>Xw3mU(bs36SBB%&`-9dwH zr@jfu_h_xRe<11zrLZxQ+W`tcq3T!C(a~E~YbP@|UF_6Y@j{c^B_xk}=iiL@A=P z;;OkK4b%Of0Rw1L_uNf0$XnoODu~O^4HhTTOc&99K<9Ab+KTg0^fyjM#{u-As2?uk z8bH-!MV9fELtrnqda_vw`+Q1bn?9+rZ`@?E#p`mz;^xA0`N52V>Vym~IQ1scy;&Li zB33Bez$$n=wF*+n8DQV{^ES)y*ez>qciUlV`0=(d3~hiX+G>*$X885pbMa5_XXdAVM!(8+N{#@6~aKla<| zc$gdcuosm(D-b|;oun%ZL+W+8IV~3m5F3{6I~=qSbS&t4PT(%{%+5poBa>+{7XwM1 zo~#td-BQ*olYC#S@8yaZH)XaiUvtql-qC2*5P^FPM)c&O^9Ni6*Ttx#-~w zP0?T3Dc)2!&lz`b#mz*AGCICu=_(-?5113FrzW6I@0Bm+n^Bk7RoHz&_mt!>dl|^m zoKJc*@i=H#!oFzb^sIBURVKT>r?XN;0M>~%L1o-?;-jT(;uy>4j~s>gbRN$0*jJ&J zi5{Yq37o9T_~$DM?IVtYHIpeklQmCmlt7+F_N>p;re4Bh0vY(z`eT?hHDNfICfKe< zbM?wUadk5z_eP5LGk71|o5YoMbsSIlXkN4dN%mX{h8e%R1jN_1MlE}L&#dCB6tQ}{ z4ycv#0>Zm+h=y*PXN*=WUW6;E9`X#_;`k@&vNCm;v?`d|4VdenoID$UFo@z~Ynas0AIbe8w)WrAZ=n6i|0^ zu+Wb{cehrlRYsr`W<~DxDrP>K9mOso zE+G~Vtxis12Num6-*=D##-_5M)YZYQ#g{+?xh`D@&5d^ z=FJcta|If$*aucT(}KXSXXTq0@y&}gXe=xv9I~caxHO~w;o)*!Xov4ZsucMnsan?^X!`z(6Tt+2JK+I@8kfs`>Nzb6L@yC+|9W?{vw8?%yjo5DDiP*S* z#!K9qLj?(4%bbstRTUwqe+|muod;9I=nv3;XocWoBN42``UrxBuoZa1A^X>5S%O|*E5ezEe zgilWoJ<%L8+aXopBrmvzK`AdT9lia~n@~|vEkeg``_|v0BM5N^Sza#nj4Vkh2z#X? waHMDGr_;>uNr`K?r9-I;J5e#3U58o)A4u2?VGJaK4Oo -Modulegetreuer/keycode_string -Version2025-03-07 -MaintainerPascal Getreuer (@getreuer) -LicenseApache 2.0 -Documentation -https://getreuer.info/posts/keyboards/keycode-string - - - -This is a community module adaptation of [Keycode -String](https://getreuer.info/posts/keyboards/keycode-string), a utility to -convert QMK keycodes to human-readable strings. It's much nicer to read names -like "`LT(2,KC_D)`" than numerical codes like "`0x4207`." - -Add the following to your `keymap.json`: - -```json -{ - "modules": ["getreuer/keycode_string"] -} -``` - -Then use `get_keycode_string(keycode)` like: - -```c -dprintf("kc=%s\n", get_keycode_string(keycode)); -``` - -Many common QMK keycodes are understood out of the box by -`get_keycode_string()`, but not all. Optionally, use `KEYCODE_STRING_NAMES_USER` -in keymap.c to define names for additional keycodes or override how any keycode -is formatted. - -See the [Keycode String -documentation](https://getreuer.info/posts/keyboards/keycode-string) for further -details. - diff --git a/modules/getreuer/keycode_string/introspection.h b/modules/getreuer/keycode_string/introspection.h deleted file mode 100644 index 74e0c84..0000000 --- a/modules/getreuer/keycode_string/introspection.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once -#include "keycode_string.h" -#include "print.h" - diff --git a/modules/getreuer/keycode_string/keycode_string.c b/modules/getreuer/keycode_string/keycode_string.c deleted file mode 100644 index 046eacb..0000000 --- a/modules/getreuer/keycode_string/keycode_string.c +++ /dev/null @@ -1,510 +0,0 @@ -// Copyright 2024-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file keycode_string.c - * @brief Keycode String community module implementation - * - * @note When parsing keycodes, avoid hardcoded numerical codes or twiddling - * bits directly, use QMK's APIs instead. Keycode encoding is internal to QMK - * and may change between versions. - * - * For full documentation, see - * - */ - -#include "keycode_string.h" - -#include - -#include "quantum.h" - -ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(1, 0, 0); - -typedef int_fast8_t index_t; - -// clang-format off -/** Packs a 7-char keycode name, ignoring the third char, as 3 words. */ -#define KEYCODE_NAME7(c0, c1, unused_c2, c3, c4, c5, c6) \ - ((uint16_t)c0) | (((uint16_t)c1) << 8), \ - ((uint16_t)c3) | (((uint16_t)c4) << 8), \ - ((uint16_t)c5) | (((uint16_t)c6) << 8) - -/** - * @brief Names of some common keycodes. - * - * Each (keycode, name) entry is stored flat in 8 bytes in PROGMEM. Names in - * this table must be at most 7 chars long and have an underscore '_' for the - * third char. This underscore is assumed and not actually stored. - */ -static const uint16_t common_names[] PROGMEM = { - KC_TRNS, KEYCODE_NAME7('K', 'C', '_', 'T', 'R', 'N', 'S'), - KC_ENT , KEYCODE_NAME7('K', 'C', '_', 'E', 'N', 'T', 0 ), - KC_ESC , KEYCODE_NAME7('K', 'C', '_', 'E', 'S', 'C', 0 ), - KC_BSPC, KEYCODE_NAME7('K', 'C', '_', 'B', 'S', 'P', 'C'), - KC_TAB , KEYCODE_NAME7('K', 'C', '_', 'T', 'A', 'B', 0 ), - KC_SPC , KEYCODE_NAME7('K', 'C', '_', 'S', 'P', 'C', 0 ), - KC_MINS, KEYCODE_NAME7('K', 'C', '_', 'M', 'I', 'N', 'S'), - KC_EQL , KEYCODE_NAME7('K', 'C', '_', 'E', 'Q', 'L', 0 ), - KC_LBRC, KEYCODE_NAME7('K', 'C', '_', 'L', 'B', 'R', 'C'), - KC_RBRC, KEYCODE_NAME7('K', 'C', '_', 'R', 'B', 'R', 'C'), - KC_BSLS, KEYCODE_NAME7('K', 'C', '_', 'B', 'S', 'L', 'S'), - KC_NUHS, KEYCODE_NAME7('K', 'C', '_', 'N', 'U', 'H', 'S'), - KC_SCLN, KEYCODE_NAME7('K', 'C', '_', 'S', 'C', 'L', 'N'), - KC_QUOT, KEYCODE_NAME7('K', 'C', '_', 'Q', 'U', 'O', 'T'), - KC_GRV , KEYCODE_NAME7('K', 'C', '_', 'G', 'R', 'V', 0 ), - KC_COMM, KEYCODE_NAME7('K', 'C', '_', 'C', 'O', 'M', 'M'), - KC_DOT , KEYCODE_NAME7('K', 'C', '_', 'D', 'O', 'T', 0 ), - KC_SLSH, KEYCODE_NAME7('K', 'C', '_', 'S', 'L', 'S', 'H'), - KC_CAPS, KEYCODE_NAME7('K', 'C', '_', 'C', 'A', 'P', 'S'), - KC_PSCR, KEYCODE_NAME7('K', 'C', '_', 'P', 'S', 'C', 'R'), - KC_PAUS, KEYCODE_NAME7('K', 'C', '_', 'P', 'A', 'U', 'S'), - KC_INS , KEYCODE_NAME7('K', 'C', '_', 'I', 'N', 'S', 0 ), - KC_HOME, KEYCODE_NAME7('K', 'C', '_', 'H', 'O', 'M', 'E'), - KC_PGUP, KEYCODE_NAME7('K', 'C', '_', 'P', 'G', 'U', 'P'), - KC_DEL , KEYCODE_NAME7('K', 'C', '_', 'D', 'E', 'L', 0 ), - KC_END , KEYCODE_NAME7('K', 'C', '_', 'E', 'N', 'D', 0 ), - KC_PGDN, KEYCODE_NAME7('K', 'C', '_', 'P', 'G', 'D', 'N'), - KC_RGHT, KEYCODE_NAME7('K', 'C', '_', 'R', 'G', 'H', 'T'), - KC_LEFT, KEYCODE_NAME7('K', 'C', '_', 'L', 'E', 'F', 'T'), - KC_DOWN, KEYCODE_NAME7('K', 'C', '_', 'D', 'O', 'W', 'N'), - KC_UP , KEYCODE_NAME7('K', 'C', '_', 'U', 'P', 0 , 0 ), - KC_NUBS, KEYCODE_NAME7('K', 'C', '_', 'N', 'U', 'B', 'S'), - KC_HYPR, KEYCODE_NAME7('K', 'C', '_', 'H', 'Y', 'P', 'R'), - KC_MEH , KEYCODE_NAME7('K', 'C', '_', 'M', 'E', 'H', 0 ), -#ifdef EXTRAKEY_ENABLE - KC_WHOM, KEYCODE_NAME7('K', 'C', '_', 'W', 'H', 'O', 'M'), - KC_WBAK, KEYCODE_NAME7('K', 'C', '_', 'W', 'B', 'A', 'K'), - KC_WFWD, KEYCODE_NAME7('K', 'C', '_', 'W', 'F', 'W', 'D'), - KC_WSTP, KEYCODE_NAME7('K', 'C', '_', 'W', 'S', 'T', 'P'), - KC_WREF, KEYCODE_NAME7('K', 'C', '_', 'W', 'R', 'E', 'F'), - KC_MNXT, KEYCODE_NAME7('K', 'C', '_', 'M', 'N', 'X', 'T'), - KC_MPRV, KEYCODE_NAME7('K', 'C', '_', 'M', 'P', 'R', 'V'), - KC_MPLY, KEYCODE_NAME7('K', 'C', '_', 'M', 'P', 'L', 'Y'), - KC_MUTE, KEYCODE_NAME7('K', 'C', '_', 'M', 'U', 'T', 'E'), - KC_VOLU, KEYCODE_NAME7('K', 'C', '_', 'V', 'O', 'L', 'U'), - KC_VOLD, KEYCODE_NAME7('K', 'C', '_', 'V', 'O', 'L', 'D'), -#endif // EXTRAKEY_ENABLE -#ifdef MOUSEKEY_ENABLE - MS_LEFT, KEYCODE_NAME7('M', 'S', '_', 'L', 'E', 'F', 'T'), - MS_RGHT, KEYCODE_NAME7('M', 'S', '_', 'R', 'G', 'H', 'T'), - MS_UP , KEYCODE_NAME7('M', 'S', '_', 'U', 'P', 0 , 0 ), - MS_DOWN, KEYCODE_NAME7('M', 'S', '_', 'D', 'O', 'W', 'N'), - MS_WHLL, KEYCODE_NAME7('M', 'S', '_', 'W', 'H', 'L', 'L'), - MS_WHLR, KEYCODE_NAME7('M', 'S', '_', 'W', 'H', 'L', 'R'), - MS_WHLU, KEYCODE_NAME7('M', 'S', '_', 'W', 'H', 'L', 'U'), - MS_WHLD, KEYCODE_NAME7('M', 'S', '_', 'W', 'H', 'L', 'D'), -#endif // MOUSEKEY_ENABLE -#ifdef SWAP_HANDS_ENABLE - SH_ON , KEYCODE_NAME7('S', 'H', '_', 'O', 'N', 0 , 0 ), - SH_OFF , KEYCODE_NAME7('S', 'H', '_', 'O', 'F', 'F', 0 ), - SH_MON , KEYCODE_NAME7('S', 'H', '_', 'M', 'O', 'N', 0 ), - SH_MOFF, KEYCODE_NAME7('S', 'H', '_', 'M', 'O', 'F', 'F'), - SH_TOGG, KEYCODE_NAME7('S', 'H', '_', 'T', 'O', 'G', 'G'), - SH_TT , KEYCODE_NAME7('S', 'H', '_', 'T', 'T', 0 , 0 ), -# if !defined(NO_ACTION_ONESHOT) - SH_OS , KEYCODE_NAME7('S', 'H', '_', 'O', 'S', 0 , 0 ), -# endif // !defined(NO_ACTION_ONESHOT) -#endif // SWAP_HANDS_ENABLE -#ifdef LEADER_ENABLE - QK_LEAD, KEYCODE_NAME7('Q', 'K', '_', 'L', 'E', 'A', 'D'), -#endif // LEADER_ENABLE -#ifdef TRI_LAYER_ENABLE - TL_LOWR, KEYCODE_NAME7('T', 'L', '_', 'L', 'O', 'W', 'R'), - TL_UPPR, KEYCODE_NAME7('T', 'L', '_', 'U', 'P', 'P', 'R'), -#endif // TRI_LAYER_ENABLE -#ifdef GRAVE_ESC_ENABLE - QK_GESC, KEYCODE_NAME7('Q', 'K', '_', 'G', 'E', 'S', 'C'), -#endif // GRAVE_ESC_ENABLE -#ifdef CAPS_WORD_ENABLE - CW_TOGG, KEYCODE_NAME7('C', 'W', '_', 'T', 'O', 'G', 'G'), -#endif // CAPS_WORD_ENABLE -#ifdef LAYER_LOCK_ENABLE - QK_LLCK, KEYCODE_NAME7('Q', 'K', '_', 'L', 'L', 'C', 'K'), -#endif // LAYER_LOCK_ENABLE - EE_CLR , KEYCODE_NAME7('E', 'E', '_', 'C', 'L', 'R', 0 ), - QK_BOOT, KEYCODE_NAME7('Q', 'K', '_', 'B', 'O', 'O', 'T'), - DB_TOGG, KEYCODE_NAME7('D', 'B', '_', 'T', 'O', 'G', 'G'), -}; -// clang-format on - -/** Users can override this to define names of additional keycodes. */ -__attribute__((weak)) const keycode_string_name_t* keycode_string_names_data_user = NULL; -__attribute__((weak)) uint16_t keycode_string_names_size_user = 0; -/** Names of the 4 mods on each hand. */ -static const char mod_names[] PROGMEM = "CTL\0SFT\0ALT\0GUI"; -/** Internal buffer for holding a stringified keycode. */ -static char buffer[32]; -#define BUFFER_MAX_LEN (sizeof(buffer) - 1) -static index_t buffer_len; - -/** Finds the name of a keycode in `common_names` or returns NULL. */ -static const char* search_common_names(uint16_t keycode) { - static uint8_t buffer[8]; - - for (int_fast16_t offset = 0; offset < ARRAY_SIZE(common_names); offset += 4) { - if (keycode == pgm_read_word(common_names + offset)) { - const uint16_t w0 = pgm_read_word(common_names + offset + 1); - const uint16_t w1 = pgm_read_word(common_names + offset + 2); - const uint16_t w2 = pgm_read_word(common_names + offset + 3); - buffer[0] = (uint8_t)w0; - buffer[1] = (uint8_t)(w0 >> 8); - buffer[2] = '_'; - buffer[3] = (uint8_t)w1; - buffer[4] = (uint8_t)(w1 >> 8); - buffer[5] = (uint8_t)w2; - buffer[6] = (uint8_t)(w2 >> 8); - buffer[7] = 0; - return (const char*)buffer; - } - } - - return NULL; -} - -/** - * @brief Finds the name of a keycode in table or returns NULL. - * - * @param data Pointer to table to be searched. - * @param size Numer of entries in the table. - * @return Name string for the keycode, or NULL if not found. - */ -static const char* search_table( - const keycode_string_name_t* data, uint16_t size, uint16_t keycode) { - if (data != NULL) { - for (uint16_t i = 0; i < size; ++i) { - if (data[i].keycode == keycode) { - return data[i].name; - } - } - } - return NULL; -} - -/** Formats `number` in `base`, either 10 or 16. */ -static char* number_string(uint16_t number, int8_t base) { - static char result[7]; - result[sizeof(result) - 1] = '\0'; - index_t i = sizeof(result) - 1; - do { - const uint8_t digit = number % base; - number /= base; - result[--i] = (digit < 10) ? (char)(digit + UINT8_C('0')) - : (char)(digit + (UINT8_C('A') - 10)); - } while (number > 0 && i > 0); - - if (base == 16 && i >= 2) { - result[--i] = 'x'; - result[--i] = '0'; - } - return result + i; -} - -/** Appends `str` to `buffer`, truncating if the result would overflow. */ -static void append(const char* str) { - char* dest = buffer + buffer_len; - index_t i; - for (i = 0; buffer_len + i < BUFFER_MAX_LEN && str[i]; ++i) { - dest[i] = str[i]; - } - buffer_len += i; - buffer[buffer_len] = '\0'; -} - -/** Same as append(), but where `str` is a PROGMEM string. */ -static void append_P(const char* str) { - char* dest = buffer + buffer_len; - index_t i; - for (i = 0; buffer_len + i < BUFFER_MAX_LEN; ++i) { - const char c = pgm_read_byte(&str[i]); - if (c == '\0') { - break; - } - dest[i] = c; - } - buffer_len += i; - buffer[buffer_len] = '\0'; -} - -/** Appends a single char to `buffer` if there is space. */ -static void append_char(char c) { - if (buffer_len < BUFFER_MAX_LEN) { - buffer[buffer_len] = c; - buffer[++buffer_len] = '\0'; - } -} - -/** Formats `number` in `base`, either 10 or 16, and appends it to `buffer`. */ -static void append_number(uint16_t number, int8_t base) { - append(number_string(number, base)); -} - -/** Stringifies 5-bit mods and appends it to `buffer`. */ -static void append_5_bit_mods(uint8_t mods) { - const bool is_rhs = mods > 15; - const uint8_t csag = mods & 15; - if (csag != 0 && (csag & (csag - 1)) == 0) { // One mod is set. - append_P(PSTR("MOD_")); - append_char(is_rhs ? 'R' : 'L'); - append_P(&mod_names[4 * biton(csag)]); - } else { // Fallback: write the mod as a hex value. - append_number(mods, 16); - } -} - -/** - * @brief Writes a keycode of the format `name` + "(" + `param` + ")". - * @note `name` is a PROGMEM string, `param` is not. - */ -static void append_unary_keycode(const char* name, const char* param) { - append_P(name); - append_char('('); - append(param); - append_char(')'); -} - -/** Stringifies `keycode` and appends it to `buffer`. */ -static void append_keycode(uint16_t keycode) { - // In case there is overlap among tables, search `keycode_string_names_user` - // first so that it takes precedence. - const char* keycode_name = search_table( - keycode_string_names_data_user, keycode_string_names_size_user, keycode); - if (keycode_name) { - append(keycode_name); - return; - } - keycode_name = search_common_names(keycode); - if (keycode_name) { - append(keycode_name); - return; - } - - if (keycode <= 255) { // Basic keycodes. - switch (keycode) { - // Modifiers KC_LSFT, KC_RCTL, etc. - case MODIFIER_KEYCODE_RANGE: { - const uint8_t i = keycode - KC_LCTL; - const bool is_rhs = i > 3; - append_P(PSTR("KC_")); - append_char(is_rhs ? 'R' : 'L'); - append_P(&mod_names[4 * (i & 3)]); - } return; - - // Letters A-Z. - case KC_A ... KC_Z: - append_P(PSTR("KC_")); - append_char((char)(keycode + (UINT8_C('A') - KC_A))); - return; - - // Digits 0-9 (NOTE: Unlike the ASCII order, KC_0 comes *after* KC_9.) - case KC_1 ... KC_0: - append_P(PSTR("KC_")); - append_char('0' + (char)((keycode - (KC_1 - 1)) % 10)); - return; - - // Keypad digits. - case KC_KP_1 ... KC_KP_0: - append_P(PSTR("KC_KP_")); - append_char('0' + (char)((keycode - (KC_KP_1 - 1)) % 10)); - return; - - // Function keys. F1-F12 and F13-F24 are coded in separate ranges. - case KC_F1 ... KC_F12: - append_P(PSTR("KC_F")); - append_number(keycode - (KC_F1 - 1), 10); - return; - - case KC_F13 ... KC_F24: - append_P(PSTR("KC_F")); - append_number(keycode - (KC_F13 - 13), 10); - return; - } - } - - // clang-format off - switch (keycode) { - // A modified keycode, like S(KC_1) for Shift + 1 = !. This implementation - // only covers modified keycodes where one modifier is applied, e.g. a - // Ctrl + Shift + kc or Hyper + kc keycode is not formatted. - case QK_MODS ... QK_MODS_MAX: { - uint8_t mods = QK_MODS_GET_MODS(keycode); - const bool is_rhs = mods > 15; - mods &= 15; - if (mods != 0 && (mods & (mods - 1)) == 0) { // One mod is set. - const char* name = &mod_names[4 * biton(mods)]; - if (is_rhs) { - append_char('R'); - append_P(name); - } else { - append_char(pgm_read_byte(&name[0])); - } - append_char('('); - append_keycode(QK_MODS_GET_BASIC_KEYCODE(keycode)); - append_char(')'); - return; - } - } break; - -#if !defined(NO_ACTION_ONESHOT) - // One-shot mod OSM(mod) key. - case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX: - append_P(PSTR("OSM(")); - append_5_bit_mods(QK_ONE_SHOT_MOD_GET_MODS(keycode)); - append_char(')'); - return; -#endif // !defined(NO_ACTION_ONESHOT) - - // Various layer switch keys. - case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: // Layer-tap LT(layer,kc) key. - append_P(PSTR("LT(")); - append_number(QK_LAYER_TAP_GET_LAYER(keycode), 10); - append_char(','); - append_keycode(QK_LAYER_TAP_GET_TAP_KEYCODE(keycode)); - append_char(')'); - return; - - case QK_LAYER_MOD ... QK_LAYER_MOD_MAX: // LM(layer,mod) key. - append_P(PSTR("LM(")); - append_number(QK_LAYER_MOD_GET_LAYER(keycode), 10); - append_char(','); - append_5_bit_mods(QK_LAYER_MOD_GET_MODS(keycode)); - append_char(')'); - return; - - case QK_TO ... QK_TO_MAX: // TO(layer) key. - append_unary_keycode(PSTR("TO"), - number_string(QK_TO_GET_LAYER(keycode), 10)); - return; - - case QK_MOMENTARY ... QK_MOMENTARY_MAX: // MO(layer) key. - append_unary_keycode(PSTR("MO"), - number_string(QK_MOMENTARY_GET_LAYER(keycode), 10)); - return; - - case QK_DEF_LAYER ... QK_DEF_LAYER_MAX: // DF(layer) key. - append_unary_keycode(PSTR("DF"), - number_string(QK_DEF_LAYER_GET_LAYER(keycode), 10)); - return; - - case QK_TOGGLE_LAYER ... QK_TOGGLE_LAYER_MAX: // TG(layer) key. - append_unary_keycode(PSTR("TG"), - number_string(QK_TOGGLE_LAYER_GET_LAYER(keycode), 10)); - return; - -#if !defined(NO_ACTION_ONESHOT) - case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX: // OSL(layer) key. - append_unary_keycode(PSTR("OSL"), - number_string(QK_ONE_SHOT_LAYER_GET_LAYER(keycode), 10)); - return; -#endif // !defined(NO_ACTION_ONESHOT) - - case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX: // TT(layer) key. - append_unary_keycode(PSTR("TT"), - number_string(QK_LAYER_TAP_TOGGLE_GET_LAYER(keycode), 10)); - return; - - // PDF(layer) key. - case QK_PERSISTENT_DEF_LAYER ... QK_PERSISTENT_DEF_LAYER_MAX: - append_unary_keycode(PSTR("PDF"), - number_string(QK_PERSISTENT_DEF_LAYER_GET_LAYER(keycode), 10)); - return; - - // Mod-tap MT(mod,kc) key. This implementation formats the MT keys where - // one modifier is applied. For MT keys with multiple modifiers, the mod - // arg is written numerically as a hex code. - case QK_MOD_TAP ... QK_MOD_TAP_MAX: { - const uint8_t mods = QK_MOD_TAP_GET_MODS(keycode); - const bool is_rhs = mods > 15; - const uint8_t csag = mods & 15; - if (csag != 0 && (csag & (csag - 1)) == 0) { // One mod is set. - append_char(is_rhs ? 'R' : 'L'); - append_P(&mod_names[4 * biton(csag)]); - append_P(PSTR("_T(")); - } else if (mods == MOD_HYPR) { - append_P(PSTR("HYPR_T(")); - } else if (mods == MOD_MEH) { - append_P(PSTR("MEH_T(")); - } else { - append_P(PSTR("MT(")); - append_number(mods, 16); - append_char(','); - } - append_keycode(QK_MOD_TAP_GET_TAP_KEYCODE(keycode)); - append_char(')'); - } return; - - case QK_TAP_DANCE ... QK_TAP_DANCE_MAX: // Tap dance TD(i) key. - append_unary_keycode(PSTR("TD"), - number_string(QK_TAP_DANCE_GET_INDEX(keycode), 10)); - return; - -#ifdef UNICODE_ENABLE - case QK_UNICODE ... QK_UNICODE_MAX: // Unicode UC(codepoint) key. - append_unary_keycode(PSTR("UC"), - number_string(QK_UNICODE_GET_CODE_POINT(keycode), 16)); - return; -#elif defined(UNICODEMAP_ENABLE) - case QK_UNICODEMAP ... QK_UNICODEMAP_MAX: // Unicode Map UM(i) key. - append_unary_keycode(PSTR("UM"), - number_string(QK_UNICODEMAP_GET_INDEX(keycode), 10)); - return; - - case QK_UNICODEMAP_PAIR ... QK_UNICODEMAP_PAIR_MAX: { // UP(i,j) key. - const uint8_t i = QK_UNICODEMAP_PAIR_GET_UNSHIFTED_INDEX(keycode); - const uint8_t j = QK_UNICODEMAP_PAIR_GET_SHIFTED_INDEX(keycode); - append_P(PSTR("UP(")); - append_number(i, 10); - append_char(','); - append_number(j, 10); - append_char(')'); - } return; -#endif -#ifdef MOUSEKEY_ENABLE - case MS_BTN1 ... MS_BTN8: // Mouse button keycode. - append_P(PSTR("MS_BTN")); - append_number(keycode - (MS_BTN1 - 1), 10); - return; -#endif // MOUSEKEY_ENABLE -#ifdef SWAP_HANDS_ENABLE - case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX: // Swap Hands SH_T(kc) key. - if (!IS_SWAP_HANDS_KEYCODE(keycode)) { - append_P(PSTR("SH_T(")); - append_keycode(QK_SWAP_HANDS_GET_TAP_KEYCODE(keycode)); - append_char(')'); - return; - } - break; -#endif // SWAP_HANDS_ENABLE - - case KB_KEYCODE_RANGE: // Keyboard range keycode. - append_P(PSTR("QK_KB_")); - append_number(keycode - QK_KB_0, 10); - return; - - case USER_KEYCODE_RANGE: // User range keycode. - append_P(PSTR("QK_USER_")); - append_number(keycode - QK_USER_0, 10); - return; - } - // clang-format on - - append_number(keycode, 16); // Fallback: write keycode as hex value. -} - -const char* get_keycode_string(uint16_t keycode) { - buffer_len = 0; - buffer[0] = '\0'; - append_keycode(keycode); - return buffer; -} diff --git a/modules/getreuer/keycode_string/keycode_string.h b/modules/getreuer/keycode_string/keycode_string.h deleted file mode 100644 index 7b7f1dc..0000000 --- a/modules/getreuer/keycode_string/keycode_string.h +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2024-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file keycode_string.h - * @brief Keycode String community model: format keycodes as readable strings. - * - * Example use: Output the keycode and other event information to debug logging. - * This supposes the Console is enabled (see https://docs.qmk.fm/faq_debug). - * - * bool process_record_user(uint16_t keycode, keyrecord_t* record) { - * const uint8_t layer = read_source_layers_cache(record->event.key); - * xprintf("L%-2u: %-7s kc=%s\n", - * layer, record->event.pressed ? "press" : "release", - * get_keycode_string(keycode)); - * - * // Macros... - * return true; - * } - * - * For full documentation, see - * - */ - -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Formats a QMK keycode as a human-readable string. - * - * Given a keycode, like `KC_A`, this function returns a formatted string, like - * "KC_A". This is useful for debugging and diagnostics so that keys are more - * easily identified than they would be by raw numerical codes. - * - * @note The returned char* string should be used right away. The string memory - * is reused and will be overwritten by the next call to `get_keycode_string()`. - * - * Many common QMK keycodes are understood by this function, but not all. - * Recognized keycodes include: - * - * - Most basic keycodes, including letters `KC_A` - `KC_Z`, digits `KC_0` - - * `KC_9`, function keys `KC_F1` - `KC_F24`, and modifiers like `KC_LSFT`. - * - * - Modified basic keycodes, like `S(KC_1)` (Shift + 1 = !). - * - * - `MO`, `TO`, `TG`, `TT`, `DF`, `PDF`, `OSL`, `LM(layer,mod)`, - * `LT(layer,kc)` layer switches. - * - * - One-shot mod `OSM(mod)` keycodes. - * - * - Mod-tap `MT(mod, kc)` keycodes. - * - * - Tap dance keycodes `TD(i)`. - * - * - Swap hands keycodes `SH_T(kc)`, `SH_TOGG`, etc. - * - * - Unicode `UC(codepoint)` and Unicode Map `UM(i)` and `UP(i,j)` keycodes. - * - * - Keyboard range keycodes `QK_KB_*`. - * - * - User range (SAFE_RANGE) keycodes `QK_USER_*`. - * - * Keycodes involving mods like `OSM`, `LM`, `MT` are fully supported only where - * a single mod is applied. - * - * Unrecognized keycodes are printed numerically as hex values like `0x1ABC`. - * - * Optionally, use `KEYCODE_STRING_NAMES_USER` to define names for additional - * keycodes or override how any of the above are formatted. - * - * @param keycode QMK keycode. - * @return Stringified keycode. - */ -const char* get_keycode_string(uint16_t keycode); - -#define KEYCODE_STRING_NAME(kc) {(kc), #kc} - -/** Defines a human-readable name for a keycode. */ -typedef struct { - uint16_t keycode; - const char* name; -} keycode_string_name_t; - -/** - * @brief Defines names for additional keycodes for `get_keycode_string()`. - * - * Define `KEYCODE_STRING_NAMES_USER` in your keymap.c to add names for - * additional keycodes to `keycode_string()`. This table may also be used to - * override how `keycode_string()` formats a keycode. For example, supposing - * keymap.c defines `MYMACRO1` and `MYMACRO2` as custom keycodes: - * - * KEYCODE_STRING_NAMES_USER( - * KEYCODE_STRING_NAME(MYMACRO1), - * KEYCODE_STRING_NAME(MYMACRO2), - * KEYCODE_STRING_NAME(KC_EXLM), - * ); - * - * The above defines names for `MYMACRO1` and `MYMACRO2`, and overrides - * `KC_EXLM` to format as "KC_EXLM" instead of the default "S(KC_1)". - */ -#define KEYCODE_STRING_NAMES_USER(...) \ - static const keycode_string_name_t keycode_string_names_user[] = \ - {__VA_ARGS__}; \ - uint16_t keycode_string_names_size_user = \ - sizeof(keycode_string_names_user) / sizeof(keycode_string_name_t); \ - const keycode_string_name_t* keycode_string_names_data_user = \ - keycode_string_names_user - -/** Helper to define a keycode_string_name_t. */ -#define KEYCODE_STRING_NAME(kc) {(kc), #kc} -// clang-format on - -extern const keycode_string_name_t* keycode_string_names_data_user; -extern uint16_t keycode_string_names_size_user; - -#ifdef __cplusplus -} -#endif diff --git a/modules/getreuer/keycode_string/qmk_module.json b/modules/getreuer/keycode_string/qmk_module.json deleted file mode 100644 index 7eff986..0000000 --- a/modules/getreuer/keycode_string/qmk_module.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "module_name": "Keycode String", - "maintainer": "getreuer", - "url": "https://getreuer.info/posts/keyboards/keycode-string" -} diff --git a/modules/getreuer/mouse_turbo_click/README.md b/modules/getreuer/mouse_turbo_click/README.md deleted file mode 100644 index 1d27549..0000000 --- a/modules/getreuer/mouse_turbo_click/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Mouse Turbo Click - - - - - - - -
Modulegetreuer/mouse_turbo_click
Version2025-03-07
MaintainerPascal Getreuer (@getreuer)
LicenseApache 2.0
Documentation -https://getreuer.info/posts/keyboards/mouse-turbo-click -
- -This is a community module adaptation of [Mouse Turbo -Click](https://getreuer.info/posts/keyboards/mouse-turbo-click) to click the -mouse rapidly. - -Add the following to your `keymap.json`: - -```json -{ - "modules": ["getreuer/mouse_turbo_click"] -} -``` - -Then use the keycode `TURBO` somewhere in your layout. This key clicks the mouse -rapidly, implemented using mouse keys and a periodic callback function: - -* Pressing and holding the Turbo Click button sends rapid mouse clicks, - about 12 clicks per second. - -* Quickly double tapping the Turbo Click button "locks" it. Rapid mouse - clicks are sent until the Turbo Click button is tapped again. - -Optionally, the click rate and keycode can be customized. See the [Mouse Turbo -Click documentation](https://getreuer.info/posts/keyboards/mouse-turbo-click) -for details. - diff --git a/modules/getreuer/mouse_turbo_click/mouse_turbo_click.c b/modules/getreuer/mouse_turbo_click/mouse_turbo_click.c deleted file mode 100644 index 05d55a9..0000000 --- a/modules/getreuer/mouse_turbo_click/mouse_turbo_click.c +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2022-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file mouse_turbo_click.c - * @brief Mouse Turbo Click community module: click the mouse rapidly - * - * This module implements a "Turbo Click" button that clicks the mouse rapidly, - * implemented using mouse keys and a periodic callback function: - * - * * Pressing and holding the Turbo Click button sends rapid mouse clicks, - * about 12 clicks per second. - * - * * Quickly double tapping the Turbo Click button "locks" it. Rapid mouse - * clicks are sent until the Turbo Click button is tapped again. - * - * For full documentation, see - * - */ - -#include "quantum.h" - -ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(1, 0, 0); - -// The keycode to be repeatedly clicked, `MS_BTN1` mouse button 1 by default. -#ifndef MOUSE_TURBO_CLICK_KEY -#define MOUSE_TURBO_CLICK_KEY MS_BTN1 -#endif // MOUSE_TURBO_CLICK_KEY - -// The click period in milliseconds. For instance a period of 200 ms would be 5 -// clicks per second. Smaller period implies faster clicking. -// -// WARNING: The keyboard might become unresponsive if the period is too small. -// I suggest setting this no smaller than 10. -#ifndef MOUSE_TURBO_CLICK_PERIOD -#define MOUSE_TURBO_CLICK_PERIOD 80 -#endif // MOUSE_TURBO_CLICK_PERIOD - -static deferred_token click_token = INVALID_DEFERRED_TOKEN; -static bool click_registered = false; - -// Callback used with deferred execution. It alternates between registering and -// unregistering (pressing and releasing) `MOUSE_TURBO_CLICK_KEY`. -static uint32_t turbo_click_callback(uint32_t trigger_time, void* cb_arg) { - if (click_registered) { - unregister_code16(MOUSE_TURBO_CLICK_KEY); - click_registered = false; - } else { - click_registered = true; - register_code16(MOUSE_TURBO_CLICK_KEY); - } - return MOUSE_TURBO_CLICK_PERIOD / 2; // Execute again in half a period. -} - -// Starts Turbo Click, begins the `turbo_click_callback()` callback. -static void turbo_click_start(void) { - if (click_token == INVALID_DEFERRED_TOKEN) { - uint32_t next_delay_ms = turbo_click_callback(0, NULL); - click_token = defer_exec(next_delay_ms, turbo_click_callback, NULL); - } -} - -// Stops Turbo Click, cancels the callback. -static void turbo_click_stop(void) { - if (click_token != INVALID_DEFERRED_TOKEN) { - cancel_deferred_exec(click_token); - click_token = INVALID_DEFERRED_TOKEN; - if (click_registered) { - // If `MOUSE_TURBO_CLICK_KEY` is currently registered, release it. - unregister_code16(MOUSE_TURBO_CLICK_KEY); - click_registered = false; - } - } -} - -bool process_record_mouse_turbo_click(uint16_t keycode, keyrecord_t* record) { - static bool locked = false; - static bool tapped = false; - static uint16_t tap_timer = 0; - - if (keycode == MOUSE_TURBO_CLICK) { - if (record->event.pressed) { // Turbo Click key was pressed. - if (tapped && !timer_expired(record->event.time, tap_timer)) { - // If the key was recently tapped, lock turbo click. - locked = true; - } else if (locked) { - // Otherwise if currently locked, unlock and stop. - locked = false; - tapped = false; - turbo_click_stop(); - return false; - } - // Set that the first tap occurred in a potential double tap. - tapped = true; - tap_timer = record->event.time + TAPPING_TERM; - - turbo_click_start(); - } else if (!locked) { - // If not currently locked, stop on key release. - turbo_click_stop(); - } - - return false; - } else { - // On an event with any other key, reset the double tap state. - tapped = false; - return true; - } -} - diff --git a/modules/getreuer/mouse_turbo_click/qmk_module.json b/modules/getreuer/mouse_turbo_click/qmk_module.json deleted file mode 100644 index aa47073..0000000 --- a/modules/getreuer/mouse_turbo_click/qmk_module.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "module_name": "Mouse Turbo Click", - "maintainer": "getreuer", - "url": "https://getreuer.info/posts/keyboards/mouse-turbo-click", - "features": { - "deferred_exec": true, - "mousekeys": true - }, - "keycodes": [ - { - "key": "MOUSE_TURBO_CLICK", - "aliases": ["TURBO"] - } - ] -} diff --git a/modules/getreuer/orbital_mouse/README.md b/modules/getreuer/orbital_mouse/README.md deleted file mode 100644 index ff21996..0000000 --- a/modules/getreuer/orbital_mouse/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Orbital Mouse - - - - - - - -
Modulegetreuer/orbital_mouse
Version2025-03-07
MaintainerPascal Getreuer (@getreuer)
LicenseApache 2.0
Documentation -https://getreuer.info/posts/keyboards/orbital-mouse -
- -This is a community module adaptation of [Orbital -Mouse](https://getreuer.info/posts/keyboards/orbital-mouse) for a polar approach -to mouse control. Orbital Mouse replaces QMK Mouse Keys. The pointer moves -according to a heading direction. Two keys move forward and backward along that -direction while another two keys steer. - -Add the following to your `keymap.json`: - -```json -{ - "modules": ["getreuer/orbital_mouse"] -} -``` - -Then use the "`OM_*`" Orbital Mouse keycodes in your layout. - -| Keycode | Aliases | Description | -|-------------|-------------|------------------------------------------------| -| `OM_U` | `MS_UP` | Move forward. | -| `OM_D` | `MS_DOWN` | Move backward. | -| `OM_L` | `MS_LEFT` | Steer left (counter-clockwise). | -| `OM_R` | `MS_RGHT` | Steer right (clockwise). | -| `OM_BTN`*n* | `MS_BTN`*n* | Press mouse button *n*, for *n* = 1, ..., 8. | -| `OM_W_U` | `MS_WHLU` | Mouse wheel up. | -| `OM_W_D` | `MS_WHLD` | Mouse wheel down. | -| `OM_W_L` | `MS_WHLL` | Mouse wheel left. | -| `OM_W_R` | `MS_WHLR` | Mouse wheel right. | -| `OM_SLOW` | | Slow mode. Movement is slower while held. | -| `OM_SEL`*n* | | Select mouse button *n*, for *n* = 1, ..., 8. | -| `OM_BTNS` | | Press the selected mouse button. | -| `OM_DBLS` | | Double click the selected mouse button. | -| `OM_HLDS` | | Hold the selected mouse button. | -| `OM_RELS` | | Release the selected mouse button. | - -A suggested right-handed layout for Orbital Mouse control is - - OM_W_U , OM_BTNS, OM_U , OM_DBLS, _______, - OM_W_D , OM_L , OM_D , OM_R , OM_SLOW, - OM_RELS, OM_HLDS, OM_SEL1, OM_SEL2, OM_SEL3, - -Optionally, there are config options to customize Sentence Case. See the -[Orbital Mouse -documentation](https://getreuer.info/posts/keyboards/orbital-mouse) for details. - diff --git a/modules/getreuer/orbital_mouse/introspection.h b/modules/getreuer/orbital_mouse/introspection.h deleted file mode 100644 index 50c80e1..0000000 --- a/modules/getreuer/orbital_mouse/introspection.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once -#include "orbital_mouse.h" - diff --git a/modules/getreuer/orbital_mouse/orbital_mouse.c b/modules/getreuer/orbital_mouse/orbital_mouse.c deleted file mode 100644 index 75369b1..0000000 --- a/modules/getreuer/orbital_mouse/orbital_mouse.c +++ /dev/null @@ -1,364 +0,0 @@ -// Copyright 2023-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file orbital_mouse.c - * @brief Orbital Mouse community module implementation - * - * For documentation, see - * - */ - -#include "orbital_mouse.h" - -#ifndef ORBITAL_MOUSE_RADIUS -#define ORBITAL_MOUSE_RADIUS 36 -#endif // ORBITAL_MOUSE_RADIUS -#ifndef ORBITAL_MOUSE_SLOW_MOVE_FACTOR -#define ORBITAL_MOUSE_SLOW_MOVE_FACTOR 0.333 -#endif // ORBITAL_MOUSE_SLOW_MOVE_FACTOR -#ifndef ORBITAL_MOUSE_SLOW_TURN_FACTOR -#define ORBITAL_MOUSE_SLOW_TURN_FACTOR 0.5 -#endif // ORBITAL_MOUSE_SLOW_TURN_FACTOR -#ifndef ORBITAL_MOUSE_WHEEL_SPEED -#define ORBITAL_MOUSE_WHEEL_SPEED 0.2 -#endif // ORBITAL_MOUSE_WHEEL_SPEED -#ifndef ORBITAL_MOUSE_DBL_DELAY_MS -#define ORBITAL_MOUSE_DBL_DELAY_MS 50 -#endif // ORBITAL_MOUSE_DBL_DELAY_MS -#ifndef ORBITAL_MOUSE_SPEED_CURVE -#define ORBITAL_MOUSE_SPEED_CURVE \ - {24, 24, 24, 32, 58, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66} -// | | | | | -// t = 0.000 1.024 2.048 3.072 3.840 s -#endif // ORBITAL_MOUSE_SPEED_CURVE -#ifndef ORBITAL_MOUSE_INTERVAL_MS -#define ORBITAL_MOUSE_INTERVAL_MS 16 -#endif // ORBITAL_MOUSE_INTERVAL_MS - -#if !(0 <= ORBITAL_MOUSE_RADIUS && ORBITAL_MOUSE_RADIUS <= 63) -#error "Invalid ORBITAL_MOUSE_RADIUS. Value must be in [0, 63]." -#endif - -enum { - /** Number of distinct angles. */ - NUM_ANGLES = 64, - /** Number of intervals in speed curve table. */ - NUM_SPEED_CURVE_INTERVALS = 16, - /** Orbit radius in pixels as a Q6.2 value. */ - RADIUS_Q6_2 = (uint8_t)((ORBITAL_MOUSE_RADIUS) * 4 + 0.5), - /** Slow mode movement speed factor as a Q.8 value. */ - SLOW_MOVE_FACTOR_Q_8 = (ORBITAL_MOUSE_SLOW_MOVE_FACTOR) < 0.99 - ? ((uint8_t)((ORBITAL_MOUSE_SLOW_MOVE_FACTOR) * 256 + 0.5)) : 255, - /** Slow mode turn speed factor as a Q.8 value. */ - SLOW_TURN_FACTOR_Q_8 = (ORBITAL_MOUSE_SLOW_TURN_FACTOR) < 0.99 - ? ((uint8_t)((ORBITAL_MOUSE_SLOW_TURN_FACTOR) * 256 + 0.5)) : 255, - /** Wheel speed in steps/frame as a Q2.6 value. */ - WHEEL_SPEED_Q2_6 = (ORBITAL_MOUSE_WHEEL_SPEED) < 3.99 - ? ((uint8_t)((ORBITAL_MOUSE_WHEEL_SPEED) * 64 + 0.5)) : 255, - /** Double click delay in units of intervals. */ - DOUBLE_CLICK_DELAY_INTERVALS = - (ORBITAL_MOUSE_DBL_DELAY_MS) / (ORBITAL_MOUSE_INTERVAL_MS), -}; - -// Masks for the `held_keys` bitfield. -enum { - HELD_U = 1, - HELD_D = 2, - HELD_L = 4, - HELD_R = 8, - HELD_W_U = 16, - HELD_W_D = 32, - HELD_W_L = 64, - HELD_W_R = 128, -}; - -static const uint8_t init_speed_curve[NUM_SPEED_CURVE_INTERVALS] = - ORBITAL_MOUSE_SPEED_CURVE; -static struct { - report_mouse_t report; - // Current speed curve, should point to a table of 16 values. - const uint8_t* speed_curve; - // Time when the Orbital Mouse task function should next run. - uint16_t timer; - // Fractional displacement of the cursor as Q7.8 values. - int16_t x; - int16_t y; - // Fractional displacement of the mouse wheel as Q9.6 values. - int16_t wheel_x; - int16_t wheel_y; - // Current cursor movement speed as a Q9.6 value. - int16_t speed; - // Bitfield tracking which movement keys are currently held. - uint8_t held_keys; - // Cursor movement time, counted in number of intervals. - uint8_t move_t; - // Cursor movement direction, 1 => forward, -1 => backward. - int8_t move_dir; - // Steering direction, 1 => counter-clockwise, -1 => clockwise. - int8_t steer_dir; - // Mouse wheel movement directions. - int8_t wheel_x_dir; - int8_t wheel_y_dir; - // Heading direction as a Q6.8 value, with 0 => up, 16 * 256 => left, etc. - uint16_t angle; - // Selected mouse button as a base-0 index. - uint8_t selected_button; - // Tracks double click action. - uint8_t double_click_frame; - // When true, movement and turning are slower. - bool slow; -} state = {.speed_curve = init_speed_curve}; - -/** - * Fixed-point sine with specified amplitude and phase. - * - * @param amplitude Nonnegative Q6.2 value. - * @param phase Value in [0, 63]. - * @returns Result as a Q6.8 value. - */ -static int16_t scaled_sin(uint8_t amplitude, uint8_t phase) { - // Look up table covers half a cycle of a sine wave. - static const uint8_t lut[NUM_ANGLES / 2] PROGMEM = { - 0, 25, 50, 74, 98, 120, 142, 162, 180, 197, 212, - 225, 236, 244, 250, 254, 255, 254, 250, 244, 236, 225, - 212, 197, 180, 162, 142, 120, 98, 74, 50, 25}; - // amplitude Q6.2 and lut is Q0.8. Shift down by 2 so that the result is Q6.8. - int16_t value = (int16_t)(((uint16_t)amplitude - * pgm_read_byte(lut + (phase & (NUM_ANGLES / 2 - 1))) + 2) >> 2); - return ((NUM_ANGLES / 2) & phase) == 0 ? value : -value; -} - -/** Computes fixed-point cosine. */ -static int16_t scaled_cos(uint8_t amplitude, uint8_t phase) { - return scaled_sin(amplitude, phase + (NUM_ANGLES / 4)); -} - -/** Wakes the Orbital Mouse task. */ -static void wake_orbital_mouse_task(void) { - if (!state.timer) { - state.timer = timer_read() | 1; - } -} - -/** Converts a keycode to a mask for the `held_keys` bitfield. */ -static uint8_t keycode_to_held_mask(uint16_t keycode) { - switch (keycode) { - case OM_U: return HELD_U; - case OM_D: return HELD_D; - case OM_L: return HELD_L; - case OM_R: return HELD_R; - case OM_W_U: return HELD_W_U; - case OM_W_D: return HELD_W_D; - case OM_W_L: return HELD_W_L; - case OM_W_R: return HELD_W_R; - } - return 0; -} - -/** Presses mouse button i, with i being a base-0 index. */ -static void press_mouse_button(uint8_t i, bool pressed) { - if (i >= 8) { - i = state.selected_button; - } - const uint8_t mask = 1 << i; - if (pressed) { - state.report.buttons |= mask; - } else { - state.report.buttons &= ~mask; - } - wake_orbital_mouse_task(); -} - -/** Selects mouse button i. */ -static void select_mouse_button(uint8_t i) { - state.selected_button = i; - // Reset buttons and double-click state when switching selection. - state.report.buttons = 0; - state.double_click_frame = 0; - wake_orbital_mouse_task(); -} - -static int8_t get_dir_from_held_keys(uint8_t bit_shift) { - static const int8_t dir[4] = {0, 1, -1, 0}; - return dir[(state.held_keys >> bit_shift) & 3]; -} - -void set_orbital_mouse_speed_curve(const uint8_t* speed_curve) { - state.speed_curve = (speed_curve != NULL) ? speed_curve : init_speed_curve; -} - -uint8_t get_orbital_mouse_angle(void) { - return (state.angle >> 8) & (NUM_ANGLES - 1); -} - -static void set_orbital_mouse_angle_fractional(uint16_t angle) { - state.x += scaled_sin(RADIUS_Q6_2, state.angle >> 8); - state.y += scaled_cos(RADIUS_Q6_2, state.angle >> 8); - state.angle = angle; - state.x -= scaled_sin(RADIUS_Q6_2, angle >> 8); - state.y -= scaled_cos(RADIUS_Q6_2, angle >> 8); - wake_orbital_mouse_task(); -} - -void set_orbital_mouse_angle(uint8_t angle) { - set_orbital_mouse_angle_fractional((uint16_t)angle << 8); -} - -bool process_record_orbital_mouse(uint16_t keycode, keyrecord_t* record) { - if (!(IS_MOUSE_KEYCODE(keycode) || - (OM_SLOW <= keycode && keycode <= OM_SEL8))) { - return true; - } - - uint8_t held_mask = keycode_to_held_mask(keycode); - if (held_mask != 0) { - // Update `held_keys` bitfield. - if (record->event.pressed) { - state.held_keys |= held_mask; - } else { - state.held_keys &= ~held_mask; - } - } else { - switch (keycode) { - case OM_BTN1 ... OM_BTN8: - press_mouse_button(keycode - OM_BTN1, record->event.pressed); - return false; - case OM_BTNS: - press_mouse_button(255, record->event.pressed); - return false; - case OM_HLDS: - if (record->event.pressed) { - press_mouse_button(255, true); - } - return false; - case OM_RELS: - if (record->event.pressed) { - press_mouse_button(255, false); - } - return false; - case OM_DBLS: - if (record->event.pressed) { - state.double_click_frame = 1; - } - break; - case OM_SLOW: - state.slow = record->event.pressed; - return false; - case OM_SEL1 ... OM_SEL8: - if (record->event.pressed) { - select_mouse_button(keycode - OM_SEL1); - } - return false; - } - } - - // Update cursor movement direction. - const int8_t dir = get_dir_from_held_keys(0); - if (state.move_dir != dir) { - state.move_dir = dir; - state.move_t = 0; - } - // Update steering direction. - state.steer_dir = get_dir_from_held_keys(2); - // Update wheel movement. - state.wheel_y_dir = get_dir_from_held_keys(4); - state.wheel_x_dir = get_dir_from_held_keys(6); - wake_orbital_mouse_task(); - - return false; -} - -void housekeeping_task_orbital_mouse(void) { - const uint16_t now = timer_read(); - if (!state.timer || !timer_expired(now, state.timer)) { - return; - } - - bool active = false; - - // Update position if moving. - if (state.move_dir) { - // Update speed, interpolated from speed_curve. - if (state.move_t <= 16 * (NUM_SPEED_CURVE_INTERVALS - 1)) { - if (state.move_t == 0) { - state.speed = (int16_t)state.speed_curve[0] * 16; - } else { - const uint8_t i = (state.move_t - 1) / 16; - state.speed += (int16_t)state.speed_curve[i + 1] - - (int16_t)state.speed_curve[i]; - } - - ++state.move_t; - } - // Round and cast from Q9.6 to Q6.2. - uint8_t speed = (state.speed + 8) / 16; - if (state.slow) { - speed = ((uint16_t)speed) * (1 + (uint16_t)SLOW_MOVE_FACTOR_Q_8) >> 8; - } - - state.x -= state.move_dir * scaled_sin(speed, state.angle >> 8); - state.y -= state.move_dir * scaled_cos(speed, state.angle >> 8); - active = true; - } - - // Update heading angle if steering. - if (state.steer_dir) { - int16_t angle_step = state.slow ? SLOW_TURN_FACTOR_Q_8 : 256; - if (state.steer_dir == -1) { - angle_step = -angle_step; - } - set_orbital_mouse_angle_fractional(state.angle + angle_step); - active = true; - } - - // Update mouse wheel if active. - if (state.wheel_x_dir || state.wheel_y_dir) { - state.wheel_x -= state.wheel_x_dir * WHEEL_SPEED_Q2_6; - state.wheel_y += state.wheel_y_dir * WHEEL_SPEED_Q2_6; - active = true; - } - - // Update double click action. - if (state.double_click_frame) { - ++state.double_click_frame; - const uint8_t mask = 1 << state.selected_button; - switch (state.double_click_frame) { - case 2: - case 3: - case 4 + DOUBLE_CLICK_DELAY_INTERVALS: - state.report.buttons ^= mask; - break; - case 5 + DOUBLE_CLICK_DELAY_INTERVALS: - state.report.buttons &= ~mask; - state.double_click_frame = 0; - } - active = true; - } - - // Schedule when task should run again, or go to sleep if inactive. - state.timer = active ? ((now + ORBITAL_MOUSE_INTERVAL_MS) | 1) : 0; - - // Set whole part of movement deltas in report and retain fractional parts. - state.report.x = state.x / 256; - state.report.y = state.y / 256; - state.x -= (int16_t)state.report.x * 256; - state.y -= (int16_t)state.report.y * 256; - state.report.h = state.wheel_x / 64; - state.report.v = state.wheel_y / 64; - state.wheel_x -= (int16_t)state.report.h * 64; - state.wheel_y -= (int16_t)state.report.v * 64; - host_mouse_send(&state.report); -} - diff --git a/modules/getreuer/orbital_mouse/orbital_mouse.h b/modules/getreuer/orbital_mouse/orbital_mouse.h deleted file mode 100644 index 45d8aec..0000000 --- a/modules/getreuer/orbital_mouse/orbital_mouse.h +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2023-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file orbital_mouse.h - * @brief Orbital Mouse community module: a polar approach to mouse control. - * - * Orbital Mouse is a module that replaces QMK Mouse Keys. The pointer moves - * according to a heading direction. Two keys move forward and backward along - * that direction while another two keys steer. - * - * For documentation, see - * - */ - -#pragma once - -#include "quantum.h" - -/** - * Sets the pointer movement speed curve at run time. - * - * This function enables dynamically switching between multiple speed curves. - * - * @param speed_curve Pointer to an array of size 16. If NULL, the speed curve - * defined by ORBITAL_MOUSE_SPEED_CURVE is set. - */ -void set_orbital_mouse_speed_curve(const uint8_t* speed_curve); - -/** - * Gets the heading direction as a value in 0-63. - * - * Value 0 is up, and values increase in the counter-clockwise direction. - * - * 0 = up 32 = down - * 8 = up-left 40 = down-right - * 16 = left 48 = right - * 24 = down-left 56 = up-right - */ -uint8_t get_orbital_mouse_angle(void); - -/** Sets the heading direction. */ -void set_orbital_mouse_angle(uint8_t angle); - -// The following defines keycodes for Orbital Mouse. Being a Mouse Keys -// replacement, we repurpose the Mouse Keys keycodes (`MS_UP`, `MS_BTN1`, -// etc.) for the analogous functions in Orbital Mouse. -enum { - /** Move forward. */ - OM_U = MS_UP, - /** Move backward. */ - OM_D = MS_DOWN, - /** Steer left (counter-clockwise). */ - OM_L = MS_LEFT, - /** Steer right (clockwise). */ - OM_R = MS_RGHT, - /** Mouse wheel up. */ - OM_W_U = MS_WHLU, - /** Mouse wheel down. */ - OM_W_D = MS_WHLD, - /** Mouse wheel left. */ - OM_W_L = MS_WHLL, - /** Mouse wheel right. */ - OM_W_R = MS_WHLR, - /** Press mouse button 1. */ - OM_BTN1 = MS_BTN1, - /** Press mouse button 2. */ - OM_BTN2 = MS_BTN2, - /** Press mouse button 3. */ - OM_BTN3 = MS_BTN3, - /** Press mouse button 4. */ - OM_BTN4 = MS_BTN4, - /** Press mouse button 5. */ - OM_BTN5 = MS_BTN5, - /** Press mouse button 6. */ - OM_BTN6 = MS_BTN6, - /** Press mouse button 7. */ - OM_BTN7 = MS_BTN7, - /** Press mouse button 8. */ - OM_BTN8 = MS_BTN8, -}; - diff --git a/modules/getreuer/orbital_mouse/qmk_module.json b/modules/getreuer/orbital_mouse/qmk_module.json deleted file mode 100644 index efb2783..0000000 --- a/modules/getreuer/orbital_mouse/qmk_module.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "module_name": "Orbital Mouse", - "maintainer": "getreuer", - "url": "https://getreuer.info/posts/keyboards/orbital-mouse", - "features": { - "mouse": true - }, - "keycodes": [ - {"key": "OM_SLOW"}, - {"key": "OM_BTNS"}, - {"key": "OM_DBLS"}, - {"key": "OM_HLDS"}, - {"key": "OM_RELS"}, - {"key": "OM_SEL1"}, - {"key": "OM_SEL2"}, - {"key": "OM_SEL3"}, - {"key": "OM_SEL4"}, - {"key": "OM_SEL5"}, - {"key": "OM_SEL6"}, - {"key": "OM_SEL7"}, - {"key": "OM_SEL8"} - ] -} diff --git a/modules/getreuer/palettefx/README.md b/modules/getreuer/palettefx/README.md deleted file mode 100644 index 2235b1c..0000000 --- a/modules/getreuer/palettefx/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# PaletteFx - - - - - - - -
Modulegetreuer/palettefx
Version2025-03-07
MaintainerPascal Getreuer (@getreuer)
LicenseApache 2.0
Documentation -https://getreuer.info/posts/keyboards/palettefx -
- -This is a community module adaptation of -[PaletteFx](https://getreuer.info/posts/keyboards/palettefx) for colorful -palette-based RGB matrix effects. While most of QMK's built-in RGB matrix -effects are based on a single color hue, sampling from a palette of colors -allows for more personality. PaletteFx is a suite of custom effects for QMK's -RGB Matrix in which the effect colors are sampled from a palette, aka color -gradient or color map. - - -## Add PaletteFx to your keymap - -Add the following to your `keymap.json`: - -```json -{ - "modules": ["getreuer/palettefx"] -} -``` - -Then in your keymap folder, create a file `rgb_matrix_user.inc` with the -following content, or if it already exists, add this as the first line: - -```c -#include "palettefx.inc" -``` - -## Using PaletteFx - -**Selecting effects:** PaletteFx effects are appended to the list of existing -RGB Matrix effects. Use the usual `RM_NEXT` / `RM_PREV` keycodes to switch to -the PaletteFx effects. - -**Selecting palettes:** PaletteFx effects repurpose the RGB Matrix hue setting to -select which palette to use. Use the hue keycodes `RM_HUEU` / `RM_HUED` to cycle -through them. The `i`th palette corresponds to hue = `RGB_MATRIX_HUE_STEP * i`. - - -## Further details - -Optionally, you can define your own palettes and palette-based effects. See the -[PaletteFx documentation](https://getreuer.info/posts/keyboards/palettefx) for -details. - diff --git a/modules/getreuer/palettefx/introspection.h b/modules/getreuer/palettefx/introspection.h deleted file mode 100644 index 08de2ad..0000000 --- a/modules/getreuer/palettefx/introspection.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once -#include "palettefx.h" - diff --git a/modules/getreuer/palettefx/palettefx.c b/modules/getreuer/palettefx/palettefx.c deleted file mode 100644 index 6762209..0000000 --- a/modules/getreuer/palettefx/palettefx.c +++ /dev/null @@ -1,493 +0,0 @@ -// Copyright 2024-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file palettefx.c - * @brief PaletteFx community module implementation - * - * For full documentation, see - * - */ - -#include "palettefx.h" -#include "quantum.h" - -#include - -ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(1, 0, 0); - -/** - * @brief 16-bit HSV color. - * - * Represents a color with a 16-bit value in hue-saturation-value (HSV) space. - * Components are packed as: - * - * - Hue: lowest 8 bits. - * - Saturation: middle 4 bits. - * - Value: highest 4 bits. - */ -#define HSV16(h, s, v) ((((v) >> 4) << 12) | (((s) >> 4) << 8) | ((h) & 0xff)) - -/** Unpacks 16-bit HSV color to hsv_t. */ -static hsv_t unpack_hsv16(uint16_t hsv16) { - return (hsv_t){ - .h = (uint8_t)hsv16, - .s = ((uint8_t)(hsv16 >> 8) & 0x0f) * 17, - .v = ((uint8_t)(hsv16 >> 12) & 0x0f) * 17, - }; -} - -/** PaletteFx palette color data. */ -static const uint16_t palettefx_palettes[][16] PROGMEM = { -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_AFTERBURN_ENABLE) - // "Afterburn" palette. - { - HSV16(139, 255, 85), - HSV16(134, 255, 85), - HSV16(131, 255, 102), - HSV16(128, 255, 102), - HSV16(127, 187, 102), - HSV16(125, 119, 102), - HSV16(124, 51, 102), - HSV16(125, 0, 119), - HSV16( 15, 17, 119), - HSV16( 17, 51, 153), - HSV16( 18, 102, 170), - HSV16( 19, 153, 204), - HSV16( 21, 187, 238), - HSV16( 23, 238, 255), - HSV16( 26, 255, 255), - HSV16( 30, 255, 255), - }, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_AMBER_ENABLE) - // "Amber" palette. - { - HSV16( 5, 187, 170), - HSV16( 15, 221, 170), - HSV16( 22, 238, 187), - HSV16( 23, 238, 187), - HSV16( 25, 255, 204), - HSV16( 27, 255, 221), - HSV16( 29, 255, 238), - HSV16( 29, 255, 255), - HSV16( 28, 187, 255), - HSV16( 30, 68, 255), - HSV16( 32, 34, 255), - HSV16( 32, 34, 255), - HSV16( 26, 119, 255), - HSV16( 24, 238, 255), - HSV16( 21, 221, 255), - HSV16( 16, 204, 255), - }, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_BADWOLF_ENABLE) - // "Bad Wolf" palette. Inspired by the Bad Wolf theme by Steve Losh, which is - // distributed under MIT/X11 license. - // https://github.com/sjl/badwolf/tree/61d75affa51d40213d671edc9c8ff83992d7fd6f - { - HSV16( 14, 34, 0), - HSV16(245, 255, 255), - HSV16(245, 255, 255), - HSV16(245, 255, 255), - HSV16( 14, 34, 0), - HSV16( 14, 34, 0), - HSV16( 14, 34, 0), - HSV16( 14, 34, 0), - HSV16( 14, 34, 0), - HSV16( 14, 34, 0), - HSV16( 24, 17, 136), - HSV16( 28, 0, 255), - HSV16( 24, 17, 136), - HSV16( 14, 34, 0), - HSV16( 14, 34, 0), - HSV16( 14, 34, 0), - }, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_CARNIVAL_ENABLE) - // "Carnival" palette. - { - HSV16(132, 255, 85), - HSV16(121, 187, 85), - HSV16(108, 170, 102), - HSV16( 90, 153, 119), - HSV16( 70, 187, 136), - HSV16( 57, 255, 153), - HSV16( 50, 255, 187), - HSV16( 44, 255, 204), - HSV16( 39, 255, 238), - HSV16( 32, 255, 255), - HSV16( 27, 255, 255), - HSV16( 22, 255, 255), - HSV16( 18, 255, 255), - HSV16( 8, 221, 238), - HSV16(252, 204, 238), - HSV16(241, 255, 221), - }, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_CLASSIC_ENABLE) - // "Classic" palette. - { - HSV16(150, 255, 85), - HSV16(151, 238, 119), - HSV16(160, 136, 119), - HSV16(176, 102, 136), - HSV16(193, 85, 136), - HSV16(216, 85, 136), - HSV16(234, 102, 153), - HSV16(247, 119, 187), - HSV16( 3, 153, 204), - HSV16( 10, 204, 221), - HSV16( 15, 255, 255), - HSV16( 18, 255, 255), - HSV16( 23, 255, 255), - HSV16( 24, 204, 255), - HSV16( 25, 136, 255), - HSV16( 26, 85, 255), - }, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_DRACULA_ENABLE) - // "Dracula" palette. Inspired by the Dracula theme by Zeno Rocha, which is - // distributed under MIT license. - // https://github.com/dracula/dracula-theme/tree/ac4dc82dab2a3c35e5cac0cd80c97fbf4c2ca986 - { - HSV16(165, 153, 119), - HSV16(167, 153, 136), - HSV16(170, 170, 187), - HSV16(173, 170, 221), - HSV16(177, 170, 238), - HSV16(183, 170, 255), - HSV16(190, 170, 255), - HSV16(200, 153, 255), - HSV16(216, 153, 255), - HSV16(230, 170, 255), - HSV16( 2, 153, 255), - HSV16( 29, 153, 238), - HSV16( 44, 255, 255), - HSV16( 53, 238, 255), - HSV16( 63, 238, 238), - HSV16( 85, 238, 238), - }, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_GROOVY_ENABLE) - // "Groovy" palette. Inspired by the Gruvbox theme by Pavel Pertsev, which is - // distributed under MIT/X11 license. - // https://github.com/morhetz/gruvbox/tree/f1ecde848f0cdba877acb0c740320568252cc482 - { - HSV16( 26, 136, 187), - HSV16( 23, 85, 102), - HSV16( 21, 85, 85), - HSV16( 21, 85, 85), - HSV16( 21, 85, 85), - HSV16( 17, 102, 102), - HSV16( 5, 204, 255), - HSV16( 4, 255, 255), - HSV16( 4, 255, 255), - HSV16( 7, 204, 255), - HSV16( 23, 136, 187), - HSV16( 26, 136, 187), - HSV16( 28, 136, 187), - HSV16( 50, 238, 238), - HSV16( 54, 255, 238), - HSV16( 54, 255, 238), - }, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_NOTPINK_ENABLE) - // "Not Pink" palette. - { - HSV16( 9, 255, 153), - HSV16( 3, 221, 187), - HSV16(252, 204, 187), - HSV16(248, 204, 187), - HSV16(247, 187, 204), - HSV16(245, 187, 238), - HSV16(244, 170, 255), - HSV16(245, 153, 255), - HSV16(252, 102, 255), - HSV16( 16, 68, 255), - HSV16( 40, 34, 255), - HSV16( 32, 51, 255), - HSV16( 6, 85, 255), - HSV16(248, 136, 255), - HSV16(247, 187, 255), - HSV16(249, 221, 255), - }, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_PHOSPHOR_ENABLE) - // "Phosphor" palette. - { - HSV16(116, 102, 34), - HSV16(113, 170, 51), - HSV16(113, 255, 68), - HSV16(110, 255, 68), - HSV16(109, 255, 85), - HSV16(105, 255, 102), - HSV16( 95, 238, 102), - HSV16( 85, 238, 119), - HSV16( 84, 255, 136), - HSV16( 84, 255, 153), - HSV16( 83, 255, 187), - HSV16( 80, 238, 204), - HSV16( 69, 221, 238), - HSV16( 46, 204, 255), - HSV16( 42, 153, 255), - HSV16( 40, 102, 255), - }, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_POLARIZED_ENABLE) - // "Polarized" palette. Inspired by the Solarized theme by Ethan Schoonover, - // which is distributed under MIT license. - // https://github.com/altercation/solarized/tree/62f656a02f93c5190a8753159e34b385588d5ff3 - { - HSV16(139, 255, 68), - HSV16(139, 221, 85), - HSV16(139, 204, 102), - HSV16(138, 204, 119), - HSV16(137, 204, 136), - HSV16(137, 187, 170), - HSV16(137, 153, 187), - HSV16(132, 170, 187), - HSV16(126, 187, 238), - HSV16(124, 102, 255), - HSV16(124, 102, 255), - HSV16(124, 187, 255), - HSV16(130, 170, 204), - HSV16(137, 153, 187), - HSV16(137, 153, 204), - HSV16(137, 136, 221), - }, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_ROSEGOLD_ENABLE) - // "Rose Gold" palette. - { - HSV16(246, 255, 204), - HSV16(250, 238, 221), - HSV16(253, 221, 238), - HSV16( 1, 221, 255), - HSV16( 6, 221, 255), - HSV16( 12, 221, 255), - HSV16( 18, 204, 255), - HSV16( 22, 170, 255), - HSV16( 20, 204, 255), - HSV16( 17, 221, 255), - HSV16( 12, 221, 255), - HSV16( 7, 221, 255), - HSV16(255, 204, 255), - HSV16(248, 204, 255), - HSV16(241, 238, 255), - HSV16(235, 255, 255), - }, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_SPORT_ENABLE) - // "Sport" palette. - { - HSV16(156, 102, 51), - HSV16(155, 102, 68), - HSV16(155, 102, 68), - HSV16(154, 102, 68), - HSV16(155, 102, 85), - HSV16(156, 85, 102), - HSV16(158, 68, 119), - HSV16(159, 51, 136), - HSV16(158, 17, 153), - HSV16(130, 0, 170), - HSV16( 49, 17, 204), - HSV16( 42, 51, 238), - HSV16( 39, 85, 255), - HSV16( 37, 119, 255), - HSV16( 36, 170, 255), - HSV16( 36, 255, 255), - }, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_SYNTHWAVE_ENABLE) - // "Synthwave" palette. - { - HSV16(170, 221, 119), - HSV16(180, 221, 153), - HSV16(196, 238, 153), - HSV16(209, 255, 153), - HSV16(220, 255, 170), - HSV16(227, 255, 204), - HSV16(233, 255, 238), - HSV16(245, 204, 255), - HSV16( 3, 170, 255), - HSV16( 13, 187, 255), - HSV16( 21, 204, 255), - HSV16( 28, 221, 255), - HSV16( 43, 255, 255), - HSV16( 42, 255, 255), - HSV16( 68, 68, 255), - HSV16(132, 255, 255), - }, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_THERMAL_ENABLE) - // "Thermal" palette. - { - HSV16( 7, 0, 17), - HSV16( 8, 0, 17), - HSV16( 12, 0, 34), - HSV16( 13, 0, 51), - HSV16( 15, 17, 51), - HSV16( 16, 17, 68), - HSV16( 20, 34, 85), - HSV16( 95, 0, 102), - HSV16(126, 85, 119), - HSV16(112, 85, 153), - HSV16( 54, 136, 187), - HSV16( 39, 238, 221), - HSV16( 33, 255, 255), - HSV16( 25, 238, 255), - HSV16( 13, 238, 255), - HSV16( 5, 238, 255), - }, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_VIRIDIS_ENABLE) - // "Viridis" palette. Inspired by the Viridis colormap by Stefan van der Walt - // and Nathaniel Smith, which is distributed under CC0 license. - // https://github.com/BIDS/colormap/blob/bc549477db0c12b54a5928087552ad2cf274980f/colormaps.py - { - HSV16(204, 221, 102), - HSV16(194, 187, 119), - HSV16(183, 153, 136), - HSV16(169, 119, 153), - HSV16(155, 153, 153), - HSV16(145, 170, 153), - HSV16(137, 187, 153), - HSV16(130, 204, 153), - HSV16(123, 221, 153), - HSV16(117, 221, 170), - HSV16(108, 187, 187), - HSV16( 93, 153, 204), - HSV16( 71, 170, 221), - HSV16( 56, 221, 221), - HSV16( 46, 255, 238), - HSV16( 39, 255, 255), - }, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_WATERMELON_ENABLE) - // "Watermelon" palette. - { - HSV16( 67, 255, 102), - HSV16( 55, 255, 153), - HSV16( 47, 255, 204), - HSV16( 48, 136, 238), - HSV16( 48, 68, 255), - HSV16( 45, 34, 255), - HSV16( 39, 34, 255), - HSV16( 17, 85, 255), - HSV16( 8, 187, 255), - HSV16( 1, 255, 255), - HSV16( 2, 238, 255), - HSV16( 2, 238, 255), - HSV16( 0, 238, 255), - HSV16(253, 255, 238), - HSV16(254, 255, 238), - HSV16(255, 255, 221), - }, -#endif -#if __has_include("palettefx_user.inc") // Include user palettes if present. -#include "palettefx_user.inc" -#endif -}; -/** Number of palettes. User palettes, if any, are included in the count. */ -#define NUM_PALETTEFX_PALETTES \ - (sizeof(palettefx_palettes) / sizeof(*palettefx_palettes)) - -// Validate at compile time that `1 <= NUM_PALETTEFX_PALETTES <= 32`. The upper -// limit is due to using RGB Matrix's hue config to select palettes. -_Static_assert( - NUM_PALETTEFX_PALETTES >= 1, - "palettefx: No palettes are enabled. To fix: enable all built-in palettes by adding in config.h `#define PALETTE_ENABLE_ALL_PALETTES`, or enable at least one with `#define PALETTE__ENABLE`, or define at least one custom palette in palettefx_user.inc."); -_Static_assert( - NUM_PALETTEFX_PALETTES <= 256 / RGB_MATRIX_HUE_STEP, - "palettefx: Too many palettes. Up to 32 (= 256 / RGB_MATRIX_HUE_STEP) palettes are supported. Otherwise, some palettes would be unreachable."); - -/** Gets the index of the selected palette. */ -static uint8_t palettefx_get_palette(void) { - uint8_t i = - (rgb_matrix_get_hue() / RGB_MATRIX_HUE_STEP) % NUM_PALETTEFX_PALETTES; - - if (256 % (RGB_MATRIX_HUE_STEP * NUM_PALETTEFX_PALETTES) != 0) { - // Hue wraps mod 256. If NUM_PALETTEFX_PALETTES is not a power of 2, modulo - // 256 wraps would jump, and adjustment is needed to cycle as desired: - // - // * If the hue is a step or less from 256, assume it has wrapped down - // from 0 and the last palette is selected. - // * Otherwise, i = ((hue / step) % NUM_PALETTEFX_PALETTES) is used. If - // 256 - 2 * step <= hue < 256 - step, hue is set to (step * i). - hsv_t hsv = rgb_matrix_get_hsv(); - if (hsv.h >= 256 - 2 * RGB_MATRIX_HUE_STEP) { - if (hsv.h >= 256 - RGB_MATRIX_HUE_STEP) { - i = NUM_PALETTEFX_PALETTES - 1; - hsv.h = RGB_MATRIX_HUE_STEP * ( - (256 / (RGB_MATRIX_HUE_STEP * NUM_PALETTEFX_PALETTES)) * - NUM_PALETTEFX_PALETTES - - 1); - } else { - i %= NUM_PALETTEFX_PALETTES; - hsv.h = RGB_MATRIX_HUE_STEP * i; - } - rgb_matrix_sethsv_noeeprom(hsv.h, hsv.s, hsv.v); - } - } - - return i; -} - -const uint16_t* palettefx_get_palette_data(void) { - return palettefx_palettes[palettefx_get_palette()]; -} - -const uint16_t* palettefx_get_palette_data_by_index(uint8_t i) { - return palettefx_palettes[i % NUM_PALETTEFX_PALETTES]; -} - -uint8_t palettefx_num_palettes(void) { - return NUM_PALETTEFX_PALETTES; -} - -hsv_t palettefx_interp_color(const uint16_t* palette, uint8_t x) { - // Clamp `x` to [8, 247] and subtract 8, mapping to the range [0, 239]. - x = (x <= 8) ? 0 : ((x < 247) ? (x - 8) : 239); - // Get index into the palette, 0 <= i <= 14. - const uint8_t i = x >> 4; - // Fractional position between i and (i + 1). - const uint8_t frac = x << 4; - - // Look up palette colors at i and (i + 1). - hsv_t a = unpack_hsv16(pgm_read_word(&palette[i])); - hsv_t b = unpack_hsv16(pgm_read_word(&palette[i + 1])); - - // Linearly interpolate in HSV, accounting for wrapping in hue. - const uint8_t hue_wrap = 128 & (a.h >= b.h ? (a.h - b.h) : (b.h - a.h)); - return (hsv_t){ - .h = lerp8by8(a.h ^ hue_wrap, b.h ^ hue_wrap, frac) ^ hue_wrap, - .s = scale8(lerp8by8(a.s, b.s, frac), rgb_matrix_config.hsv.s), - .v = scale8(lerp8by8(a.v, b.v, frac), rgb_matrix_config.hsv.v), - }; -} - -uint16_t palettefx_scaled_time(uint32_t timer, uint8_t scale) { - static uint16_t wrap_correction = 0; - static uint8_t last_high_byte = 0; - const uint8_t high_byte = (uint8_t)(timer >> 16); - - if (last_high_byte != high_byte) { - last_high_byte = high_byte; - wrap_correction += ((uint16_t)scale) << 8; - } - - return scale16by8(timer, scale) + wrap_correction; -} - diff --git a/modules/getreuer/palettefx/palettefx.h b/modules/getreuer/palettefx/palettefx.h deleted file mode 100644 index a78f599..0000000 --- a/modules/getreuer/palettefx/palettefx.h +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2024-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file palettefx.h - * @brief PaletteFx community module: Add more colors to your keyboard - * - * - * For full documentation, see - * - */ - -#pragma once - -#include -#include "color.h" -#include "palettefx_default_config.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** Gets the color data for the selected palette. */ -const uint16_t* palettefx_get_palette_data(void); - -/** Gets the color data for the ith palette. */ -const uint16_t* palettefx_get_palette_data_by_index(uint8_t i); - -/** Returns the number of palettes. */ -uint8_t palettefx_num_palettes(void); - -/** - * @brief Computes the interpolated HSV palette color at 0 <= x < 256. - * - * Looks up and linearly interpolates `palette` at 0 <= x < 256. The color - * saturation and value are scaled according to rgb_matrix_config. - * - * @note `palette` must point to a PROGMEM address. - * - * @param palette Pointer to PROGMEM of a size-16 table of HSV16 colors. - * @param x Palette lookup position, a value in 0-255. - * @return HSV color. - */ -hsv_t palettefx_interp_color(const uint16_t* palette, uint8_t x); - -/** - * @brief Compute a scaled 16-bit time that wraps smoothly. - * - * Computes `timer` scaled by `scale`, returning the result as a uint16. - * Generally, the scaled time would have a discontinuity every ~65 seconds when - * `timer` wraps 16-bit range. This is corrected for to wrap smoothly mod 2^16. - * - * @param timer A 32-bit timer. - * @param scale Scale value in 0-255. - * @return Scaled time. - */ -uint16_t palettefx_scaled_time(uint32_t timer, uint8_t scale); - -// The following enum constants may be used to refer to PaletteFx palettes by -// name. To set a particular palette programmatically, do e.g. -// -// void keyboard_post_init_user(void) { -// uint8_t i = PALETTEFX_CARNIVAL; // Set Carnival palette at startup. -// rgb_matrix_sethsv_noeeprom(RGB_MATRIX_HUE_STEP * i, 255, 255); -// } -// -// If you have defined additional palettes in palettefx_user.inc, they may be -// referred to by `PALETTEFX_USER_0`, `PALETTEFX_USER_1`, etc. -enum { -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_AFTERBURN_ENABLE) - PALETTEFX_AFTERBURN, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_AMBER_ENABLE) - PALETTEFX_AMBER, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_BADWOLF_ENABLE) - PALETTEFX_BADWOLF, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_CARNIVAL_ENABLE) - PALETTEFX_CARNIVAL, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_CLASSIC_ENABLE) - PALETTEFX_CLASSIC, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_DRACULA_ENABLE) - PALETTEFX_DRACULA, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_GROOVY_ENABLE) - PALETTEFX_GROOVY, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_NOTPINK_ENABLE) - PALETTEFX_NOTPINK, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_PHOSPHOR_ENABLE) - PALETTEFX_PHOSPHOR, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_POLARIZED_ENABLE) - PALETTEFX_POLARIZED, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_ROSEGOLD_ENABLE) - PALETTEFX_ROSEGOLD, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_SPORT_ENABLE) - PALETTEFX_SPORT, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_SYNTHWAVE_ENABLE) - PALETTEFX_SYNTHWAVE, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_THERMAL_ENABLE) - PALETTEFX_THERMAL, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_VIRIDIS_ENABLE) - PALETTEFX_VIRIDIS, -#endif -#if defined(PALETTEFX_ENABLE_ALL_PALETTES) || defined(PALETTEFX_WATERMELON_ENABLE) - PALETTEFX_WATERMELON, -#endif - PALETTEFX_USER_0, - PALETTEFX_USER_1, - PALETTEFX_USER_2, - PALETTEFX_USER_3, - PALETTEFX_USER_4, - PALETTEFX_USER_5, - PALETTEFX_USER_6, - PALETTEFX_USER_7, - PALETTEFX_USER_8, - PALETTEFX_USER_9, - PALETTEFX_USER_10, - PALETTEFX_USER_11, - PALETTEFX_USER_12, - PALETTEFX_USER_13, - PALETTEFX_USER_14, - PALETTEFX_USER_15, -}; - -#ifdef __cplusplus -} -#endif - diff --git a/modules/getreuer/palettefx/palettefx.inc b/modules/getreuer/palettefx/palettefx.inc deleted file mode 100644 index 66019a8..0000000 --- a/modules/getreuer/palettefx/palettefx.inc +++ /dev/null @@ -1,345 +0,0 @@ -// Copyright 2024-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file palettefx.inc - * @brief PaletteFx community module effects definitions - * - * For full documentation, see - * - */ - -#ifdef COMMUNITY_MODULE_PALETTEFX_ENABLE - -#include "palettefx_default_config.h" - -#if defined(PALETTEFX_ENABLE_ALL_EFFECTS) || defined(PALETTEFX_GRADIENT_ENABLE) -RGB_MATRIX_EFFECT(PALETTEFX_GRADIENT) -#endif -#if defined(PALETTEFX_ENABLE_ALL_EFFECTS) || defined(PALETTEFX_FLOW_ENABLE) -RGB_MATRIX_EFFECT(PALETTEFX_FLOW) -#endif -#if defined(PALETTEFX_ENABLE_ALL_EFFECTS) || defined(PALETTEFX_RIPPLE_ENABLE) -RGB_MATRIX_EFFECT(PALETTEFX_RIPPLE) -#endif -#if defined(PALETTEFX_ENABLE_ALL_EFFECTS) || defined(PALETTEFX_SPARKLE_ENABLE) -RGB_MATRIX_EFFECT(PALETTEFX_SPARKLE) -#endif -#if defined(PALETTEFX_ENABLE_ALL_EFFECTS) || defined(PALETTEFX_VORTEX_ENABLE) -RGB_MATRIX_EFFECT(PALETTEFX_VORTEX) -#endif -#if defined(RGB_MATRIX_KEYREACTIVE_ENABLED) && ( \ - defined(PALETTEFX_ENABLE_ALL_EFFECTS) || defined(PALETTEFX_REACTIVE_ENABLE)) -RGB_MATRIX_EFFECT(PALETTEFX_REACTIVE) -#endif - -#ifdef RGB_MATRIX_CUSTOM_EFFECT_IMPLS - -#include "palettefx.h" - -#if !(defined(PALETTEFX_ENABLE_ALL_EFFECTS) || \ - defined(PALETTEFX_GRADIENT_ENABLE) || \ - defined(PALETTEFX_FLOW_ENABLE) || \ - defined(PALETTEFX_RIPPLE_ENABLE) || \ - defined(PALETTEFX_SPARKLE_ENABLE) || \ - defined(PALETTEFX_VORTEX_ENABLE) || \ - (defined(RGB_MATRIX_KEYREACTIVE_ENABLED) && \ - defined(PALETTEFX_REACTIVE_ENABLE))) -#pragma message \ - "palettefx: No palettefx effects are enabled. Enable all effects by adding in config.h `#define PALETTEFX_ENABLE_ALL_EFFECTS`, or enable individual effects with `#define PALETTE__ENABLE`." -#endif - -#if defined(PALETTEFX_ENABLE_ALL_EFFECTS) || defined(PALETTEFX_GRADIENT_ENABLE) -// "Gradient" static effect. This is essentially a palette-colored version of -// RGB_MATRIX_GRADIENT_UP_DOWN. A vertically-sloping gradient is made, with the -// highest color on the top keys of keyboard and the lowest color at the bottom. -static bool PALETTEFX_GRADIENT(effect_params_t* params) { - // On first call, compute and cache the slope of the gradient. - static uint8_t gradient_slope = 0; - if (!gradient_slope) { - uint8_t y_max = 64; // To avoid overflow below, x_max must be at least 64. - for (uint8_t i = 0; i < RGB_MATRIX_LED_COUNT; ++i) { - if (g_led_config.point[i].y > y_max) { - y_max = g_led_config.point[i].y; - } - } - // Compute the quotient `255 / y_max` with 6 fractional bits and rounding. - gradient_slope = (64 * 255 + y_max / 2) / y_max; - } - - RGB_MATRIX_USE_LIMITS(led_min, led_max); - const uint16_t* palette = palettefx_get_palette_data(); - - for (uint8_t i = led_min; i < led_max; ++i) { - RGB_MATRIX_TEST_LED_FLAGS(); - const uint8_t y = g_led_config.point[i].y; - const uint8_t value = 255 - (((uint16_t)y * (uint16_t)gradient_slope) >> 6); - rgb_t rgb = rgb_matrix_hsv_to_rgb(palettefx_interp_color(palette, value)); - rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); - } - - return rgb_matrix_check_finished_leds(led_max); -} -#endif - -#if defined(PALETTEFX_ENABLE_ALL_EFFECTS) || defined(PALETTEFX_FLOW_ENABLE) -// "Flow" animated effect. Draws moving wave patterns mimicking the appearance -// of flowing liquid. For interesting variety of patterns, space coordinates are -// slowly rotated and a function of several sine waves is evaluated. -static bool PALETTEFX_FLOW(effect_params_t* params) { - RGB_MATRIX_USE_LIMITS(led_min, led_max); - const uint16_t* palette = palettefx_get_palette_data(); - const uint16_t time = - palettefx_scaled_time(g_rgb_timer, 1 + rgb_matrix_config.speed / 8); - // Compute rotation coefficients with 7 fractional bits. - const int8_t rot_c = cos8(time / 4) - 128; - const int8_t rot_s = sin8(time / 4) - 128; - const uint8_t omega = 32 + sin8(time) / 4; - - for (uint8_t i = led_min; i < led_max; ++i) { - RGB_MATRIX_TEST_LED_FLAGS(); - const uint8_t x = g_led_config.point[i].x; - const uint8_t y = g_led_config.point[i].y; - - // Rotate (x, y) by the 2x2 rotation matrix described by rot_c, rot_s. - const uint8_t x1 = (uint8_t)((((int16_t)rot_c) * ((int16_t)x)) / 128) - - (uint8_t)((((int16_t)rot_s) * ((int16_t)y)) / 128); - const uint8_t y1 = (uint8_t)((((int16_t)rot_s) * ((int16_t)x)) / 128) - + (uint8_t)((((int16_t)rot_c) * ((int16_t)y)) / 128); - - uint8_t value = scale8(sin8(x1 - 2 * time), omega) + y1 + time / 4; - // Evaluate `sawtooth(value)`. - value = 2 * ((value <= 127) ? value : (255 - value)); - - rgb_t rgb = rgb_matrix_hsv_to_rgb(palettefx_interp_color(palette, value)); - rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); - } - - return rgb_matrix_check_finished_leds(led_max); -} -#endif - -#if defined(PALETTEFX_ENABLE_ALL_EFFECTS) || defined(PALETTEFX_RIPPLE_ENABLE) -// "Ripple" animated effect. Draws circular rings emanating from random points, -// simulating water drops falling in a quiet pool. -static bool PALETTEFX_RIPPLE(effect_params_t* params) { - RGB_MATRIX_USE_LIMITS(led_min, led_max); - const uint16_t* palette = palettefx_get_palette_data(); - - // Each instance of this struct represents one water drop. For efficiency, at - // most 3 drops are active at any time. - static struct { - uint16_t time; - uint8_t x; - uint8_t y; - uint8_t amplitude; - uint8_t scale; - uint8_t phase; - } drops[3]; - static uint32_t drop_timer = 0; - static uint8_t drops_tail = 0; - - if (params->iter == 0) { - if (params->init) { - for (uint8_t j = 0; j < 3; ++j) { - drops[j].amplitude = 0; - } - drop_timer = g_rgb_timer; - } - - if (drops[drops_tail].amplitude == 0 && - timer_expired32(g_rgb_timer, drop_timer)) { - // Spawn a new drop, located at a random LED. - const uint8_t i = random8_max(RGB_MATRIX_LED_COUNT); - drops[drops_tail].time = (uint16_t)g_rgb_timer; - drops[drops_tail].x = g_led_config.point[i].x; - drops[drops_tail].y = g_led_config.point[i].y; - drops[drops_tail].amplitude = 1; - ++drops_tail; - if (drops_tail == 3) { drops_tail = 0; } - drop_timer = g_rgb_timer + 1000; - } - - uint8_t amplitude(uint8_t t) { // Drop amplitude as a function of time. - if (t <= 55) { - return (t < 32) ? (3 + 5 * t) : 192; - } else { - t = (((uint16_t)(255 - t)) * UINT16_C(123)) >> 7; - return scale8(t, t); - } - } - - for (uint8_t j = 0; j < 3; ++j) { - if (drops[j].amplitude == 0) { continue; } - const uint16_t tick = scale16by8(g_rgb_timer - drops[j].time, - 1 + rgb_matrix_config.speed / 4); - if (tick < 4 * 255) { - const uint8_t t = (uint8_t)(tick / 4); - drops[j].amplitude = amplitude(t); - drops[j].scale = 255 / (1 + t / 2); - drops[j].phase = (uint8_t)tick; - } else { - drops[j].amplitude = 0; // Animation for this drop is complete. - } - } - } - - for (uint8_t i = led_min; i < led_max; ++i) { - RGB_MATRIX_TEST_LED_FLAGS(); - int16_t value = 128; - - for (uint8_t j = 0; j < 3; ++j) { - if (drops[j].amplitude == 0) { continue; } - - const uint8_t x = abs8((g_led_config.point[i].x - drops[j].x) / 2); - const uint8_t y = abs8((g_led_config.point[i].y - drops[j].y) / 2); - const uint8_t r = sqrt16(x * x + y * y); - const uint16_t r_scaled = (uint16_t)r * (uint16_t)drops[j].scale; - - if (r_scaled < 255) { - // The drop is made from a radial sine wave modulated by a smooth bump - // to localize its spatial extent. - const uint8_t bump = scale8(ease8InOutApprox(255 - (uint8_t)r_scaled), - drops[j].amplitude); - const int8_t wave = (int16_t)cos8(8 * r - drops[j].phase) - 128; - value += ((int16_t)wave * (int16_t)bump) / 128; - } - } - - // Clip `value` to 0-255 range. - if (value < 0) { value = 0; } - if (value > 255) { value = 255; } - rgb_t rgb = - rgb_matrix_hsv_to_rgb(palettefx_interp_color(palette, (uint8_t)value)); - rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); - } - - return rgb_matrix_check_finished_leds(led_max); -} -#endif - -#if defined(PALETTEFX_ENABLE_ALL_EFFECTS) || defined(PALETTEFX_SPARKLE_ENABLE) -// "Sparkle" effect. Each LED is animated by a sine wave with pseudorandom -// phase, so that the matrix "sparkles." All the LED sines are modulated by a -// global amplitude factor, which varies by a slower sine wave, so that the -// matrix as a whole periodically brightens and dims. -static bool PALETTEFX_SPARKLE(effect_params_t* params) { - RGB_MATRIX_USE_LIMITS(led_min, led_max); - const uint16_t* palette = palettefx_get_palette_data(); - const uint8_t time = - palettefx_scaled_time(g_rgb_timer, 1 + rgb_matrix_config.speed / 8); - const uint8_t amplitude = 128 + sin8(time) / 2; - uint16_t rand_state = 1 + params->iter; - - for (uint8_t i = led_min; i < led_max; ++i) { - RGB_MATRIX_TEST_LED_FLAGS(); - // Multiplicative congruential generator for a random phase for each LED. - rand_state *= UINT16_C(36563); - const uint8_t phase = (uint8_t)(rand_state >> 8); - - const uint8_t value = scale8(sin8(2 * time + phase), amplitude); - - rgb_t rgb = rgb_matrix_hsv_to_rgb(palettefx_interp_color(palette, value)); - rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); - } - - return rgb_matrix_check_finished_leds(led_max); -} -#endif - -#if defined(PALETTEFX_ENABLE_ALL_EFFECTS) || defined(PALETTEFX_VORTEX_ENABLE) -// "Vortex" animated effect. LEDs are animated according to a polar function -// with the appearance of a spinning vortex centered on k_rgb_matrix_center. -static bool PALETTEFX_VORTEX(effect_params_t* params) { - RGB_MATRIX_USE_LIMITS(led_min, led_max); - const uint16_t* palette = palettefx_get_palette_data(); - const uint16_t time = - palettefx_scaled_time(g_rgb_timer, 1 + rgb_matrix_config.speed / 4); - - for (uint8_t i = led_min; i < led_max; ++i) { - RGB_MATRIX_TEST_LED_FLAGS(); - const int16_t x = g_led_config.point[i].x - k_rgb_matrix_center.x; - const int16_t y = g_led_config.point[i].y - k_rgb_matrix_center.y; - uint8_t value = sin8(atan2_8(y, x) + time - sqrt16(x * x + y * y) / 2); - - rgb_t rgb = rgb_matrix_hsv_to_rgb(palettefx_interp_color(palette, value)); - rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); - } - - return rgb_matrix_check_finished_leds(led_max); -} -#endif - -#if defined(RGB_MATRIX_KEYREACTIVE_ENABLED) && ( \ - defined(PALETTEFX_ENABLE_ALL_EFFECTS) || defined(PALETTEFX_REACTIVE_ENABLE)) -// Reactive animated effect. This effect is "reactive," it responds to key -// presses. For each key press, LEDs near the key change momentarily. -static bool PALETTEFX_REACTIVE(effect_params_t* params) { - RGB_MATRIX_USE_LIMITS(led_min, led_max); - const uint16_t* palette = palettefx_get_palette_data(); - const uint8_t count = g_last_hit_tracker.count; - - uint8_t amplitude(uint8_t t) { // Bump amplitude as a function of time. - if (t <= 55) { - return (t < 32) ? (4 + 8 * t) : 255; - } else { - t = (((uint16_t)(255 - t)) * UINT16_C(164)) >> 7; - return scale8(t, t); - } - } - - uint8_t hit_amplitude[LED_HITS_TO_REMEMBER] = {0}; - for (uint8_t j = 0; j < count; ++j) { - const uint16_t tick = scale16by8(g_last_hit_tracker.tick[j], - 1 + rgb_matrix_config.speed / 4); - if (tick <= 255) { - hit_amplitude[j] = amplitude((uint8_t)tick); - } - } - - for (uint8_t i = led_min; i < led_max; ++i) { - RGB_MATRIX_TEST_LED_FLAGS(); - uint8_t value = 0; - - for (uint8_t j = 0; j < count; ++j) { - if (hit_amplitude[j] == 0) { continue; } - - uint8_t dx = abs8((g_led_config.point[i].x - g_last_hit_tracker.x[j]) / 2); - uint8_t dy = abs8((g_led_config.point[i].y - g_last_hit_tracker.y[j]) / 2); - if (dx < 21 && dy < 21) { - const uint16_t dist_sqr = dx * dx + dy * dy; - if (dist_sqr < 21 * 21) { // Accumulate a radial bump for each hit. - const uint8_t dist = sqrt16(dist_sqr); - value = qadd8(value, scale8(255 - 12 * dist, hit_amplitude[j])); - // Early loop exit where the value has saturated. - if (value == 255) { break; } - } - } - } - - hsv_t hsv = palettefx_interp_color(palette, value); - if (value < 32) { // Make the background dark regardless of palette. - hsv.v = scale8(hsv.v, 64 + 6 * value); - } - - const rgb_t rgb = rgb_matrix_hsv_to_rgb(hsv); - rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); - } - return rgb_matrix_check_finished_leds(led_max); -} -#endif - -#endif // RGB_MATRIX_CUSTOM_EFFECT_IMPLS -#endif // COMMUNITY_MODULE_PALETTEFX_ENABLE - diff --git a/modules/getreuer/palettefx/palettefx_default_config.h b/modules/getreuer/palettefx/palettefx_default_config.h deleted file mode 100644 index 1396346..0000000 --- a/modules/getreuer/palettefx/palettefx_default_config.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2024-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -// If nothing has been configured, default to enabling all effects and palettes. -#if !( \ - __has_include("palettefx_user.inc") \ - || defined(PALETTEFX_ENABLE_ALL_EFFECTS) \ - || defined(PALETTEFX_ENABLE_ALL_PALETTES) \ - || defined(PALETTEFX_GRADIENT_ENABLE) \ - || defined(PALETTEFX_FLOW_ENABLE) \ - || defined(PALETTEFX_RIPPLE_ENABLE) \ - || defined(PALETTEFX_SPARKLE_ENABLE) \ - || defined(PALETTEFX_VORTEX_ENABLE) \ - || defined(PALETTEFX_REACTIVE_ENABLE) \ - || defined(PALETTEFX_AFTERBURN_ENABLE) \ - || defined(PALETTEFX_AMBER_ENABLE) \ - || defined(PALETTEFX_BADWOLF_ENABLE) \ - || defined(PALETTEFX_CARNIVAL_ENABLE) \ - || defined(PALETTEFX_CLASSIC_ENABLE) \ - || defined(PALETTEFX_DRACULA_ENABLE) \ - || defined(PALETTEFX_GROOVY_ENABLE) \ - || defined(PALETTEFX_NOTPINK_ENABLE) \ - || defined(PALETTEFX_PHOSPHOR_ENABLE) \ - || defined(PALETTEFX_POLARIZED_ENABLE) \ - || defined(PALETTEFX_ROSEGOLD_ENABLE) \ - || defined(PALETTEFX_SPORT_ENABLE) \ - || defined(PALETTEFX_SYNTHWAVE_ENABLE) \ - || defined(PALETTEFX_THERMAL_ENABLE) \ - || defined(PALETTEFX_VIRIDIS_ENABLE) \ - || defined(PALETTEFX_WATERMELON_ENABLE) \ -) -#define PALETTEFX_ENABLE_ALL_EFFECTS -#define PALETTEFX_ENABLE_ALL_PALETTES -#endif - diff --git a/modules/getreuer/palettefx/qmk_module.json b/modules/getreuer/palettefx/qmk_module.json deleted file mode 100644 index 9b05a72..0000000 --- a/modules/getreuer/palettefx/qmk_module.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "module_name": "PaletteFx", - "maintainer": "getreuer", - "url": "https://getreuer.info/posts/keyboards/palettefx", - "features": { - "rgb_matrix": true - } -} diff --git a/modules/getreuer/palettefx/rules.mk b/modules/getreuer/palettefx/rules.mk deleted file mode 100644 index d4d33b6..0000000 --- a/modules/getreuer/palettefx/rules.mk +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -RGB_MATRIX_CUSTOM_USER = yes diff --git a/modules/getreuer/select_word/README.md b/modules/getreuer/select_word/README.md deleted file mode 100644 index 95d8219..0000000 --- a/modules/getreuer/select_word/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# Select Word - - - - - - - -
Modulegetreuer/select_word
Version2025-03-07
MaintainerPascal Getreuer (@getreuer)
LicenseApache 2.0
Documentation -https://getreuer.info/posts/keyboards/select-word -
- -This is a community module adaptation of [Select -Word](https://getreuer.info/posts/keyboards/select-word) for selecting words and -lines, assuming conventional text editing hotkeys. - -## Use - -Add the following to your `keymap.json`: - -```json -{ - "modules": ["getreuer/select_word"] -} -``` - -Then use one or more of the following keycodes in your layout: - -| Keycode | Short alias | Description | -|--------------------|-------------|--------------------------------------------------------| -| `SELECT_WORD` | `SELWORD` | Forward word selection. Or with Shift, line selection. | -| `SELECT_WORD_BACK` | `SELWBAK` | Backward word selection. | -| `SELECT_WORD_LINE` | `SELLINE` | Line selection. | - -Press `SELWORD` to select the current word. Press it again to extend the -selection to the following word. The effect is similar to word selection (`W`) -in the [Kakoune editor](https://kakoune.org). Or for line selection, press -`SELWORD` with shift to select the current line, and press it again to extend -the selection to the following line. - -## Mac hotkeys - -Different hotkeys are needed to perform word and line selection on Mac OS. There -are several ways that Select Word can be configured to send the appropriate -hotkeys: - -* To hardcode to Mac hotkeys, define in your `config.h` file: - - ~~~{.c} - #define SELECT_WORD_OS_MAC - ~~~ - -* If [OS Detection](https://docs.qmk.fm/features/os_detection) is enabled, - Select Word uses it determine which kind of hotkeys to send. - -* For direct control, define in `config.h`: - - ~~~{.c} - #define SELECT_WORD_OS_DYNAMIC - ~~~ - - Then in `keymap.c`, define the callback `select_word_host_is_mac()`. Return - true for Mac hotkeys, false for Windows/Linux. - -See the [Select Word -documentation](https://getreuer.info/posts/keyboards/select-word) for further -configuration options and details. - diff --git a/modules/getreuer/select_word/introspection.h b/modules/getreuer/select_word/introspection.h deleted file mode 100644 index e826ac8..0000000 --- a/modules/getreuer/select_word/introspection.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once -#include "select_word.h" - diff --git a/modules/getreuer/select_word/qmk_module.json b/modules/getreuer/select_word/qmk_module.json deleted file mode 100644 index 6ab1b1a..0000000 --- a/modules/getreuer/select_word/qmk_module.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "module_name": "Select Word", - "maintainer": "getreuer", - "url": "https://getreuer.info/posts/keyboards/select-word", - "keycodes": [ - { - "key": "SELECT_WORD", - "aliases": [ - "SELWORD" - ] - }, - { - "key": "SELECT_WORD_BACK", - "aliases": [ - "SELWBAK" - ] - }, - { - "key": "SELECT_LINE", - "aliases": [ - "SELLINE" - ] - } - ] -} diff --git a/modules/getreuer/select_word/select_word.c b/modules/getreuer/select_word/select_word.c deleted file mode 100644 index e174966..0000000 --- a/modules/getreuer/select_word/select_word.c +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright 2021-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file select_word.c - * @brief Select Word community module implementation - * - * For full documentation, see - * - */ - -#include "select_word.h" - -ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(1, 0, 0); - -// Default to a timeout of 5 seconds. -#ifndef SELECT_WORD_TIMEOUT -# define SELECT_WORD_TIMEOUT 5000 -#endif // SELECT_WORD_TIMEOUT - -static int8_t selection_dir = 0; -static bool reset_before_next_event = false; -static uint8_t registered_hotkey = KC_NO; - -// Macro `IS_MAC` determines whether to use Mac vs. Windows/Linux hotkeys: -// -// * OS Detection is used if it is enabled. -// -// * With SELECT_WORD_OS_DYNAMIC, the user may define callback -// select_word_host_is_mac(). -// -// * Otherwise, the assumed OS is set at compile time, to Window/Linux by -// default, or to Mac with SELECT_WORD_OS_MAC. -#if defined(SELECT_WORD_OS_DYNAMIC) || defined(OS_DETECTION_ENABLE) -__attribute__((weak)) bool select_word_host_is_mac(void) { -# ifdef OS_DETECTION_ENABLE // Use OS Detection if enabled. - switch (detected_host_os()) { - case OS_LINUX: - case OS_WINDOWS: - return false; - case OS_MACOS: - case OS_IOS: - return true; - default: - break; - } -# endif // OS_DETECTION_ENABLE -# ifdef SELECT_WORD_OS_MAC - return true; -# else - return false; -# endif // SELECT_WORD_OS_MAC -} -# define IS_MAC select_word_host_is_mac() -#else -# ifdef SELECT_WORD_OS_MAC -# define IS_MAC true -# else -# define IS_MAC false -# endif // SELECT_WORD_OS_MAC -#endif // defined(SELECT_WORD_OS_DYNAMIC) || defined(OS_DETECTION_ENABLE) - -// Idle timeout timer to reset Select Word after a period of inactivity. -#if SELECT_WORD_TIMEOUT > 0 -# if SELECT_WORD_TIMEOUT < 100 || SELECT_WORD_TIMEOUT > 30000 -// Constrain timeout to a sensible range. With the 16-bit timer, the longest -// representable timeout is 32768 ms, rounded here to 30000 ms = half a minute. -# error "select_word: SELECT_WORD_TIMEOUT must be between 100 and 30000 ms" -# endif - -static uint16_t idle_timer = 0; - -static void restart_idle_timer(void) { - idle_timer = (timer_read() + SELECT_WORD_TIMEOUT) | 1; -} - -void housekeeping_task_select_word(void) { - if (idle_timer && timer_expired(timer_read(), idle_timer)) { - idle_timer = 0; - selection_dir = 0; - } -} -#endif // SELECT_WORD_TIMEOUT > 0 - -static void clear_all_mods(void) { - clear_mods(); - clear_weak_mods(); -#ifndef NO_ACTION_ONESHOT - clear_oneshot_mods(); -#endif // NO_ACTION_ONESHOT -} - -static void select_word_in_dir(int8_t dir) { - // With Windows and Linux (non-Mac) systems: - // dir < 0: Backward word selection: Ctrl+Left, Ctrl+Right, Ctrl+Shift+Left. - // dir > 0: Forward word selection: Ctrl+Right, Ctrl+Left, Ctrl+Shift+Right. - // Or to extend an existing selection: - // dir < 0: Backward word selection: Ctrl+Shift+Left. - // dir > 0: Forward word selection: Ctrl+Shift+Right. - // - // With Mac OS, use Alt (Opt) instead of Ctrl: - // dir < 0: Backward word selection: Alt+Left, Alt+Right, Alt+Shift+Left. - // dir > 0: Forward word selection: Alt+Right, Alt+Left, Alt+Shift+Right. - // Or to extend an existing selection: - // dir < 0: Backward word selection: Alt+Shift+Left. - // dir > 0: Forward word selection: Alt+Shift+Right. - reset_before_next_event = false; - const uint8_t saved_mods = get_mods(); - clear_all_mods(); - - if (selection_dir && (selection_dir < 0) != (dir < 0)) { // Reversal. - send_keyboard_report(); - tap_code_delay((dir < 0) ? KC_RGHT : KC_LEFT, TAP_CODE_DELAY); - } - - add_mods(IS_MAC ? MOD_BIT_LALT : MOD_BIT_LCTRL); - - if (selection_dir == 0) { // Initial selection. - send_keyboard_report(); - send_string_with_delay_P( - (dir < 0) ? PSTR(SS_TAP(X_LEFT) SS_TAP(X_RGHT)) - : PSTR(SS_TAP(X_RGHT) SS_TAP(X_LEFT)), - TAP_CODE_DELAY); - } - - register_mods(MOD_BIT_LSHIFT); - registered_hotkey = (dir < 0) ? KC_LEFT : KC_RGHT; - register_code(registered_hotkey); - - set_mods(saved_mods); - selection_dir = dir; -} - -static void select_line(void) { - // With Windows and Linux (non-Mac) systems: - // Home, Shift+End. - // Or to extend an existing selection: - // Shift+Down. - // - // With Mac OS, use GUI (Command) + arrows: - // GUI+Left, Shift+GUI+Right. - // Or to extend an existing selection: - // Shift+Down. - reset_before_next_event = false; - const uint8_t saved_mods = get_mods(); - clear_all_mods(); - - if (selection_dir != 2) { - send_keyboard_report(); - send_string_with_delay_P( - IS_MAC ? PSTR(SS_LGUI(SS_TAP(X_LEFT) SS_LSFT(SS_TAP(X_RGHT)))) - : PSTR(SS_TAP(X_HOME) SS_LSFT(SS_TAP(X_END))), - TAP_CODE_DELAY); - } else { - register_mods(MOD_BIT_LSHIFT); - registered_hotkey = KC_DOWN; - register_code(KC_DOWN); - } - - set_mods(saved_mods); - selection_dir = 2; -} - -void select_word_register(char action) { - if (registered_hotkey) { - select_word_unregister(); - } - - switch (action) { - case 'W': - select_word_in_dir(1); - break; - case 'B': - select_word_in_dir(-1); - break; - case 'L': - select_line(); - break; - } - -#if SELECT_WORD_TIMEOUT > 0 - idle_timer = 0; -#endif // SELECT_WORD_TIMEOUT > 0 -} - -void select_word_unregister(void) { - reset_before_next_event = false; - unregister_code(registered_hotkey); - - if (registered_hotkey == KC_DOWN) { - // When using line selection to select multiple lines, tap Shift+End (or on - // Mac, GUI+Shift+Right) on release to ensure the selection extends to the - // end of the current line. - const uint8_t saved_mods = get_mods(); - clear_all_mods(); - send_keyboard_report(); - send_string_with_delay_P( - IS_MAC ? PSTR(SS_LGUI(SS_LSFT(SS_TAP(X_RGHT)))) - : PSTR(SS_LSFT(SS_TAP(X_END))), - TAP_CODE_DELAY); - set_mods(saved_mods); - } - - registered_hotkey = KC_NO; -#if SELECT_WORD_TIMEOUT > 0 - restart_idle_timer(); -#endif // SELECT_WORD_TIMEOUT > 0 -} - -bool process_record_select_word(uint16_t keycode, keyrecord_t* record) { - if (!process_record_select_word_kb(keycode, record)) { - return false; - } - - if (selection_dir) { - if (reset_before_next_event) { - selection_dir = 0; - } - - // Ignore most modifier and layer switch keys. - switch (keycode) { - case MODIFIER_KEYCODE_RANGE: - case QK_MOMENTARY ... QK_MOMENTARY_MAX: - case QK_LAYER_MOD ... QK_LAYER_MOD_MAX: - case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX: - case QK_TO ... QK_TO_MAX: - case QK_TOGGLE_LAYER ... QK_TOGGLE_LAYER_MAX: -#ifndef NO_ACTION_ONESHOT - case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX: - case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX: -#endif // NO_ACTION_ONESHOT -#ifdef LAYER_LOCK_ENABLE - case QK_LLCK: -#endif // LAYER_LOCK_ENABLE - return true; -#ifndef NO_ACTION_TAPPING - // Ignore hold events on mod-tap and layer-tap keys. - case QK_MOD_TAP ... QK_MOD_TAP_MAX: - case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: - if (record->tap.count == 0) { - return true; - } - break; -#endif // NO_ACTION_TAPPING - } - - reset_before_next_event = true; - } - -#if SELECT_WORD_TIMEOUT > 0 - if (idle_timer) { - restart_idle_timer(); - } -#endif // SELECT_WORD_TIMEOUT > 0 - - const bool shifted = MOD_MASK_SHIFT & (get_mods() | get_weak_mods() -#ifndef NO_ACTION_ONESHOT - | get_oneshot_mods() -#endif // NO_ACTION_ONESHOT - ); - - switch (keycode) { - case SELECT_WORD: - if (record->event.pressed) { - select_word_register(shifted ? 'L' : 'W'); - } else { - select_word_unregister(); - } - return false; - - case SELECT_WORD_BACK: - if (record->event.pressed) { - select_word_register('B'); - } else { - select_word_unregister(); - } - return false; - - case SELECT_LINE: - if (record->event.pressed) { - select_word_register('L'); - } else { - select_word_unregister(); - } - return false; - } - - return true; -} - diff --git a/modules/getreuer/select_word/select_word.h b/modules/getreuer/select_word/select_word.h deleted file mode 100644 index 2255308..0000000 --- a/modules/getreuer/select_word/select_word.h +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2021-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file select_word.h - * @brief Select Word community module: select words and lines. - * - * Overview - * -------- - * - * Implements a button that selects the current word, assuming conventional text - * editor hotkeys. Pressing it again extends the selection to the following - * word. The effect is similar to word selection (W) in the Kakoune editor. - * - * Pressing the button with shift selects the current line, and pressing the - * button again extends the selection to the following line. - * - * For full documentation, see - * - */ - -#pragma once - -#include "quantum.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Registers (presses) selection `action`. - * - * The `action` argument in these functions specifies the type of selection: - * - * 'W' = word selection - * 'B' = backward word selection, left of the cursor - * 'L' = line selection - * - * A selection is first registered with `select_word_register(action)`. This - * should be followed by a call to `select_word_unregister()` to unregister the - * hotkeys. The point of these separate register and unregister calls is to - * enable holding the hotkey as a means to extend the selection range. - * - * @warning Forgetting to unregister results in stuck keys: - * `select_word_register()` must be followed by `select_word_unregister()`. - * - * @param action Type of selection to perform. - */ -void select_word_register(char action); - -/** Unregisters (releases) selection hotkey. */ -void select_word_unregister(void); - -/** Registers and unregisters ("taps") selection `action.` */ -static inline void select_word_tap(char action) { - select_word_register(action); - wait_ms(TAP_CODE_DELAY); - select_word_unregister(); -} - -#if defined(SELECT_WORD_OS_DYNAMIC) || defined(OS_DETECTION_ENABLE) -/** - * @brief Callback for whether the host uses Mac vs. Windows/Linux hotkeys. - * - * Optionally, this callback may be defined to indicate dynamically whether the - * keyboard is being used with a Mac or non-Mac system. - * - * For instance suppose layer 0 is your base layer for Windows and layer 1 is - * your base layer for Mac. Indicate this by adding in keymap.c: - * - * bool select_word_host_is_mac(void) { - * return IS_LAYER_ON(1); // Supposing layer 1 = base layer for Mac. - * } - */ -bool select_word_host_is_mac(void); -#endif // defined(SELECT_WORD_OS_DYNAMIC) || defined(OS_DETECTION_ENABLE) - -#ifdef __cplusplus -} -#endif diff --git a/modules/getreuer/sentence_case/README.md b/modules/getreuer/sentence_case/README.md deleted file mode 100644 index 37307bd..0000000 --- a/modules/getreuer/sentence_case/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# Sentence Case - - - - - - - -
Modulegetreuer/sentence_case
Version2025-03-07
MaintainerPascal Getreuer (@getreuer)
LicenseApache 2.0
Documentation -https://getreuer.info/posts/keyboards/sentence-case -
- -This is a community module adaptation of [Sentence -Case](https://getreuer.info/posts/keyboards/sentence-case) to automatically -capitalize the first letter of sentences. This reduces how often you need to use -the Shift keys, which is convenient particularly if you use home row mods or -Auto Shift. - -Add the following to your `keymap.json`: - -```json -{ - "modules": ["getreuer/sentence_case"] -} -``` - -NOTE: One-shot keys must be enabled. - -Then, simply type as usual but without shifting at the start of sentences. The -feature detects when new sentences begin and capitalizes automatically. - -In detecting new sentences, Sentence Case matches patterns like - - "a. a" - "a. a" - "a? a" - "a!' 'a" - -but not - - "a... a" - "a.a. a" - -Additionally by default, abbreviations "`vs.`" and "`etc.`" are exceptionally -detected as not real sentence endings. - -Sentence Case is on by default. The following keycodes change its status. Use -function `is_sentence_case_on()` to query its status. - -| Keycode | Description | -|------------------------|---------------------------| -| `SENTENCE_CASE_ON` | Turn Sentence Case on. | -| `SENTENCE_CASE_OFF` | Turn Sentence Case off. | -| `SENTENCE_CASE_TOGGLE` | Toggle Sentence Case. | - -Optionally, you can use the callback `sentence_case_check_ending()` to define -other exceptions, and there are callbacks and config options to customize -Sentence Case. See the [Sentence Case -documentation](https://getreuer.info/posts/keyboards/sentence-case) for details. - diff --git a/modules/getreuer/sentence_case/introspection.h b/modules/getreuer/sentence_case/introspection.h deleted file mode 100644 index 7018415..0000000 --- a/modules/getreuer/sentence_case/introspection.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once -#include "sentence_case.h" - diff --git a/modules/getreuer/sentence_case/qmk_module.json b/modules/getreuer/sentence_case/qmk_module.json deleted file mode 100644 index d2078e8..0000000 --- a/modules/getreuer/sentence_case/qmk_module.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "module_name": "Sentence Case", - "maintainer": "getreuer", - "url": "https://getreuer.info/posts/keyboards/sentence-case", - "keycodes": [ - {"key": "SENTENCE_CASE_ON"}, - {"key": "SENTENCE_CASE_OFF"}, - {"key": "SENTENCE_CASE_TOGGLE"} - ] -} diff --git a/modules/getreuer/sentence_case/sentence_case.c b/modules/getreuer/sentence_case/sentence_case.c deleted file mode 100644 index e381c81..0000000 --- a/modules/getreuer/sentence_case/sentence_case.c +++ /dev/null @@ -1,380 +0,0 @@ -// Copyright 2022-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file sentence_case.c - * @brief Sentence Case community module implementation - * - * For full documentation, see - * - */ - -#include "sentence_case.h" - -#include - -#if defined(NO_ACTION_ONESHOT) -// One-shot keys must be enabled for Sentence Case. One-shot keys are enabled -// by default, but are disabled by `#define NO_ACTION_ONESHOT` in config.h. If -// your config.h includes such a line, please remove it. -#error "sentence_case: Please enable oneshot." -#else - -ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(1, 0, 0); - -// Default to a timeout of 5 seconds. -#ifndef SENTENCE_CASE_TIMEOUT -# define SENTENCE_CASE_TIMEOUT 5000 -#endif // SENTENCE_CASE_TIMEOUT - -// Number of keys of state history to retain for backspacing. -#define STATE_HISTORY_SIZE 6 - -// clang-format off -/** States in matching the beginning of a sentence. */ -enum { - STATE_INIT, /**< Initial enabled state. */ - STATE_WORD, /**< Within a word. */ - STATE_ABBREV, /**< Within an abbreviation like "e.g.". */ - STATE_ENDING, /**< Sentence ended. */ - STATE_PRIMED, /**< "Primed" state, in the space following an ending. */ - STATE_DISABLED, /**< Sentence Case is disabled. */ -}; -// clang-format on - -#if SENTENCE_CASE_TIMEOUT > 0 -static uint16_t idle_timer = 0; -#endif // SENTENCE_CASE_TIMEOUT > 0 -#if SENTENCE_CASE_BUFFER_SIZE > 1 -static uint16_t key_buffer[SENTENCE_CASE_BUFFER_SIZE] = {0}; -#endif // SENTENCE_CASE_BUFFER_SIZE > 1 -static uint8_t state_history[STATE_HISTORY_SIZE]; -static uint16_t suppress_key = KC_NO; -static uint8_t sentence_state = STATE_INIT; - -// Sets the current state to `new_state`. -static void set_sentence_state(uint8_t new_state) { -#if !defined(NO_DEBUG) && defined(SENTENCE_CASE_DEBUG) - if (debug_enable && sentence_state != new_state) { - static const char* state_names[] = { - "INIT", "WORD", "ABBREV", "ENDING", "PRIMED", "DISABLED", - }; - dprintf("Sentence case: %s\n", state_names[new_state]); - } -#endif // !NO_DEBUG && SENTENCE_CASE_DEBUG - - const bool primed = (new_state == STATE_PRIMED); - if (primed != (sentence_state == STATE_PRIMED)) { - sentence_case_primed(primed); - } - sentence_state = new_state; -} - -static void clear_state_history(void) { -#if SENTENCE_CASE_TIMEOUT > 0 - idle_timer = 0; -#endif // SENTENCE_CASE_TIMEOUT > 0 - memset(state_history, STATE_INIT, sizeof(state_history)); - if (sentence_state != STATE_DISABLED) { - set_sentence_state(STATE_INIT); - } -} - -void sentence_case_clear(void) { - clear_state_history(); - suppress_key = KC_NO; -#if SENTENCE_CASE_BUFFER_SIZE > 1 - memset(key_buffer, 0, sizeof(key_buffer)); -#endif // SENTENCE_CASE_BUFFER_SIZE > 1 -} - -void sentence_case_on(void) { - if (sentence_state == STATE_DISABLED) { - sentence_state = STATE_INIT; - sentence_case_clear(); - } -} - -void sentence_case_off(void) { - if (sentence_state != STATE_DISABLED) { - set_sentence_state(STATE_DISABLED); - } -} - -void sentence_case_toggle(void) { - if (sentence_state != STATE_DISABLED) { - sentence_case_off(); - } else { - sentence_case_on(); - } -} - -bool is_sentence_case_on(void) { return sentence_state != STATE_DISABLED; } -bool is_sentence_case_primed(void) { return sentence_state == STATE_PRIMED; } - -#if SENTENCE_CASE_TIMEOUT > 0 -#if SENTENCE_CASE_TIMEOUT < 100 || SENTENCE_CASE_TIMEOUT > 30000 -// Constrain timeout to a sensible range. With the 16-bit timer, the longest -// representable timeout is 32768 ms, rounded here to 30000 ms = half a minute. -#error "sentence_case: SENTENCE_CASE_TIMEOUT must be between 100 and 30000 ms" -#endif - -void housekeeping_task_sentence_case(void) { - if (idle_timer && timer_expired(timer_read(), idle_timer)) { - clear_state_history(); // Timed out; clear all state. - } -} -#endif // SENTENCE_CASE_TIMEOUT > 0 - -bool process_record_sentence_case(uint16_t keycode, keyrecord_t* record) { - // Only process while enabled, and only process press events. - if (sentence_state == STATE_DISABLED || !record->event.pressed) { - return true; - } - -#if SENTENCE_CASE_TIMEOUT > 0 - idle_timer = (record->event.time + SENTENCE_CASE_TIMEOUT) | 1; -#endif // SENTENCE_CASE_TIMEOUT > 0 - - switch (keycode) { - case SENTENCE_CASE_ON: - sentence_case_on(); - return false; - case SENTENCE_CASE_OFF: - sentence_case_off(); - return false; - case SENTENCE_CASE_TOGGLE: - sentence_case_toggle(); - return false; - - case KC_LCTL ... KC_RGUI: // Ignore mod keys. - case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX: // Ignore one-shot mod. - // Ignore MO, TO, TG, TT, OSL, TL layer switch keys. - case QK_MOMENTARY ... QK_MOMENTARY_MAX: - case QK_TO ... QK_TO_MAX: - case QK_TOGGLE_LAYER ... QK_TOGGLE_LAYER_MAX: - case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX: - case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX: // Ignore one-shot layer. -#ifdef TRI_LAYER_ENABLE // Ignore Tri Layer keys. - case QK_TRI_LAYER_LOWER: - case QK_TRI_LAYER_UPPER: -#endif // TRI_LAYER_ENABLE - return true; - -#ifndef NO_ACTION_TAPPING - case QK_MOD_TAP ... QK_MOD_TAP_MAX: - if (record->tap.count == 0) { - return true; - } - keycode = QK_MOD_TAP_GET_TAP_KEYCODE(keycode); - break; -#ifndef NO_ACTION_LAYER - case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: - if (record->tap.count == 0) { - return true; - } - keycode = QK_LAYER_TAP_GET_TAP_KEYCODE(keycode); - break; -#endif // NO_ACTION_LAYER -#endif // NO_ACTION_TAPPING - -#ifdef SWAP_HANDS_ENABLE - case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX: - if (IS_SWAP_HANDS_KEYCODE(keycode) || record->tap.count == 0) { - return true; - } - keycode = QK_SWAP_HANDS_GET_TAP_KEYCODE(keycode); - break; -#endif // SWAP_HANDS_ENABLE - } - - if (keycode == KC_BSPC) { - // Backspace key pressed. Rewind the state and key buffers. - set_sentence_state(state_history[STATE_HISTORY_SIZE - 1]); - - memmove(state_history + 1, state_history, STATE_HISTORY_SIZE - 1); - state_history[0] = STATE_INIT; -#if SENTENCE_CASE_BUFFER_SIZE > 1 - memmove(key_buffer + 1, key_buffer, - (SENTENCE_CASE_BUFFER_SIZE - 1) * sizeof(uint16_t)); - key_buffer[0] = KC_NO; -#endif // SENTENCE_CASE_BUFFER_SIZE > 1 - return true; - } - - const uint8_t mods = get_mods() | get_weak_mods() | get_oneshot_mods(); - uint8_t new_state = STATE_INIT; - - // We search for sentence beginnings using a simple finite state machine. It - // matches things like "a. a" and "a. a" but not "a.. a" or "a.a. a". The - // state transition matrix is: - // - // 'a' '.' ' ' '\'' - // +------------------------------------- - // INIT | WORD INIT INIT INIT - // WORD | WORD ENDING INIT WORD - // ABBREV | ABBREV ABBREV INIT ABBREV - // ENDING | ABBREV INIT PRIMED ENDING - // PRIMED | match! INIT PRIMED PRIMED - char code = sentence_case_press_user(keycode, record, mods); -#if defined SENTENCE_CASE_DEBUG - dprintf("Sentence Case: code = '%c' (%d)\n", code, (int)code); -#endif // SENTENCE_CASE_DEBUG - switch (code) { - case '\0': // Current key should be ignored. - return true; - - case 'a': // Current key is a letter. - switch (sentence_state) { - case STATE_ABBREV: - case STATE_ENDING: - new_state = STATE_ABBREV; - break; - - case STATE_PRIMED: - // This is the start of a sentence. - if (keycode != suppress_key) { - suppress_key = keycode; - set_oneshot_mods(MOD_BIT(KC_LSFT)); // Shift mod to capitalize. - new_state = STATE_WORD; - } - break; - - default: - new_state = STATE_WORD; - } - break; - - case '.': // Current key is sentence-ending punctuation. - switch (sentence_state) { - case STATE_WORD: - new_state = STATE_ENDING; - break; - - default: - new_state = STATE_ABBREV; - } - break; - - case ' ': // Current key is a space. - if (sentence_state == STATE_PRIMED || - (sentence_state == STATE_ENDING -#if SENTENCE_CASE_BUFFER_SIZE > 1 - && sentence_case_check_ending(key_buffer) -#endif // SENTENCE_CASE_BUFFER_SIZE > 1 - )) { - new_state = STATE_PRIMED; - suppress_key = KC_NO; - } - break; - - case '\'': // Current key is a quote. - new_state = sentence_state; - break; - } - - // Slide key_buffer and state_history buffers one element to the left. - // Optimization note: Using manual loops instead of memmove() here saved - // ~100 bytes on AVR. -#if SENTENCE_CASE_BUFFER_SIZE > 1 - for (int8_t i = 0; i < SENTENCE_CASE_BUFFER_SIZE - 1; ++i) { - key_buffer[i] = key_buffer[i + 1]; - } -#endif // SENTENCE_CASE_BUFFER_SIZE > 1 - for (int8_t i = 0; i < STATE_HISTORY_SIZE - 1; ++i) { - state_history[i] = state_history[i + 1]; - } - -#if SENTENCE_CASE_BUFFER_SIZE > 1 - key_buffer[SENTENCE_CASE_BUFFER_SIZE - 1] = keycode; - if (new_state == STATE_ENDING && !sentence_case_check_ending(key_buffer)) { -#if defined SENTENCE_CASE_DEBUG - dprintf("Not a real ending.\n"); -#endif // SENTENCE_CASE_DEBUG - new_state = STATE_INIT; - } -#endif // SENTENCE_CASE_BUFFER_SIZE > 1 - state_history[STATE_HISTORY_SIZE - 1] = sentence_state; - - set_sentence_state(new_state); - return true; -} - -bool sentence_case_just_typed_P(const uint16_t* buffer, const uint16_t* pattern, - int8_t pattern_len) { -#if SENTENCE_CASE_BUFFER_SIZE > 1 - buffer += SENTENCE_CASE_BUFFER_SIZE - pattern_len; - for (int8_t i = 0; i < pattern_len; ++i) { - if (buffer[i] != pgm_read_word(pattern + i)) { - return false; - } - } - return true; -#else - return false; -#endif // SENTENCE_CASE_BUFFER_SIZE > 1 -} - -__attribute__((weak)) bool sentence_case_check_ending(const uint16_t* buffer) { -#if SENTENCE_CASE_BUFFER_SIZE >= 5 - // Don't consider the abbreviations "vs." and "etc." to end the sentence. - if (SENTENCE_CASE_JUST_TYPED(KC_SPC, KC_V, KC_S, KC_DOT) || - SENTENCE_CASE_JUST_TYPED(KC_SPC, KC_E, KC_T, KC_C, KC_DOT)) { - return false; // Not a real sentence ending. - } -#endif // SENTENCE_CASE_BUFFER_SIZE >= 5 - return true; // Real sentence ending; capitalize next letter. -} - -__attribute__((weak)) char sentence_case_press_user(uint16_t keycode, - keyrecord_t* record, - uint8_t mods) { - if ((mods & ~(MOD_MASK_SHIFT | MOD_BIT(KC_RALT))) == 0) { - const bool shifted = mods & MOD_MASK_SHIFT; - switch (keycode) { - case KC_A ... KC_Z: - return 'a'; // Letter key. - - case KC_DOT: // . is punctuation, Shift . is a symbol (>) - return !shifted ? '.' : '#'; - case KC_1: - case KC_SLSH: - return shifted ? '.' : '#'; - case KC_EXLM: - case KC_QUES: - return '.'; - case KC_2 ... KC_0: // 2 3 4 5 6 7 8 9 0 - case KC_AT ... KC_RPRN: // @ # $ % ^ & * ( ) - case KC_MINS ... KC_SCLN: // - = [ ] backslash ; - case KC_UNDS ... KC_COLN: // _ + { } | : - case KC_GRV: - case KC_COMM: - return '#'; // Symbol key. - - case KC_SPC: - return ' '; // Space key. - - case KC_QUOT: - return '\''; // Quote key. - } - } - - // Otherwise clear Sentence Case to initial state. - sentence_case_clear(); - return '\0'; -} - -__attribute__((weak)) void sentence_case_primed(bool primed) {} - -#endif // NO_ACTION_ONESHOT diff --git a/modules/getreuer/sentence_case/sentence_case.h b/modules/getreuer/sentence_case/sentence_case.h deleted file mode 100644 index 5d1a46e..0000000 --- a/modules/getreuer/sentence_case/sentence_case.h +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2022-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file sentence_case.h - * @brief Sentence Case community module: automatic sentence capitalization. - * - * This module automatically capitalizes the first letter of sentences, reducing - * the need to explicitly use shift. To use it, you simply type as usual but - * without shifting at the start of sentences. The feature detects when new - * sentences begin and capitalizes automatically. - * - * Sentence Case matches patterns like - * - * "a. a" - * "a. a" - * "a? a" - * "a!' 'a" - * - * but not - * - * "a... a" - * "a.a. a" - * - * Additionally by default, abbreviations "vs." and "etc." are exceptionally - * detected as not real sentence endings. You can use the callback - * `sentence_case_check_ending()` to define other exceptions. - * - * @note One-shot keys must be enabled. - * - * For full documentation, see - * - */ - -#pragma once - -#include "quantum.h" - -#ifdef __cplusplus -extern "C" { -#endif - -// The size of the keycode buffer for `sentence_case_check_ending()`. It must be -// at least as large as the longest pattern checked. If less than 2, buffering -// is disabled and the callback is not called. -#ifndef SENTENCE_CASE_BUFFER_SIZE -#define SENTENCE_CASE_BUFFER_SIZE 8 -#endif // SENTENCE_CASE_BUFFER_SIZE - -void sentence_case_on(void); /**< Enables Sentence Case. */ -void sentence_case_off(void); /**< Disables Sentence Case. */ -void sentence_case_toggle(void); /**< Toggles Sentence Case. */ -bool is_sentence_case_on(void); /**< Gets whether currently enabled. */ -bool is_sentence_case_primed(void); /**< Whether currently primed. */ -void sentence_case_clear(void); /**< Clears Sentence Case to initial state. */ - -/** - * Optional callback to indicate primed state. - * - * This callback gets called when Sentence Case changes to or from a "primed" - * state, useful to indicate with an LED or otherwise that the next letter typed - * will be capitalized. - */ -void sentence_case_primed(bool primed); - -/** - * Optional callback to determine whether there is a real sentence ending. - * - * When a sentence-ending punctuation key is typed, this callback is called to - * determine whether it is a real sentence ending, meaning the first letter of - * the following word should be capitalized. For instance, abbreviations like - * "vs." are usually not real sentence endings. The input argument is a buffer - * of the last SENTENCE_CASE_BUFFER_SIZE keycodes. Returning true means it is a - * real sentence ending; returning false means it is not. - * - * The default implementation checks for the abbreviations "vs." and "etc.": - * - * bool sentence_case_check_ending(const uint16_t* buffer) { - * // Don't consider "vs." and "etc." to end the sentence. - * if (SENTENCE_CASE_JUST_TYPED(KC_SPC, KC_V, KC_S, KC_DOT) || - * SENTENCE_CASE_JUST_TYPED(KC_SPC, KC_E, KC_T, KC_C, KC_DOT)) { - * return false; // Not a real sentence ending. - * } - * return true; // Real sentence ending; capitalize next letter. - * } - * - * @note This callback is used only if `SENTENCE_CASE_BUFFER_SIZE >= 2`. - * Otherwise it has no effect. - * - * @param buffer Buffer of the last `SENTENCE_CASE_BUFFER_SIZE` keycodes. - * @return whether there is a real sentence ending. - */ -bool sentence_case_check_ending(const uint16_t* buffer); - -/** - * Macro to be used in `sentence_case_check_ending()`. - * - * Returns true if a given pattern of keys was just typed by comparing with the - * keycode buffer. This is useful for defining exceptions in - * `sentence_case_check_ending()`. - * - * For example, `SENTENCE_CASE_JUST_TYPED(KC_SPC, KC_V, KC_S, KC_DOT)` returns - * true if " vs." were the last four keys typed. - * - * @note The pattern must be no longer than `SENTENCE_CASE_BUFFER_SIZE`. - */ -#define SENTENCE_CASE_JUST_TYPED(...) \ - ({ \ - static const uint16_t PROGMEM pattern[] = {__VA_ARGS__}; \ - sentence_case_just_typed_P(buffer, pattern, \ - sizeof(pattern) / sizeof(uint16_t)); \ - }) -bool sentence_case_just_typed_P(const uint16_t* buffer, const uint16_t* pattern, - int8_t pattern_len); - -/** - * Optional callback defining which keys are letter, punctuation, etc. - * - * This callback may be useful if you type non-US letters or have customized the - * shift behavior of the punctuation keys. The return value tells Sentence Case - * how to interpret the key: - * - * 'a' Key is a letter, by default KC_A to KC_Z. If occurring at the start of - * a sentence, Sentence Case applies shift to capitalize it. - * - * '.' Key is sentence-ending punctuation. Default: . ? ! - * - * '#' Key types a backspaceable character that isn't part of a word. - * Default includes - = [ ] ; ' , < > / _ + @ # $ % ^ & * ( ) { } digits - * - * ' ' Key is a space. Default: KC_SPC - * - * '\'' Key is a quote or double quote character. Default: KC_QUOT. - * - * '\0' Sentence Case should ignore this key. - * - * If a hotkey or navigation key is pressed (or another key that performs an - * action that backspace doesn't undo), then the callback should call - * `sentence_case_clear()` to clear the state and then return '\0'. - * - * The default callback is: - * - * char sentence_case_press_user(uint16_t keycode, - * keyrecord_t* record, - * uint8_t mods) { - * if ((mods & ~(MOD_MASK_SHIFT | MOD_BIT(KC_RALT))) == 0) { - * const bool shifted = mods & MOD_MASK_SHIFT; - * switch (keycode) { - * case KC_A ... KC_Z: - * return 'a'; // Letter key. - * - * case KC_DOT: // . is punctuation, Shift . is a symbol (>) - * return !shifted ? '.' : '#'; - * case KC_1: - * case KC_SLSH: - * return shifted ? '.' : '#'; - * case KC_EXLM: - * case KC_QUES: - * return '.'; - * case KC_2 ... KC_0: // 2 3 4 5 6 7 8 9 0 - * case KC_AT ... KC_RPRN: // @ # $ % ^ & * ( ) - * case KC_MINS ... KC_SCLN: // - = [ ] backslash ; - * case KC_UNDS ... KC_COLN: // _ + { } | : - * case KC_GRV: - * case KC_COMM: - * return '#'; // Symbol key. - * - * case KC_SPC: - * return ' '; // Space key. - * - * case KC_QUOT: - * return '\''; // Quote key. - * } - * } - * - * // Otherwise clear Sentence Case to initial state. - * sentence_case_clear(); - * return '\0'; - * } - * - * To customize, copy the above function into your keymap and add/remove - * keycodes to the above cases. - * - * @param keycode Current keycode. - * @param record record_t for the current press event. - * @param mods equal to `get_mods() | get_weak_mods() | get_oneshot_mods()` - * @return char code 'a', '.', '#', ' ', or '\0' indicating how the key is to be - * interpreted as described above. - */ -char sentence_case_press_user(uint16_t keycode, keyrecord_t* record, - uint8_t mods); - -#ifdef __cplusplus -} -#endif diff --git a/modules/getreuer/socd_cleaner/README.md b/modules/getreuer/socd_cleaner/README.md deleted file mode 100644 index 7ab80ca..0000000 --- a/modules/getreuer/socd_cleaner/README.md +++ /dev/null @@ -1,79 +0,0 @@ -# SOCD Cleaner - - - - - - - -
Modulegetreuer/socd_cleaner
Version2025-03-07
MaintainerPascal Getreuer (@getreuer)
LicenseApache 2.0
Documentation -https://getreuer.info/posts/keyboards/socd-cleaner -
- -This is a community module adaptation of [SOCD Cleaner -Case](https://getreuer.info/posts/keyboards/socd-cleaner) for Simultaneous -Opposing Cardinal Directions (SOCD) filtering. What this mouthful of a name -means is that when two keys of opposing direction are held at the same time, a -rule is applied to decide which key is sent to the computer. Such filtering is -popular for fast inputs on the WASD keys in gaming. - -**Caution: Check game rules before using.** Notably, [Counter-Strike does not -allow SOCD -filtering](https://store.steampowered.com/news/app/730/view/6500469346429600836). -It is your responsibility to disable SOCD Cleaner where it is prohibited. - -## Add SOCD to your keymap - -Add the following to your `keymap.json`: - -```json -{ - "modules": ["getreuer/socd_cleaner"] -} -``` - -Then in your `keymap.c`, add: - -```c -socd_cleaner_t socd_opposing_pairs[] = { - {{KC_W, KC_S}, SOCD_CLEANER_LAST}, - {{KC_A, KC_D}, SOCD_CLEANER_LAST}, -}; -``` - -These lines specify that SOCD filtering is to be performed on the WASD keys -(referred to by keycodes `KC_W`, `KC_A`, `KC_S`, `KC_D`) with last input -priority resolution (`SOCD_CLEANER_LAST`). If you want to do something else, -this is where to change that. - -Resolution strategies: - -* `SOCD_CLEANER_LAST`: (Recommended) Last input priority with reactivation. The - last key pressed wins. If the last key is released while the opposing key is - still held, the opposing key is reactivated. Rapid alternating inputs can be - made. Repeatedly tapping the `D` key while `A` is held sends `ADADADAD`. - -* `SOCD_CLEANER_NEUTRAL`: Neutral resolution. When both keys are pressed, they - cancel and neither is sent. - -* `SOCD_CLEANER_0_WINS`: Key 0 always wins, the first key listed in defining the - opposing pair. - -* `SOCD_CLEANER_1_WINS`: Key 1 always wins, the second key listed. - -* `SOCD_CLEANER_OFF`: SOCD filtering is disabled for this key pair. - -SOCD Cleaner is enabled by default. Optionally, use these keycodes to enable and -disable SOCD Cleaner globally for all opposing pairs: - -| Keycode | Description | -|-----------|---------------------------| -| `SOCDON` | Turn SOCD Cleaner on. | -| `SOCDOFF` | Turn SOCD Cleaner off. | -| `SOCDTOG` | Toggle SOCD Cleaner. | - - -See the [SOCD Cleaner -documentation](https://getreuer.info/posts/keyboards/socd-cleaner) for further -explanation and details. - diff --git a/modules/getreuer/socd_cleaner/introspection.c b/modules/getreuer/socd_cleaner/introspection.c deleted file mode 100644 index 92c862a..0000000 --- a/modules/getreuer/socd_cleaner/introspection.c +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifdef COMMUNITY_MODULE_SOCD_CLEANER_ENABLE - -uint16_t socd_opposing_pairs_count_raw(void) { - return ARRAY_SIZE(socd_opposing_pairs); -} - -__attribute__((weak)) uint16_t socd_opposing_pairs_count(void) { - return socd_opposing_pairs_count_raw(); -} - -socd_cleaner_t* socd_opposing_pairs_get_raw(uint16_t index) { - if (index >= socd_opposing_pairs_count_raw()) { - return NULL; - } - return &socd_opposing_pairs[index]; -} - -__attribute__((weak)) socd_cleaner_t* socd_opposing_pairs_get(uint16_t index) { - return socd_opposing_pairs_get_raw(index); -} - -#endif // COMMUNITY_MODULE_SOCD_CLEANER_ENABLE diff --git a/modules/getreuer/socd_cleaner/introspection.h b/modules/getreuer/socd_cleaner/introspection.h deleted file mode 100644 index 08fc430..0000000 --- a/modules/getreuer/socd_cleaner/introspection.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once -#include "socd_cleaner.h" - diff --git a/modules/getreuer/socd_cleaner/qmk_module.json b/modules/getreuer/socd_cleaner/qmk_module.json deleted file mode 100644 index d0b5837..0000000 --- a/modules/getreuer/socd_cleaner/qmk_module.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "module_name": "SOCD Cleaner", - "maintainer": "getreuer", - "url": "https://getreuer.info/posts/keyboards/socd-cleaner", - "keycodes": [ - {"key": "SOCDON"}, - {"key": "SOCDOFF"}, - {"key": "SOCDTOG"} - ] -} diff --git a/modules/getreuer/socd_cleaner/socd_cleaner.c b/modules/getreuer/socd_cleaner/socd_cleaner.c deleted file mode 100644 index 6721dd2..0000000 --- a/modules/getreuer/socd_cleaner/socd_cleaner.c +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2024-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file socd_cleaner.c - * @brief SOCD Cleaner community module implementation - * - * For full documentation, see - * - */ - -#include "socd_cleaner.h" - -ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(1, 0, 0); - -// Defined in introspection.c. -uint16_t socd_opposing_pairs_count(void); -socd_cleaner_t* socd_opposing_pairs_get(uint16_t index); - -bool socd_cleaner_enabled = true; - -static void update_key(uint8_t keycode, bool press) { - if (press) { - add_key(keycode); - } else { - del_key(keycode); - } -} - -static bool process_opposing_pair( - uint16_t keycode, keyrecord_t* record, socd_cleaner_t* state) { - if (!state || !state->resolution || - (keycode != state->keys[0] && keycode != state->keys[1])) { - return true; // Quick return when disabled or on unrelated events. - } - - // The current event corresponds to index `i`, 0 or 1, in the SOCD key pair. - const uint8_t i = (keycode == state->keys[1]); - const uint8_t opposing = i ^ 1; // Index of the opposing key. - - // Track which keys are physically held (vs. keys in the report). - state->held[i] = record->event.pressed; - - // Perform SOCD resolution for events where the opposing key is held. - if (state->held[opposing]) { - switch (state->resolution) { - case SOCD_CLEANER_LAST: // Last input priority with reactivation. - // If the current event is a press, then release the opposing key. - // Otherwise if this is a release, then press the opposing key. - update_key(state->keys[opposing], !state->held[i]); - break; - - case SOCD_CLEANER_NEUTRAL: // Neutral resolution. - // Same logic as SOCD_CLEANER_LAST, but skip default handling so that - // the current key has no effect while the opposing key is held. - update_key(state->keys[opposing], !state->held[i]); - // Send updated report (normally, default handling would do this). - send_keyboard_report(); - return false; // Skip default handling. - - case SOCD_CLEANER_0_WINS: // Key 0 wins. - case SOCD_CLEANER_1_WINS: // Key 1 wins. - if (opposing == (state->resolution - SOCD_CLEANER_0_WINS)) { - // The opposing key is the winner. The current key has no effect. - return false; // Skip default handling. - } else { - // The current key is the winner. Update logic is same as above. - update_key(state->keys[opposing], !state->held[i]); - } - break; - } - } - return true; // Continue default handling to press/release current key. -} - -bool process_record_socd_cleaner(uint16_t keycode, keyrecord_t* record) { - switch (keycode) { - case SOCDON: // Turn SOCD Cleaner on. - if (record->event.pressed) { - socd_cleaner_enabled = true; - } - return false; - case SOCDOFF: // Turn SOCD Cleaner off. - if (record->event.pressed) { - socd_cleaner_enabled = false; - } - return false; - case SOCDTOG: // Toggle SOCD Cleaner. - if (record->event.pressed) { - socd_cleaner_enabled = !socd_cleaner_enabled; - } - return false; - } - - if (socd_cleaner_enabled) { - for (int i = 0; i < (int)socd_opposing_pairs_count(); ++i) { - socd_cleaner_t* state = socd_opposing_pairs_get(i); - if (!process_opposing_pair(keycode, record, state)) { - return false; - } - } - } - return true; -} - diff --git a/modules/getreuer/socd_cleaner/socd_cleaner.h b/modules/getreuer/socd_cleaner/socd_cleaner.h deleted file mode 100644 index 4e0ca9a..0000000 --- a/modules/getreuer/socd_cleaner/socd_cleaner.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2024-2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file socd_cleaner.h - * @brief SOCD Cleaner community module: enhance WASD for fast inputs for gaming - * - * - * SOCD Cleaner is a QMK module for Simultaneous Opposing Cardinal Directions - * (SOCD) filtering, which is popular for fast inputs on the WASD keys in - * gaming. When two keys of opposing direction are physically held at the same - * time, a rule is applied to decide which key is sent to the computer. - * - * As controls vary across games, there are multiple possible SOCD resolution - * strategies. SOCD Cleaner implements the following resolutions: - * - * - SOCD_CLEANER_LAST: (Recommended) Last input priority with reactivation. - * The last key pressed wins. Rapid alternating inputs can be made. - * Repeatedly tapping the D key while A is held sends "ADADADAD." - * - * - SOCD_CLEANER_NEUTRAL: Neutral resolution. When both keys are pressed, they - * cancel and neither is sent. - * - * - SOCD_CLEANER_0_WINS: Key 0 always wins, the first key listed in defining - * the `socd_cleaner_t`. - * - * - SOCD_CLEANER_1_WINS: Key 1 always wins, the second key listed. - * - * If you don't know what to pick, SOCD_CLEANER_LAST is recommended. The - * resolution strategy on a `socd_cleaner_t` may be changed at run time by - * assigning to `.resolution`. - * - * - * For full documentation, see - * - */ - -#pragma once - -#include "quantum.h" - -#ifdef __cplusplus -extern "C" { -#endif - -enum socd_cleaner_resolution { - // Disable SOCD filtering for this key pair. - SOCD_CLEANER_OFF, - // Last input priority with reactivation. - SOCD_CLEANER_LAST, - // Neutral resolution. When both keys are pressed, they cancel. - SOCD_CLEANER_NEUTRAL, - // Key 0 always wins. - SOCD_CLEANER_0_WINS, - // Key 1 always wins. - SOCD_CLEANER_1_WINS, - // Sentinel to count the number of resolution strategies. - SOCD_CLEANER_NUM_RESOLUTIONS, -}; - -typedef struct { - uint8_t keys[2]; // Basic keycodes for the two opposing keys. - uint8_t resolution; // Resolution strategy. - bool held[2]; // Tracks which keys are physically held. -} socd_cleaner_t; - -/** Determines globally whether SOCD cleaner is enabled. */ -extern bool socd_cleaner_enabled; - -#ifdef __cplusplus -} -#endif diff --git a/modules/getreuer/tap_flow/README.md b/modules/getreuer/tap_flow/README.md deleted file mode 100644 index cbd0217..0000000 --- a/modules/getreuer/tap_flow/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Tap Flow - - - - - - - -
Modulegetreuer/tap_flow
Version2025-04-08
MaintainerPascal Getreuer (@getreuer)
LicenseApache 2.0
Documentation -https://getreuer.info/posts/keyboards/tap-flow -
- -This module is an implementation of "global quick tap" (GQT), aka "require -prior idle," for tap-hold keys. It is particularly useful for home row mods to -avoid accidental mod triggers in fast typing. - -To use this module, add the following to your `keymap.json`: - -```json -{ - "modules": ["getreuer/tap_flow"] -} -``` - -Tap Flow's term can be tuned on the fly with the following keycodes: - -| Keycode | Alias | Description | -|-------------------|-----------|-----------------------------------| -| `TAP_FLOW_PRINT` | `TFLOW_P` | Type the current value. | -| `TAP_FLOW_UP` | `TFLOW_U` | Increase by 5 ms. | -| `TAP_FLOW_DOWN` | `TFLOW_D` | Decrease by 5 ms. | - -Tap Flow's default behavior is: - -* Filtering is done only when a tap-hold press is within `TAP_FLOW_TERM` of the - previous key event, which defaults to 150 ms. Use `TFLOW_U` / `TFLOW_D` - to tune, then define `TAP_FLOW_TERM` in your `config.h` to set the value - printed by `TFLOW_P`. - -* Filtering is done only when both the tap-hold key and the previous key are - among Space, letters AZ, and - punctuations , . ; /. - -Define the `is_tap_flow_key()` or `get_tap_flow_term()` callbacks to customize. - -Tap Flow modifies the tap-hold decision such that when a tap-hold key is pressed -within a short timeout of the preceding key, the tapping function is used. The -assumption is that during fast typing, only the tap function of tap-hold keys is -desired (though perhaps with an exception for Shift or AltGr, noted below), -whereas the hold functions (mods and layers) are used in isolation, or at least -with a brief pause preceding the tap-hold key press. - -Optionally, the feature can be customized with the `is_tap_flow_key()` and -`get_tap_flow_term()` callbacks. In this way, exceptions may be made for Shift -and AltGr (or whatever you wish) to use a shorter time or to disable filtering -for those keys entirely. - -For full documentation, see - diff --git a/modules/getreuer/tap_flow/introspection.h b/modules/getreuer/tap_flow/introspection.h deleted file mode 100644 index e929752..0000000 --- a/modules/getreuer/tap_flow/introspection.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once -#include "tap_flow.h" - diff --git a/modules/getreuer/tap_flow/qmk_module.json b/modules/getreuer/tap_flow/qmk_module.json deleted file mode 100644 index 02839b9..0000000 --- a/modules/getreuer/tap_flow/qmk_module.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "module_name": "Tap Flow", - "maintainer": "getreuer", - "url": "https://getreuer.info/posts/keyboards/tap-flow", - "keycodes": [ - { - "key": "TAP_FLOW_PRINT", - "aliases": [ - "TFLOW_P" - ] - }, - { - "key": "TAP_FLOW_UP", - "aliases": [ - "TFLOW_U" - ] - }, - { - "key": "TAP_FLOW_DOWN", - "aliases": [ - "TFLOW_D" - ] - } - ] -} diff --git a/modules/getreuer/tap_flow/tap_flow.c b/modules/getreuer/tap_flow/tap_flow.c deleted file mode 100644 index cbef9b6..0000000 --- a/modules/getreuer/tap_flow/tap_flow.c +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file tap_flow.c - * @brief Tap Flow module implementation - * - * For full documentation, see - * - */ - -#include "tap_flow.h" - -ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(1, 0, 0); - -// Either the Combos feature or Repeat Key (or both) need to be enabled. Either -// of these features enables the .keycode field in keyrecord_t, which the -// tap_flow implementation relies on. -#if !defined(COMBO_ENABLE) && !defined(REPEAT_KEY_ENABLE) -#error "tap_flow: Please enable Combos (COMBO_ENABLE = true) or Repeat Key (REPEAT_KEY_ENABLE = yes), or both, in rules.mk." -#else - -uint32_t last_input = 0; -uint16_t g_tap_flow_term = TAP_FLOW_TERM; - -static uint16_t prev_keycode = KC_NO; -// Events bypass tap_flow when there are unsettled LT keys in action_tapping's -// waiting_queue. Particularly, supposing an LT settles as held, the layer state -// will change and buffered events following the LT will be reconsidered as keys -// on that layer. That may change whether tap_flow is enabled or the timeout -// to use on those keys. We don't know in advance how the LT will settle. -static uint16_t settle_timer = 0; -static uint8_t is_tapped[(MATRIX_ROWS * MATRIX_COLS + 7) / 8] = {0}; - -static uint16_t get_tap_keycode(uint16_t keycode) { - switch (keycode) { - case QK_MOD_TAP ... QK_MOD_TAP_MAX: - return QK_MOD_TAP_GET_TAP_KEYCODE(keycode); -#ifndef NO_ACTION_LAYER - case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: - return QK_LAYER_TAP_GET_TAP_KEYCODE(keycode); -#endif // NO_ACTION_LAYER - } - return keycode; -} - -#ifdef COMBO_ENABLE -#include "keymap_introspection.h" - -static bool is_combo_key(uint16_t keycode) { - for (uint16_t i = 0; i < combo_count(); ++i) { - const uint16_t* keys = combo_get(i)->keys; - uint16_t key; - do { - key = pgm_read_word(keys++); - if (key == keycode) { return true; } - } while (key != COMBO_END); - } - return false; -} -#else -#define is_combo_key(keycode) false -#endif // COMBO_ENABLE - -void housekeeping_task_tap_flow(void) { - if (settle_timer && timer_expired(timer_read(), settle_timer)) { -#ifdef TAP_FLOW_DEBUG - dprintf("tap_flow: settled.\n"); -#endif // TAP_FLOW_DEBUG - settle_timer = 0; - } -} - -bool pre_process_record_tap_flow(uint16_t keycode, keyrecord_t* record) { - const keypos_t pos = record->event.key; - - if (IS_KEYEVENT(record->event) && pos.row < MATRIX_ROWS - && pos.col < MATRIX_COLS && - (IS_QK_MOD_TAP(keycode) || IS_QK_LAYER_TAP(keycode))) { - // The event is on an MT or LT with a valid matrix position. - const uint16_t tap_keycode = get_tap_keycode(keycode); - - // Determine the key's index in the bit arrays. - const uint16_t index = pos.row * MATRIX_COLS + pos.col; - const uint16_t array_index = index / 8; - const uint8_t bit_mask = UINT8_C(1) << (index % 8); - - if (record->event.pressed) { // On press. - const uint32_t idle_time = timer_elapsed32(last_input); - uint16_t tap_flow_term = get_tap_flow(keycode, record, prev_keycode); - if (tap_flow_term > 500) { - tap_flow_term = 500; - } - - if (!settle_timer && !is_combo_key(keycode) && - idle_time < 500 && idle_time < tap_flow_term) { -#ifdef TAP_FLOW_DEBUG - dprintf("tap_flow: %02x%02xd within term (%u < %u) converted to tap.\n", - pos.row, pos.col, (uint16_t)idle_time, tap_flow_term); -#endif // TAP_FLOW_DEBUG - - // Rewrite the event as a press of the tap keycode. This way, it - // bypasses the usual action_tapping logic. - record->keycode = tap_keycode; - // Record this key as tapped. - is_tapped[array_index] |= bit_mask; - } else { - // Otherwise if this is an LT key, track when it will settle according - // to its tapping term. - // NOTE: To be precise, the key could settle before the tapping term. - // This is an approximation. -#ifdef TAP_FLOW_DEBUG - if (settle_timer) { - dprintf("tap_flow: %02x%02xd unchanged (unsettled state).\n", - pos.row, pos.col); - } else if (is_combo_key(keycode)) { - dprintf("tap_flow: %02x%02xd unchanged (combo key).\n", - pos.row, pos.col); - } else { - dprintf("tap_flow: %02x%02xd unchanged (outside time).\n", - pos.row, pos.col); - } -#endif // TAP_FLOW_DEBUG - - if (IS_QK_LAYER_TAP(keycode)) { - const uint16_t term = GET_TAPPING_TERM(keycode, record); - const uint16_t now = timer_read(); - if (!settle_timer || term > TIMER_DIFF_16(settle_timer, now)) { - settle_timer = (now + term) | 1; - } - } - } - } else if ((is_tapped[array_index] & bit_mask) != 0) { // On tap release. -#ifdef TAP_FLOW_DEBUG - dprintf("tap_flow: %02x%02xu tap release.\n", pos.row, pos.col); -#endif // TAP_FLOW_DEBUG - - // Rewrite the event as a release of the tap keycode. - record->keycode = tap_keycode; - // Record the key as released. - is_tapped[array_index] &= ~bit_mask; - } - } - - if (record->event.pressed) { // Track the previous key press. - prev_keycode = keycode; - } - - // Update last input time. Ignore mods and mod-tap keys in this update to - // allow for chording multiple mods for hotkeys like "Ctrl+Shift+arrow". - if (!IS_MODIFIER_KEYCODE(keycode) && !IS_QK_MOD_TAP(keycode)) { - last_input = timer_read32(); - } - - return true; -} - -bool process_record_tap_flow(uint16_t keycode, keyrecord_t* record) { - if (record->event.pressed) { - switch (keycode) { - case TAP_FLOW_PRINT: - send_string(get_u16_str(g_tap_flow_term, ' ')); - return false; - case TAP_FLOW_UP: - g_tap_flow_term += 5; - return false; - case TAP_FLOW_DOWN: - g_tap_flow_term -= 5; - return false; - } - } - return true; -} - -// By default, enable Tap Flow for Space, A-Z, or main alphas area punctuation. -__attribute__((weak)) bool is_tap_flow_key(uint16_t keycode) { - switch (get_tap_keycode(keycode)) { - case KC_SPC: - case KC_A ... KC_Z: - case KC_DOT: - case KC_COMM: - case KC_SCLN: - case KC_SLSH: - return true; - } - return false; -} - -__attribute__((weak)) uint16_t get_tap_flow_term( - uint16_t keycode, keyrecord_t* record, uint16_t prev_keycode) { - return get_tap_flow(keycode, record, prev_keycode); -} - -// By default, enable filtering when both the tap-hold key and previous key -// return true for `is_tap_flow_key()`. -__attribute__((weak)) uint16_t get_tap_flow( - uint16_t keycode, keyrecord_t* record, uint16_t prev_keycode) { - if (is_tap_flow_key(keycode) && is_tap_flow_key(prev_keycode)) { - return g_tap_flow_term; - } - return 0; // Disable Tap Flow. -} - -#endif // !defined(COMBO_ENABLE) && !defined(REPEAT_KEY_ENABLE) - diff --git a/modules/getreuer/tap_flow/tap_flow.h b/modules/getreuer/tap_flow/tap_flow.h deleted file mode 100644 index 2a2ddfe..0000000 --- a/modules/getreuer/tap_flow/tap_flow.h +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @file tap_flow.h - * @brief Tap Flow module: disable HRMs during fast typing - * - * This module is an implementation of "global quick tap" (GQT), aka "require - * prior idle," for tap-hold keys. It is particularly useful for home row mods - * to avoid accidental mod triggers in fast typing. - * - * Tap Flow modifies the tap-hold decision such that when a tap-hold key is - * pressed within a short timeout of the preceding key, the tapping function is - * used. The assumption is that during fast typing, only the tap function of - * tap-hold keys is desired (though perhaps with an exception for Shift or - * AltGr, noted below), whereas the hold functions (mods and layers) are - * used in isolation, or at least with a brief pause preceding the tap-hold key - * press. - * - * Optionally, the feature can be customized with the `get_tap_flow()` callback. - * In this way, exceptions may be made for Shift and AltGr (or whatever you - * wish) to use a shorter time or to disable filtering for those keys entirely. - * - * - * For full documentation, see - * - */ - -#pragma once -#include "quantum.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef TAP_FLOW_TERM -# define TAP_FLOW_TERM 150 -#endif // TAP_FLOW_TERM - -/** - * Optional callback to customize where Tap Flow is enabled. - * - * Tap Flow is constrained to certain keys by the following rule: this callback - * is called for both the tap-hold key *and* the key press immediately preceding - * it. If the callback returns true for both keycodes, Tap Flow may apply. - * - * The default implementation of this callback corresponds to - * - * bool is_tap_flow_key(uint16_t keycode) { - * switch (keycode) { - * case QK_MOD_TAP ... QK_MOD_TAP_MAX: - * keycode = QK_MOD_TAP_GET_TAP_KEYCODE(keycode); - * break; - * case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: - * keycode = QK_LAYER_TAP_GET_TAP_KEYCODE(keycode); - * break; - * } - * switch (keycode) { - * case KC_SPC: - * case KC_A ... KC_Z: - * case KC_DOT: - * case KC_COMM: - * case KC_SCLN: - * case KC_SLSH: - * return true; - * } - * return false; - * } - * - * @param keycode Keycode of the key. - * @return Whether to enable Tap Flow for this key. - */ -bool is_tap_flow_key(uint16_t keycode); - -/** - * Optional callback to customize filtering. - * - * Tap Flow acts only when key events are closer together than this time. - * - * Return a time of 0 to disable filtering. In this way, Tap Flow may be - * disabled for certain tap-hold keys, or when following certain previous keys. - * - * The default implementation of this callback is - * - * uint16_t get_tap_flow_term(uint16_t keycode, keyrecord_t* record, - * uint16_t prev_keycode) { - * if (is_tap_flow_key(keycode) && is_tap_flow_key(prev_keycode)) { - * return g_tap_flow_term; - * } - * return 0; - * } - * - * NOTE: If both `is_tap_flow_key()` and `get_tap_flow_term()` are defined, then - * `get_tap_flow_term()` takes precedence. - * - * @param keycode Keycode of the tap-hold key. - * @param record keyrecord_t of the tap-hold event. - * @param prev_keycode Keycode of the previously pressed key. - * @return Timeout in milliseconds. - */ -uint16_t get_tap_flow_term(uint16_t keycode, keyrecord_t* record, - uint16_t prev_keycode); - -/** @deprecated Use `get_tap_flow_term()` instead. */ -uint16_t get_tap_flow(uint16_t keycode, keyrecord_t* record, - uint16_t prev_keycode); - -extern uint16_t g_tap_flow_term; - -#ifdef __cplusplus -} -#endif - diff --git a/update.sh b/update.sh deleted file mode 100755 index 83546b4..0000000 --- a/update.sh +++ /dev/null @@ -1,5 +0,0 @@ -# update the modules -rm -rf modules/getreuer -git clone https://github.com/getreuer/qmk-modules.git modules/getreuer -rm -rf modules/getreuer/.git -git add modules/getreuer