The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .devcontainer
    └── devcontainer.json
├── .github
    ├── ISSUE_TEMPLATE
    │   ├── bug_report.yml
    │   ├── config.yml
    │   └── feature_request.yml
    ├── pull_request_template.md
    └── workflows
    │   ├── macos-build.yml
    │   ├── rust.yml
    │   └── windows-build.yml
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── EnableUIAccess
    ├── EnableUIAccess_launch.ahk
    ├── Lib
    │   └── EnableUIAccess.ahk
    └── README.md
├── LICENSE
├── README.md
├── assets
    ├── Interception.zip
    ├── kanata-icon.svg
    ├── kanata.ico
    └── reload_32px.png
├── build.rs
├── cfg_samples
    ├── artsey.kbd
    ├── automousekeys-full-map.kbd
    ├── automousekeys-only.kbd
    ├── chords.tsv
    ├── colemak.kbd
    ├── deflayermap.kbd
    ├── f13_f24.kbd
    ├── fancy_symbols.kbd
    ├── home-row-mod-advanced.kbd
    ├── home-row-mod-basic.kbd
    ├── included-file.kbd
    ├── japanese_mac_eisu_kana.kbd
    ├── jtroo.kbd
    ├── kanata.kbd
    ├── key-toggle_press-only_release-only.kbd
    ├── minimal.kbd
    ├── simple.kbd
    └── tray-icon
    │   ├── 3trans.parent.png
    │   ├── 6name-match.png
    │   ├── _custom-icons
    │       └── s.png
    │   ├── icons
    │       └── 1symbols.png
    │   ├── img
    │       └── 2Nav Num.png
    │   ├── license_icons.txt
    │   ├── tray-icon.kbd
    │   └── tray-icon.png
├── docs
    ├── README.md
    ├── config-stylesheet.css
    ├── config.adoc
    ├── design.md
    ├── fancy_symbols.md
    ├── interception.md
    ├── kanata-basic-diagram.svg
    ├── kmonad_comparison.md
    ├── locales.adoc
    ├── platform-known-issues.adoc
    ├── release-template.md
    ├── sequence-adding-chords-ideas.md
    ├── setup-linux.md
    ├── simulated_output
    │   ├── sim.kbd
    │   ├── sim.txt
    │   └── sim_out.txt
    ├── simulated_passthru_ahk
    │   ├── [COPY HERE] kanata_passthru.dll _
    │   ├── kanata_dll.kbd
    │   └── kanata_passthru.ahk
    ├── switch-design
    └── win-tray
    │   ├── win-tray-layer-change.gif
    │   └── win-tray-screen.png
├── example_tcp_client
    ├── .gitignore
    ├── Cargo.toml
    └── src
    │   └── main.rs
├── interception
    ├── Cargo.lock
    ├── Cargo.toml
    └── src
    │   ├── lib.rs
    │   └── scancode.rs
├── justfile
├── key-sort-add
    ├── Cargo.lock
    ├── Cargo.toml
    ├── README.md
    ├── mapping.txt
    └── src
    │   └── main.rs
├── keyberon
    ├── .gitignore
    ├── CHANGELOG.md
    ├── Cargo.toml
    ├── KEYBOARDS.md
    ├── LICENSE
    ├── README.md
    ├── images
    │   └── keyberon.jpg
    ├── keyberon-macros
    │   ├── Cargo.toml
    │   ├── README.md
    │   └── src
    │   │   └── lib.rs
    └── src
    │   ├── action.rs
    │   ├── action
    │       └── switch.rs
    │   ├── chord.rs
    │   ├── key_code.rs
    │   ├── layout.rs
    │   ├── lib.rs
    │   └── multikey_buffer.rs
├── parser
    ├── .gitignore
    ├── Cargo.toml
    ├── LICENSE
    ├── README.md
    ├── src
    │   ├── cfg
    │   │   ├── alloc.rs
    │   │   ├── chord.rs
    │   │   ├── custom_tap_hold.rs
    │   │   ├── defcfg.rs
    │   │   ├── deftemplate.rs
    │   │   ├── error.rs
    │   │   ├── fake_key.rs
    │   │   ├── is_a_button.rs
    │   │   ├── key_outputs.rs
    │   │   ├── key_override.rs
    │   │   ├── layer_opts.rs
    │   │   ├── list_actions.rs
    │   │   ├── mod.rs
    │   │   ├── permutations.rs
    │   │   ├── platform.rs
    │   │   ├── sexpr.rs
    │   │   ├── str_ext.rs
    │   │   ├── switch.rs
    │   │   ├── tests.rs
    │   │   ├── tests
    │   │   │   ├── ambiguous.rs
    │   │   │   ├── defcfg.rs
    │   │   │   ├── device_detect.rs
    │   │   │   ├── environment.rs
    │   │   │   └── macros.rs
    │   │   └── zippychord.rs
    │   ├── custom_action.rs
    │   ├── keys
    │   │   ├── linux.rs
    │   │   ├── macos.rs
    │   │   ├── mappings.rs
    │   │   ├── mod.rs
    │   │   └── windows.rs
    │   ├── layers.rs
    │   ├── lib.rs
    │   ├── lsp_hints.rs
    │   ├── sequences.rs
    │   ├── subset.rs
    │   └── trie.rs
    └── test_cfgs
    │   ├── all_keys_in_defsrc.kbd
    │   ├── ancestor_seq.kbd
    │   ├── bad_multi.kbd
    │   ├── descendant_seq.kbd
    │   ├── icon_bad_dupe.kbd
    │   ├── icon_good.kbd
    │   ├── include-bad.kbd
    │   ├── include-bad2.kbd
    │   ├── include-good-optional-absent.kbd
    │   ├── include-good.kbd
    │   ├── included-bad.kbd
    │   ├── included-bad2.kbd
    │   ├── included-good.kbd
    │   ├── macro-chord-dont-panic.kbd
    │   ├── multiline_comment.kbd
    │   ├── nested_tap_hold.kbd
    │   ├── test.zch
    │   ├── testzch.kbd
    │   ├── unknown_defcfg_opt.kbd
    │   ├── utf8bom-included.kbd
    │   └── utf8bom.kbd
├── simulated_input
    ├── Cargo.lock
    ├── Cargo.toml
    ├── README.md
    └── src
    │   └── sim.rs
├── simulated_passthru
    ├── Cargo.lock
    ├── Cargo.toml
    ├── ReadMe.md
    └── src
    │   ├── key_in.rs
    │   ├── key_out.rs
    │   ├── lib_passthru.rs
    │   └── log_win.rs
├── src
    ├── gui
    │   ├── mod.rs
    │   ├── win.rs
    │   ├── win_dbg_logger
    │   │   ├── mod.rs
    │   │   └── win_dbg_logger.toml
    │   └── win_nwg_ext
    │   │   ├── license-MIT
    │   │   ├── license-nwg-MIT
    │   │   └── mod.rs
    ├── kanata.exe.manifest.rc
    ├── kanata
    │   ├── caps_word.rs
    │   ├── cfg_forced.rs
    │   ├── clipboard.rs
    │   ├── cmd.rs
    │   ├── dynamic_macro.rs
    │   ├── key_repeat.rs
    │   ├── linux.rs
    │   ├── macos.rs
    │   ├── millisecond_counting.rs
    │   ├── mod.rs
    │   ├── output_logic.rs
    │   ├── output_logic
    │   │   └── zippychord.rs
    │   ├── sequences.rs
    │   ├── unknown.rs
    │   └── windows
    │   │   ├── exthook.rs
    │   │   ├── interception.rs
    │   │   ├── llhook.rs
    │   │   └── mod.rs
    ├── lib.rs
    ├── main.rs
    ├── main_lib
    │   ├── mod.rs
    │   └── win_gui.rs
    ├── oskbd
    │   ├── linux.rs
    │   ├── macos.rs
    │   ├── mod.rs
    │   ├── sim_passthru.rs
    │   ├── simulated.rs
    │   └── windows
    │   │   ├── exthook_os.rs
    │   │   ├── interception.rs
    │   │   ├── interception_convert.rs
    │   │   ├── llhook.rs
    │   │   ├── mod.rs
    │   │   └── scancode_to_usvk.rs
    ├── tcp_server.rs
    ├── tests.rs
    └── tests
    │   └── sim_tests
    │       ├── block_keys_tests.rs
    │       ├── capsword_sim_tests.rs
    │       ├── chord_sim_tests.rs
    │       ├── delay_tests.rs
    │       ├── layer_sim_tests.rs
    │       ├── macro_sim_tests.rs
    │       ├── mod.rs
    │       ├── oneshot_tests.rs
    │       ├── override_tests.rs
    │       ├── release_sim_tests.rs
    │       ├── repeat_sim_tests.rs
    │       ├── seq_sim_tests.rs
    │       ├── switch_sim_tests.rs
    │       ├── tap_hold_tests.rs
    │       ├── template_sim_tests.rs
    │       ├── timing_tests.rs
    │       ├── unicode_sim_tests.rs
    │       ├── unmod_sim_tests.rs
    │       ├── use_defsrc_sim_tests.rs
    │       ├── vkey_sim_tests.rs
    │       └── zippychord_sim_tests.rs
├── tcp_protocol
    ├── Cargo.toml
    └── src
    │   └── lib.rs
├── wasm
    ├── .gitignore
    ├── Cargo.toml
    ├── README.md
    └── src
    │   └── lib.rs
└── windows_key_tester
    ├── Cargo.lock
    ├── Cargo.toml
    ├── README.md
    └── src
        ├── main.rs
        ├── windows.rs
        └── windows
            ├── interception.rs
            └── llhook.rs


/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
 1 | {
 2 | 	"name": "Rust",
 3 | 	"image": "mcr.microsoft.com/devcontainers/rust:1-buster"
 4 | 
 5 | 	// Features to add to the dev container. More info: https://containers.dev/implementors/features.
 6 | 	// "features": {},
 7 | 
 8 | 	// Use 'forwardPorts' to make a list of ports inside the container available locally.
 9 | 	// "forwardPorts": [],
10 | 
11 | 	// Use 'postCreateCommand' to run commands after the container is created.
12 | 	// "postCreateCommand": "rustc --version",
13 | 
14 | 	// Configure tool-specific properties.
15 | 	// "customizations": {},
16 | 
17 | 	// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
18 | 	// "remoteUser": "root"
19 | }
20 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
 1 | name: "Bug report"
 2 | description: Create a report to help us improve.
 3 | labels: ["bug"]
 4 | assignees: ["jtroo"]
 5 | title: "Bug: title_goes_here"
 6 | body:
 7 |   - type: checkboxes
 8 |     attributes:
 9 |       label: Requirements
10 |       description: Before you create a bug report, please check the following
11 |       options:
12 |         - label: I've searched [platform-specific issues](https://github.com/jtroo/kanata/blob/main/docs/platform-known-issues.adoc), [issues](https://github.com/jtroo/kanata/issues) and [discussions](https://github.com/jtroo/kanata/discussions) to see if this has been reported before.
13 |           required: true
14 |         - label: My issue does not involve multiple simultaneous key presses, OR it does but I've confirmed it is not [key rollover or ghosting](https://github.com/jtroo/kanata/discussions/822).
15 |           required: true
16 |   - type: textarea
17 |     id: summary
18 |     attributes:
19 |       label: Describe the bug
20 |       description: A clear and concise description of what the bug is.
21 |     validations:
22 |       required: true
23 |   - type: textarea
24 |     id: config
25 |     attributes:
26 |       label: Relevant kanata config
27 |       description: E.g. defcfg, defsrc, deflayer, defalias items. If in doubt, feel free to include your entire config. Please ensure to use code formatting, e.g. surround with triple backticks to avoid pinging users with the @ character.
28 |     validations:
29 |       required: false
30 |   - type: textarea
31 |     id: reproduce
32 |     attributes:
33 |       label: To Reproduce
34 |       description: |
35 |         Walk us through the steps needed to reproduce the bug.
36 |       value: |
37 |         1.
38 |         2.
39 |         3.
40 |     validations:
41 |       required: true
42 |   - type: textarea
43 |     id: expected
44 |     attributes:
45 |       label: Expected behavior
46 |       description: A clear and concise description of what you expected to happen.
47 |     validations:
48 |       required: true
49 |   - type: input
50 |     id: version
51 |     attributes:
52 |       label: Kanata version
53 |       description: The kanata version prints in the log on startup, or you can also print it by passing the `--version` flag when running on the command line.
54 |       placeholder: e.g. kanata 1.3.0
55 |     validations:
56 |       required: true
57 |   - type: textarea
58 |     id: logs
59 |     attributes:
60 |       label: Debug logs
61 |       description: If you think it might help with a non-obvious issue, run kanata from the command line and pass the `--debug` flag. This will print more info. Include the relevant log outputs this section if you did so.
62 |     validations:
63 |       required: false
64 |   - type: input
65 |     id: os
66 |     attributes:
67 |       label: Operating system
68 |       description: Linux or Windows?
69 |       placeholder: e.g. Windows 11
70 |     validations:
71 |       required: true
72 |   - type: textarea
73 |     id: additional
74 |     attributes:
75 |       label: Additional context
76 |       description: Add any other context about the problem here.
77 |     validations:
78 |       required: false
79 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 | contact_links:
3 |   - name: Discussions
4 |     url: https://github.com/jtroo/kanata/discussions
5 |     about: Ask for help or interact with the community.


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
 1 | name: "Feature request"
 2 | description: Suggest an idea for this project
 3 | title: 'Feature request: feature_summary_goes_here'
 4 | labels: ["enhancement"]
 5 | assignees: []
 6 | body:
 7 |   - type: textarea
 8 |     attributes:
 9 |       label: Is your feature request related to a problem? Please describe.
10 |       description: |
11 |         A clear and concise description of what the problem is.
12 |       placeholder: Ex. I'm always frustrated when [...]
13 |     validations:
14 |       required: true
15 |   - type: textarea
16 |     attributes:
17 |       label: Describe the solution you'd like.
18 |       description: |
19 |         A clear and concise description of what you want to happen.
20 |     validations:
21 |       required: true
22 |   - type: textarea
23 |     attributes:
24 |       label: Describe alternatives you've considered.
25 |       description: |
26 |         A clear and concise description of any alternative solutions or features you've considered.
27 |     validations:
28 |       required: true
29 |   - type: textarea
30 |     attributes:
31 |       label: Additional context
32 |       description: |
33 |         Add any other context or screenshots about the feature request here.
34 |     validations:
35 |       required: false
36 | 


--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
 1 | ## Describe your changes. Use imperative present tense.
 2 | 
 3 | ## Checklist
 4 | 
 5 | - Add documentation to docs/config.adoc
 6 |   - [ ] Yes or N/A
 7 | - Add example and basic docs to cfg_samples/kanata.kbd
 8 |   - [ ] Yes or N/A
 9 | - Update error messages
10 |   - [ ] Yes or N/A
11 | - Added tests, or did manual testing
12 |   - [ ] Yes
13 | 


--------------------------------------------------------------------------------
/.github/workflows/macos-build.yml:
--------------------------------------------------------------------------------
 1 | name: macos-build
 2 | 
 3 | on:
 4 |   workflow_dispatch:
 5 |     branches: [ "main" ]
 6 | 
 7 | env:
 8 |   CARGO_TERM_COLOR: always
 9 |   RUSTFLAGS: "-Dwarnings"
10 | 
11 | jobs:
12 |   build-macos-aarch:
13 |     runs-on: macos-latest
14 |     steps:
15 |       - uses: actions/checkout@v3
16 |       - uses: actions-rs/toolchain@v1
17 |         with:
18 |           profile: minimal
19 |           toolchain: stable
20 |           override: true
21 |           target: aarch64-apple-darwin
22 |       - uses: Swatinem/rust-cache@v2
23 |       - name: Do the stuff on arm64
24 |         shell: bash
25 |         run: |
26 |           mkdir -p artifacts-arm64
27 |           cargo build --release --target aarch64-apple-darwin
28 |           mv target/aarch64-apple-darwin/release/kanata artifacts-arm64/kanata_macos_arm64
29 |           cargo build --release --features cmd --target aarch64-apple-darwin
30 |           mv target/aarch64-apple-darwin/release/kanata artifacts-arm64/kanata_macos_cmd_allowed_arm64
31 |       - uses: actions/upload-artifact@v4
32 |         with:
33 |           name: macos-binaries-arm64
34 |           path: |
35 |             artifacts-arm64/kanata_macos_arm64
36 |             artifacts-arm64/kanata_macos_cmd_allowed_arm64
37 | 
38 |   build-macos:
39 |     runs-on: macos-13
40 | 
41 |     steps:
42 |     - uses: actions/checkout@v3
43 |     - uses: Swatinem/rust-cache@v2
44 |       with:
45 |         shared-key: "persist-cross-job"
46 |     - name: Do the stuff on x86-64
47 |       shell: bash
48 |       run: |
49 |         mkdir -p artifacts
50 |         cargo build --release
51 |         mv target/release/kanata artifacts/kanata_macos_x86_64
52 |         cargo build --release --features cmd
53 |         mv target/release/kanata artifacts/kanata_macos_cmd_allowed_x86_64
54 |     - uses: actions/upload-artifact@v4
55 |       with:
56 |         name: macos-binaries-x86-64
57 |         path: |
58 |           artifacts/kanata_macos_x86_64
59 |           artifacts/kanata_macos_cmd_allowed_x86_64
60 |       
61 | 


--------------------------------------------------------------------------------
/.github/workflows/windows-build.yml:
--------------------------------------------------------------------------------
 1 | name: windows-build
 2 | 
 3 | on:
 4 |   workflow_dispatch:
 5 |     branches: [ "main" ]
 6 | 
 7 | env:
 8 |   CARGO_TERM_COLOR: always
 9 |   RUSTFLAGS: "-Dwarnings"
10 | 
11 | jobs:
12 |   build-windows:
13 |     runs-on: windows-latest
14 | 
15 |     steps:
16 |       - uses: actions/checkout@v3
17 |       - uses: Swatinem/rust-cache@v2
18 |         with:
19 |           shared-key: "persist-cross-job"
20 |       - name: Build x64
21 |         shell: bash
22 |         run: |
23 |           mkdir -p artifacts
24 |           cargo build --release --target x86_64-pc-windows-msvc
25 |           move target\release\kanata.exe artifacts\kanata_windows_x64.exe
26 |           cargo build --release --features cmd --target x86_64-pc-windows-msvc
27 |           move target\release\kanata.exe artifacts\kanata_windows_cmd_allowed_x64.exe
28 |       - uses: actions/upload-artifact@v4
29 |         with:
30 |           name: windows-binaries-x64
31 |           path: |
32 |             artifacts/kanata_windows_x64.exe
33 |             artifacts/kanata_windows_cmd_allowed_x64.exe
34 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/target
2 | .vscode/
3 | 


--------------------------------------------------------------------------------
/EnableUIAccess/README.md:
--------------------------------------------------------------------------------
1 | # EnableUIAccess
2 | 
3 | See [the guide documentation for context](https://github.com/jtroo/kanata/blob/main/docs/config.adoc#windows-only-work-elevated).
4 | 


--------------------------------------------------------------------------------
/assets/Interception.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtroo/kanata/dd02c3e6bd6305d4e4593de492ad2dec5abe3684/assets/Interception.zip


--------------------------------------------------------------------------------
/assets/kanata.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtroo/kanata/dd02c3e6bd6305d4e4593de492ad2dec5abe3684/assets/kanata.ico


--------------------------------------------------------------------------------
/assets/reload_32px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtroo/kanata/dd02c3e6bd6305d4e4593de492ad2dec5abe3684/assets/reload_32px.png


--------------------------------------------------------------------------------
/build.rs:
--------------------------------------------------------------------------------
 1 | fn main() -> std::io::Result<()> {
 2 |     #[cfg(all(target_os = "windows", any(feature = "win_manifest", feature = "gui")))]
 3 |     {
 4 |         windows::build()?;
 5 |     }
 6 |     Ok(())
 7 | }
 8 | 
 9 | #[cfg(all(target_os = "windows", any(feature = "win_manifest", feature = "gui")))]
10 | mod windows {
11 |     use indoc::formatdoc;
12 |     use regex::Regex;
13 |     use std::fs::File;
14 |     use std::io::Write;
15 |     extern crate embed_resource;
16 | 
17 |     // println! during build
18 |     macro_rules! pb {
19 |       ($($tokens:tt)*) => {println!("cargo:warning={}", format!($($tokens)*))}}
20 | 
21 |     pub(super) fn build() -> std::io::Result<()> {
22 |         let manifest_path: &str = "./target/kanata.exe.manifest";
23 | 
24 |         // Note about expected version format:
25 |         // MS says "Use the four-part version format: mmmmm.nnnnn.ooooo.ppppp"
26 |         // https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests
27 | 
28 |         let re_ver_build = Regex::new(r"^(?<vpre>(\d+\.){2}\d+)[-a-zA-Z]+(?<vpos>\d+)
quot;).unwrap();
29 |         let re_ver_build2 = Regex::new(r"^(?<vpre>(\d+\.){2}\d+)[-a-zA-Z]+
quot;).unwrap();
30 |         let re_version3 = Regex::new(r"^(\d+\.){2}\d+
quot;).unwrap();
31 |         let mut version: String = env!("CARGO_PKG_VERSION").to_string();
32 | 
33 |         if re_version3.find(&version).is_some() {
34 |             version = format!("{}.0", version);
35 |         } else if re_ver_build.find(&version).is_some() {
36 |             version = re_ver_build
37 |                 .replace_all(&version, r"$vpre.$vpos")
38 |                 .to_string();
39 |         } else if re_ver_build2.find(&version).is_some() {
40 |             version = re_ver_build2.replace_all(&version, r"$vpre.0").to_string();
41 |         } else {
42 |             pb!("unknown version format '{}', using '0.0.0.0'", version);
43 |             version = "0.0.0.0".to_string();
44 |         }
45 | 
46 |         let manifest_str = formatdoc!(
47 |             r#"<?xml version="1.0" encoding="utf-8" standalone="yes"?>
48 |                <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:v3="urn:schemas-microsoft-com:asm.v3">
49 |                  <assemblyIdentity name="kanata.exe" version="{}" type="win32"></assemblyIdentity>
50 |                  <v3:trustInfo><v3:security>
51 |                    <v3:requestedPrivileges><v3:requestedExecutionLevel level="asInvoker" uiAccess="false"></v3:requestedExecutionLevel></v3:requestedPrivileges>
52 |                  </v3:security></v3:trustInfo>
53 |                  <v3:application>
54 |                    <v3:windowsSettings>
55 |                      <dpiAware     xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
56 |                      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
57 |                    </v3:windowsSettings>
58 |                  </v3:application>
59 |                  <dependency><dependentAssembly>
60 |                    <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls"
61 |                      version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"></assemblyIdentity></dependentAssembly>
62 |                  </dependency>
63 |                </assembly>
64 |             "#,
65 |             version
66 |         );
67 |         let mut manifest_f = File::create(manifest_path)?;
68 |         write!(manifest_f, "{}", manifest_str)?;
69 |         embed_resource::compile("./src/kanata.exe.manifest.rc", embed_resource::NONE);
70 |         Ok(())
71 |     }
72 | }
73 | 


--------------------------------------------------------------------------------
/cfg_samples/artsey.kbd:
--------------------------------------------------------------------------------
 1 | ;; ARTSEY MINI 0.2 https://github.com/artseyio/artsey/issues/7
 2 | 
 3 | ;; Exactly one defcfg entry is required. This is used for configuration key-pairs.
 4 | (defcfg
 5 |   ;; Your keyboard device will likely differ from this.
 6 |   linux-dev /dev/input/event1
 7 | 
 8 |   ;; Windows doesn't need any input/output configuration entries; however, there
 9 |   ;; must still be a defcfg entry. You can keep the linux-dev entry or delete
10 |   ;; it and leave it empty.
11 | )
12 | 
13 | (defsrc
14 |   q    w    e
15 |   a    s    d
16 | )
17 | 
18 | (deflayer base
19 |   (chord base A) (chord base R) (chord base T)
20 |   (chord base S) (chord base E) (chord base Y)
21 | )
22 | 
23 | (deflayer meta
24 |   (chord meta A) (chord meta R) (chord meta T)
25 |   (chord meta S) (chord meta E) (chord meta Y)
26 | )
27 | 
28 | (defchords base 5000
29 |   (A R T S E Y) (layer-switch meta)
30 |   (A R T      ) (one-shot 2000 lsft)
31 |   (      S E Y) spc
32 |   (A          ) a
33 |   (  R T S    ) b
34 |   (  R   S    ) c
35 |   (A       E Y) d
36 |   (        E  ) e
37 |   (A R        ) f
38 |   (A       E  ) g
39 |   (      S   Y) h
40 |   (  R     E  ) i
41 |   (    T S E  ) j
42 |   (    T   E  ) k
43 |   (      S E  ) l
44 |   (  R T      ) m
45 |   (        E Y) n
46 |   (A     S    ) o
47 |   (A R       Y) p
48 |   (    T     Y) q
49 |   (  R        ) r
50 |   (      S    ) s
51 |   (    T      ) t
52 |   (A   T      ) u
53 |   (A   T   E  ) v
54 |   (    T S    ) w
55 |   (A         Y) x
56 |   (          Y) y
57 |   (  R   S E  ) z
58 | )
59 | 
60 | (defchords meta 5000
61 |   (A R T S E Y) (layer-switch base)
62 |   (      S E Y) spc
63 |   (A R T      ) caps ;; should technically be shift lock, probably need to use fake keys for that 
64 |   (A R        ) bspc
65 |   (  R T      ) del
66 |   (      S E  ) C-c
67 |   (        E Y) C-v
68 |   (A          ) home
69 |   (  R        ) up
70 |   (    T      ) end
71 |   (      S    ) left
72 |   (        E  ) down
73 |   (          Y) rght
74 | )
75 | 


--------------------------------------------------------------------------------
/cfg_samples/automousekeys-full-map.kbd:
--------------------------------------------------------------------------------
 1 | (defcfg
 2 |   ;; F* keys and arrow keys are left unmapped
 3 |   process-unmapped-keys yes
 4 | 
 5 |   ;; you may wish to only capture a trackpoint and keyboard
 6 |   ;; but not e.g. a trackpad or external mouse
 7 |   ;;linux-dev-names-include (
 8 |   ;;                         "AT Translated Set 2 keyboard"
 9 |   ;;                         "TPPS/2 Elan TrackPoint"
10 |   ;;)
11 |   ;; optional, but useful with the trackpoint
12 |   ;;linux-use-trackpoint-property yes
13 | 
14 |   mouse-movement-key mvmt
15 | )
16 | 
17 | ;; ANSI layout for eg thinkpad internal or external keyboard
18 | (defsrc
19 |   grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc
20 |   tab  q    w    e    r    t    y    u    i    o    p    [    ]    \
21 |   caps a    s    d    f    g    h    j    k    l    ;    '    ret
22 |   lsft z    x    c    v    b    n    m    ,    .    /    rsft
23 |   lctl lmet lalt           spc            ralt menu rctl
24 |   mvmt
25 | )
26 | 
27 | (defvirtualkeys
28 |   mouse (layer-while-held mouse-layer)
29 | )
30 | 
31 | (defalias
32 |   mhld (hold-for-duration 750 mouse)
33 | 
34 |   moff (on-press release-vkey mouse)
35 | 
36 |   _ (multi
37 |      @moff
38 |      _
39 |   )
40 | 
41 |    ;; mouse click extended time out for double tap
42 |   mdbt (hold-for-duration 500 mouse)
43 |   mbl (multi
44 |        mlft
45 |        @mdbt
46 |   )
47 |   mbm (multi
48 |        mmid
49 |        @mdbt
50 |   )
51 |   mbr (multi
52 |        mrgt
53 |        @mdbt
54 |   )
55 | )
56 | 
57 | ;; no mappings
58 | (deflayer qwerty
59 |   grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc
60 |   tab  q    w    e    r    t    y    u    i    o    p    [    ]    \
61 |   caps a    s    d    f    g    h    j    k    l    ;    '    ret
62 |   lsft z    x    c    v    b    n    m    ,    .    /    rsft
63 |   lctl lmet lalt           spc            ralt menu rctl
64 |   @mhld
65 | )
66 | 
67 | ;; places mouse keys on the row above the home row.
68 | ;; pressing any other keys exits the mouse layer until mouse movement stops and restarts again.
69 | (deflayer mouse-layer
70 |   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_
71 |   @_   @_   mrgt mmid @mbl @_   @_   @mbl mmid mrgt @_   @_   @_   @_
72 |   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_
73 |   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_
74 |   @_   @_   @_             @_             @_   @_   @_
75 |   @mhld
76 | )
77 | 


--------------------------------------------------------------------------------
/cfg_samples/automousekeys-only.kbd:
--------------------------------------------------------------------------------
 1 | (defcfg
 2 |   ;; we are only mapping the keys we want to use for mouse keys
 3 |   process-unmapped-keys yes
 4 | 
 5 |   ;; you may wish to only capture a trackpoint and keyboard
 6 |   ;; but not e.g. a trackpad or external mouse
 7 |   ;;linux-dev-names-include (
 8 |   ;;                         "Lenovo TrackPoint Keyboard II"
 9 |   ;;)
10 |   ;; optional, but useful with the trackpoint
11 |   ;;linux-use-trackpoint-property yes
12 | 
13 |   mouse-movement-key mvmt
14 | )
15 | 
16 | (defsrc)
17 | 
18 | (defvirtualkeys
19 |   mouse (layer-while-held mouse-layer)
20 | )
21 | 
22 | (defalias
23 |   mhld (hold-for-duration 750 mouse)
24 | 
25 |   moff (on-press release-vkey mouse)
26 | 
27 |   _ (multi
28 |      @moff
29 |      _
30 |   )
31 | 
32 |   ;; mouse click extended time out for double tap
33 |   mdbt (hold-for-duration 500 mouse)
34 |   mbl (multi
35 |        mlft
36 |        @mdbt
37 |   )
38 |   mbm (multi
39 |        mmid
40 |        @mdbt
41 |   )
42 |   mbr (multi
43 |        mrgt
44 |        @mdbt
45 |   )
46 | )
47 | 
48 | ;; no key mappings
49 | (deflayermap (base)
50 |              mvmt @mhld
51 | )
52 | 
53 | ;; places mouse keys on the row above the home row.
54 | ;; pressing any other keys exits the mouse layer until mouse movement stops and restarts again.
55 | (deflayermap (mouse-layer)
56 |              w mrgt
57 |              e mmid
58 |              r @mbl
59 | 
60 |              u @mbl
61 |              i mmid
62 |              o mrgt
63 | 
64 |              mvmt @mhld
65 |              ___ @_
66 | )
67 | 


--------------------------------------------------------------------------------
/cfg_samples/chords.tsv:
--------------------------------------------------------------------------------
  1 | rus	rust
  2 | col	cool
  3 | nice	nice
  4 | you	you
  5 | th	the
  6 |  a	a
  7 |  an	an
  8 | man	man
  9 | name	name
 10 | an	and
 11 | as	as
 12 | or	or
 13 | bu	but
 14 | if	if
 15 | so	so
 16 | dn	then
 17 | bc	because
 18 | 
 19 | to	to
 20 | of	of
 21 | in	in
 22 |  f	for
 23 |  w	with
 24 | on	on
 25 | at	at
 26 | fm	from
 27 | by	by
 28 | abt	about
 29 | up	up
 30 | io	into
 31 | ov	over
 32 | af	after
 33 | wo	without
 34 |  i	I
 35 |  me	me
 36 |  my	my
 37 | ou	you
 38 | ur	your
 39 | he	he
 40 | hm	him
 41 | his	his
 42 | sh	she
 43 | hr	her
 44 | it	it
 45 | ts	its
 46 | we	we
 47 | us	us
 48 | our	our
 49 | dz	they
 50 | dr	their
 51 | dm	them
 52 | wc	which
 53 | wn	when
 54 | wt	what
 55 | wr	where
 56 | ho	who
 57 | hw	how
 58 | wz	why
 59 | is	is
 60 | ar	are
 61 | wa	was
 62 | er	were
 63 | be	be
 64 | hv	have
 65 | hs	has
 66 | hd	had
 67 | nt	not
 68 | cn	can
 69 | do	do
 70 | wl	will
 71 | cd	could
 72 | wd	would
 73 | sd	should
 74 | li	like
 75 | bn	been
 76 | ge	get
 77 | maz	may
 78 | mad	made
 79 | mk	make
 80 | ai	said
 81 | wk	work
 82 | uz	use
 83 | sz	say
 84 |  g	go
 85 | kn	know
 86 | tk	take
 87 |  se	see
 88 | lk	look
 89 | cm	come
 90 | thk	think
 91 | wnt	want
 92 | gi	give
 93 | ct	cannot
 94 | de	does
 95 | di	did
 96 | sem	seem
 97 | cl	call
 98 | tha	thank
 99 | 
100 |  im	I'm
101 |  id	I'd
102 | dt	that
103 | dis	this
104 | des	these
105 | tes	test
106 | al	all
107 |  o	one
108 | mo	more
109 | the	there
110 | out	out
111 | ao	also
112 | tm	time
113 | sm	some
114 | js	just
115 | ne	new
116 | odr	other
117 | pl	people
118 |  n	no
119 | dan	than
120 | oz	only
121 |  m	most
122 | ay	any
123 | may	many
124 | el	well
125 | fs	first
126 | vy	very
127 | much	much
128 | now	now
129 | ev	even
130 | go	good
131 | grt	great
132 | way	way
133 |  t	two
134 | yr	year
135 | bk	back
136 | day	day
137 | qn	question
138 | sc	second
139 | dg	thing
140 |  y	yes
141 | cn'	can't
142 | dif	different
143 | dgh	though
144 | tru	through
145 | sr	sorry
146 | mv	move
147 | dir	dir
148 | stop	stop
149 | tye	type
150 | nx	next
151 | sam	same
152 | tp	top
153 | cod	code
154 | git	git
155 |  to	TODO
156 | cls	class
157 | clus	cluster
158 | sure	sure
159 | lets	let's
160 | sup	super
161 | such	such
162 | thig	thing
163 | yet	yet
164 | don	done
165 | sem	seem
166 | ran	ran
167 | job	job
168 | bot	bot
169 | fx	effect
170 | nce	once
171 | rad	read
172 | ltr	later
173 | lot	lot
174 | brw	brew
175 | unst	uninstall
176 | rmv	remove
177 |  ad	add
178 | poe	problem
179 | buld	build
180 |  tol	tool
181 | got	got
182 | les	less
183 |  0	zero
184 |  1	one
185 |  2	two
186 |  3	three
187 |  4	four
188 |  5	five
189 |  6	six
190 |  7	seven
191 |  8	eight
192 |  9	nine
193 | 


--------------------------------------------------------------------------------
/cfg_samples/colemak.kbd:
--------------------------------------------------------------------------------
 1 | ;;
 2 | ;;  Learn Colemak, a few keys at a time.
 3 | ;;
 4 | ;;  The "j" key moves around the keyboard each step,
 5 | ;;  until you reach the full Colemak layout.
 6 | ;;
 7 | ;;  To select the layout for your current step, press the
 8 | ;;  letter "m" and the number of your current step, as a chord.
 9 | ;;
10 | ;;  Check out:  https://dreymar.colemak.org/tarmak-intro.html
11 | ;;        and:  https://colemak.com
12 | ;;
13 | 
14 | (defsrc
15 |   q w e r t y u i o p
16 |   a s d f g h j k l ;
17 |   z x c v b n m
18 | )
19 | 
20 | (deflayer colemak_j1
21 |   _ _ j _ _ _ _ _ _ _
22 |   _ _ _ _ _ _ n e _ _
23 |   _ _ _ _ _ k _
24 | )
25 | 
26 | (deflayer colemak_j2
27 |   _ _ f _ g _ _ _ _ _
28 |   _ _ _ t j _ n e _ _
29 |   _ _ _ _ _ k _
30 | )
31 | 
32 | (deflayer colemak_j3
33 |   _ _ f j g _ _ _ _ _
34 |   _ r s t d _ n e _ _
35 |   _ _ _ _ _ k _
36 | )
37 | 
38 | (deflayer colemak_j4
39 |   _ _ f p g j _ _ y ;
40 |   _ r s t d _ n e _ o
41 |   _ _ _ _ _ k _
42 | )
43 | 
44 | (deflayer colemak
45 |   _ _ f p g j l u y ;
46 |   _ r s t d _ n e i o
47 |   _ _ _ _ _ k _
48 | )
49 | 
50 | (defcfg
51 |   process-unmapped-keys   yes
52 |   concurrent-tap-hold     yes
53 |   allow-hardware-repeat   no
54 | )
55 | 
56 | (defchordsv2
57 |   (m 1) (layer-switch colemak_j1) 300 all-released ()
58 |   (m 2) (layer-switch colemak_j2) 300 all-released ()
59 |   (m 3) (layer-switch colemak_j3) 300 all-released ()
60 |   (m 4) (layer-switch colemak_j4) 300 all-released ()
61 |   (m 5) (layer-switch colemak) 300 all-released ()
62 | )
63 | 
64 | 


--------------------------------------------------------------------------------
/cfg_samples/deflayermap.kbd:
--------------------------------------------------------------------------------
 1 | ;; A configuration showcasing deflayermap.
 2 | ;;
 3 | ;; The process-unmapped-keys defcfg item is not used
 4 | ;; and the lctl and ralt keys are unmapped
 5 | ;; because mapping them can cause problems on Windows
 6 | ;; with non-US layouts.
 7 | 
 8 | (defsrc
 9 |   grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc
10 |   tab  q    w    e    r    t    y    u    i    o    p    [    ]    \
11 |   caps a    s    d    f    g    h    j    k    l    ;    '    ret
12 |   lsft z    x    c    v    b    n    m    ,    .    /    rsft
13 |        lmet lalt           spc                 rmet rctl
14 | )
15 | 
16 | (deflayermap (base)
17 |   caps (tap-hold 200 200 (caps-word 2000) lctl)
18 |   spc  (tap-hold 200 200 spc (layer-while-held nav))
19 | )
20 | 
21 | (deflayermap (nav)
22 |   i up
23 |   j left
24 |   k down
25 |   l right
26 | )
27 | 


--------------------------------------------------------------------------------
/cfg_samples/f13_f24.kbd:
--------------------------------------------------------------------------------
 1 | (defcfg
 2 |   linux-dev /dev/input/by-path/platform-i8042-serio-0-event-kbd
 3 | )
 4 | 
 5 | (defsrc
 6 |        f13  f14  f15  f16  f17  f18  f19  f20  f21  f22  f23  f24
 7 | )
 8 | 
 9 | (deflayer test
10 |        f13  f14  f15  f16  f17  f18  f19  f20  f21  f22  f23  f24
11 | )
12 | 
13 | 


--------------------------------------------------------------------------------
/cfg_samples/fancy_symbols.kbd:
--------------------------------------------------------------------------------
 1 | ;; Turns   ⎇›            RightAlt into a symbol key to insert valid kanata unicode symbols for the pressed key
 2 | ;; Turns ⇧›⎇› RightShift+RightAlt into a symbol key to insert extra symbols for the same keys
 3 | ;; e.g., ⎇›Delete will print ␡
 4 | (defcfg)
 5 | (defalias
 6 |    🔣 (layer-while-held fancy-symbol)
 7 |   ⇧🔣 (layer-while-held ⇧fancy-symbol))
 8 | (defsrc
 9 |   ‹🖰	🖰›	🖰3	🖰4	🖰5
10 |   ▶⏸	◀◀	▶▶	🔇 	🔉	🔊	🔅	🔆	🎛	⌨💡+	⌨💡−
11 |   ⎋
12 |   ˋ 	  	1 	2	3	4	5	6	7	8 	9	0	- 	=	␈	⎀	⇤	⇞	⇭ 	🔢⁄	🔢∗	🔢₋
13 |   ⭾ 	  	q 	w	e	r	t	y	u	i 	o	p	[ 	]	\	␡	⇥	⇟	🔢₇	🔢₈	🔢₉	🔢₊
14 |   ⇪ 	  	a 	s	d	f	g	h	j	k 	l	;	' 	⏎	 	 	 	 	🔢₄	🔢₅	🔢₆
15 |   ‹⇧	  	z 	x	c	v	b	n	m	, 	.	/	⇧›	 	▲	 	 	 	🔢₁	🔢₂	🔢₃	🔢⏎
16 |   ‹⎈	‹◆	‹⎇	␠	 	 	 	 	 	⎇›	 	☰	⎈›	◀	▼	▶	 	 	🔢₀	🔢⸴           )
17 | (deflayer qwerty ;; =base with ⎇› as a fancy symbol key
18 |   ‗	‗	‗	‗	‗
19 |   ‗	‗	‗	‗	‗	‗	‗	‗	‗	‗	‗
20 |   ‗
21 |   ‗	‗	‗	‗	‗	‗	‗	‗	‗	‗ 	‗	‗	‗	‗	‗	‗	‗	‗	‗	‗	‗
22 |   ‗	‗	‗	‗	‗	‗	‗	‗	‗	‗ 	‗	‗	‗	‗	‗	‗	‗	‗	‗	‗	‗
23 |   ‗	‗	‗	‗	‗	‗	‗	‗	‗	‗ 	‗	‗	‗	 	 	 	 	‗	‗	‗
24 |   ‗	‗	‗	‗	‗	‗	‗	‗	‗	‗ 	‗	‗	 	‗	 	 	 	‗	‗	‗	‗
25 |   ‗	‗	‗	‗	 	 	 	 	 	@🔣	 	‗	‗	‗	‗	‗	 	‗	‗           )
26 | (deflayer  fancy-symbol ;; •block all other keys
27 |   🔣‹🖰	🔣🖰›	🔣🖰3	🔣🖰4	🔣🖰5
28 |   🔣▶⏸	🔣◀◀	🔣▶▶	🔣🔇 	🔣🔉	🔣🔊	🔣🔅	🔣🔆	🔣🎛	🔣⌨💡+	🔣⌨💡−
29 |   🔣⎋
30 |   🔣ˋ	  	• 	• 	•	•	•	•	•	• 	• 	• 	🔣‐ 	🔣₌	🔣␈	🔣⎀	🔣⇤	🔣⇞	🔣⇭   	🔣🔢⁄	🔣🔢∗	🔣🔢₋
31 |   🔣⭾	  	• 	• 	•	•	•	•	•	• 	• 	• 	🔣【 	🔣】	🔣⧵	🔣␡	🔣⇥	🔣⇟	🔣🔢₇  	🔣🔢₈	🔣🔢₉	🔣🔢₊
32 |   🔣⇪	  	• 	• 	•	•	•	•	•	• 	• 	🔣︔	'  	🔣⏎	  	  	  	  	  🔣🔢₄	🔣🔢₅	🔣🔢₆
33 |   🔣⇧	  	• 	• 	•	•	•	•	•	🔣⸴	🔣.	🔣⁄	@⇧🔣	  	🔣▲	  	  	  	  🔣🔢₁	🔣🔢₂	🔣🔢₃	🔣🔢⏎
34 |   🔣⎈	🔣◆	🔣⎇	🔣␠	 	 	 	 	 	• 	  	🔣☰	•  	🔣◀	🔣▼	🔣▶	  	  	  🔣🔢₀	🔣🔢⸴  )
35 | (deflayer ⇧fancy-symbol ;; •block all other keys
36 |   🔣🖰1	🔣🖰2	•	•    	•
37 |   •  	•  	•	🔣🔈⓪⓿₀	•	🔣🔈−➖₋⊖	🔣🔈+➕₊⊕	•	•	🔣⌨💡➕₊⊕	🔣⌨💡➖₋⊖
38 |   •
39 |   🔣˜	   	• 	• 	•	•	•	•	•	•  	•	•	-   	=   	🔣⌫	• 	🔣⤒↖	🔣🔢	•	•	•	•
40 |   🔣↹	   	• 	• 	•	•	•	•	•	•  	•	•	🔣「〔⎡	🔣」〕⎣	🔣\	🔣⌦	🔣⤓↘	• 	•	•	•	•
41 |   • 	   	• 	• 	•	•	•	•	•	•  	•	•	•   	🔣↩⌤␤	  	  	   	  	 	•	•	•
42 |   • 	   	• 	• 	•	•	•	•	•	•  	•	/	•   	    	• 	  	   	  	 	•	•	•	🔣🔢↩⌤␤
43 |   🔣⌃	🔣❖⌘	🔣⌥	🔣␣	 	 	 	 	 	🔣▤𝌆	 	•	•   	•   	• 	• 	   	  	•	•   )
44 | 


--------------------------------------------------------------------------------
/cfg_samples/home-row-mod-advanced.kbd:
--------------------------------------------------------------------------------
 1 | ;; Home row mods QWERTY example with more complexity.
 2 | ;; Some of the changes from the basic example:
 3 | ;; - when a home row mod activates tap, the home row mods are disabled
 4 | ;;   while continuing to type rapidly
 5 | ;; - tap-hold-release helps make the hold action more responsive
 6 | ;; - pressing another key on the same half of the keyboard
 7 | ;;   as the home row mod will activate an early tap action
 8 | 
 9 | (defcfg
10 |   process-unmapped-keys yes
11 | )
12 | (defsrc
13 |   a   s   d   f   j   k   l   ;
14 | )
15 | (defvar
16 |   ;; Note: consider using different time values for your different fingers.
17 |   ;; For example, your pinkies might be slower to release keys and index
18 |   ;; fingers faster.
19 |   tap-time 200
20 |   hold-time 150
21 | 
22 |   left-hand-keys (
23 |     q w e r t
24 |     a s d f g
25 |     z x c v b
26 |   )
27 |   right-hand-keys (
28 |     y u i o p
29 |     h j k l ;
30 |     n m , . /
31 |   )
32 | )
33 | (deflayer base
34 |   @a  @s  @d  @f  @j  @k  @l  @;
35 | )
36 | 
37 | (deflayer nomods
38 |   a   s   d   f   j   k   l   ;
39 | )
40 | (deffakekeys
41 |   to-base (layer-switch base)
42 | )
43 | (defalias
44 |   tap (multi
45 |     (layer-switch nomods)
46 |     (on-idle-fakekey to-base tap 20)
47 |   )
48 | 
49 |   a (tap-hold-release-keys $tap-time $hold-time (multi a @tap) lmet $left-hand-keys)
50 |   s (tap-hold-release-keys $tap-time $hold-time (multi s @tap) lalt $left-hand-keys)
51 |   d (tap-hold-release-keys $tap-time $hold-time (multi d @tap) lctl $left-hand-keys)
52 |   f (tap-hold-release-keys $tap-time $hold-time (multi f @tap) lsft $left-hand-keys)
53 |   j (tap-hold-release-keys $tap-time $hold-time (multi j @tap) rsft $right-hand-keys)
54 |   k (tap-hold-release-keys $tap-time $hold-time (multi k @tap) rctl $right-hand-keys)
55 |   l (tap-hold-release-keys $tap-time $hold-time (multi l @tap) ralt $right-hand-keys)
56 |   ; (tap-hold-release-keys $tap-time $hold-time (multi ; @tap) rmet $right-hand-keys)
57 | )


--------------------------------------------------------------------------------
/cfg_samples/home-row-mod-basic.kbd:
--------------------------------------------------------------------------------
 1 | ;; Basic home row mods example using QWERTY
 2 | ;; For a more complex but perhaps usable configuration,
 3 | ;; see home-row-mod-advanced.kbd
 4 | 
 5 | (defcfg
 6 |   process-unmapped-keys yes
 7 | )
 8 | (defsrc
 9 |   a   s   d   f   j   k   l   ;
10 | )
11 | (defvar
12 |   ;; Note: consider using different time values for your different fingers.
13 |   ;; For example, your pinkies might be slower to release keys and index
14 |   ;; fingers faster.
15 |   tap-time 200
16 |   hold-time 150
17 | )
18 | (defalias
19 |   a (tap-hold $tap-time $hold-time a lmet)
20 |   s (tap-hold $tap-time $hold-time s lalt)
21 |   d (tap-hold $tap-time $hold-time d lctl)
22 |   f (tap-hold $tap-time $hold-time f lsft)
23 |   j (tap-hold $tap-time $hold-time j rsft)
24 |   k (tap-hold $tap-time $hold-time k rctl)
25 |   l (tap-hold $tap-time $hold-time l ralt)
26 |   ; (tap-hold $tap-time $hold-time ; rmet)
27 | )
28 | (deflayer base
29 |   @a  @s  @d  @f  @j  @k  @l  @;
30 | )


--------------------------------------------------------------------------------
/cfg_samples/included-file.kbd:
--------------------------------------------------------------------------------
1 | (defalias
2 |   included-alias (macro i spc a m spc i n c l u d e d)
3 | )
4 | 


--------------------------------------------------------------------------------
/cfg_samples/japanese_mac_eisu_kana.kbd:
--------------------------------------------------------------------------------
 1 | #|
 2 |   Using meta keys as japanese eisu and kana on Mac with US keyboard.
 3 | 
 4 |   | Source  | Tap          | Hold |
 5 |   | ------- | ------------ | ---- |
 6 |   | lmet    | lang2 (eisu) | lmet |
 7 |   | rmet    | lang1 (kana) | rmet |
 8 | 
 9 | |#
10 | 
11 | (defcfg
12 |   process-unmapped-keys yes
13 | )
14 | 
15 | (defsrc
16 |   lmet  rmet
17 | )
18 | 
19 | (deflayer default
20 |   @lmet @rmet
21 | )
22 | 
23 | (defalias
24 |   lmet (tap-hold-press 200 200 eisu lmet)
25 |   rmet (tap-hold-press 200 200 kana rmet)
26 | )
27 | 
28 | 


--------------------------------------------------------------------------------
/cfg_samples/key-toggle_press-only_release-only.kbd:
--------------------------------------------------------------------------------
 1 | #|
 2 | 
 3 | This configuration showcases all of:
 4 | 	- key toggle
 5 | 	- press-only
 6 | 	- release-only
 7 | 
 8 | |#
 9 | 
10 | (deftemplate toggle-key (vkey-name output-key alias)
11 | 	(defvirtualkeys $vkey-name $output-key)
12 | 	(defalias $alias (on-press toggle-vkey $vkey-name))
13 | )
14 | 
15 | (deftemplate press-only-release-only-pair
16 | 		(vkey-name output-key press-alias release-alias)
17 | 	(defvirtualkeys $vkey-name $output-key)
18 | 	(defalias $press-alias (on-press press-vkey $vkey-name))
19 | 	(defalias $release-alias (on-press release-vkey $vkey-name))
20 | )
21 | 
22 | (template-expand toggle-key v-lctl lctl lcl)
23 | (template-expand toggle-key v-rctl rctl rcl)
24 | 
25 | ;; t! is a short form of template-expand
26 | (t! press-only-release-only-pair v-lalt lalt p-a r-a)
27 | 
28 | (defsrc
29 | 	lctl rctl lalt ralt
30 | )
31 | 
32 | (deflayer base
33 | 	@lcl @rcl @p-a @r-a
34 | )
35 | 


--------------------------------------------------------------------------------
/cfg_samples/minimal.kbd:
--------------------------------------------------------------------------------
 1 | #|
 2 | This minimal config changes Caps Lock to act as Caps Lock on quick tap, but
 3 | if held, it will act as Left Ctrl. It also changes the backtick/grave key to
 4 | act as backtick/grave on quick tap, but change ijkl keys to arrow keys on hold.
 5 | 
 6 | This text between the two pipe+octothorpe sequences is a multi-line comment.
 7 | |#
 8 | 
 9 | ;; Text after double-semicolons are single-line comments.
10 | 
11 | #|
12 | One defcfg entry may be added, which is used for configuration key-pairs. These
13 | configurations change kanata's behaviour at a more global level than the other
14 | configuration entries.
15 | |#
16 | 
17 | (defcfg
18 |   #|
19 |   This configuration will process all keys pressed inside of kanata, even if
20 |   they are not mapped in defsrc. This is so that certain actions can activate
21 |   at the right time for certain input sequences. By default, unmapped keys are
22 |   not processed through kanata due to a Windows issue related to AltGr. If you
23 |   use AltGr in your keyboard, you will likely want to follow the simple.kbd
24 |   file while unmapping lctl and ralt from defsrc.
25 |   |#
26 |   process-unmapped-keys yes
27 | )
28 | 
29 | (defsrc
30 |   caps grv         i
31 |               j    k    l
32 |   lsft rsft
33 | )
34 | 
35 | (deflayer default
36 |   @cap @grv        _
37 |               _    _    _
38 |   _    _
39 | )
40 | 
41 | (deflayer arrows
42 |   _    _           up
43 |               left down rght
44 |   _    _
45 | )
46 | 
47 | (defalias
48 |   cap (tap-hold-press 200 200 caps lctl)
49 |   grv (tap-hold-press 200 200 grv (layer-toggle arrows))
50 | )
51 | 


--------------------------------------------------------------------------------
/cfg_samples/simple.kbd:
--------------------------------------------------------------------------------
 1 | ;; Comments are prefixed by double-semicolon. A single semicolon is parsed as the
 2 | ;; keyboard key. Comments are ignored for the configuration file.
 3 | ;;
 4 | ;; This configuration language is Lisp-like. If you're unfamiliar with Lisp,
 5 | ;; don't be alarmed. The maintainer jtroo is also unfamiliar with Lisp. You
 6 | ;; don't need to know Lisp in-depth to be able to configure kanata.
 7 | ;;
 8 | ;; If you follow along with the examples, you should be fine. Kanata should
 9 | ;; also hopefully have helpful error messages in case something goes wrong.
10 | ;; If you need help, you are welcome to ask.
11 | 
12 | ;; Only one defsrc is allowed.
13 | ;;
14 | ;; defsrc defines the keys that will be intercepted by kanata. The order of the
15 | ;; keys matches with deflayer declarations and all deflayer declarations must
16 | ;; have the same number of keys as defsrc. Any keys not listed in defsrc will
17 | ;; be passed straight to the operating system.
18 | (defsrc
19 |   grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc
20 |   tab  q    w    e    r    t    y    u    i    o    p    [    ]    \
21 |   caps a    s    d    f    g    h    j    k    l    ;    '    ret
22 |   lsft z    x    c    v    b    n    m    ,    .    /    rsft
23 |   lctl lmet lalt           spc            ralt rmet rctl
24 | )
25 | 
26 | ;; The first layer defined is the layer that will be active by default when
27 | ;; kanata starts up. This layer is the standard QWERTY layout except for the
28 | ;; backtick/grave key (@grl) which is an alias for a tap-hold key.
29 | (deflayer qwerty
30 |   @grl 1    2    3    4    5    6    7    8    9    0    -    =    bspc
31 |   tab  q    w    e    r    t    y    u    i    o    p    [    ]    \
32 |   caps a    s    d    f    g    h    j    k    l    ;    '    ret
33 |   lsft z    x    c    v    b    n    m    ,    .    /    rsft
34 |   lctl lmet lalt           spc            ralt rmet rctl
35 | )
36 | 
37 | ;; The dvorak layer remaps the keys to the dvorak layout. In addition there is
38 | ;; another tap-hold key: @cap. This key retains caps lock functionality when
39 | ;; quickly tapped but is read as left-control when held.
40 | (deflayer dvorak
41 |   @grl 1    2    3    4    5    6    7    8    9    0    [    ]    bspc
42 |   tab  '    ,    .    p    y    f    g    c    r    l    /    =    \
43 |   @cap a    o    e    u    i    d    h    t    n    s    -    ret
44 |   lsft ;    q    j    k    x    b    m    w    v    z    rsft
45 |   lctl lmet lalt           spc            ralt rmet rctl
46 | )
47 | 
48 | ;; defalias is used to declare a shortcut for a more complicated action to keep
49 | ;; the deflayer declarations clean and aligned. The alignment in deflayers is not
50 | ;; necessary, but is strongly recommended for ease of understanding visually.
51 | ;;
52 | ;; Aliases are referred to by `@<alias_name>`.
53 | (defalias
54 |   ;; tap: backtick (grave), hold: toggle layer-switching layer while held
55 |   grl (tap-hold 200 200 grv (layer-toggle layers))
56 | 
57 |   ;; layer-switch changes the base layer.
58 |   dvk (layer-switch dvorak)
59 |   qwr (layer-switch qwerty)
60 | 
61 |   ;; tap for capslk, hold for lctl
62 |   cap (tap-hold 200 200 caps lctl)
63 | )
64 | 
65 | ;; The `lrld` action stands for "live reload". This will re-parse everything
66 | ;; except for linux-dev, meaning you cannot live reload and switch keyboard
67 | ;; devices.
68 | ;;
69 | ;; The keys 1 and 2 switch the base layer to qwerty and dvorak respectively.
70 | ;;
71 | ;; Apart from the layer switching and live reload, all other keys are the
72 | ;; underscore _ which means "transparent". Transparent means the base layer
73 | ;; behaviour is used when pressing that key.
74 | (deflayer layers
75 |   _    @qwr @dvk lrld _    _    _    _    _    _    _    _    _    _
76 |   _    _    _    _    _    _    _    _    _    _    _    _    _    _
77 |   _    _    _    _    _    _    _    _    _    _    _    _    _
78 |   _    _    _    _    _    _    _    _    _    _    _    _
79 |   _    _    _              _              _    _    _
80 | )
81 | 


--------------------------------------------------------------------------------
/cfg_samples/tray-icon/3trans.parent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtroo/kanata/dd02c3e6bd6305d4e4593de492ad2dec5abe3684/cfg_samples/tray-icon/3trans.parent.png


--------------------------------------------------------------------------------
/cfg_samples/tray-icon/6name-match.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtroo/kanata/dd02c3e6bd6305d4e4593de492ad2dec5abe3684/cfg_samples/tray-icon/6name-match.png


--------------------------------------------------------------------------------
/cfg_samples/tray-icon/_custom-icons/s.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtroo/kanata/dd02c3e6bd6305d4e4593de492ad2dec5abe3684/cfg_samples/tray-icon/_custom-icons/s.png


--------------------------------------------------------------------------------
/cfg_samples/tray-icon/icons/1symbols.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtroo/kanata/dd02c3e6bd6305d4e4593de492ad2dec5abe3684/cfg_samples/tray-icon/icons/1symbols.png


--------------------------------------------------------------------------------
/cfg_samples/tray-icon/img/2Nav Num.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtroo/kanata/dd02c3e6bd6305d4e4593de492ad2dec5abe3684/cfg_samples/tray-icon/img/2Nav Num.png


--------------------------------------------------------------------------------
/cfg_samples/tray-icon/license_icons.txt:
--------------------------------------------------------------------------------
 1 | BSD 2-Clause License
 2 | 
 3 | Copyright (c) 2024, Fred Vatin
 4 | 
 5 | Redistribution and use in source and binary forms, with or without
 6 | modification, are permitted provided that the following conditions are met:
 7 | 
 8 | 1. Redistributions of source code must retain the above copyright notice, this
 9 |    list of conditions and the following disclaimer.
10 | 
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 |    this list of conditions and the following disclaimer in the documentation
13 |    and/or other materials provided with the distribution.
14 | 
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | 


--------------------------------------------------------------------------------
/cfg_samples/tray-icon/tray-icon.kbd:
--------------------------------------------------------------------------------
 1 | (defcfg
 2 |   process-unmapped-keys		yes	;;|no| enable processing of keys that are not in defsrc, useful if mapping a few keys in defsrc instead of most of the keys on your keyboard. Without this, the tap-hold-release and tap-hold-press actions will not activate for keys that are not in defsrc. Disabled because some keys may not work correctly if they are intercepted. E.g. rctl/altgr on Windows; see the windows-altgr configuration item above for context.
 3 |   log-layer-changes    		yes	;;|no| overhead
 4 |   tray-icon "./_custom-icons/s.png" ;; should activate for layers without icons like '5no-icn'
 5 |   ;;opt                   	val  	  |≝|
 6 |   icon-match-layer-name   	yes  	;;|yes| match layer name to icon files even without an explicit (icon name.ico) config
 7 |   tooltip-layer-changes   	yes  	;;|false|
 8 |   tooltip-show-blank      	yes  	;;|no|
 9 |   tooltip-duration        	500  	;;|500|
10 |   tooltip-size            	24,24	;;|24 24|
11 |   notify-cfg-reload       	yes  	;;|yes|
12 |   notify-cfg-reload-silent	no   	;;|no|
13 |   notify-error            	yes  	;;|yes|
14 | )
15 | (defalias l1 (layer-while-held 1emoji))
16 | (defalias l2 (layer-while-held 2icon-quote))
17 | (defalias l3 (layer-while-held 3emoji_alt))
18 | (defalias l4 (layer-while-held 4my-lmap))
19 | (defalias l5 (layer-while-held 5no-icn))
20 | (defalias l6 (layer-while-held 6name-match))
21 | 
22 | (defsrc     	            	                            	1  	2  	3  	4  	5  	6)
23 | (deflayer   	(⌂          	icon base.png)              	@l1	@l2	@l3	@l4	@l5	@l6)	;; find in the 'icon'  subfolder
24 | (deflayer   	(1emoji     	🖻 1symbols.png)             	q  	q  	q  	q  	q  	q)  	;; find in the 'icons' subfolder
25 | (deflayer   	(2icon-quote	🖻 "2Nav Num.png")           	w  	w  	w  	w  	w  	w)  	;; find in the 'img'   subfolder
26 | (deflayer   	(3emoji_alt 	🖼 3trans.parent)            	e  	e  	e  	e  	e  	e)  	;; find '.png'
27 | (deflayermap	(4my-lmap   	🖻 "..\..\assets\kanata.ico")	1 r	2 r	3 r	4 r	5 r	6 r) ;; find in relative path
28 | (deflayer   	5no-icn     	                            	t  	t  	t  	t  	t  	t) ;; match file name from 'tray-icon' config, whithout which would fall back to 'tray-icon.png' as it's the only valid icon matching 'tray-icon.kbd' name
29 | (deflayer   	6name-match 	                            	y  	y  	y  	y  	y  	y) ;; uses '6name-match' with any valid extension since 'icon-match-layer-name' is set to 'yes'
30 | 


--------------------------------------------------------------------------------
/cfg_samples/tray-icon/tray-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtroo/kanata/dd02c3e6bd6305d4e4593de492ad2dec5abe3684/cfg_samples/tray-icon/tray-icon.png


--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | 
2 | ### Converting ".adoc" to html
3 | 
4 | To generate html from the these documentation files, use ["asciidoctor"](https://asciidoctor.org)
5 | (they are not fully compatible with the separate "asciidoc" project)
6 | 
7 | 


--------------------------------------------------------------------------------
/docs/design.md:
--------------------------------------------------------------------------------
 1 | # Design doc
 2 | 
 3 | ## Obligatory diagram
 4 | 
 5 | <img src="./kanata-basic-diagram.svg">
 6 | 
 7 | ## main
 8 | 
 9 | - read args
10 | - read config
11 | - start event loops
12 | 
13 | ## event loop
14 | 
15 | - read key events
16 | - send events to processing loop on channel
17 | 
18 | ## processing loop
19 | 
20 | - check for events on mpsc
21 | - if event: send event to layout
22 | - tick() the keyberon layout, send any events needed
23 | - if no event: sleep for 1ms
24 | - separate monotonic time checks, because can't rely on sleep to be
25 |   fine-grained or accurate
26 | - send `ServerMessage`s to the TCP server
27 | 
28 | ## TCP server
29 | 
30 | - listen for `ClientMessage`s and act on them
31 | - recv `ServerMessage`s from processing loop and forward to all connected
32 |   clients
33 | 
34 | ## layout
35 | 
36 | - uses keyberon
37 | - indices of `kanata_keyberon::layout::Event::{Press, Release}(x,y)`:
38 | 
39 |       x = 0 or 1 (0 is for physical key presses, 1 is for fake keys)
40 |       y = OS code of key used as an index
41 | 
42 | ## OS-specific code
43 | 
44 | Most of the OS specific code is in `oskbd/` and `keys/`. There's a bit of it in
45 | `kanata/` since the event loops to receive OS events are different.
46 | 


--------------------------------------------------------------------------------
/docs/fancy_symbols.md:
--------------------------------------------------------------------------------
 1 | ### Supported key symbols
 2 | 
 3 |   |Symbol(s)[^1] 	|Key `name`                                	|
 4 |   |---------     	|--------                                  	|
 5 |   |‹x x›         	| Left/Right modifiers (e.g., ‹⎈ LCtrl)    	|
 6 |   |⇧             	| Shift                                    	|
 7 |   |⎈ ⌃           	| Control                                  	|
 8 |   |⌘ ◆ ❖         	| Windows/Command                          	|
 9 |   |⎇ ⌥           	| Alt                                      	|
10 |   |⇪             	| capslock                                 	|
11 |   |⎋             	|`escape`                                  	|
12 |   |⭾ ↹           	|`tab`                                     	|
13 |   |␠ ␣           	| `spc` spacebar                             	|
14 |   |␈ ⌫           	|`bspc` backspace (delete backward)         |
15 |   |␡ ⌦           	|`del` delete forward                      	|
16 |   |⏎ ↩ ⌤ ␤       	|`ret` return or enter                     	|
17 |   |︔ ⸴ .⁄        	|semicolon `;` / comma `,` / period `.` / slash `/`	|
18 |   |⧵ \           	| backslash `\`                            	|
19 |   |﹨ <           	|`non_us_backslash`                        	|
20 |   |【 「 〔 ⎡       	|`open_bracket`                            	|
21 |   |】 」 〕 ⎣       	|`close_bracket`                           	|
22 |   |ˋ ˜           	|`grave_accent_and_tilde`                  	|
23 |   |‐ ₌           	|`hyphen` `equal_sign`                     	|
24 |   |▲ ▼ ◀ ▶       	|`up`/`down`/`left`/`right` (arrows)        |
25 |   |⇞ ⇟           	|`pgup`/`pgdn` (page up, page down)	      |
26 |   |⎀             	|`insert`                               	|
27 |   |⇤ ⤒ ↖         	|`home`                                 	|
28 |   |⇥ ⤓ ↘         	|`end`                                  	|
29 |   |⇭             	|`numlock`                              	|
30 |   |🔢₁ 🔢₂ 🔢₃ 🔢₄ 🔢₅	|`keypad_` `1`–`5`                      	|
31 |   |🔢₆ 🔢₇ 🔢₈ 🔢₉ 🔢₀	|`keypad_` `6`–`0`                      	|
32 |   |🔢₋ 🔢₌ 🔢₊      	|`keypad_` `hyphen`/`equal_sign`/`plus` 	|
33 |   |🔢⁄ 🔢.🔢∗       	|`keypad_` `slash`/`period`/`asterisk`  	|
34 |   |◀◀ ▶⏸ ▶▶      	|`vk_consumer_` `previous`/`play`/`next`	|
35 |   |🔊 🔈+ or ➕₊⊕   	|`volume_up`                            	|
36 |   |🔉 🔈− or ➖₋⊖   	|`volume_down`                          	|
37 |   |🔇 🔈⓪ or ⓿ ₀   	|`mute`                                 	|
38 |   |🔆 🔅           	|`vk_consumer_brightness_` `up`/`down`  	|
39 |   |⌨💡+ or ➕₊⊕    	|`vk_consumer_illumination_up`          	|
40 |   |⌨💡− or ➖₋⊖    	|`vk_consumer_illumination_down`        	|
41 |   |🎛             	|`vk_dashboard`                         	|
42 |   |▤ ☰ 𝌆         	|`application`                          	|
43 |   |🖰1 🖰2 ... 🖰5  	|`button` `1`–`5`                       	|
44 |   |‹🖰 🖰›         	|`button` `1` `2`                       	|
45 | 
46 | [^1]: space-separated list of keys; `or` means only last symbol in a pair changes
47 | 


--------------------------------------------------------------------------------
/docs/interception.md:
--------------------------------------------------------------------------------
 1 | # Windows Interception driver implementation notes
 2 | 
 3 | - Interception handle is `!Send` and `!Sync`
 4 |   - means a single thread should own both input and output
 5 |   - `KbdOut` will need to send keyboard output events to that thread as opposed
 6 |     to Linux using `uinput` and the original Windows code using `SendInput`
 7 |     which are independent of the input devices.
 8 |   - Maybe save channel in kanata struct as part of new kanata
 9 | - Interception can filter for only keyboard events
10 |   - should use this filter feature; don't want to intercept mouse
11 | - Need to save previous device for sending to, in case wait/receive (with
12 |   timeout) don't return anything so that sending stuff can be sent to some
13 |   device.
14 | - Input `ScanCode` maps to the keyberon `KeyCode`; they both use the USB
15 |   standard codes.
16 |   - For ease of integration will probably need to unfortunately convert it to
17 |     an `OsCode` even though the processing loop will soon after just convert it
18 |     back to `KeyCode`. Oh well.
19 | 


--------------------------------------------------------------------------------
/docs/kmonad_comparison.md:
--------------------------------------------------------------------------------
 1 | # Comparison with kmonad
 2 | 
 3 | The kmonad project is the closest alternative for this project.
 4 | 
 5 | ## Benefits of kmonad over kanata
 6 | 
 7 | - ~MacOS support~ (this is implemented now)
 8 | - Different features
 9 | 
10 | ## Why I built and use kanata
11 | 
12 | - [Double-tapping a tap-hold key](https://github.com/kmonad/kmonad/issues/163) did not behave
13 |   [how I want it to](https://docs.qmk.fm/#/tap_hold?id=tapping-force-hold)
14 | - Some key sequences with tap-hold keys [didn't behave how I want](https://github.com/kmonad/kmonad/issues/466):
15 |   - `(press lsft) (press a) (release lsft) (release a)` (a is a tap-hold key)
16 |   - The above outputs `a` in kmonad, but I want it to output `A`
17 | - kmonad was missing [mouse buttons](https://github.com/kmonad/kmonad/issues/150)
18 | 
19 | The issues listed are all fixable in kmonad and I hope they are one day! For me
20 | though, I didn't and still don't know Haskell well enough to contribute to
21 | kmonad. That's why I instead built kanata based off of the excellent work that
22 | had already gone into the
23 | [keyberon](https://github.com/TeXitoi/keyberon),
24 | [ktrl](https://github.com/ItayGarin/ktrl), and
25 | [kbremap](https://github.com/timokroeger/kbremap) projects.
26 | 
27 | If you want to see the features that kanata offers, the
28 | [configuration guide](./config.adoc) is a good starting point.
29 | 
30 | I dogfood kanata myself and it works great for my use cases. Though kanata is a
31 | younger project than kmonad, it now has more features. If you give kanata a
32 | try, feel free to ask for help in an issue or discussion, or let me know how it
33 | went 🙂.
34 | 


--------------------------------------------------------------------------------
/docs/platform-known-issues.adoc:
--------------------------------------------------------------------------------
 1 | = Hardware known issues
 2 | 
 3 | At the electric circuit layer of many keyboards,
 4 | cost-saving measures can lead to key presses not registering
 5 | when pressing multiple keys simultaneously.
 6 | Usually this happens with at least 3 key presses.
 7 | Kanata cannot fix this issue.
 8 | You can work around it by avoiding
 9 | the problem key combination,
10 | or using a different keyboard.
11 | 
12 | = Platform-dependent known issues
13 | 
14 | == Preface
15 | 
16 | This document contains a list of known issues
17 | which are unique to a given platform.
18 | The platform supported by the core maintainer (jtroo)
19 | are Windows 11 and Linux.
20 | Windows 10 is expected to work fine,
21 | but as Windows 10 end-of-support is approaching in 2025,
22 | jtroo no longer has any devices with it installed.
23 | 
24 | On Windows, there are two backing mechanisms that can be used
25 | for keyboard input/output and they have different issues.
26 | These will be differentiated by the words "LLHOOK" and "Interception",
27 | which map to the binaries
28 | `kanata.exe` and `kanata_wintercept.exe` respectively.
29 | 
30 | == Windows 11 LLHOOK
31 | 
32 | * Mouse inputs cannot be used for processing or remapping
33 | ** https://github.com/jtroo/kanata/issues/108
34 | ** https://github.com/jtroo/kanata/issues/170
35 | * Some input key combinations (e.g. Win+L) cannot be intercepted before
36 |   running their default action
37 | ** https://github.com/jtroo/kanata/issues/192
38 | ** https://github.com/jtroo/kanata/discussions/428
39 | * OS-level key remapping behaves differently vs. Linux or Interception
40 | ** Does not affect winiov2 variant
41 | ** https://github.com/jtroo/kanata/issues/152
42 | * Certain applications that also use the LLHOOK mechanism may not behave correctly
43 | ** https://github.com/jtroo/kanata/issues/55
44 | ** https://github.com/jtroo/kanata/issues/250
45 | ** https://github.com/jtroo/kanata/issues/430
46 | ** https://github.com/espanso/espanso/issues/1488
47 | * AltGr / ralt / Right Alt can misbehave
48 | ** https://github.com/jtroo/kanata/blob/main/docs/config.adoc#windows-only-windows-altgr
49 | * NumLock state can mess with arrow keys in unexpected ways
50 | ** Does not affect winiov2 variant
51 | ** https://github.com/jtroo/kanata/issues/78
52 | ** https://github.com/jtroo/kanata/issues/667
53 | ** Workaround: use the correct https://github.com/jtroo/kanata/discussions/354[numlock state]
54 | * Without `process-unmapped-keys yes`, using arrow keys
55 | without also having the shift keys in `defsrc` will break shift highlighting
56 | ** Does not affect winiov2 variant
57 | ** https://github.com/jtroo/kanata/issues/858
58 | ** Workaround: add shift keys to `defsrc` or use `process-unmapped-keys yes` in `defcfg`
59 | 
60 | == Windows 11 Interception
61 | 
62 | * Sleeping your system or unplugging/replugging devices enough times causes
63 |   inputs to stop working
64 | ** https://github.com/oblitum/Interception/issues/25
65 | * Some less-frequently used keys are not supported or handled correctly
66 | ** https://github.com/jtroo/kanata/issues/127
67 | ** https://github.com/jtroo/kanata/issues/164
68 | ** https://github.com/jtroo/kanata/issues/425
69 | ** https://github.com/jtroo/kanata/issues/532
70 | 
71 | == Linux
72 | 
73 | * Key repeats can occur when they normally wouldn't in some cases
74 | ** https://github.com/jtroo/kanata/discussions/422
75 | ** https://github.com/jtroo/kanata/issues/450
76 | ** https://github.com/jtroo/kanata/issues/1441
77 | * Unicode support has limitations, using xkb is a more consistent solution
78 | ** https://github.com/jtroo/kanata/discussions/703
79 | * Key actions can behave incorrectly due to the rapidity of key events
80 | ** https://github.com/jtroo/kanata/discussions/733
81 | ** https://github.com/jtroo/kanata/issues/740
82 | ** adjusting https://github.com/jtroo/kanata/blob/main/docs/config.adoc#rapid-event-delay[rapid-event-delay] can potentially be a workaround
83 | * Macro keys on certain gaming keyboards might stop being processed
84 | ** Context: search for `POTENTIAL PROBLEM - G-keys` in
85 | link:../src/kanata/mod.rs[the code].
86 | ** Workaround: leave `process-unmapped-keys` disabled
87 | and explicitly map keys in `defsrc` instead
88 | 
89 | == MacOS
90 | 
91 | * Only left, right, and middle mouse buttons are implemented for clicking
92 | * Mouse input processing is not implemented, e.g. putting `mlft` into `defsrc` does nothing
93 | 


--------------------------------------------------------------------------------
/docs/setup-linux.md:
--------------------------------------------------------------------------------
  1 | # Instructions
  2 | 
  3 | In Linux, kanata needs to be able to access the input and uinput subsystem to inject events. To do this, your user needs to have permissions. Follow the steps in this page to obtain user permissions.
  4 | 
  5 | ### 1. If the uinput group does not exist, create a new group
  6 | 
  7 | ```bash
  8 | sudo groupadd uinput
  9 | ```
 10 | 
 11 | ### 2. Add your user to the input and the uinput group
 12 | 
 13 | ```bash
 14 | sudo usermod -aG input $USER
 15 | sudo usermod -aG uinput $USER
 16 | ```
 17 | 
 18 | Make sure that it's effective by running `groups`. You might have to logout and login.
 19 | 
 20 | ### 3. Make sure the uinput device file has the right permissions.
 21 | 
 22 | #### Create a new file:
 23 | `/etc/udev/rules.d/99-input.rules`
 24 | 
 25 | #### Insert the following in the code
 26 | ```bash
 27 | KERNEL=="uinput", MODE="0660", GROUP="uinput", OPTIONS+="static_node=uinput"
 28 | ```
 29 | 
 30 | #### Machine reboot or run this to reload
 31 | ```bash
 32 | sudo udevadm control --reload-rules && sudo udevadm trigger
 33 | ```
 34 | 
 35 | #### Verify settings by following command:
 36 | ```bash
 37 | ls -l /dev/uinput
 38 | ```
 39 | 
 40 | #### Output:
 41 | ```bash
 42 | crw-rw---- 1 root date uinput /dev/uinput
 43 | ```
 44 | 
 45 | ### 4. Make sure the uinput drivers are loaded
 46 | 
 47 | You may need to run this command whenever you start kanata for the first time:
 48 | 
 49 | ```
 50 | sudo modprobe uinput
 51 | ```
 52 | ### 5a. To create and enable a systemd daemon service
 53 | 
 54 | Run this command first:
 55 | ```bash
 56 | mkdir -p ~/.config/systemd/user
 57 | ```
 58 | 
 59 | Then add this to: `~/.config/systemd/user/kanata.service`:
 60 | ```bash
 61 | [Unit]
 62 | Description=Kanata keyboard remapper
 63 | Documentation=https://github.com/jtroo/kanata
 64 | 
 65 | [Service]
 66 | Environment=PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/bin
 67 | #   Uncomment the 4 lines beneath this to increase process priority
 68 | #   of Kanata in case you encounter lagginess when resource constrained.
 69 | #   WARNING: doing so will require the service to run as an elevated user such as root.
 70 | #   Implementing least privilege access is an exercise left to the reader.
 71 | #
 72 | # CPUSchedulingPolicy=rr
 73 | # CPUSchedulingPriority=99
 74 | # IOSchedulingClass=realtime
 75 | # Nice=-20
 76 | Type=simple
 77 | ExecStart=/usr/bin/sh -c 'exec $(which kanata) --cfg ${HOME}/.config/kanata/config.kbd'
 78 | Restart=no
 79 | 
 80 | [Install]
 81 | WantedBy=default.target
 82 | ```
 83 | 
 84 | Make sure to update the executable location for sh in the snippet above.
 85 | This would be the line starting with `ExecStart=/usr/bin/sh -c`.
 86 | You can check the executable path with:
 87 | ```bash
 88 | which sh
 89 | ```
 90 | 
 91 | Also, verify if the path to kanata is included in the line `Environment=PATH=[...]`.
 92 | For example, if executing `which kanata` returns `/home/[user]/.cargo/bin/kanata`, the `PATH` line should be appended with `/home/[user]/.cargo/bin` or `:%h/.cargo/bin`.
 93 | `%h` is one of the specifiers allowed in systemd, more can be found in https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#Specifiers
 94 | 
 95 | Then run:
 96 | ```bash
 97 | systemctl --user daemon-reload
 98 | systemctl --user enable kanata.service
 99 | systemctl --user start kanata.service
100 | systemctl --user status kanata.service   # check whether the service is running
101 | ```
102 | ### 5b. To create and enable an OpenRC daemon service
103 | Edit new file `/etc/init.d/kanata` as root, replacing \<username\> as appropriate:
104 | ```bash
105 | #!/sbin/openrc-run
106 | 
107 | command="/home/<username>/.cargo/bin/kanata"
108 | #command_args="--config=/home/<username>/.config/kanata/kanata.kbd"
109 | 
110 | command_background=true
111 | pidfile="/run/${RC_SVCNAME}.pid"
112 | 
113 | command_user="<username>"
114 | ```
115 | 
116 | Then run:
117 | ```
118 | sudo chmod +x /etc/init.d/kanata # script must be executable
119 | sudo rc-service kanata start
120 | rc-status # check that kanata isn't listed as [ crashed ]
121 | sudo rc-update add kanata default # start the service automatically at boot
122 | ```
123 | 
124 | # Credits
125 | 
126 | The original text was taken and adapted from: https://github.com/kmonad/kmonad/blob/master/doc/faq.md#linux
127 | 


--------------------------------------------------------------------------------
/docs/simulated_output/sim.kbd:
--------------------------------------------------------------------------------
 1 | (defcfg
 2 |   process-unmapped-keys	yes	;;|no| enable processing of keys that are not in defsrc, useful if mapping a few keys in defsrc instead of most of the keys on your keyboard. Without this, the tap-hold-release and tap-hold-press actions will not activate for keys that are not in defsrc. Disabled because some keys may not work correctly if they are intercepted. E.g. rctl/altgr on Windows; see the windows-altgr configuration item above for context.
 3 |   log-layer-changes    	yes	;;|no| overhead
 4 | )
 5 | (defvar ;; declare commonly-used values. prefix with $ to call them. They are refered with `
lt;var name>`
 6 |   tap-repress-timeout 	1000 ;;|500|
 7 |   hold-timeout	        1500 ;;|500|
 8 |   🕐↕          	        $tap-repress-timeout
 9 |   🕐🠿          	        $hold-timeout
10 | )
11 | (defalias
12 |   ;; home row mods ↕tap 🠿hold
13 |   ;; pinky ring middle index | index middle ring pinky
14 |   ;; 	          timeout ↕tap 🠿hold¦↕tap 🠿hold action
15 |   ⌂‹◆	(tap-hold-release $🕐↕ $🕐🠿 a ‹◆)	;;
16 |   ⌂‹⎇	(tap-hold-release $🕐↕ $🕐🠿 s ‹⎇)	;;
17 |   ⌂‹⎈	(tap-hold-release $🕐↕ $🕐🠿 d ‹⎈)	;;
18 |   ⌂‹⇧	(tap-hold-release $🕐↕ $🕐🠿 f ‹⇧)	;;
19 |   ⌂⇧›	(tap-hold-release $🕐↕ $🕐🠿 j ⇧›)	;; same actions for the right side
20 |   ⌂⎈›	(tap-hold-release $🕐↕ $🕐🠿 k ⎈›)	;;
21 |   ⌂⎇›	(tap-hold-release $🕐↕ $🕐🠿 l ⎇›)	;;
22 |   ⌂◆›	(tap-hold-release $🕐↕ $🕐🠿 ; ◆›)	;;
23 | )
24 | 
25 | (defsrc
26 | ` 1 2
27 | a	s	d	f			j	k	l	;)
28 | (deflayer ⌂ ;; modtap layer for home row mods and 1 printing a 🤲🏿 char (will appear as 🤲 until kanata's unicode feature is extended)
29 |   ‗ 🔣🤲🏿 ‗
30 |   @⌂‹◆ @⌂‹⎇ @⌂‹⎈ @⌂‹⇧        @⌂⇧› @⌂⎈› @⌂⎇› @⌂◆›)
31 | 


--------------------------------------------------------------------------------
/docs/simulated_output/sim.txt:
--------------------------------------------------------------------------------
1 | ↓j 🕐1600 ↓l 🕐5000 ↓1 🕐50 ↑1 🕐50 ↓1 🕐50 ↑1 🕐50 ↑j 🕐50 ↑l 🕐50
2 | 


--------------------------------------------------------------------------------
/docs/simulated_output/sim_out.txt:
--------------------------------------------------------------------------------
 1 | 🕐Δms│           1500    100        1500    3500            50      50            50      50              50
 2 | In───┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 3 |  k↑  │                                                      1                     1       J               L
 4 |  k↓  │  J                L                  1                       1
 5 |  k⟳  │
 6 | Σin  │ ↓J 🕐1600         ↓L   🕐5000         ↓1    🕐50       ↑1  🕐50 ↓1  🕐50       ↑1  🕐50 ↑J  🕐50         ↑L  🕐50
 7 | Out──┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 8 |  k↑  │                                                                                            ⇧›              ⎇›
 9 |  k↓  │           ⇧›                 ⎇›
10 |  🖰↑  │
11 |  🖰↓  │
12 |  🖰   │
13 |  🔣  │                                                🤲                     🤲
14 |  code│
15 |  raw↑│
16 |  raw↓│
17 | Σout │          ↓⇧›                ↓⎇›                🤲                     🤲                    ↑⇧›             ↑⎇›
18 | 


--------------------------------------------------------------------------------
/docs/simulated_passthru_ahk/[COPY HERE] kanata_passthru.dll _:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtroo/kanata/dd02c3e6bd6305d4e4593de492ad2dec5abe3684/docs/simulated_passthru_ahk/[COPY HERE] kanata_passthru.dll _


--------------------------------------------------------------------------------
/docs/simulated_passthru_ahk/kanata_dll.kbd:
--------------------------------------------------------------------------------
 1 | ;;Test config for kanata.dll use by AutoHotkey, only maps two keys (f,j) to left/right modtap home row mod Shifts
 2 | (defcfg
 3 |   process-unmapped-keys	yes	;;|no| enable processing of keys that are not in defsrc
 4 |   log-layer-changes    	no 	;;|no| overhead
 5 | )
 6 | 
 7 | (defvar
 8 |   🕐↕	1000 ;;|500| tap-repress-timeout
 9 |   🕐🠿	1500 ;;|500| hold-timeout
10 |   )
11 | (defalias	;;      timeout→ 	tap	  hold   ¦	 tap	 hold ←action
12 |   f⌂‹⇧   	(tap-hold-release	$🕐↕	$🕐🠿       	f   	‹⇧)
13 |   j⌂⇧›   	(tap-hold-release	$🕐↕	$🕐🠿       	j   	⇧›)
14 |   )
15 | 
16 | (defsrc      f     j   )
17 | (deflayer ⌂ @f⌂‹⇧ @j⌂⇧›)
18 | 


--------------------------------------------------------------------------------
/docs/switch-design:
--------------------------------------------------------------------------------
  1 | # Preface:
  2 | 
  3 | This document is a scratch space for the design of the switch action.
  4 | It may be out of date and is kept around for posterity.
  5 | 
  6 | .syntax:
  7 | ----
  8 | (switch
  9 |   (or a b c) (cmd <cmd1>) break
 10 |   (and a b (or c d)) (cmd <cmd2>) fallthrough
 11 |   (and a b (or c d) (or e f)) fallthrough
 12 |   ()
 13 | )
 14 | ----
 15 | 
 16 | 
 17 | .opcode format examples:
 18 | ----
 19 | (or a b c)
 20 | OR-4 a b c
 21 | 
 22 | (and a b (or c d))
 23 | AND-6 a b OR-6 c d
 24 | 
 25 | (and a b (or c d) (or e f))
 26 | AND-9 a b OR-6 c d OR-9 e f
 27 | ----
 28 | 
 29 | .opcodes:
 30 | ----
 31 | key: all values < 1024
 32 | OR/AND: OP & 0xF000
 33 |   OR : 0x1000
 34 |   AND: 0x2000
 35 |   length: OP & 0x0FFF
 36 | ----
 37 | 
 38 | .Rough algorithm for opcodes:
 39 | ----
 40 | value=true
 41 | push first opcode
 42 | WHILE stack is not empty
 43 |   WHILE index <= ending_index
 44 |     switch
 45 |       opcode:
 46 |         push, continue
 47 |       key(OR):
 48 |         value=true: skip to index, pop
 49 |         value=false: continue
 50 |       key(AND):
 51 |         value=true: continue
 52 |         value=false: skip to index, pop
 53 |   pop
 54 |   switch
 55 |     current_value(OR):
 56 |       value=true: skip to index, pop
 57 |       value=false: continue
 58 |     current_value(AND):
 59 |       value=true: continue
 60 |       value=false: skip to index, pop
 61 | return value
 62 | ----
 63 | 
 64 | .statestruct:
 65 | ----
 66 |   value
 67 |   current_index
 68 |   current_end_index
 69 |   current_op
 70 |   stack (op, ending_index)
 71 | ----
 72 | 
 73 | .rough sequence 1:
 74 | ----
 75 | pressed:       y y      y y      y y
 76 | opcodes: AND-9 a b OR-6 c d OR-9 e f
 77 | 
 78 | index: 0
 79 |   push: AND-9
 80 |   stack: AND-9
 81 | 
 82 | index: 1
 83 |   val: true
 84 | 
 85 | index: 2
 86 |   val: true
 87 | 
 88 | index: 3
 89 |   push: OR-6
 90 |   stack: AND-9 OR-6
 91 | 
 92 | index: 4
 93 |   val: true
 94 |   skip to 6
 95 |   pop
 96 |   stack: AND-9
 97 | 
 98 | index: 6
 99 |   push: OR-9
100 |   stack: AND-9 OR-9
101 | 
102 | index: 7
103 |   val: true
104 |   skip to 9
105 |   pop
106 |   stack: AND-9-true
107 | 
108 | index 9:
109 |   pop
110 |   stack: empty
111 |   return val: true
112 | ----
113 | 
114 | .rough sequence 2:
115 | ----
116 | pressed:       y y      n n      y y
117 | opcodes: AND-9 a b OR-6 c d OR-9 e f
118 | 
119 | index: 0
120 |   push: AND-9
121 |   stack: AND-9
122 | 
123 | index: 1
124 |   val: true
125 | 
126 | index: 2
127 |   val: true
128 | 
129 | index: 3
130 |   push: OR-6
131 |   stack: AND-9 OR-6
132 |   val: true
133 | 
134 | index: 4
135 |   val: false
136 | 
137 | index: 5
138 |   val: false
139 | 
140 | index: 6
141 |   val: false
142 |   pop
143 |   stack: AND-9
144 |   skip to 9
145 |   pop
146 |   stack: empty
147 |   return val: false
148 | ----
149 | 
150 | .rough sequence 3:
151 | ----
152 | pressed:       n y      n n      y y
153 | opcodes: AND-9 a b OR-6 c d OR-9 e f
154 | 
155 | index: 0
156 |   push: AND-9
157 |   stack: AND-9
158 | 
159 | index: 1
160 |   val: false
161 |   skip to 9
162 |   pop
163 |   stack: empty
164 |   return val: false
165 | ----
166 | 
167 | 
168 | .pseudo code again:
169 | ----
170 | let mut value = true
171 | let mut current_index = 1
172 | let mut current_end_index = first_opcode - end_index
173 | let mut current_op = OR
174 | while current_index < slice_length {
175 |   if index >= current_end_index:
176 |     if stack is empty:
177 |       break
178 |     else:
179 |       pop stack to current_op and current_end_index
180 |       switch
181 |         current_value(OR):
182 |           value=true: skip to current_end_index; continue
183 |         current_value(AND):
184 |           value=false: skip to current_end_index; continue
185 |   switch
186 |     opcode:
187 |       push (current_end_index,current_op)
188 |       update (current_end_index,current_op) with opcode
189 |     key(OR):
190 |       value=true: skip to current_end_index; continue
191 |       value=false
192 |     key(AND):
193 |       value=true
194 |       value=false: skip to current_end_index; continue
195 |   current_index++;
196 | }
197 | return value
198 | ----
199 | 


--------------------------------------------------------------------------------
/docs/win-tray/win-tray-layer-change.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtroo/kanata/dd02c3e6bd6305d4e4593de492ad2dec5abe3684/docs/win-tray/win-tray-layer-change.gif


--------------------------------------------------------------------------------
/docs/win-tray/win-tray-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtroo/kanata/dd02c3e6bd6305d4e4593de492ad2dec5abe3684/docs/win-tray/win-tray-screen.png


--------------------------------------------------------------------------------
/example_tcp_client/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | 


--------------------------------------------------------------------------------
/example_tcp_client/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name = "kanata_example_tcp_client"
 3 | description = "Example kanata TCP client"
 4 | version = "1.1.0"
 5 | edition = "2021"
 6 | license = "LGPL-3.0"
 7 | authors = ["jtroo <j.andreitabs@gmail.com>"]
 8 | 
 9 | [dependencies]
10 | anyhow = "1"
11 | clap = { version = "4", features = [ "derive" ] }
12 | kanata-tcp-protocol = { path = "../tcp_protocol" }
13 | log = "0.4.8"
14 | simplelog = "0.12"
15 | serde_json = "1"


--------------------------------------------------------------------------------
/interception/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [workspace]
 2 | members = ["."]
 3 | 
 4 | [package]
 5 | name = "kanata-interception"
 6 | description = "Safe wrapper for Interception. Forked for use with kanata."
 7 | version = "0.3.0"
 8 | authors = ["Joe Kaushal <joe.kaushal@gmail.com>"]
 9 | edition = "2018"
10 | repository = "https://github.com/jtroo/kanata"
11 | license = "MIT OR Apache-2.0"
12 | 
13 | [dependencies]
14 | interception-sys = "0.1.3"
15 | bitflags = "1.2.1"
16 | num_enum = "0.6.0"
17 | serde = { version = "1.0.114", features = ["derive"] }
18 | 


--------------------------------------------------------------------------------
/justfile:
--------------------------------------------------------------------------------
 1 | set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
 2 | 
 3 | # Build the release binaries for Linux and put the binaries+cfg in the output directory
 4 | build_release_linux output_dir:
 5 |   cargo build --release
 6 |   cp target/release/kanata "{{output_dir}}/kanata"
 7 |   strip "{{output_dir}}/kanata"
 8 |   cargo build --release --features cmd
 9 |   cp target/release/kanata "{{output_dir}}/kanata_cmd_allowed"
10 |   strip "{{output_dir}}/kanata_cmd_allowed"
11 |   cp cfg_samples/kanata.kbd "{{output_dir}}"
12 | 
13 | # Build the release binaries for Windows and put the binaries+cfg in the output directory.
14 | build_release_windows output_dir:
15 |   cargo build --release --no-default-features --features tcp_server,win_manifest; cp target/release/kanata.exe "{{output_dir}}\kanata_legacy_output.exe"
16 |   cargo build --release --features win_manifest,interception_driver; cp target/release/kanata.exe "{{output_dir}}\kanata_wintercept.exe"
17 |   cargo build --release --features win_manifest,win_sendinput_send_scancodes; cp target/release/kanata.exe "{{output_dir}}\kanata.exe"
18 |   cargo build --release --features win_manifest,win_sendinput_send_scancodes,win_llhook_read_scancodes; cp target/release/kanata.exe "{{output_dir}}\kanata_winIOv2.exe"
19 |   cargo build --release --features win_manifest,cmd,win_sendinput_send_scancodes; cp target/release/kanata.exe "{{output_dir}}\kanata_cmd_allowed.exe"
20 |   cargo build --release --features win_manifest,cmd,interception_driver; cp target/release/kanata.exe "{{output_dir}}\kanata_wintercept_cmd_allowed.exe"
21 |   cargo build --release --features passthru_ahk --package=simulated_passthru; cp target/release/kanata_passthru.dll "{{output_dir}}\kanata_passthru.dll"
22 |   cargo build --release --features win_manifest,gui    ; cp target/release/kanata.exe "{{output_dir}}\kanata_gui.exe"
23 |   cargo build --release --features win_manifest,gui,cmd; cp target/release/kanata.exe "{{output_dir}}\kanata_gui_cmd_allowed.exe"
24 |   cargo build --release --features win_manifest,gui,interception_driver    ; cp target/release/kanata.exe "{{output_dir}}\kanata_gui_wintercept.exe"
25 |   cargo build --release --features win_manifest,gui,cmd,interception_driver; cp target/release/kanata.exe "{{output_dir}}\kanata_gui_wintercept_cmd_allowed.exe"
26 |   cp cfg_samples/kanata.kbd "{{output_dir}}"
27 | 
28 | # Generate the sha256sums for all files in the output directory
29 | sha256sums output_dir:
30 |   rm -f {{output_dir}}/sha256sums
31 |   cd {{output_dir}}; sha256sum * > sha256sums
32 | 
33 | test:
34 |   cargo test -p kanata -p kanata-parser -p kanata-keyberon -- --nocapture
35 |   cargo test --features=simulated_output sim_tests
36 |   cargo clippy --all
37 | 
38 | fmt:
39 |   cargo fmt --all
40 | 
41 | guic:
42 |   cargo check              --features=gui
43 | guif:
44 |   cargo fmt    --all
45 |   cargo clippy --all --fix --features=gui -- -D warnings
46 | 
47 | ahkc:
48 |   cargo check              --features=passthru_ahk
49 | ahkf:
50 |   cargo fmt    --all
51 |   cargo clippy --all --fix --features=passthru_ahk -- -D warnings
52 | 
53 | change_subcrate_versions version:
54 |   sed -i 's/^version = ".*"$/version = "{{version}}"/' parser/Cargo.toml tcp_protocol/Cargo.toml keyberon/Cargo.toml
55 |   sed -i 's/^\(#\? \?kanata-\(keyberon\|parser\|tcp-protocol\).*version\) = "[0-9.]*"/\1 = "{{version}}"/' Cargo.toml parser/Cargo.toml
56 | 
57 | cov:
58 |   cargo llvm-cov clean --workspace
59 |   cargo llvm-cov --no-report --workspace --no-default-features
60 |   cargo llvm-cov --no-report --workspace
61 |   cargo llvm-cov --no-report --workspace --features=cmd,win_llhook_read_scancodes,win_sendinput_send_scancodes
62 |   cargo llvm-cov --no-report --workspace --features=cmd,interception_driver,win_sendinput_send_scancodes
63 |   cargo llvm-cov --no-report --features=simulated_output -- sim_tests
64 |   cargo llvm-cov report --html
65 | 
66 | publish:
67 |   cd keyberon && cargo publish
68 |   cd tcp_protocol && cargo publish
69 |   cd parser && cargo publish
70 |   cargo publish
71 | 
72 | # Include the trailing `\` or `/` in the output_dir parameter. The parameter should be an absolute path.
73 | cfg_to_html output_dir:
74 |   cd docs ; asciidoctor config.adoc
75 |   cd docs ; cp config.html "{{output_dir}}config.html"; rm config.html
76 | 
77 | # Include the trailing `\` or `/` in the output_dir parameter. The parameter should be an absolute path.
78 | wasm_pack output_dir:
79 |   cd wasm; wasm-pack build --target web; cd pkg; cp kanata_wasm_bg.wasm "{{output_dir}}"; cp kanata_wasm.js "{{output_dir}}"
80 | 


--------------------------------------------------------------------------------
/key-sort-add/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 | 
5 | [[package]]
6 | name = "key-sort-add"
7 | version = "0.1.0"
8 | 


--------------------------------------------------------------------------------
/key-sort-add/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [workspace]
 2 | members = ["."]
 3 | 
 4 | [package]
 5 | name = "key-sort-add"
 6 | version = "0.1.0"
 7 | edition = "2021"
 8 | 
 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
10 | 
11 | [dependencies]
12 | 


--------------------------------------------------------------------------------
/key-sort-add/README.md:
--------------------------------------------------------------------------------
1 | # key-sort-add
2 | 
3 | A small program that was used to sort and fill in OsCode mappings.
4 | 


--------------------------------------------------------------------------------
/key-sort-add/src/main.rs:
--------------------------------------------------------------------------------
  1 | //! one:
  2 | //!
  3 | //! Takes a file formatted as:
  4 | //!
  5 | //!     KEY_RESERVED = 0,
  6 | //!     KEY_ESC = 1,
  7 | //!     KEY_1 = 2,
  8 | //!     KEY_2 = 3,
  9 | //!     KEY_3 = 4,
 10 | //!     KEY_4 = 5,
 11 | //!     ...
 12 | //!
 13 | //! Outputs to stdout a sorted version of the file with numeric gaps filled in with:
 14 | //!
 15 | //!     KEY_X = X,
 16 | //!
 17 | //! two: mapping.txt to ensure KeyCode and OsCode can simply be transmuted into each other.
 18 | 
 19 | use std::io::Read;
 20 | 
 21 | fn main() {
 22 |     match std::env::args().nth(1).expect("function parameter").as_str() {
 23 |         "one" => one(),
 24 |         "two" => two(),
 25 |         _ => panic!("unknown capabality"),
 26 |     }
 27 | }
 28 | 
 29 | fn one() {
 30 |     let mut f = std::fs::File::open(std::env::args().nth(2).expect("filename parameter"))
 31 |         .expect("file open");
 32 |     let mut s = String::new();
 33 |     f.read_to_string(&mut s).expect("read file");
 34 |     let mut keys = s
 35 |         .lines()
 36 |         .map(|l| {
 37 |             let mut segments = l.trim_end_matches(',').trim().split(" = ");
 38 |             let key = segments.next().expect("a string");
 39 |             let num: u16 = u16::from_str_radix(
 40 |                 segments
 41 |                     .next()
 42 |                     .map(|s| s.trim_start_matches("0x"))
 43 |                     .expect("string after ="),
 44 |                 10,
 45 |             )
 46 |             .expect("u16");
 47 |             (key.to_owned(), num)
 48 |         })
 49 |         .collect::<Vec<_>>();
 50 |     keys.sort_by_key(|k| k.1);
 51 |     let mut keys_to_add = vec![];
 52 |     let mut cur_key = keys.iter();
 53 |     let mut prev_key = keys.iter();
 54 |     cur_key.next();
 55 |     for cur in cur_key {
 56 |         let prev = prev_key.next().expect("lagging iterator is valid");
 57 |         for missing in prev.1 + 1..cur.1 {
 58 |             keys_to_add.push((format!("K{missing}"), missing));
 59 |         }
 60 |     }
 61 |     keys.append(&mut keys_to_add);
 62 |     keys.sort_by_key(|k| k.1);
 63 |     for key in keys {
 64 |         println!("{} = {},", key.0, key.1);
 65 |     }
 66 | }
 67 | 
 68 | fn two() {
 69 |     use std::collections::HashMap;
 70 | 
 71 |     let mut f = std::fs::File::open(std::env::args().nth(2).expect("filename parameter"))
 72 |         .expect("file open");
 73 |     let mut s = String::new();
 74 |     f.read_to_string(&mut s).expect("read file");
 75 |     let mut lines = s.lines();
 76 | 
 77 |     // filter out useless lines
 78 |     while let Some(line) = lines.next() {
 79 |         if line == "=== kc to osc" {
 80 |             break;
 81 |         }
 82 |     }
 83 | 
 84 |     // parse kc to osc
 85 |     let mut kc_to_osc: HashMap<&str, &str> = HashMap::new();
 86 |     while let Some(line) = lines.next() {
 87 |         if line.trim().is_empty() {
 88 |             continue;
 89 |         }
 90 |         if line == "=== osc to u16" {
 91 |             break;
 92 |         }
 93 |         let (kc, osc) = line.split_once(" => ").expect("arrow separator");
 94 |         let kc = kc.trim_start_matches("KeyCode::");
 95 |         let osc = osc.trim_end_matches(',')
 96 |                 .trim_start_matches("OsCode::");
 97 |         kc_to_osc.insert(kc, osc);
 98 |     }
 99 | 
100 |     // parse osc to u16
101 |     let mut osc_vals: HashMap<&str, u16> = HashMap::new();
102 |     while let Some(line) = lines.next() {
103 |         if line.trim().is_empty() {
104 |             continue;
105 |         }
106 |         if line == "=== all kcs" {
107 |             break;
108 |         }
109 |         let (kc, num) = line.split_once(" = ").expect("equal separator");
110 |         let num = num.trim_end_matches(',').parse::<u16>().expect("u16");
111 |         osc_vals.insert(kc, num);
112 |     }
113 | 
114 |     // parse kcs
115 |     let mut kc_vals: Vec<(&str, Option<u16>)> = vec![];
116 |     while let Some(line) = lines.next() {
117 |         if line.trim().is_empty() {
118 |             continue;
119 |         }
120 |         let kc = line.trim_end_matches(',');
121 |         let val: Option<u16> = kc_to_osc.get(&kc)
122 |             .and_then(|osc| osc_vals.get(osc))
123 |             .copied();
124 |         kc_vals.push((kc, val));
125 |     }
126 | 
127 |     for (kc, val) in kc_vals.iter() {
128 |         println!("{kc} = {},", val.unwrap_or(65535));
129 |     }
130 | }
131 | 


--------------------------------------------------------------------------------
/keyberon/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore Cargo.lock since this is a library crate
2 | Cargo.lock
3 | 


--------------------------------------------------------------------------------
/keyberon/CHANGELOG.md:
--------------------------------------------------------------------------------
 1 | # v0.2.0
 2 | 
 3 | * New Keyboard::leds_mut function for getting underlying leds object.
 4 | * Made Layout::current_layer public for getting current active layer.
 5 | * Added a procedural macro for defining layouts (`keyberon::layout::layout`)
 6 | * Corrected HID report descriptor
 7 | * Add max_packet_size() to HidDevice to allow differing report sizes
 8 | * Allows default layer to be set on a Layout externally
 9 | * Add Chording for multiple keys pressed at the same time to equal another key
10 | 
11 | Breaking changes:
12 | * Row and Column pins are now a simple array. For the STM32 MCU, you
13 |   should now use `.downgrade()` to have an homogenous array. 
14 | * `Action::HoldTap` now takes a configuration for different behaviors.
15 | * `Action::HoldTap` now takes the `tap_hold_interval` field. Not
16 |   implemented yet.
17 | * `Action` is now generic, for the `Action::Custom(T)` variant,
18 |   allowing custom actions to be handled outside of keyberon. This
19 |   functionality can be used to drive non keyboard actions, such as resetting
20 |   the microcontroller, driving leds (for backlight or underglow for
21 |   example), managing a mouse emulation, or any other ideas you can
22 |   have. As there is a default value for the type parameter, the update
23 |   should be transparent.
24 | * Layers don't sum anymore, the last pressed layer action set the layer.
25 | * Rename MeidaCoffee in MediaCoffee to fix typo.
26 | 
27 | # v0.1.1
28 | 
29 | *  HidClass::control_xxx: check interface number [#26](https://github.com/TeXitoi/keyberon/pull/26)
30 | 
31 | # v0.1.0
32 | 
33 | First published version.
34 | 


--------------------------------------------------------------------------------
/keyberon/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name = "kanata-keyberon"
 3 | version = "0.190.0"
 4 | authors = ["Guillaume Pinot <texitoi@texitoi.eu>", "Robin Krahl <robin.krahl@ireas.org>", "jtroo <j.andreitabs@gmail.com>"]
 5 | edition = "2021"
 6 | description = "Pure Rust keyboard firmware. Fork intended for use with kanata."
 7 | documentation = "https://docs.rs/keyberon"
 8 | repository = "https://github.com/TeXitoi/keyberon"
 9 | keywords = ["keyboard", "kanata"]
10 | categories = ["no-std"]
11 | license = "MIT"
12 | readme = "README.md"
13 | 
14 | [dependencies]
15 | kanata-keyberon-macros = { version = "0.2.0" }
16 | heapless = "0.7.16"
17 | rustc-hash = "1.1.0"
18 | arraydeque = { version = "0.5.1", default-features = false }
19 | 


--------------------------------------------------------------------------------
/keyberon/KEYBOARDS.md:
--------------------------------------------------------------------------------
 1 | | Keyboard                                                                   | PCB or Handwired | MCU       | Feature Status                                                                                 |
 2 | | -                                                                          | -                | -         | -                                                                                              |
 3 | | [KeySeeBee](https://github.com/TeXitoi/keyseebee)                          | PCB              | STM32F072 | <ul><li>[x] Matrix </li><li>[x] Split</li></ul>                                                |
 4 | | [Keyberon-f4](https://github.com/TeXitoi/keyberon-f4)                      | Handwired        | STM32F401 | <ul><li>[x] Matrix </li></ul>                                                                  |
 5 | | [Arisu](https://github.com/help-14/arisu-handwired)                        | Handwired        | STM32F401 | <ul><li>[x] Matrix </li></ul>                                                                  |
 6 | | [ortho60-keyberon](https://github.com/TeXitoi/ortho60-keyberon)            | PCB              | STM32F103 | <ul><li>[x] Matrix </li></ul>                                                                  |
 7 | | [keyberon-grid](https://github.com/TeXitoi/keyberon-grid)                  | Handwired        | STM32F103 | <ul><li>[x] Matrix </li></ul>                                                                  |
 8 | | [Clueboard 66% LP](https://github.com/wezm/clueboard-rust-firmware)        | PCB              | STM32F303 | <ul><li>[x] Matrix </li><li>[ ] LEDs</li><li>[ ] Speakers</li></ul>                            |
 9 | | [anne-keyberon](https://github.com/hdhoang/anne-keyberon)                  | PCB              | STM32L151 | <ul><li>[ ] Matrix </li><li>[ ] BT proto </li><li>[ ] LED proto </li><li>[ ] LED MCU </li></ul>                                                                  |
10 | | [corne-xiao](https://github.com/lehmanju/corne-xiao)                       | PCB              | ATSAMD21  | <ul><li>[x] Matrix </li></ul>                                                                  |
11 | | [pinci](https://github.com/camrbuss/pinci)                                 | PCB              | RP2040    | <ul><li>[x] Matrix </li><li>[x] Split</li></ul>                                                |
12 | | [nibble-rp2040-rs](https://github.com/DrewTChrist/nibble-rp2040-rs)        | PCB              | RP2040    | <ul><li>[x] Matrix </li><li>[ ] Rotary Encoder</li><li>[ ] RGB LEDs</li><li>[ ] OLED</li></ul> |
13 | | [keyboard-labs](https://github.com/rgoulter/keyboard-labs)                 | PCB              | STM32F401 | <ul><li>[x] Matrix </li><li>[x] Split</li></ul>                                                |
14 | | [makerdiary M60](https://github.com/jamesmunns/m60-keyboard/)              | PCB              | nRF52840  | <ul><li>[x] Matrix </li><li>[x] RGB LEDs</li> |
15 | | [PouetPouet](https://github.com/dkm/pouetpouet-board)                          | PCB              | STM32F072 | <ul><li>[x] Matrix </li></ul>                                                |
16 | | [corne](https://github.com/simmsb/keyboard)                                | PCB              | nRF52840  | <ul><li>[x] Matrix </li><li>[x] RGB LEDs</li><li>[x] OLED</li><li>[x] split</li></ul>          |
17 | | [Cantor](https://github.com/dariogoetz/cantor-firmware-keyberon)           | PCB              | STM32F401 | <ul><li>[x] Matrix </li><li>[ ] LEDs</li><li>[x] Split</li><li>[x] Diodeless</li></ul>         |
18 | 


--------------------------------------------------------------------------------
/keyberon/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2019 Guillaume P.
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/keyberon/README.md:
--------------------------------------------------------------------------------
1 | # kanata-keyberon
2 | 
3 | ## Note
4 | 
5 | This is a fork intended for use by the [kanata keyboard remapper software](https://github.com/jtroo/kanata).
6 | Please make contributions to the [original project](https://github.com/TeXitoi/keyberon) where applicable.
7 | 
8 | This crate does not follow semver. It tracks the version of kanata.
9 | 


--------------------------------------------------------------------------------
/keyberon/images/keyberon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtroo/kanata/dd02c3e6bd6305d4e4593de492ad2dec5abe3684/keyberon/images/keyberon.jpg


--------------------------------------------------------------------------------
/keyberon/keyberon-macros/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name = "kanata-keyberon-macros"
 3 | version = "0.2.0"
 4 | authors = ["Antoni Simka <antonisimka.8@gmail.com>"]
 5 | edition = "2018"
 6 | description = "Macros for keyberon. Fork for kanata project"
 7 | license = "MIT"
 8 | 
 9 | [lib]
10 | proc-macro = true
11 | 
12 | [dependencies]
13 | proc-macro2 = "1.0"
14 | quote = "1.0"
15 | 


--------------------------------------------------------------------------------
/keyberon/keyberon-macros/README.md:
--------------------------------------------------------------------------------
1 | # kanata keyberon macros
2 | 
3 | ## Note
4 | 
5 | This is a fork intended for use by the [kanata keyboard remapper software](https://github.com/jtroo/keyberon).
6 | Please make contributions to the [original project](https://github.com/TeXitoi/keyberon) where applicable.
7 | 


--------------------------------------------------------------------------------
/keyberon/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! This is a fork intended for use by the [kanata keyboard remapper software](https://github.com/jtroo/kanata).
2 | //! Please make contributions to the original project.
3 | 
4 | pub mod action;
5 | pub mod chord;
6 | pub mod key_code;
7 | pub mod layout;
8 | mod multikey_buffer;
9 | 


--------------------------------------------------------------------------------
/keyberon/src/multikey_buffer.rs:
--------------------------------------------------------------------------------
 1 | //! Module for `MultiKeyBuffer`.
 2 | 
 3 | use std::{array, slice};
 4 | 
 5 | use crate::action::{Action, ONE_SHOT_MAX_ACTIVE};
 6 | use crate::key_code::KeyCode;
 7 | 
 8 | // Presumably this should be plenty.
 9 | // ONE_SHOT_MAX_ACTIVE is already likely unreasonably large enough.
10 | // This buffer capacity adds more onto that,
11 | // just in case somebody finds a way to use all of the one-shot capacity.
12 | const BUFCAP: usize = ONE_SHOT_MAX_ACTIVE + 4;
13 | 
14 | /// This is an unsafe container that enables a mutable Action::MultipleKeyCodes.
15 | pub(crate) struct MultiKeyBuffer<'a, T> {
16 |     buf: [KeyCode; BUFCAP],
17 |     size: usize,
18 |     ptr: *mut &'static [KeyCode],
19 |     ac: *mut Action<'a, T>,
20 | }
21 | 
22 | unsafe impl<T> Send for MultiKeyBuffer<'_, T> {}
23 | 
24 | impl<'a, T> MultiKeyBuffer<'a, T> {
25 |     /// Create a new instance of `MultiKeyBuffer`.
26 |     ///
27 |     /// # Safety
28 |     ///
29 |     /// The program should not have any references to the inner buffer when the struct is dropped.
30 |     pub(crate) unsafe fn new() -> Self {
31 |         Self {
32 |             buf: array::from_fn(|_| KeyCode::Escape),
33 |             size: 0,
34 |             ptr: Box::leak(Box::new(slice::from_raw_parts(
35 |                 core::ptr::NonNull::dangling().as_ptr(),
36 |                 0,
37 |             ))),
38 |             ac: Box::leak(Box::new(Action::NoOp)),
39 |         }
40 |     }
41 | 
42 |     /// Set the current size of the buffer to zero.
43 |     ///
44 |     /// # Safety
45 |     ///
46 |     /// The program should not have any references to the inner buffer.
47 |     pub(crate) unsafe fn clear(&mut self) {
48 |         self.size = 0;
49 |     }
50 | 
51 |     /// Push to the end of the buffer. If the buffer is full, this silently fails.
52 |     ///
53 |     /// # Safety
54 |     ///
55 |     /// The program should not have any references to the inner buffer.
56 |     pub(crate) unsafe fn push(&mut self, kc: KeyCode) {
57 |         if self.size < BUFCAP {
58 |             self.buf[self.size] = kc;
59 |             self.size += 1;
60 |         }
61 |     }
62 | 
63 |     /// Get a reference to the inner buffer in the form of an `Action`.
64 |     /// The `Action` will be the variant `MultipleKeyCodes`,
65 |     /// containing all keys that have been pushed.
66 |     ///
67 |     /// # Safety
68 |     ///
69 |     /// The program should not have any references to the inner buffer before calling.
70 |     /// The program should not mutate the buffer after calling this function until after the
71 |     /// returned reference is dropped.
72 |     pub(crate) unsafe fn get_ref(&self) -> &'a Action<'a, T> {
73 |         *self.ac = Action::NoOp;
74 |         *self.ptr = slice::from_raw_parts(self.buf.as_ptr(), self.size);
75 |         *self.ac = Action::MultipleKeyCodes(&*self.ptr);
76 |         &*self.ac
77 |     }
78 | }
79 | 
80 | impl<T> Drop for MultiKeyBuffer<'_, T> {
81 |     fn drop(&mut self) {
82 |         unsafe {
83 |             drop(Box::from_raw(self.ac));
84 |             drop(Box::from_raw(self.ptr));
85 |         }
86 |     }
87 | }
88 | 


--------------------------------------------------------------------------------
/parser/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore Cargo.lock since this is a library crate
2 | Cargo.lock
3 | target


--------------------------------------------------------------------------------
/parser/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name = "kanata-parser"
 3 | version = "0.190.0"
 4 | authors = ["jtroo <j.andreitabs@gmail.com>"]
 5 | description = "A parser for configuration language of kanata, a keyboard remapper."
 6 | keywords = ["kanata", "parser"]
 7 | homepage = "https://github.com/jtroo/kanata"
 8 | repository = "https://github.com/jtroo/kanata"
 9 | readme = "README.md"
10 | license = "LGPL-3.0-only"
11 | edition = "2021"
12 | 
13 | [dependencies]
14 | anyhow = "1"
15 | bitflags = "2.5.0"
16 | bytemuck = "1.15.0"
17 | itertools = "0.12"
18 | log = { version = "0.4.8", default-features = false }
19 | miette = { version = "5.7.0", features = ["fancy"] }
20 | once_cell = "1"
21 | parking_lot = "0.12"
22 | patricia_tree = "0.8"
23 | rustc-hash = "1.1.0"
24 | thiserror = "1.0.38"
25 | 
26 | kanata-keyberon = { path = "../keyberon", version = "0.190.0" }
27 | 
28 | [dev-dependencies]
29 | simplelog = "0.12.0"
30 | 
31 | [features]
32 | cmd = []
33 | interception_driver = []
34 | gui = []
35 | lsp = []
36 | win_llhook_read_scancodes = []
37 | win_sendinput_send_scancodes = []
38 | zippychord = []
39 | 


--------------------------------------------------------------------------------
/parser/README.md:
--------------------------------------------------------------------------------
1 | # kanata-parser
2 | 
3 | A parser for configuration language of [kanata](https://github.com/jtroo/kanata).
4 | 
5 | This crate does not follow semver. It tracks the version of kanata.
6 | 


--------------------------------------------------------------------------------
/parser/src/cfg/alloc.rs:
--------------------------------------------------------------------------------
 1 | //! This module contains a helper struct for generating 'static lifetime allocations while still
 2 | //! keeping track of them so that they can be freed later.
 3 | 
 4 | use parking_lot::Mutex;
 5 | use std::sync::Arc;
 6 | 
 7 | /// This struct tracks the allocations that are leaked by its provided methods and frees them when
 8 | /// dropped. The `new` function is unsafe because dropping the struct can create dangling
 9 | /// references. Care must be taken to ensure that all allocations made by this struct's methods are
10 | /// no longer referenced when the struct gets dropped.
11 | ///
12 | /// In practice, this is not difficult to do in the `cfg` module which only exposes a single public
13 | /// method.
14 | pub(crate) struct Allocations {
15 |     allocations: Mutex<Vec<usize>>,
16 | }
17 | 
18 | impl std::fmt::Debug for Allocations {
19 |     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 |         f.debug_struct("Allocations").finish()
21 |     }
22 | }
23 | 
24 | impl Drop for Allocations {
25 |     fn drop(&mut self) {
26 |         log::debug!(
27 |             "freeing allocations of length {}",
28 |             self.allocations.lock().len()
29 |         );
30 |         for p in self.allocations.lock().iter().rev().copied() {
31 |             unsafe { drop(Box::from_raw(p as *mut usize)) };
32 |         }
33 |     }
34 | }
35 | 
36 | impl Allocations {
37 |     /// Create a new allocations group.
38 |     ///
39 |     /// # Safety
40 |     ///
41 |     /// Ensure that all associated allocations are no longer referenced before dropping all
42 |     /// clones of the `Arc`.
43 |     pub(crate) unsafe fn new() -> Arc<Self> {
44 |         Arc::new(Self {
45 |             allocations: Mutex::new(vec![]),
46 |         })
47 |     }
48 | 
49 |     /// Returns a `&'static T` by leaking a newly created Box of `v`.
50 |     pub(crate) fn sref<T>(&self, v: T) -> &'static T {
51 |         let p = Box::into_raw(Box::new(v));
52 |         if (p as usize) < 16 {
53 |             panic!("sref bad ptr");
54 |         }
55 |         self.allocations.lock().push(p as usize);
56 |         Box::leak(unsafe { Box::from_raw(p) })
57 |     }
58 | 
59 |     pub(crate) fn bref_slice<T>(&self, v: Box<[T]>) -> &'static [T] {
60 |         // An empty slice has no backing allocation. `Box<[T]>` is a fat pointer so the leaked return
61 |         // will contain a length of 0 and an invalid pointer.
62 |         if !v.is_empty() {
63 |             self.allocations.lock().push(v.as_ptr() as usize);
64 |         }
65 |         Box::leak(v)
66 |     }
67 | 
68 |     /// Returns a &'static [&'static T] from a `Vec<T>` by converting to a boxed slice and leaking it.
69 |     pub(crate) fn sref_vec<T>(&self, v: Vec<T>) -> &'static [T] {
70 |         self.bref_slice(v.into_boxed_slice())
71 |     }
72 | 
73 |     /// Returns a `&'static [&'static T]` by leaking a newly created box and boxed slice of `v`.
74 |     pub(crate) fn sref_slice<T>(&self, v: T) -> &'static [&'static T] {
75 |         self.bref_slice(vec![self.sref(v)].into_boxed_slice())
76 |     }
77 | }
78 | 


--------------------------------------------------------------------------------
/parser/src/cfg/custom_tap_hold.rs:
--------------------------------------------------------------------------------
 1 | use kanata_keyberon::layout::{Event, QueuedIter, WaitingAction};
 2 | 
 3 | use crate::keys::OsCode;
 4 | 
 5 | use super::alloc::Allocations;
 6 | 
 7 | /// Returns a closure that can be used in `HoldTapConfig::Custom`, which will return early with a
 8 | /// Tap action in the case that any of `keys` are pressed. Otherwise it behaves as
 9 | /// `HoldTapConfig::PermissiveHold` would.
10 | pub(crate) fn custom_tap_hold_release(
11 |     keys: &[OsCode],
12 |     a: &Allocations,
13 | ) -> &'static (dyn Fn(QueuedIter) -> (Option<WaitingAction>, bool) + Send + Sync) {
14 |     let keys = a.sref_vec(Vec::from_iter(keys.iter().copied()));
15 |     a.sref(
16 |         move |mut queued: QueuedIter| -> (Option<WaitingAction>, bool) {
17 |             while let Some(q) = queued.next() {
18 |                 if q.event().is_press() {
19 |                     let (i, j) = q.event().coord();
20 |                     // If any key matches the input, do a tap right away.
21 |                     if keys.iter().copied().map(u16::from).any(|j2| j2 == j) {
22 |                         return (Some(WaitingAction::Tap), false);
23 |                     }
24 |                     // Otherwise do the PermissiveHold algorithm.
25 |                     let target = Event::Release(i, j);
26 |                     if queued.clone().copied().any(|q| q.event() == target) {
27 |                         return (Some(WaitingAction::Hold), false);
28 |                     }
29 |                 }
30 |             }
31 |             (None, false)
32 |         },
33 |     )
34 | }
35 | 
36 | pub(crate) fn custom_tap_hold_except(
37 |     keys: &[OsCode],
38 |     a: &Allocations,
39 | ) -> &'static (dyn Fn(QueuedIter) -> (Option<WaitingAction>, bool) + Send + Sync) {
40 |     let keys = a.sref_vec(Vec::from_iter(keys.iter().copied()));
41 |     a.sref(
42 |         move |mut queued: QueuedIter| -> (Option<WaitingAction>, bool) {
43 |             for q in queued.by_ref() {
44 |                 if q.event().is_press() {
45 |                     let (_i, j) = q.event().coord();
46 |                     // If any key matches the input, do a tap.
47 |                     if keys.iter().copied().map(u16::from).any(|j2| j2 == j) {
48 |                         return (Some(WaitingAction::Tap), false);
49 |                     }
50 |                     // Otherwise continue with default behavior
51 |                     return (None, false);
52 |                 }
53 |             }
54 |             // Otherwise skip timeout
55 |             (None, true)
56 |         },
57 |     )
58 | }
59 | 


--------------------------------------------------------------------------------
/parser/src/cfg/error.rs:
--------------------------------------------------------------------------------
 1 | use miette::{Diagnostic, NamedSource, SourceSpan};
 2 | use thiserror::Error;
 3 | 
 4 | use super::{sexpr::Span, *};
 5 | 
 6 | pub type MResult<T> = miette::Result<T>;
 7 | pub type Result<T> = std::result::Result<T, ParseError>;
 8 | 
 9 | #[derive(Debug, Clone)]
10 | pub struct ParseError {
11 |     pub msg: String,
12 |     pub span: Option<Span>,
13 | }
14 | 
15 | impl ParseError {
16 |     pub fn new(span: Span, err_msg: impl AsRef<str>) -> Self {
17 |         Self {
18 |             msg: err_msg.as_ref().to_string(),
19 |             span: Some(span),
20 |         }
21 |     }
22 | 
23 |     pub fn new_without_span(err_msg: impl AsRef<str>) -> Self {
24 |         Self {
25 |             msg: err_msg.as_ref().to_string(),
26 |             span: None,
27 |         }
28 |     }
29 | 
30 |     pub fn from_expr(expr: &sexpr::SExpr, err_msg: impl AsRef<str>) -> Self {
31 |         Self::new(expr.span(), err_msg)
32 |     }
33 | 
34 |     pub fn from_spanned<T>(spanned: &Spanned<T>, err_msg: impl AsRef<str>) -> Self {
35 |         Self::new(spanned.span.clone(), err_msg)
36 |     }
37 | }
38 | 
39 | impl From<anyhow::Error> for ParseError {
40 |     fn from(value: anyhow::Error) -> Self {
41 |         Self::new_without_span(value.to_string())
42 |     }
43 | }
44 | 
45 | impl From<ParseError> for miette::Error {
46 |     fn from(val: ParseError) -> Self {
47 |         let diagnostic = CfgError {
48 |             err_span: val
49 |                 .span
50 |                 .as_ref()
51 |                 .map(|s| SourceSpan::new(s.start().into(), (s.end() - s.start()).into())),
52 |             help_msg: help(val.msg),
53 |             file_name: val.span.as_ref().map(|s| s.file_name()),
54 |             file_content: val.span.as_ref().map(|s| s.file_content()),
55 |         };
56 | 
57 |         let report: miette::Error = diagnostic.into();
58 | 
59 |         if let Some(span) = val.span {
60 |             report.with_source_code(NamedSource::new(span.file_name(), span.file_content()))
61 |         } else {
62 |             report
63 |         }
64 |     }
65 | }
66 | 
67 | #[derive(Error, Debug, Diagnostic, Clone)]
68 | #[error("Error in configuration")]
69 | #[diagnostic()]
70 | struct CfgError {
71 |     // Snippets and highlights can be included in the diagnostic!
72 |     #[label("Error here")]
73 |     err_span: Option<SourceSpan>,
74 |     #[help]
75 |     help_msg: String,
76 |     file_name: Option<String>,
77 |     file_content: Option<String>,
78 | }
79 | 
80 | pub(super) fn help(err_msg: impl AsRef<str>) -> String {
81 |     format!(
82 |         r"{}
83 | 
84 | For more info, see the configuration guide:
85 | https://github.com/jtroo/kanata/blob/main/docs/config.adoc",
86 |         err_msg.as_ref(),
87 |     )
88 | }
89 | 


--------------------------------------------------------------------------------
/parser/src/cfg/is_a_button.rs:
--------------------------------------------------------------------------------
  1 | pub(crate) fn is_a_button(osc: u16) -> bool {
  2 |     if cfg!(target_os = "windows") {
  3 |         matches!(osc, 1..=6 | 256..)
  4 |     } else {
  5 |         osc >= 256
  6 |     }
  7 | }
  8 | 
  9 | #[test]
 10 | fn mouse_inputs_most_care_about_are_considered_buttons() {
 11 |     use crate::keys::{OsCode, OsCode::*};
 12 |     const MOUSE_INPUTS: &[OsCode] = &[
 13 |         MouseWheelUp,
 14 |         MouseWheelDown,
 15 |         MouseWheelLeft,
 16 |         MouseWheelRight,
 17 |         BTN_LEFT,
 18 |         BTN_RIGHT,
 19 |         BTN_MIDDLE,
 20 |         BTN_SIDE,
 21 |         BTN_EXTRA,
 22 |         BTN_FORWARD,
 23 |         BTN_BACK,
 24 |     ];
 25 |     for input in MOUSE_INPUTS.iter().copied() {
 26 |         println!("{input}");
 27 |         assert!(is_a_button(input.into()));
 28 |     }
 29 | }
 30 | 
 31 | #[test]
 32 | fn standard_keys_are_not_considered_buttons() {
 33 |     use crate::keys::{OsCode, OsCode::*};
 34 |     const KEY_INPUTS: &[OsCode] = &[
 35 |         KEY_0,
 36 |         KEY_1,
 37 |         KEY_2,
 38 |         KEY_3,
 39 |         KEY_4,
 40 |         KEY_5,
 41 |         KEY_6,
 42 |         KEY_7,
 43 |         KEY_8,
 44 |         KEY_9,
 45 |         KEY_A,
 46 |         KEY_B,
 47 |         KEY_C,
 48 |         KEY_D,
 49 |         KEY_E,
 50 |         KEY_F,
 51 |         KEY_G,
 52 |         KEY_H,
 53 |         KEY_I,
 54 |         KEY_J,
 55 |         KEY_K,
 56 |         KEY_L,
 57 |         KEY_M,
 58 |         KEY_N,
 59 |         KEY_O,
 60 |         KEY_P,
 61 |         KEY_Q,
 62 |         KEY_R,
 63 |         KEY_S,
 64 |         KEY_T,
 65 |         KEY_U,
 66 |         KEY_V,
 67 |         KEY_W,
 68 |         KEY_X,
 69 |         KEY_Y,
 70 |         KEY_Z,
 71 |         KEY_SEMICOLON,
 72 |         KEY_SLASH,
 73 |         KEY_GRAVE,
 74 |         KEY_LEFTBRACE,
 75 |         KEY_BACKSLASH,
 76 |         KEY_RIGHTBRACE,
 77 |         KEY_APOSTROPHE,
 78 |         KEY_MINUS,
 79 |         KEY_DOT,
 80 |         KEY_EQUAL,
 81 |         KEY_BACKSPACE,
 82 |         KEY_ESC,
 83 |         KEY_TAB,
 84 |         KEY_ENTER,
 85 |         KEY_LEFTCTRL,
 86 |         KEY_LEFTSHIFT,
 87 |         KEY_COMMA,
 88 |         KEY_RIGHTSHIFT,
 89 |         KEY_KPASTERISK,
 90 |         KEY_LEFTALT,
 91 |         KEY_SPACE,
 92 |         KEY_CAPSLOCK,
 93 |         KEY_F1,
 94 |         KEY_F2,
 95 |         KEY_F3,
 96 |         KEY_F4,
 97 |         KEY_F5,
 98 |         KEY_F6,
 99 |         KEY_F7,
100 |         KEY_F8,
101 |         KEY_F9,
102 |         KEY_F10,
103 |         KEY_F11,
104 |         KEY_F12,
105 |         KEY_NUMLOCK,
106 |         KEY_SCROLLLOCK,
107 |         KEY_KP0,
108 |         KEY_KP1,
109 |         KEY_KP2,
110 |         KEY_KP3,
111 |         KEY_KP4,
112 |         KEY_KP5,
113 |         KEY_KP6,
114 |         KEY_KP7,
115 |         KEY_KP8,
116 |         KEY_KP9,
117 |         KEY_KPMINUS,
118 |         KEY_KPPLUS,
119 |         KEY_KPDOT,
120 |         KEY_KPENTER,
121 |         KEY_RIGHTCTRL,
122 |         KEY_KPSLASH,
123 |         KEY_RIGHTALT,
124 |         KEY_HOME,
125 |         KEY_UP,
126 |         KEY_PAGEUP,
127 |         KEY_LEFT,
128 |         KEY_RIGHT,
129 |         KEY_END,
130 |         KEY_DOWN,
131 |         KEY_PAGEDOWN,
132 |         KEY_INSERT,
133 |         KEY_DELETE,
134 |         KEY_MUTE,
135 |         KEY_VOLUMEDOWN,
136 |         KEY_VOLUMEUP,
137 |         KEY_PAUSE,
138 |         KEY_LEFTMETA,
139 |         KEY_RIGHTMETA,
140 |         KEY_COMPOSE,
141 |         KEY_BACK,
142 |         KEY_FORWARD,
143 |         KEY_NEXTSONG,
144 |         KEY_PLAYPAUSE,
145 |         KEY_PREVIOUSSONG,
146 |         KEY_STOP,
147 |         KEY_HOMEPAGE,
148 |         KEY_MAIL,
149 |         KEY_MEDIA,
150 |         KEY_REFRESH,
151 |         KEY_F13,
152 |         KEY_F14,
153 |         KEY_F15,
154 |         KEY_F16,
155 |         KEY_F17,
156 |         KEY_F18,
157 |         KEY_F19,
158 |         KEY_F20,
159 |         KEY_F21,
160 |         KEY_F22,
161 |         KEY_F23,
162 |         KEY_F24,
163 |         KEY_HANGEUL,
164 |         KEY_HANJA,
165 |         KEY_252,
166 |         KEY_102ND,
167 |         KEY_PLAY,
168 |         KEY_PRINT,
169 |         KEY_SEARCH,
170 |         KEY_RO,
171 |         KEY_HENKAN,
172 |         KEY_MUHENKAN,
173 |     ];
174 |     for input in KEY_INPUTS.iter().copied() {
175 |         assert!(!is_a_button(input.into()));
176 |     }
177 | }
178 | 


--------------------------------------------------------------------------------
/parser/src/cfg/layer_opts.rs:
--------------------------------------------------------------------------------
 1 | use crate::cfg::*;
 2 | use crate::*;
 3 | 
 4 | pub(crate) const DEFLAYER_ICON: [&str; 3] = ["icon", "🖻", "🖼"];
 5 | pub(crate) type LayerIcons = HashMap<String, Option<String>>;
 6 | 
 7 | pub fn parse_layer_opts(list: &[SExpr]) -> Result<HashMap<String, String>> {
 8 |     let mut layer_opts: HashMap<String, String> = HashMap::default();
 9 |     let mut opts = list.chunks_exact(2);
10 |     for kv in opts.by_ref() {
11 |         let key_expr = &kv[0];
12 |         let val_expr = &kv[1];
13 |         // Read k-v pairs from the configuration
14 |         // todo: add hashmap for future options, currently only parse icons
15 |         let opt_key = key_expr.atom(None)
16 |             .ok_or_else(|| anyhow_expr!(key_expr, "No lists are allowed in {DEFLAYER} options"))
17 |             .and_then(|opt_key| {
18 |                 if DEFLAYER_ICON.contains(&opt_key) {
19 |                     if layer_opts.contains_key(DEFLAYER_ICON[0]) {
20 |                         // separate dupe check since multi-keys are stored
21 |                         // with one "canonical" repr, so '🖻' → 'icon'
22 |                         // and this info will be lost after the loop
23 |                         bail_expr!(
24 |                             key_expr,
25 |                             "Duplicate option found in {DEFLAYER}: {opt_key}, one of {DEFLAYER_ICON:?} already exists"
26 |                         );
27 |                     }
28 |                     Ok(DEFLAYER_ICON[0])
29 |                 } else {
30 |                     bail_expr!(key_expr, "Invalid option in {DEFLAYER}: {opt_key}, expected one of {DEFLAYER_ICON:?}")
31 |                 }
32 |             })?;
33 |         if layer_opts.contains_key(opt_key) {
34 |             bail_expr!(key_expr, "Duplicate option found in {DEFLAYER}: {opt_key}");
35 |         }
36 |         let opt_val = val_expr.atom(None).ok_or_else(|| {
37 |             anyhow_expr!(
38 |                 val_expr,
39 |                 "No lists are allowed in {DEFLAYER}'s option values"
40 |             )
41 |         })?;
42 |         layer_opts.insert(opt_key.to_owned(), opt_val.to_owned());
43 |     }
44 |     let rem = opts.remainder();
45 |     if !rem.is_empty() {
46 |         bail_expr!(&rem[0], "This option is missing a value.");
47 |     }
48 |     Ok(layer_opts)
49 | }
50 | 


--------------------------------------------------------------------------------
/parser/src/cfg/permutations.rs:
--------------------------------------------------------------------------------
 1 | //! Implements Heap's algorithm.
 2 | 
 3 | /*
 4 | From Wikipedia:
 5 | 
 6 | procedure generate(k: integer, A : array of any):
 7 |     if k = 1 then
 8 |         output(A)
 9 |     else
10 |         // Generate permutations with k-th unaltered
11 |         // Initially k = length(A)
12 |         generate(k - 1, A)
13 | 
14 |         // Generate permutations for k-th swapped with each k-1 initial
15 |         for i := 0; i < k-1; i += 1 do
16 |             // Swap choice dependent on parity of k (even or odd)
17 |             if k is even then
18 |                 swap(A[i], A[k-1]) // zero-indexed, the k-th is at k-1
19 |             else
20 |                 swap(A[0], A[k-1])
21 |             end if
22 |             generate(k - 1, A)
23 |         end for
24 |     end if
25 | */
26 | 
27 | /// Heap's algorithm
28 | pub fn gen_permutations<T: Clone + Default>(a: &[T]) -> Vec<Vec<T>> {
29 |     let mut a2 = vec![Default::default(); a.len()];
30 |     a2.clone_from_slice(a);
31 |     let mut outs = vec![];
32 |     heaps_alg(a.len(), &mut a2, &mut outs);
33 |     outs
34 | }
35 | 
36 | fn heaps_alg<T: Clone>(k: usize, a: &mut [T], outs: &mut Vec<Vec<T>>) {
37 |     if k == 1 {
38 |         outs.push(a.to_vec());
39 |     } else {
40 |         heaps_alg(k - 1, a, outs);
41 |         for i in 0..k - 1 {
42 |             if (k % 2) == 0 {
43 |                 a.swap(i, k - 1);
44 |             } else {
45 |                 a.swap(0, k - 1);
46 |             }
47 |             heaps_alg(k - 1, a, outs);
48 |         }
49 |     }
50 | }
51 | 


--------------------------------------------------------------------------------
/parser/src/cfg/str_ext.rs:
--------------------------------------------------------------------------------
 1 | pub trait TrimAtomQuotes {
 2 |     fn trim_atom_quotes(&self) -> &str;
 3 | }
 4 | 
 5 | impl TrimAtomQuotes for str {
 6 |     fn trim_atom_quotes(&self) -> &str {
 7 |         match self.strip_prefix("r#\"") {
 8 |             Some(a) => a.strip_suffix("\"#").unwrap_or(a),
 9 |             None => self
10 |                 .strip_prefix('"')
11 |                 .unwrap_or(self)
12 |                 .strip_suffix('"')
13 |                 .unwrap_or(self),
14 |         }
15 |     }
16 | }
17 | 
18 | impl TrimAtomQuotes for String {
19 |     fn trim_atom_quotes(&self) -> &str {
20 |         match self.as_str().strip_prefix("r#\"") {
21 |             Some(a) => a.strip_suffix("\"#").unwrap_or(a),
22 |             None => self
23 |                 .strip_prefix('"')
24 |                 .unwrap_or(self)
25 |                 .strip_suffix('"')
26 |                 .unwrap_or(self),
27 |         }
28 |     }
29 | }
30 | 


--------------------------------------------------------------------------------
/parser/src/cfg/tests/ambiguous.rs:
--------------------------------------------------------------------------------
 1 | use super::*;
 2 | 
 3 | #[test]
 4 | fn parse_double_dollar_var() {
 5 |     let source = r#"
 6 | (defsrc)
 7 | (deflayer base)
 8 | (defvar $num 100
 9 |          $num 99
10 |           num not-a-number-or-key)
11 | (defalias test
12 |          (movemouse-accel-up $num $$num $num $$num))
13 | "#;
14 |     parse_cfg(source)
15 |         .map_err(|e| eprintln!("{:?}", miette::Error::from(e)))
16 |         .expect("parses");
17 | }
18 | 
19 | #[test]
20 | fn parse_double_at_alias() {
21 |     let source = r#"
22 | (defsrc)
23 | (deflayer base)
24 |           ;; alias cannot be used in macro, @alias can
25 | (defalias @alias 0
26 |            alias (tap-hold 9 9 a b)
27 |            test (macro @@alias))
28 | "#;
29 |     parse_cfg(source)
30 |         .map_err(|e| eprintln!("{:?}", miette::Error::from(e)))
31 |         .expect("parses");
32 | }
33 | 


--------------------------------------------------------------------------------
/parser/src/cfg/tests/defcfg.rs:
--------------------------------------------------------------------------------
  1 | use super::*;
  2 | 
  3 | #[test]
  4 | fn disallow_same_key_in_defsrc_unmapped_except() {
  5 |     let source = "
  6 | (defcfg process-unmapped-keys (all-except bspc))
  7 | (defsrc bspc)
  8 | (deflayermap (name) 0 0)
  9 | ";
 10 |     parse_cfg(source)
 11 |         .map(|_| ())
 12 |         //.map_err(|e| eprintln!("{:?}", miette::Error::from(e)))
 13 |         .expect_err("fails");
 14 | }
 15 | 
 16 | #[test]
 17 | fn unmapped_except_keys_cannot_have_dupes() {
 18 |     let source = "
 19 | (defcfg process-unmapped-keys (all-except bspc bspc))
 20 | (defsrc)
 21 | (deflayermap (name) 0 0)
 22 | ";
 23 |     parse_cfg(source)
 24 |         .map(|_| ())
 25 |         //.map_err(|e| eprintln!("{:?}", miette::Error::from(e)))
 26 |         .expect_err("fails");
 27 | }
 28 | 
 29 | #[test]
 30 | fn unmapped_except_keys_must_be_known() {
 31 |     let source = "
 32 | (defcfg process-unmapped-keys (all-except notakey))
 33 | (defsrc)
 34 | (deflayermap (name) 0 0)
 35 | ";
 36 |     parse_cfg(source)
 37 |         .map(|_| ())
 38 |         //.map_err(|e| eprintln!("{:?}", miette::Error::from(e)))
 39 |         .expect_err("fails");
 40 | }
 41 | 
 42 | #[test]
 43 | fn unmapped_except_keys_respects_deflocalkeys() {
 44 |     let source = "
 45 | (deflocalkeys-win         lkey90 555)
 46 | (deflocalkeys-winiov2     lkey90 555)
 47 | (deflocalkeys-wintercept  lkey90 555)
 48 | (deflocalkeys-linux       lkey90 555)
 49 | (deflocalkeys-macos       lkey90 555)
 50 | (defcfg process-unmapped-keys (all-except lkey90))
 51 | (defsrc)
 52 | (deflayermap (name) 0 0)
 53 | ";
 54 |     let cfg = parse_cfg(source)
 55 |         .map_err(|e| eprintln!("{:?}", miette::Error::from(e)))
 56 |         .expect("passes");
 57 |     assert!(!cfg.mapped_keys.contains(&OsCode::from(555u16)));
 58 |     assert!(cfg.mapped_keys.contains(&OsCode::KEY_ENTER));
 59 |     for osc in 0..KEYS_IN_ROW as u16 {
 60 |         if let Some(osc) = OsCode::from_u16(osc) {
 61 |             match KeyCode::from(osc) {
 62 |                 KeyCode::No | KeyCode::K555 => {
 63 |                     assert!(!cfg.mapped_keys.contains(&osc));
 64 |                 }
 65 |                 _ => {
 66 |                     assert!(cfg.mapped_keys.contains(&osc));
 67 |                 }
 68 |             }
 69 |         }
 70 |     }
 71 | }
 72 | 
 73 | #[test]
 74 | fn unmapped_except_keys_is_removed_from_mapping() {
 75 |     let source = "
 76 | (defcfg process-unmapped-keys (all-except 1 2 3))
 77 | (defsrc)
 78 | (deflayermap (name) 0 0)
 79 | ";
 80 |     let cfg = parse_cfg(source)
 81 |         .map_err(|e| eprintln!("{:?}", miette::Error::from(e)))
 82 |         .expect("passes");
 83 |     assert!(cfg.mapped_keys.contains(&OsCode::KEY_A));
 84 |     assert!(cfg.mapped_keys.contains(&OsCode::KEY_0));
 85 |     assert!(!cfg.mapped_keys.contains(&OsCode::KEY_1));
 86 |     assert!(!cfg.mapped_keys.contains(&OsCode::KEY_2));
 87 |     assert!(!cfg.mapped_keys.contains(&OsCode::KEY_3));
 88 |     assert!(cfg.mapped_keys.contains(&OsCode::KEY_4));
 89 |     for osc in 0..KEYS_IN_ROW as u16 {
 90 |         if let Some(osc) = OsCode::from_u16(osc) {
 91 |             match KeyCode::from(osc) {
 92 |                 KeyCode::No | KeyCode::Kb1 | KeyCode::Kb2 | KeyCode::Kb3 => {
 93 |                     assert!(!cfg.mapped_keys.contains(&osc));
 94 |                 }
 95 |                 _ => {
 96 |                     assert!(cfg.mapped_keys.contains(&osc));
 97 |                 }
 98 |             }
 99 |         }
100 |     }
101 | }
102 | 


--------------------------------------------------------------------------------
/parser/src/cfg/tests/device_detect.rs:
--------------------------------------------------------------------------------
 1 | #[cfg(target_os = "linux")]
 2 | mod linux {
 3 |     use super::super::*;
 4 | 
 5 |     #[test]
 6 |     fn linux_device_parses_properly() {
 7 |         let source = r#"
 8 | (defcfg linux-device-detect-mode any)
 9 | (defsrc) (deflayer base)"#;
10 |         let icfg = parse_cfg(source)
11 |             .map_err(|e| log::info!("{:?}", miette::Error::from(e)))
12 |             .expect("no error");
13 |         assert_eq!(
14 |             icfg.options.linux_opts.linux_device_detect_mode,
15 |             Some(DeviceDetectMode::Any)
16 |         );
17 | 
18 |         let source = r#"
19 | (defcfg linux-device-detect-mode keyboard-only)
20 | (defsrc) (deflayer base)"#;
21 |         let icfg = parse_cfg(source)
22 |             .map_err(|e| log::info!("{:?}", miette::Error::from(e)))
23 |             .expect("no error");
24 |         assert_eq!(
25 |             icfg.options.linux_opts.linux_device_detect_mode,
26 |             Some(DeviceDetectMode::KeyboardOnly)
27 |         );
28 | 
29 |         let source = r#"
30 | (defcfg linux-device-detect-mode keyboard-mice)
31 | (defsrc) (deflayer base)"#;
32 |         let icfg = parse_cfg(source)
33 |             .map_err(|e| log::info!("{:?}", miette::Error::from(e)))
34 |             .expect("no error");
35 |         assert_eq!(
36 |             icfg.options.linux_opts.linux_device_detect_mode,
37 |             Some(DeviceDetectMode::KeyboardMice)
38 |         );
39 | 
40 |         let source = r#"(defsrc mmid) (deflayer base 1)"#;
41 |         let icfg = parse_cfg(source)
42 |             .map_err(|e| log::info!("{:?}", miette::Error::from(e)))
43 |             .expect("no error");
44 |         assert_eq!(
45 |             icfg.options.linux_opts.linux_device_detect_mode,
46 |             Some(DeviceDetectMode::Any)
47 |         );
48 | 
49 |         let source = r#"(defsrc a) (deflayer base b)"#;
50 |         let icfg = parse_cfg(source)
51 |             .map_err(|e| log::info!("{:?}", miette::Error::from(e)))
52 |             .expect("no error");
53 |         assert_eq!(
54 |             icfg.options.linux_opts.linux_device_detect_mode,
55 |             Some(DeviceDetectMode::KeyboardMice)
56 |         );
57 | 
58 |         let source = r#"
59 | (defcfg linux-device-detect-mode not an opt)
60 | (defsrc) (deflayer base)"#;
61 |         parse_cfg(source)
62 |             .map(|_| ())
63 |             .map_err(|e| log::info!("{:?}", miette::Error::from(e)))
64 |             .expect_err("error should happen");
65 |     }
66 | }
67 | 


--------------------------------------------------------------------------------
/parser/src/cfg/tests/environment.rs:
--------------------------------------------------------------------------------
 1 | use super::*;
 2 | 
 3 | fn parse_cfg_env(cfg: &str, env_vars: Vec<(String, String)>) -> Result<IntermediateCfg> {
 4 |     let _lk = lock(&CFG_PARSE_LOCK);
 5 |     let mut s = ParserState::default();
 6 |     parse_cfg_raw_string(
 7 |         cfg,
 8 |         &mut s,
 9 |         &PathBuf::from("test"),
10 |         &mut FileContentProvider {
11 |             get_file_content_fn: &mut |_| unimplemented!(),
12 |         },
13 |         DEF_LOCAL_KEYS,
14 |         Ok(env_vars),
15 |     )
16 | }
17 | 
18 | #[test]
19 | fn parse_env() {
20 |     parse_cfg_env(
21 |         r#"
22 |         (environment (hello "") (defsrc a))
23 |         (environment (goodbye "") (deflayer 1 (layer-switch 2)))
24 |         (environment (farewell val) (deflayer 2 (layer-switch 1)))
25 |         ;; below would conflict if environment did not cancel
26 |         (environment (hello yea) (defsrc))
27 |         (environment (goodbye yea) (deflayer 1))
28 |         (environment (farewell notval) (deflayer 2))
29 |         "#,
30 |         vec![
31 |             ("goodbye".into(), "".into()),
32 |             ("farewell".into(), "val".into()),
33 |         ],
34 |     )
35 |     .map_err(|e| eprintln!("{:?}", miette::Error::from(e)))
36 |     .unwrap();
37 | }
38 | 


--------------------------------------------------------------------------------
/parser/src/cfg/tests/macros.rs:
--------------------------------------------------------------------------------
 1 | use super::*;
 2 | 
 3 | #[test]
 4 | fn unsupported_action_in_macro_triggers_error() {
 5 |     let source = r#"
 6 | (defsrc)
 7 | (deflayer base)
 8 | (defalias a (macro (multi a b c))) "#;
 9 |     parse_cfg(source)
10 |         .map(|_| ())
11 |         .map_err(|e| log::info!("{:?}", miette::Error::from(e)))
12 |         .expect_err("errors");
13 | }
14 | 
15 | #[test]
16 | fn incorrectly_configured_supported_action_in_macro_triggers_useful_error() {
17 |     let source = r#"
18 | (defsrc)
19 | (deflayer base)
20 | (defalias a (macro (on-press press-vkey does-not-exist))) "#;
21 |     parse_cfg(source)
22 |         .map(|_| ())
23 |         .map_err(|e| {
24 |             let e = miette::Error::from(e);
25 |             let msg = format!("{e:?}");
26 |             log::info!("{msg}");
27 |             assert!(msg.contains("unknown virtual key name: does-not-exist"));
28 |         })
29 |         .expect_err("errors");
30 | }
31 | 


--------------------------------------------------------------------------------
/parser/src/layers.rs:
--------------------------------------------------------------------------------
 1 | use kanata_keyberon::key_code::KeyCode;
 2 | use kanata_keyberon::layout::*;
 3 | 
 4 | use crate::cfg::alloc::*;
 5 | use crate::cfg::KanataAction;
 6 | use crate::custom_action::*;
 7 | use crate::keys::OsCode;
 8 | 
 9 | use std::sync::Arc;
10 | 
11 | // OsCode::KEY_MAX is the biggest OsCode
12 | pub const KEYS_IN_ROW: usize = OsCode::KEY_MAX as usize;
13 | pub const LAYER_ROWS: usize = 2;
14 | pub const DEFAULT_ACTION: KanataAction = KanataAction::KeyCode(KeyCode::ErrorUndefined);
15 | 
16 | pub type IntermediateLayers = Box<[[Row; LAYER_ROWS]]>;
17 | 
18 | pub type KLayers =
19 |     Layers<'static, KEYS_IN_ROW, LAYER_ROWS, &'static &'static [&'static CustomAction]>;
20 | 
21 | pub struct KanataLayers {
22 |     pub(crate) layers:
23 |         Layers<'static, KEYS_IN_ROW, LAYER_ROWS, &'static &'static [&'static CustomAction]>,
24 |     _allocations: Arc<Allocations>,
25 | }
26 | 
27 | impl std::fmt::Debug for KanataLayers {
28 |     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 |         f.debug_struct("KanataLayers").finish()
30 |     }
31 | }
32 | 
33 | pub type Row = [kanata_keyberon::action::Action<'static, &'static &'static [&'static CustomAction]>;
34 |     KEYS_IN_ROW];
35 | 
36 | pub fn new_layers(layers: usize) -> IntermediateLayers {
37 |     let actual_num_layers = layers;
38 |     // Note: why construct it like this?
39 |     // Because don't want to construct KanataLayers on the stack.
40 |     // The stack will overflow because of lack of placement new.
41 |     let mut layers = Vec::with_capacity(actual_num_layers);
42 |     for _ in 0..actual_num_layers {
43 |         layers.push([[DEFAULT_ACTION; KEYS_IN_ROW], [DEFAULT_ACTION; KEYS_IN_ROW]]);
44 |     }
45 |     layers.into_boxed_slice()
46 | }
47 | 
48 | impl KanataLayers {
49 |     /// # Safety
50 |     ///
51 |     /// The allocations must hold all of the &'static pointers found in layers.
52 |     pub(crate) unsafe fn new(layers: KLayers, allocations: Arc<Allocations>) -> Self {
53 |         Self {
54 |             layers,
55 |             _allocations: allocations,
56 |         }
57 |     }
58 | 
59 |     pub(crate) fn get(&self) -> (KLayers, Arc<Allocations>) {
60 |         (self.layers, self._allocations.clone())
61 |     }
62 | }
63 | 


--------------------------------------------------------------------------------
/parser/src/lib.rs:
--------------------------------------------------------------------------------
 1 | //! A parser for configuration language of [kanata](https://github.com/jtroo/kanata), a keyboard remapper.
 2 | 
 3 | pub mod cfg;
 4 | pub mod custom_action;
 5 | pub mod keys;
 6 | pub mod layers;
 7 | pub mod lsp_hints;
 8 | pub mod sequences;
 9 | pub mod subset;
10 | pub mod trie;
11 | 


--------------------------------------------------------------------------------
/parser/src/lsp_hints.rs:
--------------------------------------------------------------------------------
 1 | pub use inner::*;
 2 | 
 3 | #[cfg(not(feature = "lsp"))]
 4 | mod inner {
 5 |     #[derive(Debug, Default)]
 6 |     pub struct LspHints {}
 7 | }
 8 | 
 9 | #[cfg(feature = "lsp")]
10 | mod inner {
11 |     use crate::cfg::sexpr::{Span, Spanned};
12 |     type HashMap<K, V> = rustc_hash::FxHashMap<K, V>;
13 | 
14 |     #[derive(Debug, Default)]
15 |     pub struct LspHints {
16 |         pub inactive_code: Vec<InactiveCode>,
17 |         pub definition_locations: DefinitionLocations,
18 |         pub reference_locations: ReferenceLocations,
19 |     }
20 | 
21 |     #[derive(Debug, Clone)]
22 |     pub struct InactiveCode {
23 |         pub span: Span,
24 |         pub reason: String,
25 |     }
26 | 
27 |     #[derive(Debug, Default, Clone)]
28 |     pub struct DefinitionLocations {
29 |         pub alias: HashMap<String, Span>,
30 |         pub variable: HashMap<String, Span>,
31 |         pub virtual_key: HashMap<String, Span>,
32 |         pub layer: HashMap<String, Span>,
33 |         pub template: HashMap<String, Span>,
34 |     }
35 | 
36 |     #[derive(Debug, Default, Clone)]
37 |     pub struct ReferenceLocations {
38 |         pub alias: ReferencesMap,
39 |         pub variable: ReferencesMap,
40 |         pub virtual_key: ReferencesMap,
41 |         pub layer: ReferencesMap,
42 |         pub template: ReferencesMap,
43 |         pub include: ReferencesMap,
44 |     }
45 | 
46 |     #[derive(Debug, Default, Clone)]
47 |     pub struct ReferencesMap(pub HashMap<String, Vec<Span>>);
48 | 
49 |     #[allow(unused)]
50 |     impl ReferencesMap {
51 |         pub(crate) fn push_from_atom(&mut self, atom: &Spanned<String>) {
52 |             match self.0.get_mut(&atom.t) {
53 |                 Some(refs) => refs.push(atom.span.clone()),
54 |                 None => {
55 |                     self.0.insert(atom.t.clone(), vec![atom.span.clone()]);
56 |                 }
57 |             };
58 |         }
59 | 
60 |         pub(crate) fn push(&mut self, name: &str, span: Span) {
61 |             match self.0.get_mut(name) {
62 |                 Some(refs) => refs.push(span),
63 |                 None => {
64 |                     self.0.insert(name.to_owned(), vec![span]);
65 |                 }
66 |             };
67 |         }
68 |     }
69 | }
70 | 


--------------------------------------------------------------------------------
/parser/src/sequences.rs:
--------------------------------------------------------------------------------
 1 | use kanata_keyberon::key_code::KeyCode;
 2 | 
 3 | pub const MASK_KEYCODES: u16 = 0x03FF;
 4 | pub const MASK_MODDED: u16 = 0xFC00;
 5 | pub const KEY_OVERLAP: KeyCode = KeyCode::ErrorRollOver;
 6 | pub const KEY_OVERLAP_MARKER: u16 = 0x0400;
 7 | 
 8 | pub fn mod_mask_for_keycode(kc: KeyCode) -> u16 {
 9 |     use KeyCode::*;
10 |     match kc {
11 |         LShift | RShift => 0x8000,
12 |         LCtrl | RCtrl => 0x4000,
13 |         LAlt => 0x2000,
14 |         RAlt => 0x1000,
15 |         LGui | RGui => 0x0800,
16 |         // This is not real... this is a marker to help signify that key presses should be
17 |         // overlapping. The way this will look in the chord sequence is as such:
18 |         //
19 |         //   [ (0x0400 | X), (0x0400 | Y), (0x0400) ]
20 |         ErrorRollOver => KEY_OVERLAP_MARKER,
21 |         _ => 0,
22 |     }
23 | }
24 | 
25 | #[test]
26 | fn keys_fit_within_mask() {
27 |     use crate::keys::OsCode;
28 |     assert!(MASK_KEYCODES >= u16::from(OsCode::KEY_MAX));
29 | }
30 | 


--------------------------------------------------------------------------------
/parser/src/trie.rs:
--------------------------------------------------------------------------------
 1 | //! Wrapper around a trie type for (hopefully) easier swapping of libraries if desired.
 2 | 
 3 | use bytemuck::cast_slice;
 4 | use patricia_tree::map::PatriciaMap;
 5 | 
 6 | pub type TrieKeyElement = u16;
 7 | 
 8 | #[derive(Debug, Clone)]
 9 | pub struct Trie<T> {
10 |     inner: patricia_tree::map::PatriciaMap<T>,
11 | }
12 | 
13 | #[derive(Debug, Copy, Clone, PartialEq, Eq)]
14 | pub enum GetOrDescendentExistsResult<T> {
15 |     NotInTrie,
16 |     InTrie,
17 |     HasValue(T),
18 | }
19 | 
20 | use GetOrDescendentExistsResult::*;
21 | 
22 | impl<T> Default for Trie<T> {
23 |     fn default() -> Self {
24 |         Self::new()
25 |     }
26 | }
27 | 
28 | fn key_len(k: impl AsRef<[u16]>) -> usize {
29 |     debug_assert!(std::mem::size_of::<TrieKeyElement>() == 2 * std::mem::size_of::<u8>());
30 |     k.as_ref().len() * 2
31 | }
32 | 
33 | impl<T> Trie<T> {
34 |     pub fn new() -> Self {
35 |         Self {
36 |             inner: PatriciaMap::new(),
37 |         }
38 |     }
39 | 
40 |     pub fn ancestor_exists(&self, key: impl AsRef<[u16]>) -> bool {
41 |         self.inner
42 |             .get_longest_common_prefix(cast_slice(key.as_ref()))
43 |             .is_some()
44 |     }
45 | 
46 |     pub fn descendant_exists(&self, key: impl AsRef<[u16]>) -> bool {
47 |         // Length of the [u8] interpretation of the [u16] key is doubled.
48 |         self.inner
49 |             .longest_common_prefix_len(cast_slice(key.as_ref()))
50 |             == key_len(key)
51 |     }
52 | 
53 |     pub fn insert(&mut self, key: impl AsRef<[u16]>, val: T) {
54 |         self.inner.insert(cast_slice(key.as_ref()), val);
55 |     }
56 | 
57 |     pub fn get_or_descendant_exists(&self, key: impl AsRef<[u16]>) -> GetOrDescendentExistsResult<T>
58 |     where
59 |         T: Clone,
60 |     {
61 |         let mut descendants = self.inner.iter_prefix(cast_slice(key.as_ref()));
62 |         match descendants.next() {
63 |             None => NotInTrie,
64 |             Some(descendant) => {
65 |                 if descendant.0.len() == key_len(key.as_ref()) {
66 |                     HasValue(descendant.1.clone())
67 |                 } else {
68 |                     InTrie
69 |                 }
70 |             }
71 |         }
72 |     }
73 | 
74 |     pub fn is_empty(&self) -> bool {
75 |         self.inner.is_empty()
76 |     }
77 | }
78 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/all_keys_in_defsrc.kbd:
--------------------------------------------------------------------------------
  1 | (defcfg)
  2 | 
  3 | (defsrc
  4 |   grv
  5 |   1
  6 |   2
  7 |   3
  8 |   4
  9 |   5
 10 |   6
 11 |   7
 12 |   8
 13 |   9
 14 |   0
 15 |   min
 16 |   eql
 17 |   bspc
 18 |   tab
 19 |   q
 20 |   w
 21 |   e
 22 |   r
 23 |   t
 24 |   y
 25 |   u
 26 |   i
 27 |   o
 28 |   p
 29 |   {
 30 |   }
 31 |   \
 32 |   caps
 33 |   a
 34 |   s
 35 |   d
 36 |   f
 37 |   g
 38 |   h
 39 |   j
 40 |   k
 41 |   l
 42 |   scln
 43 |   '
 44 |   ret
 45 |   lshift
 46 |   z
 47 |   x
 48 |   c
 49 |   v
 50 |   b
 51 |   n
 52 |   m
 53 |   comm
 54 |   .
 55 |   /
 56 |   kp=
 57 |   kp0
 58 |   kp1
 59 |   kp2
 60 |   kp3
 61 |   kp4
 62 |   kp5
 63 |   kp6
 64 |   kp7
 65 |   kp8
 66 |   kp9
 67 |   kprt
 68 |   kp/
 69 |   kp+
 70 |   kp*
 71 |   kp-
 72 |   kp.
 73 |   102d
 74 |   scrlck
 75 |   pause
 76 |   wkup
 77 |   esc
 78 |   rshift
 79 |   lctrl
 80 |   lalt
 81 |   spc
 82 |   ralt
 83 |   comp
 84 |   lmeta
 85 |   rmeta
 86 |   rctrl
 87 |   del
 88 |   ins
 89 |   bck
 90 |   fwd
 91 |   pgup
 92 |   pgdn
 93 |   up
 94 |   down
 95 |   lft
 96 |   rght
 97 |   home
 98 |   end
 99 |   nlck
100 |   mute
101 |   volu
102 |   voldwn
103 |   brup
104 |   brdown
105 |   blup
106 |   bldn
107 |   next
108 |   pp
109 |   prev
110 |   f1
111 |   f2
112 |   f3
113 |   f4
114 |   f5
115 |   f6
116 |   f7
117 |   f8
118 |   f9
119 |   f10
120 |   f11
121 |   f12
122 |   f13
123 |   f14
124 |   f15
125 |   f16
126 |   f17
127 |   f18
128 |   f19
129 |   f20
130 |   f21
131 |   f22
132 |   f23
133 |   f24
134 | )
135 | 
136 | (deflayer base
137 |   grv
138 |   1
139 |   2
140 |   3
141 |   4
142 |   5
143 |   6
144 |   7
145 |   8
146 |   9
147 |   0
148 |   min
149 |   eql
150 |   bspc
151 |   tab
152 |   q
153 |   w
154 |   e
155 |   r
156 |   t
157 |   y
158 |   u
159 |   i
160 |   o
161 |   p
162 |   {
163 |   }
164 |   \
165 |   caps
166 |   a
167 |   s
168 |   d
169 |   f
170 |   g
171 |   h
172 |   j
173 |   k
174 |   l
175 |   scln
176 |   '
177 |   ret
178 |   lshift
179 |   z
180 |   x
181 |   c
182 |   v
183 |   b
184 |   n
185 |   m
186 |   comm
187 |   .
188 |   /
189 |   kp=
190 |   kp0
191 |   kp1
192 |   kp2
193 |   kp3
194 |   kp4
195 |   kp5
196 |   kp6
197 |   kp7
198 |   kp8
199 |   kp9
200 |   kprt
201 |   kp/
202 |   kp+
203 |   kp*
204 |   kp-
205 |   kp.
206 |   102d
207 |   scrlck
208 |   pause
209 |   wkup
210 |   esc
211 |   rshift
212 |   lctrl
213 |   lalt
214 |   spc
215 |   ralt
216 |   comp
217 |   lmeta
218 |   rmeta
219 |   rctrl
220 |   del
221 |   ins
222 |   bck
223 |   fwd
224 |   pgup
225 |   pgdn
226 |   up
227 |   down
228 |   lft
229 |   rght
230 |   home
231 |   end
232 |   nlck
233 |   mute
234 |   volu
235 |   voldwn
236 |   brup
237 |   brdown
238 |   blup
239 |   bldn
240 |   next
241 |   pp
242 |   prev
243 |   f1
244 |   f2
245 |   f3
246 |   f4
247 |   f5
248 |   f6
249 |   f7
250 |   f8
251 |   f9
252 |   f10
253 |   f11
254 |   f12
255 |   f13
256 |   f14
257 |   f15
258 |   f16
259 |   f17
260 |   f18
261 |   f19
262 |   f20
263 |   f21
264 |   f22
265 |   f23
266 |   f24
267 | )
268 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/ancestor_seq.kbd:
--------------------------------------------------------------------------------
 1 | (defcfg)
 2 | 
 3 | (defsrc a b c)
 4 | 
 5 | (deflayer base _ _ _)
 6 | 
 7 | (deffakekeys a a)
 8 | 
 9 | (defseq a (a b c))
10 | (defseq a (a b))
11 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/bad_multi.kbd:
--------------------------------------------------------------------------------
1 | (defcfg)
2 | (defsrc 1)
3 | (deflayer base (multi (tap-hold 1 1 a b) (tap-dance 1 (a b c))))
4 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/descendant_seq.kbd:
--------------------------------------------------------------------------------
 1 | (defcfg)
 2 | 
 3 | (defsrc a b c)
 4 | 
 5 | (deflayer base _ _ _)
 6 | 
 7 | (deffakekeys a a)
 8 | 
 9 | (defseq a (a b))
10 | (defseq a (a b c))
11 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/icon_bad_dupe.kbd:
--------------------------------------------------------------------------------
1 | ;; This config file is invalid and should be rejected
2 | (defcfg)
3 | (defsrc 1)
4 | (deflayer	(base	icon base.png 🖻 n.ico	) 1)
5 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/icon_good.kbd:
--------------------------------------------------------------------------------
1 | (defcfg)
2 | (defsrc 1)
3 | (deflayer	(base       	icon base.png   	) 1)
4 | (deflayer	(1emoji     	🖻 1symbols.png  	) 1)
5 | (deflayer	(2icon-quote	🖻 "2Nav Num.png"	) 1)
6 | (deflayer	(3emoji_alt 	🖼 3trans.parent 	) 1)
7 | (deflayermap	(4layermap 	🖼 3trans.parent 	) 0 0)
8 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/include-bad.kbd:
--------------------------------------------------------------------------------
1 | (defsrc a)
2 | (include included-bad.kbd)
3 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/include-bad2.kbd:
--------------------------------------------------------------------------------
1 | (defsrc a)
2 | (include included-bad2.kbd)
3 | (defalias no-action-uh-oh)
4 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/include-good-optional-absent.kbd:
--------------------------------------------------------------------------------
1 | (defsrc a)
2 | (include included-non-existing-file.kbd)
3 | (include included-good.kbd)
4 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/include-good.kbd:
--------------------------------------------------------------------------------
1 | (defsrc a)
2 | (include included-good.kbd)
3 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/included-bad.kbd:
--------------------------------------------------------------------------------
1 | (deflayer not-enough-elements)
2 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/included-bad2.kbd:
--------------------------------------------------------------------------------
1 | (deflayer base a)
2 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/included-good.kbd:
--------------------------------------------------------------------------------
1 | (deflayer base a)
2 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/macro-chord-dont-panic.kbd:
--------------------------------------------------------------------------------
1 | (defsrc a)
2 | (deflayer test (macro @ch|bas))
3 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/multiline_comment.kbd:
--------------------------------------------------------------------------------
 1 | (defcfg)
 2 | 
 3 | #|
 4 | 
 5 | Top level multi-line comment
 6 | Hello world
 7 | 
 8 | |#
 9 | 
10 | (defsrc #||# a #| |# b #|  |# c #|   |#)
11 | 
12 | (deflayer
13 | base _
14 | 
15 | #| --------------------------------------------------------------------------
16 | Quick reference: https://github.com/jtroo/kanata/blob/main/cfg_samples/kanata.kbd
17 | List of keycodes: https://github.com/kmonad/kmonad/blob/master/src/KMonad/Keyboard/Keycode.hs
18 | -------------------------------------------------------------------------- |#
19 | _
20 | #| inner multi-line comment block
21 | 1
22 | 2
23 | 3|#
24 | #|  |#
25 | #| |#
26 | #||#
27 | #||##||##| |##|  |#
28 | _
29 | )
30 | 
31 | #|
32 | #| || # #|
33 | |#
34 | 
35 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/nested_tap_hold.kbd:
--------------------------------------------------------------------------------
 1 | (defcfg
 2 |   linux-dev /dev/input/by-path/platform-i8042-serio-0-event-kbd
 3 | )
 4 | 
 5 | (defsrc
 6 |        a
 7 | )
 8 | 
 9 | ;; Note: this config file is invalid and should be rejected
10 | (deflayer test
11 |        (tap-hold 200 200 (tap-hold 200 200 a b) c)
12 | )
13 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/test.zch:
--------------------------------------------------------------------------------
1 | dy	day
2 | dy 1	Monday
3 |  abc	Alphabet
4 | r df	recipient
5 |  w  a	Washington
6 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/testzch.kbd:
--------------------------------------------------------------------------------
1 | (defsrc)
2 | (deflayer base)
3 | (defzippy test.zch)
4 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/unknown_defcfg_opt.kbd:
--------------------------------------------------------------------------------
1 | (defcfg
2 |   this-should-error yes
3 | )
4 | 
5 | (defsrc)
6 | (deflayer base)
7 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/utf8bom-included.kbd:
--------------------------------------------------------------------------------
 1 | #|
 2 | 
 3 | BOM should have been added via:
 4 | 
 5 |   $f = Get-Content .\utf8bom-included.kbd
 6 |   $f | Out-File -Encoding UTF8 .\utf8bom-included.kbd
 7 | 
 8 | |#
 9 | 
10 | (deflayermap (layer-included)
11 |   y (unicode 🚀)
12 | )
13 | 


--------------------------------------------------------------------------------
/parser/test_cfgs/utf8bom.kbd:
--------------------------------------------------------------------------------
 1 | #|
 2 | 
 3 | BOM should have been added via:
 4 | 
 5 |   $f = Get-Content .\utf8bom.kbd
 6 |   $f | Out-File -Encoding UTF8 .\utf8bom.kbd
 7 | 
 8 | |#
 9 | 
10 | (defsrc)
11 | (deflayermap (layer-name)
12 |   x (unicode 🙂)
13 | )
14 | 
15 | (include utf8bom-included.kbd)
16 | 


--------------------------------------------------------------------------------
/simulated_input/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name = "kanata-sim"
 3 | version = "0.1.0"
 4 | authors = ["jtroo <j.andreitabs@gmail.com>"]
 5 | description = "Simulated input using kanata"
 6 | keywords = ["kanata", "input", "simulated"]
 7 | homepage = "https://github.com/jtroo/kanata"
 8 | repository = "https://github.com/jtroo/kanata"
 9 | readme = "README.md"
10 | license = "LGPL-3.0"
11 | edition = "2021"
12 | 
13 | [[bin]]
14 | name = "kanata_simulated_input"
15 | path = "src/sim.rs"
16 | 
17 | [dependencies]
18 | anyhow = "1"
19 | clap = { version = "4", features = [ "std", "derive", "help", "suggestions" ], default-features = false }
20 | dirs = "5.0.1"
21 | log = { version = "0.4.8", default-features = false }
22 | simplelog = "0.12.0"
23 | time = "0.3.36"
24 | 
25 | kanata = { path = ".." , default-features = false }
26 | 
27 | [features]
28 | default = ["simulated_output", "tcp_server"]
29 | simulated_output = ["kanata/simulated_output"]
30 | simulated_input = ["kanata/simulated_input"]
31 | passthru_ahk = ["simulated_input","simulated_output"]
32 | tcp_server = ["kanata/tcp_server"]
33 | 


--------------------------------------------------------------------------------
/simulated_input/README.md:
--------------------------------------------------------------------------------
 1 | # Kanata simulated input
 2 | 
 3 | A CLI tool that lets you run simulated kanata input.
 4 | 
 5 | Use the `-c` flag to specify a kanata configuration file
 6 | and the `-s` flag to specify an input simulation file.
 7 | You can pass the `--help` flag for more details.
 8 | 
 9 | The input file format is described in the
10 | [guide](https://github.com/jtroo/kanata/blob/main/docs/config.adoc#test-your-config).
11 | 
12 | 


--------------------------------------------------------------------------------
/simulated_passthru/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name   	= "simulated_passthru"
 3 | version	= "0.0.1"
 4 | edition	= "2021"
 5 | 
 6 | [lib]
 7 | name      	= "kanata_passthru"
 8 | path      	= "src/lib_passthru.rs"
 9 | crate-type	= ['lib','cdylib']
10 | 
11 | [dependencies]
12 | anyhow = "1"
13 | log = { version = "0.4.8", default-features = false }
14 | parking_lot = "0.12"
15 | regex = "1.10.3"
16 | 
17 | kanata = {path=".." , default-features=false}
18 | 
19 | lazy_static = "1.4.0"
20 | 
21 | [target.'cfg(target_os = "windows")'.dependencies]
22 | encode_unicode = "0.3.6"
23 | winapi = { version = "0.3.9", features = [
24 |     "wincon",
25 |     "timeapi",
26 |     "mmsystem",
27 | ] }
28 | native-windows-gui = { version = "1.0.12", default-features = false }
29 | kanata-interception = { version = "0.3.0", optional = true }
30 | win_dbg_logger = "0.1.0"
31 | widestring = "1.1.0"
32 | 
33 | [features]
34 | default         	= ["simulated_output","tcp_server"]
35 | tcp_server      	= ["kanata/tcp_server"]
36 | simulated_output	= ["kanata/simulated_output"]
37 | simulated_input 	= ["kanata/simulated_input"]
38 | passthru_ahk    	= ["simulated_input","simulated_output","kanata/passthru_ahk"]
39 | perf_logging    	= []
40 | 


--------------------------------------------------------------------------------
/simulated_passthru/ReadMe.md:
--------------------------------------------------------------------------------
1 | # Kanata passthru (simulated input and output)
2 | 
3 | A Windows dynamic library (DLL) that lets you run simulated kanata input and get simulated kanata output.
4 | 
5 | See a simplified [example](./../docs/simulated_passthru_ahk/) of using AutoHotkey to redirect custom inputhook's key events to kanata and get them remapped using kanata config, e.g., to get better modtap functionality
6 | 


--------------------------------------------------------------------------------
/simulated_passthru/src/key_in.rs:
--------------------------------------------------------------------------------
 1 | use kanata_state_machine::oskbd::*;
 2 | use log::*;
 3 | 
 4 | use winapi::ctypes::*;
 5 | use winapi::shared::minwindef::*;
 6 | 
 7 | use crate::oskbd::HOOK_CB;
 8 | 
 9 | /// Exported function: receives key input and uses event_loop's input event handler
10 | /// callback (which will in turn communicate via the internal kanata's channels to
11 | /// keyberon state machine etc.)
12 | #[no_mangle]
13 | pub extern "win64" fn input_ev_listener(vk: c_uint, sc: c_uint, up: c_int) -> LRESULT {
14 |     #[cfg(feature = "perf_logging")]
15 |     let start = std::time::Instant::now();
16 |     let key_event = InputEvent::from_vk_sc(vk, sc, up); //{code:KEY_0,value:Press}
17 |     let mut h_cbl = HOOK_CB.lock(); // to access the closure we move its box out of the mutex
18 |                                     // and put it back after it returned
19 |     if let Some(mut fnhook) = h_cbl.take() {
20 |         // move our opt+boxed closure, replacing it with None, can't just .unwrap since Copy
21 |         // trait not implemented for dyn fnMut
22 |         let handled = fnhook(key_event); // box(closure)() = closure()
23 |         *h_cbl = Some(fnhook); // put our closure back
24 |         if handled {
25 |             // now try to get the out key events that another thread should've sent via
26 |             #[cfg(feature = "perf_logging")]
27 |             debug!(
28 |                 " 🕐{}μs   →→→✓ {key_event} from {vk} sc={sc} up={up}",
29 |                 (start.elapsed()).as_micros()
30 |             );
31 |             #[cfg(not(feature = "perf_logging"))]
32 |             debug!("   →→→✓ {key_event} from {vk} sc={sc} up={up}");
33 |             1
34 |         } else {
35 |             0
36 |         }
37 |     } else {
38 |         error!("fnHook processing key events isn't available yet {key_event} from {vk} sc={sc} up={up}");
39 |         0
40 |     }
41 | }
42 | 


--------------------------------------------------------------------------------
/simulated_passthru/src/key_out.rs:
--------------------------------------------------------------------------------
  1 | use anyhow::Result;
  2 | 
  3 | use kanata_state_machine::oskbd::*;
  4 | use log::*;
  5 | 
  6 | use winapi::ctypes::*;
  7 | use winapi::shared::minwindef::*;
  8 | 
  9 | use std::cell::Cell;
 10 | 
 11 | type CbOutEvFn = dyn Fn(i64, i64, i64) -> i64 + 'static;
 12 | thread_local! {static CBOUTEV_WRAP:Cell<Option<Box<CbOutEvFn>>> = Cell::default();}
 13 | // Stores the hook callback for the current thread
 14 | 
 15 | /// - Get the address of AutoHotkey's callback function that accepts simulated output
 16 | /// events (and sends them to the OS)
 17 | ///   - `cbKanataOut(vk,sc,up) {return 1}` All args are i64 (AHK doesn't support u64)
 18 | /// - Store it in a static thread-local Cell (AHK is single-threaded, so we can only
 19 | ///  use this callback from the main thread). KbdOut will use a channel to send a
 20 | ///  message key event that will use call the fn from this Cell
 21 | /// address: pointer-sized integer, equivalent to Int64 on ahk64 (c_longlong=i64).
 22 | /// Will be `as`-cast to a raw pointer before `transmute`ing to a function pointer to avoid
 23 | ///  an integer-to-pointer `transmute`, which can be problematic. Transmuting between raw pointers
 24 | ///  and function pointers (i.e., two pointer types) is fine.
 25 | /// AHK uses x64 calling convention: TODO: is this the same as win64? extern "C" also seems to work?
 26 | #[cfg(feature = "passthru_ahk")]
 27 | pub fn set_cb_out_ev(cb_addr: c_longlong) -> Result<()> {
 28 |     trace!("got func address {}", cb_addr);
 29 |     let ptr_fn = cb_addr as *const ();
 30 |     let cb_out_ev =
 31 |         unsafe { std::mem::transmute::<*const (), fn(vk: i64, sc: i64, up: i64) -> i64>(ptr_fn) };
 32 |     CBOUTEV_WRAP.with(|state| {
 33 |         assert!(
 34 |             state.take().is_none(),
 35 |             "Only 1 callback can be registered per thread"
 36 |         );
 37 |         state.set(Some(Box::new(cb_out_ev)));
 38 |     });
 39 |     Ok(())
 40 | }
 41 | #[cfg(not(feature = "passthru_ahk"))]
 42 | fn set_cb_out_ev(cb_addr: c_longlong) -> Result<()> {
 43 |     debug!("✗✗✗✗ unimplemented!");
 44 |     unimplemented!();
 45 |     Ok(())
 46 | }
 47 | 
 48 | pub fn send_out_ev(in_ev: InputEvent) -> Result<()> {
 49 |     // ext callback accepts vk:i64,sc:i64,up:i64
 50 |     #[cfg(feature = "perf_logging")]
 51 |     let start = std::time::Instant::now();
 52 |     let key_event = KeyEvent::try_from(in_ev);
 53 |     debug!("@send_out_ev key_event={key_event:?}");
 54 |     let vk: i64 = in_ev.code.into();
 55 |     let sc: i64 = 0;
 56 |     let up: i64 = in_ev.up.into();
 57 | 
 58 |     let mut handled = 0i64;
 59 |     CBOUTEV_WRAP.with(|state| {
 60 |         if let Some(hook) = state.take() {
 61 |             handled = hook(vk, sc, up);
 62 |             state.set(Some(hook));
 63 |         }
 64 |     });
 65 |     #[cfg(feature = "perf_logging")]
 66 |     debug!(
 67 |         "🕐{}μs ←←←{} fnHookCC {key_event:?} {vk} {sc} {up}",
 68 |         (start.elapsed()).as_micros(),
 69 |         if handled == 1 { "✓" } else { "✗" }
 70 |     );
 71 |     #[cfg(not(feature = "perf_logging"))]
 72 |     debug!(
 73 |         "←←←{} fnHookCC {key_event:?} {vk} {sc} {up}",
 74 |         if handled == 1 { "✓" } else { "✗" }
 75 |     );
 76 |     Ok(())
 77 | }
 78 | 
 79 | use crate::RX_KEY_EV_OUT;
 80 | use std::sync::mpsc::TryRecvError; // thread_local Cell<Option<Receiver<InputEvent>>>
 81 |                                    // Stores receiver for key data to be sent out for
 82 |                                    // the current thread
 83 | /// Exported function: checks if processing thread has sent key output and sends it
 84 | /// back to an external callback
 85 | #[no_mangle]
 86 | pub extern "win64" fn output_ev_check() -> LRESULT {
 87 |     let mut res: isize = 0;
 88 |     RX_KEY_EV_OUT.with(|state| {
 89 |         if let Some(rx) = state.take() {
 90 |             match rx.try_recv() {
 91 |                 Ok(in_ev) => {
 92 |                     debug!("✓ rx_kout@key_out(dll) ‘{in_ev}’");
 93 |                     if send_out_ev(in_ev).is_ok() {
 94 |                         res = 0;
 95 |                     } else {
 96 |                         res = -1;
 97 |                     };
 98 |                 }
 99 |                 Err(TryRecvError::Empty) => {
100 |                     debug!("✗ rx_kout@key_out(dll) no data yet");
101 |                     res = -2
102 |                 }
103 |                 Err(TryRecvError::Disconnected) => {
104 |                     debug!("✗ rx_kout@key_out(dll) Disconnected");
105 |                     res = -3
106 |                 }
107 |             }
108 |             state.set(Some(rx));
109 |         } else {
110 |             debug!("✗ RX_KEY_EV_OUT@key_out(dll) empty");
111 |             state.set(None);
112 |             res = -4
113 |         }
114 |     });
115 |     res
116 | }
117 | 


--------------------------------------------------------------------------------
/simulated_passthru/src/log_win.rs:
--------------------------------------------------------------------------------
  1 | //! A logger that prints to OutputDebugString (Windows only)
  2 | use log::{Level, Metadata, Record};
  3 | 
  4 | /// Implements `log::Log`, so can be used as a logging provider to
  5 | /// forward log messages to the Windows `OutputDebugString` API
  6 | pub struct WinDebugLogger;
  7 | 
  8 | /// Static instance of `WinDebugLogger`, can be directly registered using `log::set_logger`<br>
  9 | /// ```
 10 | /// use kanata_passthru::log_win;
 11 | /// let _ = log_win::init(); // Init
 12 | /// log::set_max_level(log::LevelFilter::Debug);
 13 | /// use log::debug; // Use
 14 | /// debug!("Debug log");
 15 | /// ```
 16 | pub static WINDBG_LOGGER: WinDebugLogger = WinDebugLogger;
 17 | 
 18 | /// Convert logging levels to shorter and more visible icons
 19 | pub fn iconify(lvl: log::Level) -> char {
 20 |     match lvl {
 21 |         Level::Error => '❗',
 22 |         Level::Warn => '⚠',
 23 |         Level::Info => 'ⓘ',
 24 |         Level::Debug => 'ⓓ',
 25 |         Level::Trace => 'ⓣ',
 26 |     }
 27 | }
 28 | 
 29 | use std::sync::OnceLock;
 30 | pub fn is_thread_state() -> &'static bool {
 31 |     set_thread_state(false)
 32 | }
 33 | pub fn set_thread_state(is: bool) -> &'static bool {
 34 |     // accessor function to avoid get_or_init on every call (lazycell
 35 |     // allows doing that without an extra function)
 36 |     static CELL: OnceLock<bool> = OnceLock::new();
 37 |     CELL.get_or_init(|| is)
 38 | }
 39 | 
 40 | use lazy_static::lazy_static;
 41 | use regex::Regex;
 42 | lazy_static! { // shorten source file name, no src/ no .rs ext
 43 |   static ref RE_EXT:Regex = Regex::new(r"\..*
quot;   ).unwrap();
 44 |   static ref RE_SRC:Regex = Regex::new(r"src[\\/]").unwrap();
 45 | }
 46 | fn clean_name(path: Option<&str>) -> String {
 47 |     if let Some(p) = path {
 48 |         RE_SRC.replace(&RE_EXT.replace(p, ""), "").to_string()
 49 |     } else {
 50 |         "?".to_string()
 51 |     }
 52 | }
 53 | 
 54 | #[cfg(target_os = "windows")]
 55 | use winapi::um::processthreadsapi::GetCurrentThreadId;
 56 | impl log::Log for WinDebugLogger {
 57 |     #[cfg(windows)]
 58 |     fn enabled(&self, _metadata: &Metadata) -> bool {
 59 |         true
 60 |     }
 61 |     #[cfg(not(windows))]
 62 |     fn enabled(&self, metadata: &Metadata) -> bool {
 63 |         false
 64 |     }
 65 |     fn log(&self, record: &Record) {
 66 |         #[cfg(not(target_os = "windows"))]
 67 |         let thread_id = "";
 68 |         #[cfg(target_os = "windows")]
 69 |         let thread_id = if *is_thread_state() {
 70 |             format!("¦{}¦", unsafe { GetCurrentThreadId() })
 71 |         } else {
 72 |             "".to_string()
 73 |         };
 74 |         if self.enabled(record.metadata()) {
 75 |             let s = format!(
 76 |                 "{}{}{}:{} {}",
 77 |                 thread_id,
 78 |                 iconify(record.level()),
 79 |                 clean_name(record.file()),
 80 |                 record.line().unwrap_or(0),
 81 |                 record.args()
 82 |             );
 83 |             dbg_win(&s);
 84 |         }
 85 |     }
 86 |     fn flush(&self) {}
 87 | }
 88 | 
 89 | pub fn dbg_win(s: &str) {
 90 |     //! Calls the `OutputDebugString` API to log a string (on Windows only)<br>
 91 |     //! See [`OutputDebugStringW`](https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-outputdebugstringw).
 92 |     #[cfg(windows)]
 93 |     {
 94 |         let len = s.encode_utf16().count() + 1;
 95 |         let mut s_utf16: Vec<u16> = Vec::with_capacity(len + 1);
 96 |         s_utf16.extend(s.encode_utf16());
 97 |         s_utf16.push(0);
 98 |         unsafe {
 99 |             OutputDebugStringW(&s_utf16[0]);
100 |         }
101 |     }
102 | }
103 | 
104 | #[cfg(windows)]
105 | extern "stdcall" {
106 |     fn OutputDebugStringW(chars: *const u16);
107 | }
108 | 
109 | pub fn init() {
110 |     //! Set `WinDebugLogger` as the active logger<br>
111 |     //! Doesn't panic on failure as it creates other problems for FFI etc.
112 |     match log::set_logger(&WINDBG_LOGGER) {
113 |         Ok(()) => {}
114 |         Err(_) => {
115 |             dbg_win("Warning: ✗ Failed to register WinDebugLogger\n");
116 |         }
117 |     }
118 | }
119 | 


--------------------------------------------------------------------------------
/src/gui/mod.rs:
--------------------------------------------------------------------------------
 1 | pub mod win;
 2 | pub use win::*;
 3 | pub mod win_dbg_logger;
 4 | pub mod win_nwg_ext;
 5 | pub use win_dbg_logger as log_win;
 6 | pub use win_dbg_logger::WINDBG_LOGGER;
 7 | pub use win_nwg_ext::*;
 8 | 
 9 | use crate::*;
10 | use parking_lot::Mutex;
11 | use std::sync::mpsc::Sender as ASender;
12 | use std::sync::{Arc, OnceLock};
13 | pub static CFG: OnceLock<Arc<Mutex<Kanata>>> = OnceLock::new();
14 | pub static GUI_TX: OnceLock<native_windows_gui::NoticeSender> = OnceLock::new();
15 | pub static GUI_CFG_TX: OnceLock<native_windows_gui::NoticeSender> = OnceLock::new();
16 | pub static GUI_ERR_TX: OnceLock<native_windows_gui::NoticeSender> = OnceLock::new();
17 | pub static GUI_ERR_MSG_TX: OnceLock<ASender<(String, String)>> = OnceLock::new();
18 | pub static GUI_EXIT_TX: OnceLock<native_windows_gui::NoticeSender> = OnceLock::new();
19 | 


--------------------------------------------------------------------------------
/src/gui/win_dbg_logger/win_dbg_logger.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name = "win_dbg_logger"
 3 | version = "0.1.0"
 4 | authors = ["Arlie Davis <ardavis@microsoft.com>"]
 5 | edition = "2018"
 6 | license = "MIT OR Apache-2.0"
 7 | repository = "https://github.com/sivadeilra/win_dbg_logger"
 8 | description = "A logger for use with Windows debuggers."
 9 | 
10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
11 | 
12 | [dependencies]
13 | log      	= "0.4.*"
14 | winapi   	= {version="0.3.9", features=["processthreadsapi",]}
15 | regex    	= {version="1.10.4"}
16 | simplelog	= {version="0.12.0", optional=true}
17 | 
18 | [features]
19 | simple_shared	= ["simplelog"]
20 | 


--------------------------------------------------------------------------------
/src/gui/win_nwg_ext/license-MIT:
--------------------------------------------------------------------------------
 1 | The MIT License (MIT)
 2 | =====================
 3 | 
 4 | Copyright © `2024` `Niccolò Betto`
 5 | 
 6 | Permission is hereby granted, free of charge, to any person
 7 | obtaining a copy of this software and associated documentation
 8 | files (the “Software”), to deal in the Software without
 9 | restriction, including without limitation the rights to use,
10 | copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the
12 | Software is furnished to do so, subject to the following
13 | conditions:
14 | 
15 | The above copyright notice and this permission notice shall be
16 | included in all copies or substantial portions of the Software.
17 | 
18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25 | OTHER DEALINGS IN THE SOFTWARE.
26 | 
27 | 


--------------------------------------------------------------------------------
/src/gui/win_nwg_ext/license-nwg-MIT:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2019 Gabriel Dube
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/src/kanata.exe.manifest.rc:
--------------------------------------------------------------------------------
1 | #define RT_MANIFEST 24
2 | 1 RT_MANIFEST "./target/kanata.exe.manifest"
3 | iconMain ICON "../assets/kanata.ico"
4 | imgMain IMAGE "../assets/kanata.ico"
5 | imgReload IMAGE "../assets/reload_32px.png"
6 | 


--------------------------------------------------------------------------------
/src/kanata/caps_word.rs:
--------------------------------------------------------------------------------
 1 | use kanata_keyberon::key_code::KeyCode;
 2 | use rustc_hash::FxHashSet as HashSet;
 3 | 
 4 | use kanata_parser::custom_action::CapsWordCfg;
 5 | 
 6 | #[derive(Debug)]
 7 | pub struct CapsWordState {
 8 |     /// Keys that will trigger an `lsft` key to be added to the active keys if present in the
 9 |     /// currently active keys.
10 |     pub keys_to_capitalize: HashSet<KeyCode>,
11 |     /// An extra list of keys that should **not** terminate the caps_word state, in addition to
12 |     /// keys_to_capitalize, but which don't trigger a capitalization.
13 |     pub keys_nonterminal: HashSet<KeyCode>,
14 |     /// The configured timeout for caps_word.
15 |     pub timeout: u16,
16 |     /// The number of ticks remaining for caps_word, after which its state should be cleared. The
17 |     /// number of ticks gets reset back to `timeout` when `maybe_add_lsft` is called. The reason
18 |     /// for having this timeout at all is in case somebody was in the middle of typing a word, had
19 |     /// to go do something, and forgot that caps_word was active. Having this timeout means that
20 |     /// shift won't be active for their next keypress.
21 |     pub timeout_ticks: u16,
22 | }
23 | 
24 | #[derive(PartialEq, Eq, Debug, Clone, Copy)]
25 | pub enum CapsWordNextState {
26 |     Active,
27 |     End,
28 | }
29 | 
30 | use CapsWordNextState::*;
31 | 
32 | impl CapsWordState {
33 |     pub(crate) fn new(cfg: &CapsWordCfg) -> Self {
34 |         Self {
35 |             keys_to_capitalize: cfg.keys_to_capitalize.iter().copied().collect(),
36 |             keys_nonterminal: cfg.keys_nonterminal.iter().copied().collect(),
37 |             timeout: cfg.timeout,
38 |             timeout_ticks: cfg.timeout,
39 |         }
40 |     }
41 | 
42 |     pub(crate) fn maybe_add_lsft(&mut self, active_keys: &mut Vec<KeyCode>) -> CapsWordNextState {
43 |         if self.timeout_ticks == 0 {
44 |             return End;
45 |         }
46 |         for kc in active_keys.iter() {
47 |             if !self.keys_to_capitalize.contains(kc) && !self.keys_nonterminal.contains(kc) {
48 |                 return End;
49 |             }
50 |         }
51 |         if active_keys
52 |             .last()
53 |             .map(|kc| self.keys_to_capitalize.contains(kc))
54 |             .unwrap_or(false)
55 |         {
56 |             active_keys.insert(0, KeyCode::LShift);
57 |         }
58 |         if !active_keys.is_empty() {
59 |             self.timeout_ticks = self.timeout;
60 |         }
61 |         self.timeout_ticks = self.timeout_ticks.saturating_sub(1);
62 |         Active
63 |     }
64 | }
65 | 


--------------------------------------------------------------------------------
/src/kanata/cfg_forced.rs:
--------------------------------------------------------------------------------
 1 | //! Options in the configuration file that are overidden/forced to some value other than what's in
 2 | //! the configuration file, with the primary example being CLI arguments.
 3 | 
 4 | use std::sync::OnceLock;
 5 | 
 6 | static LOG_LAYER_CHANGES: OnceLock<bool> = OnceLock::new();
 7 | 
 8 | /// Force the log_layer_changes configuration to some value.
 9 | /// This can only be called up to once. Panics if called a second time.
10 | pub fn force_log_layer_changes(v: bool) {
11 |     LOG_LAYER_CHANGES
12 |         .set(v)
13 |         .expect("force cfg fns can only be called once");
14 | }
15 | 
16 | /// Get the forced log_layer_changes configuration if it was set.
17 | pub fn get_forced_log_layer_changes() -> Option<bool> {
18 |     LOG_LAYER_CHANGES.get().copied()
19 | }
20 | 


--------------------------------------------------------------------------------
/src/kanata/macos.rs:
--------------------------------------------------------------------------------
 1 | use super::*;
 2 | use anyhow::{anyhow, bail, Result};
 3 | use log::info;
 4 | use parking_lot::Mutex;
 5 | use std::convert::TryFrom;
 6 | use std::sync::mpsc::SyncSender as Sender;
 7 | use std::sync::Arc;
 8 | 
 9 | pub(crate) static PRESSED_KEYS: Lazy<Mutex<HashSet<OsCode>>> =
10 |     Lazy::new(|| Mutex::new(HashSet::default()));
11 | 
12 | impl Kanata {
13 |     /// Enter an infinite loop that listens for OS key events and sends them to the processing thread.
14 |     pub fn event_loop(kanata: Arc<Mutex<Self>>, tx: Sender<KeyEvent>) -> Result<()> {
15 |         info!("entering the event loop");
16 | 
17 |         let k = kanata.lock();
18 |         let allow_hardware_repeat = k.allow_hardware_repeat;
19 |         let mut kb = match KbdIn::new(k.include_names.clone(), k.exclude_names.clone()) {
20 |             Ok(kbd_in) => kbd_in,
21 |             Err(e) => bail!("failed to open keyboard device(s): {}", e),
22 |         };
23 |         drop(k);
24 | 
25 |         loop {
26 |             let event = kb.read().map_err(|e| anyhow!("failed read: {}", e))?;
27 | 
28 |             let mut key_event = match KeyEvent::try_from(event) {
29 |                 Ok(ev) => ev,
30 |                 _ => {
31 |                     // Pass-through unrecognized keys
32 |                     log::debug!("{event:?} is unrecognized!");
33 |                     let mut kanata = kanata.lock();
34 |                     kanata
35 |                         .kbd_out
36 |                         .write(event)
37 |                         .map_err(|e| anyhow!("failed write: {}", e))?;
38 |                     continue;
39 |                 }
40 |             };
41 | 
42 |             check_for_exit(&key_event);
43 | 
44 |             if key_event.value == KeyValue::Repeat && !allow_hardware_repeat {
45 |                 continue;
46 |             }
47 | 
48 |             if !MAPPED_KEYS.lock().contains(&key_event.code) {
49 |                 log::debug!("{key_event:?} is not mapped");
50 |                 let mut kanata = kanata.lock();
51 |                 kanata
52 |                     .kbd_out
53 |                     .write(event)
54 |                     .map_err(|e| anyhow!("failed write: {}", e))?;
55 |                 continue;
56 |             }
57 | 
58 |             log::debug!("sending {key_event:?} to processing loop");
59 | 
60 |             match key_event.value {
61 |                 KeyValue::Release => {
62 |                     PRESSED_KEYS.lock().remove(&key_event.code);
63 |                 }
64 |                 KeyValue::Press => {
65 |                     let mut pressed_keys = PRESSED_KEYS.lock();
66 |                     if pressed_keys.contains(&key_event.code) {
67 |                         key_event.value = KeyValue::Repeat;
68 |                     } else {
69 |                         pressed_keys.insert(key_event.code);
70 |                     }
71 |                 }
72 |                 _ => {}
73 |             }
74 |             tx.try_send(key_event)?;
75 |         }
76 |     }
77 | 
78 |     pub fn check_release_non_physical_shift(&mut self) -> Result<()> {
79 |         Ok(())
80 |     }
81 | }
82 | 


--------------------------------------------------------------------------------
/src/kanata/millisecond_counting.rs:
--------------------------------------------------------------------------------
 1 | pub struct MillisecondCountResult {
 2 |     pub last_tick: instant::Instant,
 3 |     pub ms_elapsed: u128,
 4 |     pub ms_remainder_in_ns: u128,
 5 | }
 6 | 
 7 | pub fn count_ms_elapsed(
 8 |     last_tick: instant::Instant,
 9 |     now: instant::Instant,
10 |     prev_ms_remainder_in_ns: u128,
11 | ) -> MillisecondCountResult {
12 |     const NS_IN_MS: u128 = 1_000_000;
13 |     let ns_elapsed = now.duration_since(last_tick).as_nanos();
14 |     let ns_elapsed_with_rem = ns_elapsed + prev_ms_remainder_in_ns;
15 |     let ms_elapsed = ns_elapsed_with_rem / NS_IN_MS;
16 |     let ms_remainder_in_ns = ns_elapsed_with_rem % NS_IN_MS;
17 | 
18 |     let last_tick = match ms_elapsed {
19 |         0 => last_tick,
20 |         _ => now,
21 |     };
22 |     MillisecondCountResult {
23 |         last_tick,
24 |         ms_elapsed,
25 |         ms_remainder_in_ns,
26 |     }
27 | }
28 | 
29 | #[test]
30 | fn ms_counts_0_elapsed_correctly() {
31 |     use std::time::Duration;
32 |     let last_tick = instant::Instant::now();
33 |     let now = last_tick + Duration::from_nanos(999999);
34 |     let result = count_ms_elapsed(last_tick, now, 0);
35 |     assert_eq!(0, result.ms_elapsed);
36 |     assert_eq!(last_tick, result.last_tick);
37 |     assert_eq!(999999, result.ms_remainder_in_ns);
38 | }
39 | 
40 | #[test]
41 | fn ms_counts_1_elapsed_correctly() {
42 |     use std::time::Duration;
43 |     let last_tick = instant::Instant::now();
44 |     let now = last_tick + Duration::from_nanos(1234567);
45 |     let result = count_ms_elapsed(last_tick, now, 0);
46 |     assert_eq!(1, result.ms_elapsed);
47 |     assert_eq!(now, result.last_tick);
48 |     assert_eq!(234567, result.ms_remainder_in_ns);
49 | }
50 | 
51 | #[test]
52 | fn ms_counts_1_then_2_elapsed_correctly() {
53 |     use std::time::Duration;
54 |     let last_tick = instant::Instant::now();
55 |     let now = last_tick + Duration::from_micros(1750);
56 |     let result = count_ms_elapsed(last_tick, now, 0);
57 |     assert_eq!(1, result.ms_elapsed);
58 |     assert_eq!(now, result.last_tick);
59 |     assert_eq!(750000, result.ms_remainder_in_ns);
60 |     let last_tick = result.last_tick;
61 |     let now = last_tick + Duration::from_micros(1750);
62 |     let result = count_ms_elapsed(last_tick, now, result.ms_remainder_in_ns);
63 |     assert_eq!(2, result.ms_elapsed);
64 |     assert_eq!(now, result.last_tick);
65 |     assert_eq!(500000, result.ms_remainder_in_ns);
66 | }
67 | 


--------------------------------------------------------------------------------
/src/kanata/unknown.rs:
--------------------------------------------------------------------------------
 1 | use super::*;
 2 | 
 3 | pub static PRESSED_KEYS: Lazy<Mutex<HashSet<OsCode>>> =
 4 |     Lazy::new(|| Mutex::new(HashSet::default()));
 5 | 
 6 | impl Kanata {
 7 |     pub fn check_release_non_physical_shift(&mut self) -> Result<()> {
 8 |         // Silence warning
 9 |         check_for_exit(&KeyEvent::new(OsCode::KEY_UNKNOWN, KeyValue::Release));
10 |         Ok(())
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
 1 | use anyhow::{anyhow, Error, Result};
 2 | use std::net::SocketAddr;
 3 | use std::path::PathBuf;
 4 | use std::str::FromStr;
 5 | 
 6 | #[cfg(all(target_os = "windows", feature = "gui"))]
 7 | pub mod gui;
 8 | pub mod kanata;
 9 | pub mod oskbd;
10 | pub mod tcp_server;
11 | #[cfg(test)]
12 | pub mod tests;
13 | 
14 | pub use kanata::*;
15 | pub use tcp_server::TcpServer;
16 | 
17 | type CfgPath = PathBuf;
18 | 
19 | pub struct ValidatedArgs {
20 |     pub paths: Vec<CfgPath>,
21 |     #[cfg(feature = "tcp_server")]
22 |     pub tcp_server_address: Option<SocketAddrWrapper>,
23 |     #[cfg(target_os = "linux")]
24 |     pub symlink_path: Option<String>,
25 |     pub nodelay: bool,
26 | }
27 | 
28 | pub fn default_cfg() -> Vec<PathBuf> {
29 |     let mut cfgs = Vec::new();
30 | 
31 |     let default = PathBuf::from("kanata.kbd");
32 |     if default.is_file() {
33 |         cfgs.push(default);
34 |     }
35 | 
36 |     if let Some(config_dir) = dirs::config_dir() {
37 |         let fallback = config_dir.join("kanata").join("kanata.kbd");
38 |         if fallback.is_file() {
39 |             cfgs.push(fallback);
40 |         }
41 |     }
42 | 
43 |     cfgs
44 | }
45 | 
46 | #[derive(Debug, Clone)]
47 | pub struct SocketAddrWrapper(SocketAddr);
48 | 
49 | impl FromStr for SocketAddrWrapper {
50 |     type Err = Error;
51 | 
52 |     fn from_str(s: &str) -> Result<Self, Self::Err> {
53 |         let mut address = s.to_string();
54 |         if let Ok(port) = s.parse::<u16>() {
55 |             address = format!("127.0.0.1:{port}");
56 |         }
57 |         address
58 |             .parse::<SocketAddr>()
59 |             .map(SocketAddrWrapper)
60 |             .map_err(|e| anyhow!("Please specify either a port number, e.g. 8081 or an address, e.g. 127.0.0.1:8081.\n{e}"))
61 |     }
62 | }
63 | 
64 | impl SocketAddrWrapper {
65 |     pub fn into_inner(self) -> SocketAddr {
66 |         self.0
67 |     }
68 |     pub fn get_ref(&self) -> &SocketAddr {
69 |         &self.0
70 |     }
71 | }
72 | 


--------------------------------------------------------------------------------
/src/main_lib/mod.rs:
--------------------------------------------------------------------------------
1 | #[cfg(all(target_os = "windows", feature = "gui"))]
2 | pub(crate) mod win_gui;
3 | 


--------------------------------------------------------------------------------
/src/oskbd/mod.rs:
--------------------------------------------------------------------------------
  1 | //! Platform specific code for low level keyboard read/write.
  2 | 
  3 | #[cfg(target_os = "linux")]
  4 | mod linux;
  5 | #[cfg(target_os = "linux")]
  6 | pub use linux::*;
  7 | 
  8 | #[cfg(target_os = "windows")]
  9 | mod windows;
 10 | #[cfg(target_os = "windows")]
 11 | pub use windows::*;
 12 | 
 13 | #[cfg(target_os = "macos")]
 14 | mod macos;
 15 | #[cfg(target_os = "macos")]
 16 | pub use macos::*;
 17 | 
 18 | #[cfg(any(
 19 |     all(
 20 |         not(feature = "simulated_input"),
 21 |         feature = "simulated_output",
 22 |         not(feature = "passthru_ahk")
 23 |     ),
 24 |     all(
 25 |         feature = "simulated_input",
 26 |         not(feature = "simulated_output"),
 27 |         not(feature = "passthru_ahk")
 28 |     )
 29 | ))]
 30 | mod simulated; // has KbdOut
 31 | #[cfg(any(
 32 |     all(
 33 |         not(feature = "simulated_input"),
 34 |         feature = "simulated_output",
 35 |         not(feature = "passthru_ahk")
 36 |     ),
 37 |     all(
 38 |         feature = "simulated_input",
 39 |         not(feature = "simulated_output"),
 40 |         not(feature = "passthru_ahk")
 41 |     )
 42 | ))]
 43 | pub use simulated::*;
 44 | #[cfg(any(
 45 |     all(feature = "simulated_input", feature = "simulated_output"),
 46 |     all(
 47 |         feature = "simulated_input",
 48 |         feature = "simulated_output",
 49 |         feature = "passthru_ahk"
 50 |     ),
 51 | ))]
 52 | mod sim_passthru; // has KbdOut
 53 | #[cfg(any(
 54 |     all(feature = "simulated_input", feature = "simulated_output"),
 55 |     all(
 56 |         feature = "simulated_input",
 57 |         feature = "simulated_output",
 58 |         feature = "passthru_ahk"
 59 |     ),
 60 | ))]
 61 | pub use sim_passthru::*;
 62 | 
 63 | pub const HI_RES_SCROLL_UNITS_IN_LO_RES: u16 = 120;
 64 | 
 65 | // ------------------ KeyValue --------------------
 66 | 
 67 | #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 68 | pub enum KeyValue {
 69 |     Release = 0,
 70 |     Press = 1,
 71 |     Repeat = 2,
 72 |     Tap,
 73 |     WakeUp,
 74 | }
 75 | 
 76 | impl From<i32> for KeyValue {
 77 |     fn from(item: i32) -> Self {
 78 |         match item {
 79 |             0 => Self::Release,
 80 |             1 => Self::Press,
 81 |             2 => Self::Repeat,
 82 |             _ => unreachable!(),
 83 |         }
 84 |     }
 85 | }
 86 | 
 87 | impl From<bool> for KeyValue {
 88 |     fn from(up: bool) -> Self {
 89 |         match up {
 90 |             true => Self::Release,
 91 |             false => Self::Press,
 92 |         }
 93 |     }
 94 | }
 95 | 
 96 | impl From<KeyValue> for bool {
 97 |     fn from(val: KeyValue) -> Self {
 98 |         matches!(val, KeyValue::Release)
 99 |     }
100 | }
101 | 
102 | use kanata_parser::keys::OsCode;
103 | 
104 | #[derive(Clone, Copy)]
105 | pub struct KeyEvent {
106 |     pub code: OsCode,
107 |     pub value: KeyValue,
108 | }
109 | 
110 | #[allow(dead_code, unused)]
111 | impl KeyEvent {
112 |     pub fn new(code: OsCode, value: KeyValue) -> Self {
113 |         Self { code, value }
114 |     }
115 | }
116 | 
117 | use core::fmt;
118 | impl fmt::Display for KeyEvent {
119 |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120 |         use kanata_keyberon::key_code::KeyCode;
121 |         let direction = match self.value {
122 |             KeyValue::Press => "↓",
123 |             KeyValue::Release => "↑",
124 |             KeyValue::Repeat => "⟳",
125 |             KeyValue::Tap => "↕",
126 |             KeyValue::WakeUp => "!",
127 |         };
128 |         let key_name = KeyCode::from(self.code);
129 |         write!(f, "{direction}{key_name:?}")
130 |     }
131 | }
132 | 
133 | impl fmt::Debug for KeyEvent {
134 |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
135 |         f.debug_struct("KeyEvent")
136 |             .field(
137 |                 "code",
138 |                 &format_args!("{:?} ({})", self.code, self.code.as_u16()),
139 |             )
140 |             .field("value", &self.value)
141 |             .finish()
142 |     }
143 | }
144 | 


--------------------------------------------------------------------------------
/src/oskbd/windows/exthook_os.rs:
--------------------------------------------------------------------------------
  1 | //! A function listener for keyboard input events replacing Windows keyboard hook API
  2 | 
  3 | use core::fmt;
  4 | use once_cell::sync::Lazy;
  5 | use parking_lot::Mutex;
  6 | 
  7 | use winapi::ctypes::*;
  8 | use winapi::um::winuser::*;
  9 | 
 10 | use crate::oskbd::{KeyEvent, KeyValue};
 11 | use kanata_keyberon::key_code::KeyCode;
 12 | 
 13 | use kanata_parser::keys::*;
 14 | 
 15 | pub const LLHOOK_IDLE_TIME_SECS_CLEAR_INPUTS: u64 = 60;
 16 | 
 17 | type HookFn = dyn FnMut(InputEvent) -> bool + Send + Sync + 'static;
 18 | 
 19 | pub static HOOK_CB: Lazy<Mutex<Option<Box<HookFn>>>> = Lazy::new(|| Mutex::new(None)); // store thread-safe hook callback with a mutex (can be called from an external process)
 20 | 
 21 | pub struct KeyboardHook {} // reusing hook type for our listener
 22 | impl KeyboardHook {
 23 |     /// Sets input callback (panics if already registered)
 24 |     pub fn set_input_cb(
 25 |         callback: impl FnMut(InputEvent) -> bool + Send + Sync + 'static,
 26 |     ) -> KeyboardHook {
 27 |         let mut cb_opt = HOOK_CB.lock();
 28 |         assert!(
 29 |             cb_opt.take().is_none(),
 30 |             "Only 1 external listener is allowed!"
 31 |         );
 32 |         *cb_opt = Some(Box::new(callback));
 33 |         KeyboardHook {}
 34 |     }
 35 | }
 36 | #[cfg(not(feature = "passthru_ahk"))] // unused KeyboardHook will be dropped, breaking our hook, disable it
 37 | impl Drop for KeyboardHook {
 38 |     fn drop(&mut self) {
 39 |         let mut cb_opt = HOOK_CB.lock();
 40 |         cb_opt.take();
 41 |     }
 42 | }
 43 | 
 44 | #[derive(Debug, Clone, Copy)]
 45 | pub struct InputEvent {
 46 |     // Key event received by the low level keyboard hook.
 47 |     pub code: u32,
 48 |     pub up: bool, /*Key was released*/
 49 | }
 50 | impl fmt::Display for InputEvent {
 51 |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 52 |         let direction = if self.up { "↑" } else { "↓" };
 53 |         let key_name = KeyCode::from(OsCode::from(self.code));
 54 |         write!(f, "{}{:?}", direction, key_name)
 55 |     }
 56 | }
 57 | impl InputEvent {
 58 |     pub fn from_vk_sc(vk: c_uint, sc: c_uint, up: c_int) -> Self {
 59 |         let code = if vk == (VK_RETURN as u32) {
 60 |             // todo: do a proper check for numpad enter, maybe 0x11c isn't universal
 61 |             match sc {
 62 |                 0x11C => u32::from(VK_KPENTER_FAKE),
 63 |                 _ => VK_RETURN as u32,
 64 |             }
 65 |         } else {
 66 |             vk
 67 |         };
 68 |         Self {
 69 |             code,
 70 |             up: (up != 0),
 71 |         }
 72 |     }
 73 |     pub fn from_oscode(code: OsCode, val: KeyValue) -> Self {
 74 |         Self {
 75 |             code: code.into(),
 76 |             up: val.into(),
 77 |         }
 78 |     }
 79 | }
 80 | impl TryFrom<InputEvent> for KeyEvent {
 81 |     type Error = ();
 82 |     fn try_from(item: InputEvent) -> Result<Self, Self::Error> {
 83 |         Ok(Self {
 84 |             code: OsCode::from_u16(item.code as u16).ok_or(())?,
 85 |             value: match item.up {
 86 |                 true => KeyValue::Release,
 87 |                 false => KeyValue::Press,
 88 |             },
 89 |         })
 90 |     }
 91 | }
 92 | impl From<KeyEvent> for InputEvent {
 93 |     fn from(item: KeyEvent) -> Self {
 94 |         Self {
 95 |             code: item.code.into(),
 96 |             up: item.value.into(),
 97 |         }
 98 |     }
 99 | }
100 | 


--------------------------------------------------------------------------------
/src/tests/sim_tests/block_keys_tests.rs:
--------------------------------------------------------------------------------
 1 | use super::*;
 2 | 
 3 | #[test]
 4 | fn block_does_not_block_buttons() {
 5 |     let result = simulate(
 6 |         "(defcfg process-unmapped-keys yes
 7 |                    block-unmapped-keys yes)
 8 |         (defsrc)
 9 |         (deflayer base)",
10 |         "d:mlft d:mrgt d:mmid d:mbck d:mfwd t:10 d:f1
11 |          u:mlft u:mrgt u:mmid u:mbck u:mfwd t:10 u:f1",
12 |     );
13 |     assert_eq!(
14 |         "out🖰:↓Left\nt:1ms\nout🖰:↓Right\nt:1ms\nout🖰:↓Mid\nt:1ms\nout🖰:↓Backward\n\
15 |                t:1ms\nout🖰:↓Forward\nt:7ms\nout🖰:↑Left\nt:1ms\nout🖰:↑Right\nt:1ms\nout🖰:↑Mid\n\
16 |                t:1ms\nout🖰:↑Backward\nt:1ms\nout🖰:↑Forward",
17 |         result
18 |     );
19 | }
20 | 
21 | #[test]
22 | fn block_does_not_block_wheel() {
23 |     let result = simulate(
24 |         "(defcfg process-unmapped-keys yes
25 |                    block-unmapped-keys yes)
26 |         (defsrc)
27 |         (deflayer base)",
28 |         "d:mwu d:mwd d:mwl d:mwr t:10 d:f1
29 |          u:mwu u:mwd u:mwl u:mwr t:10 u:f1",
30 |     );
31 |     assert_eq!(
32 |         "scroll:Up,120\nt:1ms\nscroll:Down,120\nt:1ms\nscroll:Left,120\nt:1ms\nscroll:Right,120",
33 |         result
34 |     );
35 | }
36 | 


--------------------------------------------------------------------------------
/src/tests/sim_tests/capsword_sim_tests.rs:
--------------------------------------------------------------------------------
  1 | use super::*;
  2 | 
  3 | const CFG: &str = r##"
  4 |  (defcfg)
  5 |  (defsrc 7 8 9 0)
  6 |  (deflayer base
  7 |      (caps-word 1000)
  8 |      (caps-word-custom 200 (a) (b))
  9 |      (caps-word-toggle 1000)
 10 |      (caps-word-custom-toggle 200 (a) (b))
 11 |  )
 12 | "##;
 13 | 
 14 | #[test]
 15 | fn caps_word_behaves_correctly() {
 16 |     let result = simulate(
 17 |         CFG,
 18 |         "d:7 u:7 d:a u:a d:1 u:1 d:a u:a d:spc u:spc d:a u:a t:1000",
 19 |     )
 20 |     .no_time();
 21 |     assert_eq!(
 22 |         "out:↓LShift out:↓A out:↑LShift out:↑A \
 23 |          out:↓Kb1 out:↑Kb1 out:↓LShift out:↓A out:↑LShift out:↑A \
 24 |          out:↓Space out:↑Space out:↓A out:↑A",
 25 |         result
 26 |     );
 27 | }
 28 | 
 29 | #[test]
 30 | fn caps_word_custom_behaves_correctly() {
 31 |     let result = simulate(
 32 |         CFG,
 33 |         "d:8 u:8 d:a u:a d:b u:b d:a u:a d:1 u:1 d:a u:a t:1000",
 34 |     )
 35 |     .no_time();
 36 |     assert_eq!(
 37 |         "out:↓LShift out:↓A out:↑LShift out:↑A \
 38 |          out:↓B out:↑B out:↓LShift out:↓A out:↑LShift out:↑A \
 39 |          out:↓Kb1 out:↑Kb1 out:↓A out:↑A",
 40 |         result
 41 |     );
 42 | }
 43 | 
 44 | #[test]
 45 | fn caps_word_times_out() {
 46 |     let result = simulate(CFG, "d:7 u:7 d:a u:a t:500 d:a u:a t:1001 d:a u:a t:10").no_time();
 47 |     assert_eq!(
 48 |         "out:↓LShift out:↓A out:↑LShift out:↑A \
 49 |          out:↓LShift out:↓A out:↑LShift out:↑A \
 50 |          out:↓A out:↑A",
 51 |         result
 52 |     );
 53 | }
 54 | 
 55 | #[test]
 56 | fn caps_word_custom_times_out() {
 57 |     let result = simulate(CFG, "d:8 u:8 d:a u:a t:100 d:a u:a t:201 d:a u:a t:10").no_time();
 58 |     assert_eq!(
 59 |         "out:↓LShift out:↓A out:↑LShift out:↑A \
 60 |          out:↓LShift out:↓A out:↑LShift out:↑A \
 61 |          out:↓A out:↑A",
 62 |         result
 63 |     );
 64 | }
 65 | 
 66 | #[test]
 67 | fn caps_word_does_not_toggle() {
 68 |     let result = simulate(CFG, "d:7 u:7 d:a u:a t:100 d:7 u:7 t:100 d:a u:a t:10").no_time();
 69 |     assert_eq!(
 70 |         "out:↓LShift out:↓A out:↑LShift out:↑A \
 71 |          out:↓LShift out:↓A out:↑LShift out:↑A",
 72 |         result
 73 |     );
 74 | }
 75 | 
 76 | #[test]
 77 | fn caps_word_custom_does_not_toggle() {
 78 |     let result = simulate(CFG, "d:8 u:8 d:a u:a t:100 d:8 u:8 t:100 d:a u:a t:10").no_time();
 79 |     assert_eq!(
 80 |         "out:↓LShift out:↓A out:↑LShift out:↑A \
 81 |          out:↓LShift out:↓A out:↑LShift out:↑A",
 82 |         result
 83 |     );
 84 | }
 85 | 
 86 | #[test]
 87 | fn caps_word_toggle_does_toggle() {
 88 |     let result = simulate(CFG, "d:9 u:9 d:a u:a t:100 d:9 u:9 t:100 d:a u:a t:10").no_time();
 89 |     assert_eq!(
 90 |         "out:↓LShift out:↓A out:↑LShift out:↑A \
 91 |          out:↓A out:↑A",
 92 |         result
 93 |     );
 94 | }
 95 | 
 96 | #[test]
 97 | fn caps_word_custom_toggle_does_toggle() {
 98 |     let result = simulate(CFG, "d:0 u:0 d:a u:a t:100 d:0 u:0 t:100 d:a u:a t:10").no_time();
 99 |     assert_eq!(
100 |         "out:↓LShift out:↓A out:↑LShift out:↑A \
101 |          out:↓A out:↑A",
102 |         result
103 |     );
104 | }
105 | 


--------------------------------------------------------------------------------
/src/tests/sim_tests/delay_tests.rs:
--------------------------------------------------------------------------------
 1 | use super::*;
 2 | 
 3 | #[test]
 4 | #[ignore] // timing-based: fails intermittently
 5 | fn on_press_delay() {
 6 |     let start = std::time::Instant::now();
 7 |     let result = simulate(
 8 |         "(defsrc) (deflayermap (base) a (on-press-delay 10))",
 9 |         "d:a t:50 u:a t:50",
10 |     );
11 |     assert_eq!("", result);
12 |     let end = std::time::Instant::now();
13 |     let duration = end - start;
14 |     assert!(duration > std::time::Duration::from_millis(9));
15 |     assert!(duration < std::time::Duration::from_millis(19));
16 | }
17 | 
18 | #[test]
19 | #[ignore] // timing-based: fails intermittently
20 | fn on_release_delay() {
21 |     let start = std::time::Instant::now();
22 |     let result = simulate(
23 |         "(defsrc) (deflayermap (base) a (on-release-delay 10))",
24 |         "d:a t:50 u:a t:50",
25 |     );
26 |     assert_eq!("", result);
27 |     let end = std::time::Instant::now();
28 |     let duration = end - start;
29 |     assert!(duration > std::time::Duration::from_millis(9));
30 |     assert!(duration < std::time::Duration::from_millis(19));
31 | }
32 | 
33 | #[test]
34 | #[ignore] // timing-based: fails intermittently
35 | fn no_delay() {
36 |     let start = std::time::Instant::now();
37 |     let result = simulate("(defsrc) (deflayermap (base) a XX)", "d:a t:50 u:a t:50");
38 |     assert_eq!("", result);
39 |     let end = std::time::Instant::now();
40 |     let duration = end - start;
41 |     assert!(duration < std::time::Duration::from_millis(10));
42 | }
43 | 


--------------------------------------------------------------------------------
/src/tests/sim_tests/layer_sim_tests.rs:
--------------------------------------------------------------------------------
 1 | use super::*;
 2 | 
 3 | #[test]
 4 | fn transparent_base() {
 5 |     let result = simulate(
 6 |         "(defcfg process-unmapped-keys yes concurrent-tap-hold yes) \
 7 |          (defsrc a) \
 8 |          (deflayer base _)",
 9 |         "d:a t:50 u:a t:50",
10 |     );
11 |     assert_eq!("out:↓A\nt:50ms\nout:↑A", result);
12 | }
13 | 
14 | #[test]
15 | fn delegate_base() {
16 |     let result = simulate(
17 |         "(defcfg process-unmapped-keys   yes \
18 |                  delegate-to-first-layer yes)
19 |          (defsrc a b) \
20 |          (deflayer base c (layer-switch 2)) \
21 |          (deflayer 2 _ _)",
22 |         "d:b t:50 u:b t:50 d:a t:50 u:a t:50",
23 |     );
24 |     assert_eq!("t:100ms\nout:↓C\nt:50ms\nout:↑C", result);
25 | }
26 | 
27 | #[test]
28 | fn delegate_base_but_base_is_transparent() {
29 |     let result = simulate(
30 |         "(defcfg process-unmapped-keys   yes \
31 |                  delegate-to-first-layer yes)
32 |          (defsrc a b) \
33 |          (deflayer base _ (layer-switch 2)) \
34 |          (deflayer 2 _ _)",
35 |         "d:b t:50 u:b t:50 d:a t:50 u:a t:50",
36 |     );
37 |     assert_eq!("t:100ms\nout:↓A\nt:50ms\nout:↑A", result);
38 | }
39 | 
40 | #[test]
41 | fn layer_switching() {
42 |     let result = simulate(
43 |         "(defcfg process-unmapped-keys   yes
44 |                  delegate-to-first-layer yes)
45 |          (defsrc a b c d)
46 |          (deflayer base x y z (layer-switch 2))
47 |          (deflayer 2 e f _ (layer-switch 3))
48 |          (deflayer 3 g _ _ (layer-switch 4))
49 |          (deflayer 4 _ _ _ XX)
50 |         ",
51 |         "d:c t:20 u:c t:20 d:d t:20 u:d t:20
52 |          d:b t:20 u:b t:20
53 |          d:c t:20 u:c t:20
54 |          d:d t:20 u:d t:20
55 |          d:a t:20 u:a t:20
56 |          d:b t:20 u:b t:20
57 |          d:d t:20 u:d t:20
58 |          d:a t:20 u:a t:20",
59 |     );
60 |     assert_eq!(
61 |         "out:↓Z\nt:20ms\nout:↑Z\nt:60ms\nout:↓F\nt:20ms\nout:↑F\nt:20ms\nout:↓Z\nt:20ms\nout:↑Z\nt:60ms\nout:↓G\nt:20ms\nout:↑G\nt:20ms\nout:↓Y\nt:20ms\nout:↑Y\nt:60ms\nout:↓X\nt:20ms\nout:↑X",
62 |         result
63 |     );
64 | }
65 | 
66 | #[test]
67 | fn layer_holding() {
68 |     let result = simulate(
69 |         "(defcfg process-unmapped-keys   yes
70 |                  delegate-to-first-layer no)
71 |          (defsrc a b c d e f)
72 |          (deflayer base x y z (layer-while-held 2) XX XX)
73 |          (deflayer 2 e f _ XX (layer-while-held 3) XX)
74 |          (deflayer 3 g _ _ XX XX (layer-while-held 4))
75 |          (deflayer 4 _ _ _ XX XX XX)
76 |         ",
77 |         "d:c t:20 u:c t:20
78 |          d:d t:20
79 |          d:a t:20 u:a t:20
80 |          d:b t:20 u:b t:20
81 |          d:c t:20 u:c t:20
82 |          d:e t:20
83 |          d:a t:20 u:a t:20
84 |          d:b t:20 u:b t:20
85 |          d:c t:20 u:c t:20
86 |          d:f t:20
87 |          d:a t:20 u:a t:20
88 |          d:b t:20 u:b t:20
89 |          d:c t:20 u:c t:20",
90 |     );
91 |     assert_eq!(
92 |         "out:↓Z\nt:20ms\nout:↑Z\nt:40ms\nout:↓E\nt:20ms\nout:↑E\nt:20ms\nout:↓F\nt:20ms\nout:↑F\nt:20ms\nout:↓Z\nt:20ms\nout:↑Z\nt:40ms\nout:↓G\nt:20ms\nout:↑G\nt:20ms\nout:↓F\nt:20ms\nout:↑F\nt:20ms\nout:↓Z\nt:20ms\nout:↑Z\nt:40ms\nout:↓G\nt:20ms\nout:↑G\nt:20ms\nout:↓F\nt:20ms\nout:↑F\nt:20ms\nout:↓Z\nt:20ms\nout:↑Z",
93 |         result
94 |     );
95 | }
96 | 


--------------------------------------------------------------------------------
/src/tests/sim_tests/mod.rs:
--------------------------------------------------------------------------------
  1 | //! Contains tests that use simulated inputs.
  2 | //!
  3 | //! One way to write tests is to write the configuration, write the simulated input, and then let
  4 | //! the test fail by comparing the output to an empty string. Run the test then inspect the failure
  5 | //! and see if the real output looks sensible according to what is expected.
  6 | 
  7 | use crate::tests::*;
  8 | use crate::{
  9 |     oskbd::{KeyEvent, KeyValue},
 10 |     str_to_oscode, Kanata,
 11 | };
 12 | 
 13 | use rustc_hash::FxHashMap;
 14 | 
 15 | mod block_keys_tests;
 16 | mod capsword_sim_tests;
 17 | mod chord_sim_tests;
 18 | mod delay_tests;
 19 | mod layer_sim_tests;
 20 | mod macro_sim_tests;
 21 | mod oneshot_tests;
 22 | mod override_tests;
 23 | mod release_sim_tests;
 24 | mod repeat_sim_tests;
 25 | mod seq_sim_tests;
 26 | mod switch_sim_tests;
 27 | mod tap_hold_tests;
 28 | mod template_sim_tests;
 29 | mod timing_tests;
 30 | mod unicode_sim_tests;
 31 | mod unmod_sim_tests;
 32 | mod use_defsrc_sim_tests;
 33 | mod vkey_sim_tests;
 34 | mod zippychord_sim_tests;
 35 | 
 36 | fn simulate<S: AsRef<str>>(cfg: S, sim: S) -> String {
 37 |     simulate_with_file_content(cfg, sim, Default::default())
 38 | }
 39 | 
 40 | fn simulate_with_file_content<S: AsRef<str>>(
 41 |     cfg: S,
 42 |     sim: S,
 43 |     file_content: FxHashMap<String, String>,
 44 | ) -> String {
 45 |     init_log();
 46 |     let _lk = match CFG_PARSE_LOCK.lock() {
 47 |         Ok(guard) => guard,
 48 |         Err(poisoned) => poisoned.into_inner(),
 49 |     };
 50 |     let mut k = Kanata::new_from_str(cfg.as_ref(), file_content).expect("failed to parse cfg");
 51 |     for pair in sim.as_ref().split_whitespace() {
 52 |         match pair.split_once(':') {
 53 |             Some((kind, val)) => match kind {
 54 |                 "t" => {
 55 |                     let tick = str::parse::<u128>(val).expect("valid num for tick");
 56 |                     k.tick_ms(tick, &None).unwrap();
 57 |                 }
 58 |                 "d" => {
 59 |                     let key_code = str_to_oscode(val).expect("valid keycode");
 60 |                     k.handle_input_event(&KeyEvent {
 61 |                         code: key_code,
 62 |                         value: KeyValue::Press,
 63 |                     })
 64 |                     .expect("input handles fine");
 65 |                 }
 66 |                 "u" => {
 67 |                     let key_code = str_to_oscode(val).expect("valid keycode");
 68 |                     k.handle_input_event(&KeyEvent {
 69 |                         code: key_code,
 70 |                         value: KeyValue::Release,
 71 |                     })
 72 |                     .expect("input handles fine");
 73 |                 }
 74 |                 "r" => {
 75 |                     let key_code = str_to_oscode(val).expect("valid keycode");
 76 |                     k.handle_input_event(&KeyEvent {
 77 |                         code: key_code,
 78 |                         value: KeyValue::Repeat,
 79 |                     })
 80 |                     .expect("input handles fine");
 81 |                 }
 82 |                 _ => panic!("invalid item {pair}"),
 83 |             },
 84 |             None => panic!("invalid item {pair}"),
 85 |         }
 86 |     }
 87 |     drop(_lk);
 88 |     k.kbd_out.outputs.events.join("\n")
 89 | }
 90 | 
 91 | #[allow(unused)]
 92 | trait SimTransform {
 93 |     /// Changes newlines to spaces.
 94 |     fn to_spaces(self) -> Self;
 95 |     /// Removes out:↑_ items from the string. Also transforms newlines to spaces.
 96 |     fn no_releases(self) -> Self;
 97 |     /// Removes t:_ms items from the string. Also transforms newlines to spaces.
 98 |     fn no_time(self) -> Self;
 99 |     /// Replaces out:↓_ with dn:_ and out:↑_ with up:_. Also transforms newlines to spaces.
100 |     fn to_ascii(self) -> Self;
101 | }
102 | 
103 | impl SimTransform for String {
104 |     fn to_spaces(self) -> Self {
105 |         self.replace('\n', " ")
106 |     }
107 | 
108 |     fn no_time(self) -> Self {
109 |         self.split_ascii_whitespace()
110 |             .filter(|s| !s.starts_with("t:"))
111 |             .collect::<Vec<_>>()
112 |             .join(" ")
113 |     }
114 | 
115 |     fn no_releases(self) -> Self {
116 |         self.split_ascii_whitespace()
117 |             .filter(|s| !s.starts_with("out:↑") && !s.starts_with("up:"))
118 |             .collect::<Vec<_>>()
119 |             .join(" ")
120 |     }
121 | 
122 |     fn to_ascii(self) -> Self {
123 |         self.split_ascii_whitespace()
124 |             .map(|s| s.replace("out:↑", "up:").replace("out:↓", "dn:"))
125 |             .collect::<Vec<_>>()
126 |             .join(" ")
127 |     }
128 | }
129 | 


--------------------------------------------------------------------------------
/src/tests/sim_tests/oneshot_tests.rs:
--------------------------------------------------------------------------------
 1 | use super::*;
 2 | 
 3 | #[test]
 4 | fn oneshot_pause() {
 5 |     let result = simulate(
 6 |         "
 7 | (defsrc a lmet rmet)
 8 | (deflayer base
 9 |   1 @lme @rme)
10 | (deflayer numbers
11 |   2 @lme @rme)
12 | (deflayer navigation
13 |   (one-shot 2000 lalt) @lme @rme)
14 | (deflayer symbols
15 |   4 @lme @rme)
16 | 
17 | (defvirtualkeys
18 |   callum (switch
19 |     ((and nop1 nop2)) (layer-while-held numbers) break
20 |     (nop1) (layer-while-held navigation) break
21 |     (nop2) (layer-while-held symbols) break)
22 |   activate-callum (multi
23 |    (one-shot-pause-processing 5)
24 |    (switch
25 |     ((or nop1 nop2))
26 |      (multi (on-press release-vkey callum)
27 |             (on-press press-vkey callum))
28 |      break
29 |     () (on-press release-vkey callum) break)))
30 | 
31 | (defalias
32 |   lme (multi nop1
33 |              (on-press tap-vkey activate-callum)
34 |              (on-release tap-vkey activate-callum))
35 |   rme (multi nop2
36 |              (on-press tap-vkey activate-callum)
37 |              (on-release tap-vkey activate-callum)))
38 |         ",
39 |         "d:lmet t:10 d:a u:a t:10 u:lmet t:10 d:a u:a t:10",
40 |     )
41 |     .to_ascii();
42 |     assert_eq!("t:10ms dn:LAlt t:20ms dn:Kb1 t:5ms up:LAlt up:Kb1", result);
43 | }
44 | 


--------------------------------------------------------------------------------
/src/tests/sim_tests/override_tests.rs:
--------------------------------------------------------------------------------
 1 | use super::*;
 2 | 
 3 | #[test]
 4 | fn override_with_unmod() {
 5 |     let result = simulate(
 6 |         "
 7 | (defoverrides
 8 |  (a) (b)
 9 |  (b) (a)
10 | )
11 | 
12 | (defalias
13 |  b (unshift b)
14 |  a (unshift a)
15 | )
16 | (defsrc a b)
17 | (deflayer base @a @b)
18 |         ",
19 |         "d:lsft t:50 d:a t:50 u:a t:50 d:b t:50 u:b t:50",
20 |     )
21 |     .to_ascii()
22 |     .no_time();
23 |     assert_eq!(
24 |         "dn:LShift up:LShift dn:B up:B dn:LShift up:LShift dn:A up:A dn:LShift",
25 |         result
26 |     );
27 | }
28 | 
29 | #[test]
30 | fn override_release_mod_change_key() {
31 |     let cfg = "
32 | (defsrc)
33 | (deflayer base)
34 | (defoverrides
35 |   (lsft a) (lsft 9)
36 |   (lsft 1) (lctl 2))
37 |         ";
38 |     let result = simulate(cfg, "d:lsft t:10 d:a t:10 u:lsft t:10 u:a t:10").to_ascii();
39 |     assert_eq!("dn:LShift t:10ms dn:Kb9 t:10ms up:LShift up:Kb9", result);
40 |     let result = simulate(cfg, "d:lsft t:10 d:a t:10 u:a t:10 u:lsft t:10").to_ascii();
41 |     assert_eq!(
42 |         "dn:LShift t:10ms dn:Kb9 t:10ms up:Kb9 t:10ms up:LShift",
43 |         result
44 |     );
45 |     let result = simulate(cfg, "d:lsft t:10 d:a t:10 d:c t:10").to_ascii();
46 |     assert_eq!("dn:LShift t:10ms dn:Kb9 t:10ms up:Kb9 dn:C", result);
47 |     let result = simulate(cfg, "d:lsft t:10 d:1 t:10 d:c t:10").to_ascii();
48 |     assert_eq!(
49 |         "dn:LShift t:10ms up:LShift dn:LCtrl dn:Kb2 t:10ms up:LCtrl up:Kb2 dn:LShift dn:C",
50 |         result
51 |     );
52 | }
53 | 
54 | #[test]
55 | fn override_eagerly_releases() {
56 |     let result = simulate(
57 |         "
58 | (defcfg override-release-on-activation yes)
59 | (defsrc)
60 | (deflayer base)
61 | (defoverrides (lsft a) (lsft 9))
62 |         ",
63 |         "d:lsft t:10 d:a t:10 u:lsft t:10 u:a t:10",
64 |     )
65 |     .to_ascii();
66 |     assert_eq!(
67 |         "dn:LShift t:10ms dn:Kb9 t:1ms up:Kb9 t:9ms up:LShift",
68 |         result
69 |     );
70 | }
71 | 


--------------------------------------------------------------------------------
/src/tests/sim_tests/release_sim_tests.rs:
--------------------------------------------------------------------------------
 1 | use super::*;
 2 | 
 3 | #[test]
 4 | fn release_standard() {
 5 |     let result = simulate(
 6 |         "
 7 |          (defsrc a)
 8 |          (deflayer base (multi lalt a))
 9 |         ",
10 |         "
11 |          d:a t:10 u:a t:10
12 |         ",
13 |     )
14 |     .to_ascii();
15 |     assert_eq!("dn:LAlt dn:A t:10ms up:LAlt up:A", result);
16 | }
17 | 
18 | #[test]
19 | fn release_reversed() {
20 |     let result = simulate(
21 |         "
22 |          (defsrc a)
23 |          (deflayer base (multi lalt a reverse-release-order))
24 |         ",
25 |         "
26 |          d:a t:10 u:a t:10
27 |         ",
28 |     )
29 |     .to_ascii();
30 |     assert_eq!("dn:LAlt dn:A t:10ms up:A up:LAlt", result);
31 | }
32 | 


--------------------------------------------------------------------------------
/src/tests/sim_tests/repeat_sim_tests.rs:
--------------------------------------------------------------------------------
  1 | use super::*;
  2 | 
  3 | #[test]
  4 | fn repeat_standard() {
  5 |     let result = simulate(
  6 |         "
  7 |          (defsrc a)
  8 |          (deflayer base b)
  9 |         ",
 10 |         "
 11 |          d:a t:10 r:a t:10 r:a t:10 u:a t:10 r:a
 12 |         ",
 13 |     );
 14 |     assert_eq!(
 15 |         "out:↓B\nt:10ms\nout:↓B\nt:10ms\nout:↓B\nt:10ms\nout:↑B",
 16 |         result
 17 |     );
 18 | }
 19 | 
 20 | #[test]
 21 | fn repeat_layer_while_held() {
 22 |     let result = simulate(
 23 |         "
 24 |          (defsrc a b)
 25 |          (deflayer base a (layer-while-held held))
 26 |          (deflayer held c b)
 27 |         ",
 28 |         "
 29 |          d:b t:10 r:b t:10 d:a t:10 r:a t:10 r:a t:10 u:a t:10 r:a
 30 |         ",
 31 |     );
 32 |     assert_eq!(
 33 |         "t:20ms\nout:↓C\nt:10ms\nout:↓C\nt:10ms\nout:↓C\nt:10ms\nout:↑C",
 34 |         result
 35 |     );
 36 | }
 37 | 
 38 | #[test]
 39 | fn repeat_layer_switch() {
 40 |     let result = simulate(
 41 |         "
 42 |          (defsrc a b)
 43 |          (deflayer base a (layer-switch swtc))
 44 |          (deflayer swtc d b)
 45 |         ",
 46 |         "
 47 |          d:b t:10 r:b t:10 d:a t:10 r:a t:10 r:a t:10 u:a t:10 r:a
 48 |         ",
 49 |     );
 50 |     assert_eq!(
 51 |         "t:20ms\nout:↓D\nt:10ms\nout:↓D\nt:10ms\nout:↓D\nt:10ms\nout:↑D",
 52 |         result
 53 |     );
 54 | }
 55 | 
 56 | #[test]
 57 | fn repeat_layer_held_trans() {
 58 |     let result = simulate(
 59 |         "
 60 |          (defsrc a b)
 61 |          (deflayer base e (layer-while-held held))
 62 |          (deflayer held _ b)
 63 |         ",
 64 |         "
 65 |          d:b t:10 r:b t:10 d:a t:10 r:a t:10 r:a t:10 u:a t:10 r:a
 66 |         ",
 67 |     );
 68 |     assert_eq!(
 69 |         "t:20ms\nout:↓E\nt:10ms\nout:↓E\nt:10ms\nout:↓E\nt:10ms\nout:↑E",
 70 |         result
 71 |     );
 72 | }
 73 | 
 74 | #[test]
 75 | fn repeat_many_layer_held_trans() {
 76 |     let result = simulate(
 77 |         "
 78 |          (defsrc a b c d e)
 79 |          (deflayer base e (layer-while-held held1) _ _ _)
 80 |          (deflayer held1 f b (layer-while-held held2) _ _)
 81 |          (deflayer held2 _ _ _ (layer-while-held held3) _)
 82 |          (deflayer held3 _ _ _ _ (layer-while-held held4))
 83 |          (deflayer held4 _ _ _ _ _)
 84 |         ",
 85 |         "
 86 |          d:b t:10 r:b t:10
 87 |          d:c t:10 r:c t:10
 88 |          d:d t:10 r:d t:10
 89 |          d:e t:10 r:e t:10
 90 |          d:a t:10 r:a t:10 r:a t:10 u:a t:10 r:a
 91 |         ",
 92 |     );
 93 |     assert_eq!(
 94 |         "t:80ms\nout:↓F\nt:10ms\nout:↓F\nt:10ms\nout:↓F\nt:10ms\nout:↑F",
 95 |         result
 96 |     );
 97 | }
 98 | 
 99 | #[test]
100 | fn repeat_base_layer_trans() {
101 |     let result = simulate(
102 |         "
103 |          (defsrc a)
104 |          (deflayer base _)
105 |         ",
106 |         "
107 |          d:a t:10 r:a t:10 r:a t:10 u:a t:10 r:a
108 |         ",
109 |     );
110 |     assert_eq!(
111 |         "out:↓A\nt:10ms\nout:↓A\nt:10ms\nout:↓A\nt:10ms\nout:↑A",
112 |         result
113 |     );
114 | }
115 | 
116 | #[test]
117 | fn repeat_delegate_to_base_layer_trans() {
118 |     let result = simulate(
119 |         "
120 |          (defcfg delegate-to-first-layer yes)
121 |          (defsrc a c b)
122 |          (deflayer base e _ (layer-switch swtc))
123 |          (deflayer swtc _ _ _)
124 |         ",
125 |         "
126 |          d:b t:10 r:b t:10
127 |          d:a t:10 r:a t:10 r:a t:10 u:a t:10 r:a
128 |          d:c t:10 r:c t:10 r:c t:10 u:c t:10 r:c
129 |         ",
130 |     );
131 |     assert_eq!(
132 |         "t:20ms\nout:↓E\nt:10ms\nout:↓E\nt:10ms\nout:↓E\nt:10ms\nout:↑E\n\
133 |          t:10ms\nout:↓C\nt:10ms\nout:↓C\nt:10ms\nout:↓C\nt:10ms\nout:↑C",
134 |         result
135 |     );
136 | }
137 | 


--------------------------------------------------------------------------------
/src/tests/sim_tests/switch_sim_tests.rs:
--------------------------------------------------------------------------------
 1 | use super::*;
 2 | 
 3 | #[test]
 4 | fn sim_switch_layer() {
 5 |     let result = simulate(
 6 |         "
 7 |          (defcfg)
 8 |          (defsrc a b)
 9 |          (defalias b (switch
10 |             ((layer base)) x break
11 |             ((layer other)) y break))
12 |          (deflayer base (layer-while-held other) @b)
13 |          (deflayer other XX @b)
14 |         ",
15 |         "d:b u:b t:10 d:a d:b u:b u:a t:10",
16 |     )
17 |     .no_time();
18 |     assert_eq!("out:↓X out:↑X out:↓Y out:↑Y", result);
19 | }
20 | 
21 | #[test]
22 | fn sim_switch_base_layer() {
23 |     let result = simulate(
24 |         "
25 |          (defcfg)
26 |          (defsrc a b c)
27 |          (defalias b (switch
28 |             ((base-layer base)) x break
29 |             ((base-layer other)) y break))
30 |          (deflayer base (layer-switch other) @b c)
31 |          (deflayer other XX @b (layer-while-held base))
32 |         ",
33 |         "d:b u:b t:10 d:a d:b u:b u:a t:10 d:c t:10 d:b t:10 u:c u:b t:10",
34 |     )
35 |     .no_time();
36 |     assert_eq!("out:↓X out:↑X out:↓Y out:↑Y out:↓Y out:↑Y", result);
37 | }
38 | 


--------------------------------------------------------------------------------
/src/tests/sim_tests/tap_hold_tests.rs:
--------------------------------------------------------------------------------
 1 | use super::*;
 2 | 
 3 | #[test]
 4 | fn nested_template() {
 5 |     let result = simulate(
 6 |         "
 7 |         (defcfg concurrent-tap-hold yes )
 8 |         (defsrc a j )
 9 |         (deflayer base @a @j)
10 |         (defalias
11 |          a (tap-hold 200 1000 a lctl)
12 |          j (tap-hold 200 500 j lsft))
13 |         ",
14 |         "d:a t:100 d:j t:10 u:j t:1100 u:a t:50",
15 |     )
16 |     .to_ascii();
17 |     assert_eq!(
18 |         "t:999ms dn:LCtrl t:2ms dn:J t:6ms up:J t:203ms up:LCtrl",
19 |         result
20 |     );
21 | }
22 | 


--------------------------------------------------------------------------------
/src/tests/sim_tests/template_sim_tests.rs:
--------------------------------------------------------------------------------
 1 | use super::*;
 2 | 
 3 | #[test]
 4 | fn nested_template() {
 5 |     let result = simulate(
 6 |         "
 7 |         (deftemplate one (v1)
 8 |          a b c $v1
 9 |         )
10 |         (deftemplate two (v2)
11 |          (t! one $v2)
12 |          e f g
13 |         )
14 |         (defsrc        (t! two d))
15 |         (deflayer base (t! two x))
16 |         ",
17 |         "d:a t:10 u:a t:10 d:d t:10 u:d t:10 d:g t:10 u:g t:10",
18 |     )
19 |     .no_time();
20 |     assert_eq!("out:↓A out:↑A out:↓X out:↑X out:↓G out:↑G", result);
21 | }
22 | 


--------------------------------------------------------------------------------
/src/tests/sim_tests/timing_tests.rs:
--------------------------------------------------------------------------------
 1 | use std::thread::sleep;
 2 | use std::time::Duration;
 3 | 
 4 | use crate::Kanata;
 5 | 
 6 | use instant::Instant;
 7 | 
 8 | #[test]
 9 | fn one_second_is_roughly_1000_counted_ticks() {
10 |     let mut k = Kanata::new_from_str("(defsrc)(deflayer base)", Default::default())
11 |         .expect("failed to parse cfg");
12 | 
13 |     let mut accumulated_ticks = 0;
14 | 
15 |     let start = Instant::now();
16 |     while start.elapsed() < Duration::from_secs(1) {
17 |         sleep(Duration::from_millis(1));
18 |         accumulated_ticks += k.get_ms_elapsed();
19 |     }
20 | 
21 |     let actually_elapsed_ms = start.elapsed().as_millis();
22 | 
23 |     // Allow fudge of 1%
24 |     // In practice this is within 1ms purely due to the remainder.
25 |     eprintln!("ticks:{accumulated_ticks}, actual elapsed:{actually_elapsed_ms}");
26 |     assert!(accumulated_ticks < (actually_elapsed_ms + 10));
27 |     assert!(accumulated_ticks > (actually_elapsed_ms - 10));
28 | }
29 | 


--------------------------------------------------------------------------------
/src/tests/sim_tests/unicode_sim_tests.rs:
--------------------------------------------------------------------------------
 1 | use super::*;
 2 | 
 3 | #[test]
 4 | fn unicode() {
 5 |     let result = simulate(
 6 |         r##"
 7 |          (defcfg)
 8 |          (defsrc 6 7 8 9 0 f1)
 9 |          (deflayer base
10 |              (unicode r#"("#)
11 |              (unicode r#")"#)
12 |              (unicode r#"""#)
13 |              (unicode "(")
14 |              (unicode ")")
15 |              (tap-dance 200 (f1(unicode 😀)f2(unicode 🙂)))
16 |          )
17 |         "##,
18 |         "d:6 d:7 d:8 d:9 d:0 t:100",
19 |     )
20 |     .no_time();
21 |     assert_eq!(r#"outU:( outU:) outU:" outU:( outU:)"#, result);
22 | }
23 | 
24 | #[test]
25 | #[cfg(target_os = "macos")]
26 | fn macos_unicode_handling() {
27 |     let result = simulate(
28 |         r##"
29 |          (defcfg)
30 |          (defsrc a)
31 |          (deflayer base
32 |              (unicode "🎉")  ;; Test with an emoji that uses multi-unit UTF-16
33 |          )
34 |         "##,
35 |         "d:a t:100",
36 |     )
37 |     .no_time();
38 |     assert_eq!("outU:🎉", result);
39 | }
40 | 


--------------------------------------------------------------------------------
/src/tests/sim_tests/unmod_sim_tests.rs:
--------------------------------------------------------------------------------
  1 | use super::*;
  2 | 
  3 | #[test]
  4 | fn unmod_keys_functionality_works() {
  5 |     let result = simulate(
  6 |         "
  7 |          (defcfg)
  8 |          (defsrc f1 1 2 3 4 5 6 7 8 9 0)
  9 |          (deflayer base
 10 |              (multi lctl rctl lsft rsft lmet rmet lalt ralt)
 11 |              (unmod a)
 12 |              (unmod (lctl) b)
 13 |              (unmod (rctl) c)
 14 |              (unmod (lsft) d)
 15 |              (unmod (rsft) e)
 16 |              (unmod (lmet) f)
 17 |              (unmod (rmet) g)
 18 |              (unmod (lalt) h)
 19 |              (unmod (ralt) i)
 20 |              (unmod (lctl lsft lmet lalt) j)
 21 |          )
 22 |         ",
 23 |         "d:f1 t:5 d:1 u:1 t:5 d:2 u:2 t:5 d:3 u:3 t:5 d:4 u:4 t:5 d:5 u:5 t:5 d:6 u:6 t:5
 24 |                   d:7 u:7 t:5 d:8 u:8 t:5 d:9 u:9 t:5 d:0 u:0 t:5",
 25 |     )
 26 |     .no_time()
 27 |     .to_ascii();
 28 |     assert_eq!(
 29 |         "dn:LCtrl dn:RCtrl dn:LShift dn:RShift dn:LGui dn:RGui dn:LAlt dn:RAlt \
 30 |          up:LCtrl up:RCtrl up:LShift up:RShift up:LGui up:RGui up:LAlt up:RAlt dn:A up:A \
 31 |          dn:LCtrl dn:RCtrl dn:LShift dn:RShift dn:LGui dn:RGui dn:LAlt dn:RAlt \
 32 |          up:LCtrl dn:B up:B dn:LCtrl \
 33 |          up:RCtrl dn:C up:C dn:RCtrl \
 34 |          up:LShift dn:D up:D dn:LShift \
 35 |          up:RShift dn:E up:E dn:RShift \
 36 |          up:LGui dn:F up:F dn:LGui \
 37 |          up:RGui dn:G up:G dn:RGui \
 38 |          up:LAlt dn:H up:H dn:LAlt \
 39 |          up:RAlt dn:I up:I dn:RAlt \
 40 |          up:LCtrl up:LShift up:LGui up:LAlt dn:J up:J dn:LCtrl dn:LShift dn:LGui dn:LAlt",
 41 |         result
 42 |     );
 43 | }
 44 | 
 45 | #[test]
 46 | #[should_panic]
 47 | fn unmod_keys_mod_list_cannot_be_empty() {
 48 |     simulate(
 49 |         "
 50 |          (defcfg)
 51 |          (defsrc a)
 52 |          (deflayer base (unmod () a))
 53 |         ",
 54 |         "",
 55 |     );
 56 | }
 57 | 
 58 | #[test]
 59 | #[should_panic]
 60 | fn unmod_keys_mod_list_cannot_have_nonmod_key() {
 61 |     simulate(
 62 |         "
 63 |          (defcfg)
 64 |          (defsrc a)
 65 |          (deflayer base (unmod (lmet c) a))
 66 |         ",
 67 |         "",
 68 |     );
 69 | }
 70 | 
 71 | #[test]
 72 | #[should_panic]
 73 | fn unmod_keys_mod_list_cannot_have_empty_keys_after_mod_list() {
 74 |     simulate(
 75 |         "
 76 |          (defcfg)
 77 |          (defsrc a)
 78 |          (deflayer base (unmod (lmet)))
 79 |         ",
 80 |         "",
 81 |     );
 82 | }
 83 | 
 84 | #[test]
 85 | #[should_panic]
 86 | fn unmod_keys_mod_list_cannot_have_empty_keys() {
 87 |     simulate(
 88 |         "
 89 |          (defcfg)
 90 |          (defsrc a)
 91 |          (deflayer base (unmod))
 92 |         ",
 93 |         "",
 94 |     );
 95 | }
 96 | 
 97 | #[test]
 98 | #[should_panic]
 99 | fn unmod_keys_mod_list_cannot_have_invalid_keys() {
100 |     simulate(
101 |         "
102 |          (defcfg)
103 |          (defsrc a)
104 |          (deflayer base (unmod invalid-key))
105 |         ",
106 |         "",
107 |     );
108 | }
109 | 


--------------------------------------------------------------------------------
/src/tests/sim_tests/use_defsrc_sim_tests.rs:
--------------------------------------------------------------------------------
 1 | use super::*;
 2 | 
 3 | #[test]
 4 | fn use_defsrc_deflayer() {
 5 |     let result = simulate(
 6 |         r##"
 7 |          (defcfg)
 8 |          (defsrc a b c d)
 9 |          (deflayer base
10 |             1 2 3 (layer-while-held other)
11 |          )
12 |          (deflayer other
13 |             4 5 (layer-while-held src) XX
14 |          )
15 |          (deflayer src
16 |             use-defsrc use-defsrc XX XX
17 |          )
18 |         "##,
19 |         "d:d d:c d:b d:a t:100",
20 |     )
21 |     .to_ascii();
22 |     assert_eq!("t:2ms dn:B t:1ms dn:A", result);
23 | }
24 | 
25 | #[test]
26 | fn use_defsrc_deflayermap() {
27 |     const CFG: &str = "
28 |          (defcfg process-unmapped-keys yes)
29 |          (defsrc a b c d)
30 |          (deflayer base
31 |             1
32 |             (layer-while-held othermap1)
33 |             (layer-while-held othermap2)
34 |             (layer-while-held othermap3)
35 |          )
36 |          (deflayermap (othermap1)
37 |             a 5
38 |             ___ use-defsrc
39 |          )
40 |          (deflayermap (othermap2)
41 |             a 6
42 |             __ use-defsrc
43 |             _ x
44 |          )
45 |          (deflayermap (othermap3)
46 |             a 7
47 |             _ use-defsrc
48 |             __ x
49 |          )
50 |         ";
51 |     let result = simulate(CFG, "d:b d:a d:c d:e t:10").to_ascii();
52 |     assert_eq!("t:1ms dn:Kb5 t:1ms dn:C t:1ms dn:E", result);
53 |     let result = simulate(CFG, "d:c d:a d:c d:e t:10").to_ascii();
54 |     assert_eq!("t:1ms dn:Kb6 t:1ms dn:X t:1ms dn:E", result);
55 |     let result = simulate(CFG, "d:d d:a d:c d:e t:10").to_ascii();
56 |     assert_eq!("t:1ms dn:Kb7 t:1ms dn:C t:1ms dn:X", result);
57 | }
58 | 


--------------------------------------------------------------------------------
/src/tests/sim_tests/vkey_sim_tests.rs:
--------------------------------------------------------------------------------
 1 | use super::*;
 2 | 
 3 | const CFG: &str = r"
 4 |  (defsrc a b c)
 5 |  (defvirtualkeys lmet lmet)
 6 |  (defalias hm (hold-for-duration 50 lmet))
 7 |  (deflayer base
 8 |     (multi @hm (macro-repeat 40 @hm))
 9 |     (multi 1 @hm)
10 |     (release-key lmet)
11 |  )
12 | ";
13 | 
14 | #[test]
15 | fn hold_for_duration() {
16 |     let result = simulate(CFG, "d:a t:200 u:a t:60").to_ascii();
17 |     assert_eq!("t:1ms dn:LGui t:258ms up:LGui", result);
18 |     let result = simulate(CFG, "d:a u:a t:25 d:c u:c t:25").to_ascii();
19 |     assert_eq!("t:2ms dn:LGui t:23ms up:LGui", result);
20 |     let result = simulate(CFG, "d:a u:a t:25 d:b u:b t:25 d:b u:b t:60").to_ascii();
21 |     assert_eq!(
22 |         "t:2ms dn:LGui t:23ms dn:Kb1 t:1ms up:Kb1 t:24ms dn:Kb1 t:1ms up:Kb1 t:49ms up:LGui",
23 |         result
24 |     );
25 | }
26 | 


--------------------------------------------------------------------------------
/tcp_protocol/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name = "kanata-tcp-protocol"
 3 | version = "0.190.0"
 4 | edition = "2021"
 5 | description = "TCP protocol for kanata. This does not follow semver."
 6 | license = "LGPL-3.0-only"
 7 | 
 8 | [dependencies]
 9 | serde = { version = "1", features = ["alloc", "derive"], default-features = false }
10 | serde_derive = "1.0"
11 | serde_json = { version = "1", features = ["alloc"], default-features = false }
12 | 


--------------------------------------------------------------------------------
/tcp_protocol/src/lib.rs:
--------------------------------------------------------------------------------
 1 | use serde::{Deserialize, Serialize};
 2 | use std::str::FromStr;
 3 | 
 4 | #[derive(Debug, Serialize, Deserialize)]
 5 | pub enum ServerMessage {
 6 |     LayerChange { new: String },
 7 |     LayerNames { names: Vec<String> },
 8 |     CurrentLayerInfo { name: String, cfg_text: String },
 9 |     ConfigFileReload { new: String },
10 |     CurrentLayerName { name: String },
11 |     MessagePush { message: serde_json::Value },
12 |     Error { msg: String },
13 | }
14 | 
15 | impl ServerMessage {
16 |     pub fn as_bytes(&self) -> Vec<u8> {
17 |         let mut msg = serde_json::to_vec(self).expect("ServerMessage should serialize");
18 |         msg.push(b'\n');
19 |         msg
20 |     }
21 | }
22 | 
23 | #[derive(Debug, Serialize, Deserialize)]
24 | pub enum ClientMessage {
25 |     ChangeLayer {
26 |         new: String,
27 |     },
28 |     RequestLayerNames {},
29 |     RequestCurrentLayerInfo {},
30 |     RequestCurrentLayerName {},
31 |     ActOnFakeKey {
32 |         name: String,
33 |         action: FakeKeyActionMessage,
34 |     },
35 |     SetMouse {
36 |         x: u16,
37 |         y: u16,
38 |     },
39 | }
40 | 
41 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
42 | pub enum FakeKeyActionMessage {
43 |     Press,
44 |     Release,
45 |     Tap,
46 |     Toggle,
47 | }
48 | 
49 | impl FromStr for ClientMessage {
50 |     type Err = serde_json::Error;
51 | 
52 |     fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
53 |         serde_json::from_str(s)
54 |     }
55 | }
56 | 


--------------------------------------------------------------------------------
/wasm/.gitignore:
--------------------------------------------------------------------------------
1 | # wasm-pack output
2 | pkg/
3 | 
4 | # do not commit lockfile; not important for wasm project
5 | Cargo.lock
6 | 


--------------------------------------------------------------------------------
/wasm/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [workspace]
 2 | members = ["."]
 3 | 
 4 | [package]
 5 | name = "kanata-wasm"
 6 | version = "0.1.0"
 7 | edition = "2021"
 8 | 
 9 | [lib]
10 | crate-type = [ "cdylib", "rlib" ]
11 | 
12 | [dependencies]
13 | wasm-bindgen = "0.2.95"
14 | kanata = { path = ".." , default-features = false, features = [ "simulated_output", "wasm", "zippychord" ] }
15 | anyhow = "1.0.81"
16 | log = "0.4.21"
17 | console_error_panic_hook = "0.1.7"
18 | rustc-hash = "1.1.0"
19 | 


--------------------------------------------------------------------------------
/wasm/README.md:
--------------------------------------------------------------------------------
 1 | # Kanata WASM
 2 | 
 3 | Code to expose kanata functionality over WASM.
 4 | 
 5 | Prerequisites:
 6 | 
 7 | ```
 8 | cargo install wasm-pack
 9 | ```
10 | 
11 | You can run the command below to generate files for use in the browser:
12 | 
13 | ```
14 | wasm-pack build --target web
15 | ```
16 | 
17 | This will output files into `pkg/` which can be used for a website.
18 | This has yet not been tested with targets other than web (e.g. node).
19 | 
20 | An example project using this code is the
21 | [online kanata simulator](https://github.com/jtroo/jtroo.github.io).
22 | 


--------------------------------------------------------------------------------
/windows_key_tester/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name = "windows_key_tester"
 3 | version = "0.3.0"
 4 | authors = ["jtroo <j.andreitabs@gmail.com>"]
 5 | description = "Windows keycode tester"
 6 | keywords = []
 7 | categories = ["command-line-utilities"]
 8 | homepage = "https://github.com/jtroo/kanata"
 9 | repository = "https://github.com/jtroo/kanata"
10 | readme = "README.md"
11 | license = "LGPL-3.0"
12 | edition = "2021"
13 | 
14 | [target.'cfg(target_os = "windows")'.dependencies]
15 | clap = { version = "4", features = [ "std", "derive", "help", "suggestions" ], default-features = false }
16 | log = "0.4.8"
17 | simplelog = "0.12.0"
18 | anyhow = "1"
19 | winapi = { version = "0.3.9", features = [
20 |     "wincon",
21 |     "timeapi",
22 |     "mmsystem",
23 | ] }
24 | native-windows-gui = { version = "1.0.12", default-features = false }
25 | kanata-interception = { version = "0.3.0", optional = true }
26 | kanata = { path = "..", optional = true }
27 | 
28 | [features]
29 | interception_driver = [ "kanata-interception" ]
30 | winiov2 = [ "kanata" ]
31 | 


--------------------------------------------------------------------------------
/windows_key_tester/README.md:
--------------------------------------------------------------------------------
1 | # Windows key tester
2 | 
3 | This directory contains the code for a Windows key tester. This can be used to
4 | help test keyboard->keycode mappings in Windows that may not yet be listed in
5 | kanata. For Linux, use the existing [evtest](https://www.systutorials.com/docs/linux/man/1-evtest/).
6 | 


--------------------------------------------------------------------------------
/windows_key_tester/src/main.rs:
--------------------------------------------------------------------------------
 1 | //! This program is intended to be similar to `evtest` but for Windows. It will read keyboard
 2 | //! events, print out the event info, then forward it the keyboard event as-is to the rest of the
 3 | //! operating system handling.
 4 | 
 5 | #[cfg(target_os = "windows")]
 6 | mod windows;
 7 | #[cfg(target_os = "windows")]
 8 | use windows::*;
 9 | 
10 | #[cfg(target_os = "windows")]
11 | fn main() {
12 |     let ret = main_impl();
13 |     if let Err(ref e) = ret {
14 |         log::error!("main got error {}", e);
15 |     }
16 |     eprintln!("\nPress any key to exit");
17 |     let _ = std::io::stdin().read_line(&mut String::new());
18 | }
19 | 
20 | #[cfg(not(target_os = "windows"))]
21 | fn main() {
22 |     print!("Hello world! Wrong OS. Doing nothing.");
23 | }
24 | 


--------------------------------------------------------------------------------
/windows_key_tester/src/windows.rs:
--------------------------------------------------------------------------------
 1 | use anyhow::Result;
 2 | use simplelog::*;
 3 | 
 4 | use clap::Parser;
 5 | #[cfg(not(feature = "interception_driver"))]
 6 | mod llhook;
 7 | #[cfg(not(feature = "interception_driver"))]
 8 | use llhook::*;
 9 | 
10 | #[cfg(feature = "interception_driver")]
11 | mod interception;
12 | #[cfg(feature = "interception_driver")]
13 | use interception::*;
14 | 
15 | #[derive(Parser, Debug)]
16 | #[clap(author, version, about, long_about = None)]
17 | struct Args {
18 |     /// Enable debug logging
19 |     #[clap(short, long)]
20 |     debug: bool,
21 | 
22 |     /// Enable trace logging (implies --debug as well)
23 |     #[clap(short, long)]
24 |     trace: bool,
25 | }
26 | 
27 | #[cfg(target_os = "windows")]
28 | /// Parse CLI arguments and initialize logging.
29 | fn cli_init() {
30 |     let args = Args::parse();
31 | 
32 |     let log_lvl = match (args.debug, args.trace) {
33 |         (_, true) => LevelFilter::Trace,
34 |         (true, false) => LevelFilter::Debug,
35 |         (false, false) => LevelFilter::Info,
36 |     };
37 | 
38 |     let mut log_cfg = ConfigBuilder::new();
39 |     if let Err(e) = log_cfg.set_time_offset_to_local() {
40 |         eprintln!("WARNING: could not set log TZ to local: {e:?}");
41 |     };
42 |     CombinedLogger::init(vec![TermLogger::new(
43 |         log_lvl,
44 |         log_cfg.build(),
45 |         TerminalMode::Mixed,
46 |         ColorChoice::AlwaysAnsi,
47 |     )])
48 |     .expect("logger can init");
49 |     log::info!("windows_key_tester v{} starting", env!("CARGO_PKG_VERSION"));
50 | }
51 | 
52 | pub(crate) fn main_impl() -> Result<()> {
53 |     cli_init();
54 |     log::info!("Sleeping for 2s. Please release all keys and don't press additional ones.");
55 |     std::thread::sleep(std::time::Duration::from_secs(2));
56 |     start()?;
57 |     Ok(())
58 | }
59 | 


--------------------------------------------------------------------------------
/windows_key_tester/src/windows/llhook.rs:
--------------------------------------------------------------------------------
  1 | //! Safe abstraction over the low-level windows keyboard hook API.
  2 | 
  3 | // This file is taken from kbremap with modifications.
  4 | // https://github.com/timokroeger/kbremap
  5 | 
  6 | use std::ptr;
  7 | 
  8 | use anyhow::Result;
  9 | use winapi::ctypes::*;
 10 | use winapi::shared::minwindef::*;
 11 | use winapi::shared::windef::*;
 12 | use winapi::um::winuser::*;
 13 | 
 14 | /// Wrapper for the low-level keyboard hook API.
 15 | /// Automatically unregisters the hook when dropped.
 16 | pub struct KeyboardHook {
 17 |     handle: HHOOK,
 18 | }
 19 | 
 20 | impl KeyboardHook {
 21 |     /// Sets the low-level keyboard hook for this thread.
 22 |     ///
 23 |     /// Panics when a hook is already registered from the same thread.
 24 |     #[must_use = "The hook will immediatelly be unregistered and not work."]
 25 |     pub fn attach_hook() -> KeyboardHook {
 26 |         KeyboardHook {
 27 |             handle: unsafe {
 28 |                 SetWindowsHookExW(WH_KEYBOARD_LL, Some(hook_proc), ptr::null_mut(), 0)
 29 |                     .as_mut()
 30 |                     .expect("install low-level keyboard hook successfully")
 31 |             },
 32 |         }
 33 |     }
 34 | }
 35 | 
 36 | impl Drop for KeyboardHook {
 37 |     fn drop(&mut self) {
 38 |         unsafe { UnhookWindowsHookEx(self.handle) };
 39 |     }
 40 | }
 41 | 
 42 | /// Key event received by the low level keyboard hook.
 43 | #[allow(dead_code)]
 44 | #[derive(Debug, Clone, Copy)]
 45 | pub struct InputEvent {
 46 |     pub code: u32,
 47 |     /// Key was released
 48 |     pub up: bool,
 49 | }
 50 | 
 51 | impl InputEvent {
 52 |     #[cfg(not(feature = "winiov2"))]
 53 |     fn from_hook_lparam(lparam: &KBDLLHOOKSTRUCT) -> Self {
 54 |         Self {
 55 |             code: lparam.vkCode,
 56 |             up: lparam.flags & LLKHF_UP != 0,
 57 |         }
 58 |     }
 59 | 
 60 |     #[cfg(feature = "winiov2")]
 61 |     fn from_hook_lparam(lparam: &KBDLLHOOKSTRUCT) -> Self {
 62 |         let extended = if lparam.flags & 0x1 == 0x1 { 0xE000 } else { 0 };
 63 |         let code = kanata_state_machine::oskbd::u16_to_osc((lparam.scanCode as u16) | extended)
 64 |             .map(Into::into)
 65 |             .unwrap_or(lparam.vkCode);
 66 |         Self {
 67 |             code,
 68 |             up: lparam.flags & LLKHF_UP != 0,
 69 |         }
 70 |     }
 71 | }
 72 | 
 73 | /// The actual WinAPI compatible callback.
 74 | unsafe extern "system" fn hook_proc(code: c_int, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
 75 |     let hook_lparam = &*(lparam as *const KBDLLHOOKSTRUCT);
 76 |     let is_injected = hook_lparam.flags & LLKHF_INJECTED != 0;
 77 |     let key_event = InputEvent::from_hook_lparam(hook_lparam);
 78 |     log::info!("{code}, {wparam:?}, {is_injected}, {key_event:?}");
 79 |     CallNextHookEx(ptr::null_mut(), code, wparam, lparam)
 80 | }
 81 | 
 82 | pub fn start() -> Result<()> {
 83 |     // Display debug and panic output when launched from a terminal.
 84 |     unsafe {
 85 |         use winapi::um::wincon::*;
 86 |         if AttachConsole(ATTACH_PARENT_PROCESS) != 0 {
 87 |             panic!("Could not attach to console");
 88 |         }
 89 |     };
 90 |     native_windows_gui::init()?;
 91 |     // This callback should return `false` if the input event is **not** handled by the
 92 |     // callback and `true` if the input event **is** handled by the callback. Returning false
 93 |     // informs the callback caller that the input event should be handed back to the OS for
 94 |     // normal processing.
 95 |     let _kbhook = KeyboardHook::attach_hook();
 96 |     log::info!("hook attached, you can type now");
 97 |     // The event loop is also required for the low-level keyboard hook to work.
 98 |     native_windows_gui::dispatch_thread_events();
 99 |     Ok(())
100 | }
101 | 


--------------------------------------------------------------------------------