[
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "{\n\t\"name\": \"Rust\",\n\t\"image\": \"mcr.microsoft.com/devcontainers/rust:1-buster\"\n\n\t// Features to add to the dev container. More info: https://containers.dev/implementors/features.\n\t// \"features\": {},\n\n\t// Use 'forwardPorts' to make a list of ports inside the container available locally.\n\t// \"forwardPorts\": [],\n\n\t// Use 'postCreateCommand' to run commands after the container is created.\n\t// \"postCreateCommand\": \"rustc --version\",\n\n\t// Configure tool-specific properties.\n\t// \"customizations\": {},\n\n\t// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.\n\t// \"remoteUser\": \"root\"\n}\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto eol=lf\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: \"Bug report\"\ndescription: Create a report to help the project improve.\nlabels: [\"bug\"]\nassignees: [\"jtroo\"]\ntitle: \"Bug: title_goes_here\"\nbody:\n  - type: checkboxes\n    attributes:\n      label: Requirements\n      description: Before you create a bug report, please check the following\n      options:\n        - 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.\n          required: true\n        - 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).\n          required: true\n  - type: textarea\n    id: summary\n    attributes:\n      label: Describe the bug\n      description: |\n        A clear and concise description of what the bug is.\n        Ensure any config snippets are either in the next section or are code formatted.\n    validations:\n      required: true\n  - type: textarea\n    id: config\n    attributes:\n      label: Relevant kanata config\n      render: text\n      description: E.g. defcfg, defsrc, deflayer, defalias items. If in doubt, feel free to include your entire config.\n    validations:\n      required: false\n  - type: textarea\n    id: reproduce\n    attributes:\n      label: To Reproduce\n      description: |\n        Walk through the steps needed to reproduce the bug.\n        Use the simulator if it is not device/OS related: https://jtroo.github.io/.\n    validations:\n      required: true\n  - type: textarea\n    id: expected\n    attributes:\n      label: Expected behavior\n      description: A clear and concise description of what you expected to happen.\n    validations:\n      required: true\n  - type: input\n    id: version\n    attributes:\n      label: Kanata version\n      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.\n      placeholder: e.g. kanata 1.3.0\n    validations:\n      required: true\n  - type: textarea\n    id: logs\n    attributes:\n      label: Debug logs\n      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.\n      render: text\n    validations:\n      required: false\n  - type: input\n    id: os\n    attributes:\n      label: Operating system and I/O mechanism\n      description: E.g. Linux, macOS, Windows 10, Windows 11 with Interception driver\n      placeholder: e.g. Linux\n    validations:\n      required: true\n  - type: textarea\n    id: additional\n    attributes:\n      label: Additional context\n      description: Add any other context about the problem here.\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true\ncontact_links:\n  - name: Discussions\n    url: https://github.com/jtroo/kanata/discussions\n    about: Ask for help or interact with the community."
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: \"Feature request\"\ndescription: Suggest an idea for this project\ntitle: 'Feature request: feature_summary_goes_here'\nlabels: [\"enhancement\"]\nassignees: []\nbody:\n  - type: textarea\n    attributes:\n      label: Is your feature request related to a problem? Please describe.\n      description: |\n        A clear and concise description of what the problem is.\n      placeholder: Ex. I'm always frustrated when [...]\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Describe the solution you'd like.\n      description: |\n        A clear and concise description of what you want to happen.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Describe alternatives you've considered.\n      description: |\n        A clear and concise description of any alternative solutions or features you've considered.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Additional context\n      description: |\n        Add any other context or screenshots about the feature request here.\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "## Describe your changes. Use imperative present tense.\n\n## Checklist\n\n- Add documentation to docs/config.adoc\n  - [ ] Yes or N/A\n- Add example and basic docs to cfg_samples/kanata.kbd\n  - [ ] Yes or N/A\n- Update error messages\n  - [ ] Yes or N/A\n- Added tests, or did manual testing\n  - [ ] Yes\n"
  },
  {
    "path": ".github/workflows/build-everything.yml",
    "content": "name: build-everything\n\non:\n  workflow_dispatch:\n    branches: [ \"main\" ]\n\nenv:\n  CARGO_TERM_COLOR: always\n  RUSTFLAGS: \"-Dwarnings\"\n\njobs:\n  build-linux:\n    uses: ./.github/workflows/linux-build.yml\n  build-windows:\n    uses: ./.github/workflows/windows-build.yml\n  build-macos:\n    uses: ./.github/workflows/macos-build.yml\n"
  },
  {
    "path": ".github/workflows/linux-build.yml",
    "content": "name: linux-build\n\non:\n  workflow_dispatch:\n    branches: [ \"main\" ]\n  workflow_call:\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  build-linux-x64:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: Swatinem/rust-cache@v2\n        with:\n          shared-key: \"persist-cross-job-linux-x64\"\n      - name: Do the stuff on x64 ubuntu linux\n        shell: bash\n        run: |\n          mkdir -p artifacts-x64\n          cargo build --release\n          mv target/release/kanata artifacts-x64/kanata_linux_x64\n          cargo build --release --features cmd\n          mv target/release/kanata artifacts-x64/kanata_linux_cmd_allowed_x64\n      - uses: actions/upload-artifact@v4\n        with:\n          name: linux-binaries-x64\n          path: |\n            artifacts-x64/kanata_linux_x64\n            artifacts-x64/kanata_linux_cmd_allowed_x64\n"
  },
  {
    "path": ".github/workflows/macos-build.yml",
    "content": "name: macos-build\n\non:\n  workflow_dispatch:\n    branches: [ \"main\" ]\n  workflow_call:\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  build-macos-arm64:\n    runs-on: macos-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: dtolnay/rust-toolchain@stable\n        with:\n          toolchain: stable\n          target: aarch64-apple-darwin\n      - uses: Swatinem/rust-cache@v2\n        with:\n          shared-key: \"persist-cross-job-macos-aarch64\"\n      - name: Do the stuff on arm64\n        shell: bash\n        run: |\n          mkdir -p artifacts-arm64\n          cargo build --release --target aarch64-apple-darwin\n          mv target/aarch64-apple-darwin/release/kanata artifacts-arm64/kanata_macos_arm64\n          cargo build --release --features cmd --target aarch64-apple-darwin\n          mv target/aarch64-apple-darwin/release/kanata artifacts-arm64/kanata_macos_cmd_allowed_arm64\n      - uses: actions/upload-artifact@v4\n        with:\n          name: macos-binaries-arm64\n          path: |\n            artifacts-arm64/kanata_macos_arm64\n            artifacts-arm64/kanata_macos_cmd_allowed_arm64\n\n  build-macos-x64:\n    runs-on: macos-15-intel\n\n    steps:\n    - uses: actions/checkout@v3\n    - uses: Swatinem/rust-cache@v2\n      with:\n        shared-key: \"persist-cross-job-macos-x64\"\n    - name: Do the stuff on x64\n      shell: bash\n      run: |\n        mkdir -p artifacts\n        cargo build --release\n        mv target/release/kanata artifacts/kanata_macos_x64\n        cargo build --release --features cmd\n        mv target/release/kanata artifacts/kanata_macos_cmd_allowed_x64\n    - uses: actions/upload-artifact@v4\n      with:\n        name: macos-binaries-x64\n        path: |\n          artifacts/kanata_macos_x64\n          artifacts/kanata_macos_cmd_allowed_x64\n\n"
  },
  {
    "path": ".github/workflows/rust.yml",
    "content": "name: cargo-checks\n\non:\n  push:\n    branches: [ \"main\" ]\n    paths:\n      - Cargo.*\n      - src/**/*\n      - keyberon/**/*\n      - cfg_samples/**/*\n      - parser/**/*\n      - tcp_protocol/**/*\n      - wasm/**/*\n      - .github/workflows/rust.yml\n  pull_request:\n    branches: [ \"main\" ]\n    paths:\n      - Cargo.*\n      - src/**/*\n      - keyberon/**/*\n      - parser/**/*\n      - cfg_samples/**/*\n      - tcp_protocol/**/*\n      - wasm/**/*\n      - .github/workflows/rust.yml\n\nenv:\n  CARGO_TERM_COLOR: always\n  RUSTFLAGS: \"-Dwarnings\"\n\njobs:\n\n  fmt:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v3\n    - name: Check fmt\n      run: cargo fmt --all --check\n\n  build-android:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        include:\n          - build: linux\n            os: ubuntu-latest\n            target: aarch64-linux-android\n\n    steps:\n    - uses: actions/checkout@v3\n    - uses: Swatinem/rust-cache@v2\n      with:\n        shared-key: \"persist-cross-job\"\n        workspaces: ./\n    - run: rustup target add aarch64-linux-android\n\n    - name: Build for Android\n      run: cargo check --target aarch64-linux-android\n\n  build-test-clippy-linux:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        include:\n          - build: linux\n            os: ubuntu-latest\n            target: x86_64-unknown-linux-musl\n\n    steps:\n    - uses: actions/checkout@v3\n    - uses: Swatinem/rust-cache@v2\n      with:\n        shared-key: \"persist-cross-job\"\n        workspaces: ./\n    - run: rustup component add clippy\n\n    - name: Run tests no features\n      run: cargo test --all --no-default-features\n    - name: Run clippy no features\n      run: cargo clippy --all --no-default-features -- -D warnings\n\n    - name: Run tests default features\n      run: cargo test --all\n    - name: Run clippy default features\n      run: cargo clippy --all -- -D warnings\n\n    - name: Run tests cmd\n      run: cargo test --all --features=cmd\n    - name: Run clippy cmd\n      run: cargo clippy --all --features=cmd -- -D warnings\n\n    - name: Run tests simulated output\n      run: cargo test --features=simulated_output -- sim_tests\n    - name: Run tests simulated output on_idle\n      run: cargo test --features=simulated_output -- must_be_single_threaded --ignored --test-threads=1\n    - name: Run clippy simulated output\n      run: cargo clippy --all --features=simulated_output,cmd -- -D warnings\n\n    - name: Run clippy for parser with lsp feature\n      run: cargo clippy -p kanata-parser --features=lsp -- -D warnings\n\n  build-test-clippy-windows:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        include:\n          - build: windows\n            os: windows-latest\n            target: x86_64-pc-windows-msvc\n\n    steps:\n    - uses: actions/checkout@v3\n    - uses: Swatinem/rust-cache@v2\n      with:\n        shared-key: \"persist-cross-job\"\n        workspaces: ./\n    - run: rustup component add clippy\n\n    - name: Run tests no features\n      run: cargo test --all --no-default-features\n    - name: Run clippy no features\n      run: cargo clippy --all --no-default-features -- -D warnings\n\n    - name: Run tests default features\n      run: cargo test --all\n    - name: Run clippy default features\n      run: cargo clippy --all -- -D warnings\n\n    - name: Run tests winIOv2\n      run: cargo test --all --features=cmd,win_llhook_read_scancodes,win_sendinput_send_scancodes\n    - name: Run clippy all winIOv2\n      run: cargo clippy --all --features=cmd,win_llhook_read_scancodes,win_sendinput_send_scancodes -- -D warnings\n\n    - name: Run tests all features\n      run: cargo test  -p kanata -p kanata-parser -p kanata-keyberon -p kanata-tcp-protocol --features=cmd,interception_driver,win_sendinput_send_scancodes\n    - name: Run clippy all features\n      run: cargo clippy --all --features=cmd,interception_driver,win_sendinput_send_scancodes -- -D warnings\n\n    - name: Run tests simulated output\n      run: cargo test --features=simulated_output -- sim_tests\n    - name: Run tests simulated output on_idle\n      run: cargo test --features=simulated_output -- sim_tests::vkey_sim_tests::on_idle --ignored\n    - name: Run clippy simulated output\n      run: cargo clippy --all --features=simulated_output,cmd -- -D warnings\n\n    - name: Run tests gui\n      run: cargo test --all --features=gui\n    - name: Run clippy gui\n      run: cargo clippy --all --features=gui -- -D warnings\n\n    - name: Check gui+cmd+interception\n      run: cargo check --features gui,cmd,interception_driver\n\n  build-test-clippy-macos:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        include:\n          - build: macos\n            os: macos-latest\n            target: x86_64-apple-darwin\n\n    steps:\n    - uses: actions/checkout@v3\n    - uses: Swatinem/rust-cache@v2\n      with:\n        shared-key: \"persist-cross-job\"\n        workspaces: ./\n    - run: rustup component add clippy\n\n    - name: Run tests default features\n      run: cargo test --all\n    - name: Run clippy default features\n      run: cargo clippy --all -- -D warnings\n\n    - name: Run tests cmd\n      run: cargo test --all --features=cmd\n    - name: Run clippy all features\n      run: cargo clippy --all --features=cmd -- -D warnings\n"
  },
  {
    "path": ".github/workflows/windows-build.yml",
    "content": "name: windows-build\n\non:\n  workflow_dispatch:\n    branches: [ \"main\" ]\n  workflow_call:\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  build-windows-x64:\n    runs-on: windows-latest\n\n    steps:\n      - uses: actions/checkout@v3\n      - uses: Swatinem/rust-cache@v2\n        with:\n          shared-key: \"persist-cross-job-win-x64\"\n      - name: Build x64\n        shell: powershell\n        run: |\n          md artifacts\n          cargo build --release --features win_manifest,win_sendinput_send_scancodes,win_llhook_read_scancodes --target x86_64-pc-windows-msvc\n          mv target/x86_64-pc-windows-msvc/release/kanata.exe artifacts/kanata_windows_tty_winIOv2_x64.exe\n          cargo build --release --features win_manifest,win_sendinput_send_scancodes,win_llhook_read_scancodes,cmd --target x86_64-pc-windows-msvc\n          mv target/x86_64-pc-windows-msvc/release/kanata.exe artifacts/kanata_windows_tty_winIOv2_cmd_allowed_x64.exe\n          cargo build --release --features win_manifest,interception_driver --target x86_64-pc-windows-msvc\n          mv target/x86_64-pc-windows-msvc/release/kanata.exe artifacts/kanata_windows_tty_wintercept_x64.exe\n          cargo build --release --features win_manifest,cmd,interception_driver --target x86_64-pc-windows-msvc\n          mv target/x86_64-pc-windows-msvc/release/kanata.exe artifacts/kanata_windows_tty_wintercept_cmd_allowed_x64.exe\n          cargo build --release --features gui,win_manifest,win_sendinput_send_scancodes,win_llhook_read_scancodes --target x86_64-pc-windows-msvc\n          mv target/x86_64-pc-windows-msvc/release/kanata.exe artifacts/kanata_windows_gui_winIOv2_x64.exe\n          cargo build --release --features gui,win_manifest,win_sendinput_send_scancodes,win_llhook_read_scancodes,cmd --target x86_64-pc-windows-msvc\n          mv target/x86_64-pc-windows-msvc/release/kanata.exe artifacts/kanata_windows_gui_winIOv2_cmd_allowed_x64.exe\n          cargo build --release --features gui,win_manifest,interception_driver --target x86_64-pc-windows-msvc\n          mv target/x86_64-pc-windows-msvc/release/kanata.exe artifacts/kanata_windows_gui_wintercept_x64.exe\n          cargo build --release --features gui,win_manifest,cmd,interception_driver --target x86_64-pc-windows-msvc\n          mv target/x86_64-pc-windows-msvc/release/kanata.exe artifacts/kanata_windows_gui_wintercept_cmd_allowed_x64.exe\n          cargo build --release --features passthru_ahk --package=simulated_passthru --target x86_64-pc-windows-msvc\n          mv target/x86_64-pc-windows-msvc/release/kanata_passthru.dll artifacts/kanata_passthru_x64.dll\n      - uses: actions/upload-artifact@v4\n        with:\n          name: windows-binaries-x64\n          path: |\n            artifacts/kanata_windows_tty_winIOv2_x64.exe\n            artifacts/kanata_windows_tty_winIOv2_cmd_allowed_x64.exe\n            artifacts/kanata_windows_tty_wintercept_x64.exe\n            artifacts/kanata_windows_tty_wintercept_cmd_allowed_x64.exe\n            artifacts/kanata_windows_gui_winIOv2_x64.exe\n            artifacts/kanata_windows_gui_winIOv2_cmd_allowed_x64.exe\n            artifacts/kanata_windows_gui_wintercept_x64.exe\n            artifacts/kanata_windows_gui_wintercept_cmd_allowed_x64.exe\n            artifacts/kanata_passthru_x64.dll\n\n  build-windows-arm64:\n    runs-on: windows-11-arm\n\n    steps:\n      - uses: actions/checkout@v3\n      - uses: Swatinem/rust-cache@v2\n        with:\n          shared-key: \"persist-cross-job-win-arm64\"\n      - name: Build arm64\n        shell: powershell\n        run: |\n          md artifacts\n          cargo build --release --features win_manifest,win_sendinput_send_scancodes,win_llhook_read_scancodes --target aarch64-pc-windows-msvc\n          mv target/aarch64-pc-windows-msvc/release/kanata.exe artifacts/kanata_windows_tty_winIOv2_arm64.exe\n          cargo build --release --features win_manifest,win_sendinput_send_scancodes,win_llhook_read_scancodes,cmd --target aarch64-pc-windows-msvc\n          mv target/aarch64-pc-windows-msvc/release/kanata.exe artifacts/kanata_windows_tty_winIOv2_cmd_allowed_arm64.exe\n          cargo build --release --features gui,win_manifest,win_sendinput_send_scancodes,win_llhook_read_scancodes --target aarch64-pc-windows-msvc\n          mv target/aarch64-pc-windows-msvc/release/kanata.exe artifacts/kanata_windows_gui_winIOv2_arm64.exe\n          cargo build --release --features gui,win_manifest,win_sendinput_send_scancodes,win_llhook_read_scancodes,cmd --target aarch64-pc-windows-msvc\n          mv target/aarch64-pc-windows-msvc/release/kanata.exe artifacts/kanata_windows_gui_winIOv2_cmd_allowed_arm64.exe\n      - uses: actions/upload-artifact@v4\n        with:\n          name: windows-binaries-arm64\n          path: |\n            artifacts/kanata_windows_tty_winIOv2_arm64.exe\n            artifacts/kanata_windows_tty_winIOv2_cmd_allowed_arm64.exe\n            artifacts/kanata_windows_gui_winIOv2_arm64.exe\n            artifacts/kanata_windows_gui_winIOv2_cmd_allowed_arm64.exe\n"
  },
  {
    "path": ".gitignore",
    "content": "**/target\n.vscode/\nCLAUDE.md\n.DS_Store\nPLAN.md\n\n# Manual testing files\ntest_*.kbd\nmanual_test/\n*.test.kbd\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nmembers = [\n\t\"./\",\n\t\"parser\",\n\t\"keyberon\",\n\t\"example_tcp_client\",\n\t\"tcp_protocol\",\n\t\"windows_key_tester\",\n\t\"simulated_input\",\n\t\"simulated_passthru\",\n\t\"wasm\",\n]\nexclude = [\n\t\"interception\",\n\t\"key-sort-add\",\n]\nresolver = \"2\"\n\n[package]\nname = \"kanata\"\nversion = \"1.11.0\"\nauthors = [\"jtroo <j.andreitabs@gmail.com>\"]\ndescription = \"Multi-layer keyboard customization\"\nkeywords = [\"keyboard\", \"layout\", \"remapping\"]\ncategories = [\"command-line-utilities\"]\nhomepage = \"https://github.com/jtroo/kanata\"\nrepository = \"https://github.com/jtroo/kanata\"\nreadme = \"README.md\"\nlicense = \"LGPL-3.0-only\"\nedition = \"2024\"\ndefault-run = \"kanata\"\n\n[lib]\nname = \"kanata_state_machine\"\npath = \"src/lib.rs\"\ncrate-type = [\"rlib\", \"staticlib\"]\n\n[[bin]]\nname = \"kanata\"\npath = \"src/main.rs\"\n\n[dependencies]\nanyhow = \"1\"\nclap = { version = \"4\", features = [ \"std\", \"derive\", \"help\", \"suggestions\" ], default-features = false }\ndirs = \"5.0.1\"\nindoc = { version = \"2.0.4\", optional = true }\nlog = { version = \"0.4.8\", default-features = false }\nmiette = { version = \"5.7.0\", features = [\"fancy\"] }\nonce_cell = \"1\"\nparking_lot = \"0.12\"\nradix_trie = \"0.2\"\nrustc-hash = \"1.1.0\"\nsimplelog = \"0.12.0\"\nserde_json = { version = \"1\", features = [\"std\"], default-features = false, optional = true }\ntime = \"0.3.47\"\nweb-time = \"1.1.0\"\n\nkanata-keyberon = { path = \"keyberon\", version = \"0.1110.0\" }\nkanata-parser =   { path = \"parser\", version = \"0.1110.0\" }\nkanata-tcp-protocol = { path = \"tcp_protocol\", version = \"0.1110.0\" }\n\n[target.'cfg(not(any(target_arch = \"wasm32\", target_os = \"android\")))'.dependencies]\narboard = \"3.4\"\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\nkarabiner-driverkit = \"0.2.1\"\nobjc = \"0.2.7\"\ncore-graphics = \"0.24.0\"\nopen = { version = \"5\", optional = true }\nlibc = \"0.2\"\nos_pipe = \"1.2.1\"\n\n[target.'cfg(any(target_os = \"linux\", target_os = \"android\"))'.dependencies]\nevdev = \"0.13.0\"\ninotify = { version = \"0.10.0\", default-features = false }\nmio = { version = \"0.8.11\", features = [\"os-poll\", \"os-ext\"] }\nnix = { version = \"0.26.1\", features = [\"ioctl\"] }\nopen = { version = \"5\", optional = true }\nsignal-hook = \"0.3.14\"\nsd-notify = \"0.4.1\"\n\n[target.'cfg(target_os = \"windows\")'.dependencies]\nencode_unicode = \"0.3.6\"\nwinapi = { version = \"0.3.9\", features = [\n    \"wincon\",\n    \"timeapi\",\n    \"mmsystem\",\n    \"winuser\",\n    \"windef\",\n    \"minwindef\",\n] }\nwindows-sys = { version = \"0.52.0\", features = [\n    \"Win32_Devices_DeviceAndDriverInstallation\",\n    \"Win32_Devices_Usb\",\n    \"Win32_Foundation\",\n    \"Win32_Graphics_Gdi\",\n    \"Win32_Security\",\n    \"Win32_System_Diagnostics_Debug\",\n    \"Win32_System_Registry\",\n    \"Win32_System_Threading\",\n    \"Win32_UI_Controls\",\n    \"Win32_UI_Shell\",\n    \"Win32_UI_HiDpi\",\n    \"Win32_UI_WindowsAndMessaging\",\n    \"Win32_System_SystemInformation\",\n    \"Wdk\",\n    \"Wdk_System\",\n    \"Wdk_System_SystemServices\",\n], optional=true }\nnative-windows-gui = { version = \"1.0.13\", default-features = false}\nregex = { version = \"1.10.4\", optional = true }\nkanata-interception = { version = \"0.3.0\", optional = true }\nmuldiv = { version = \"1.0.1\", optional = true }\nstrip-ansi-escapes = { version = \"0.2.0\", optional = true }\nopen = { version = \"5\", features = [\"shellexecute-on-windows\"], optional = true}\n# shellexecute fix allows opening files already opened for writing, needs _detached mode\n\n[build-dependencies]\nembed-resource = { version = \"2.4.2\", optional = true }\nindoc = { version = \"2.0.4\", optional = true }\nregex = { version = \"1.10.4\", optional = true }\n\n[features]\ndefault = [\"tcp_server\",\"win_sendinput_send_scancodes\", \"zippychord\"]\nperf_logging = []\ntcp_server = [\"dep:serde_json\", \"kanata-keyberon/tap_hold_tracker\"]\nwin_sendinput_send_scancodes = [\"kanata-parser/win_sendinput_send_scancodes\"]\nwin_llhook_read_scancodes = [\"kanata-parser/win_llhook_read_scancodes\"]\nwiniov2 = [\"win_llhook_read_scancodes\",\"win_sendinput_send_scancodes\"]\nwin_manifest = [\"dep:embed-resource\", \"dep:indoc\", \"dep:regex\"]\n# delete cargo-clippy when the objc crate is replaced\ncargo-clippy = []\ncmd = [\"kanata-parser/cmd\"]\ninterception_driver = [\"dep:kanata-interception\", \"kanata-parser/interception_driver\"]\nsimulated_output = [\"dep:indoc\"]\nsimulated_input = [\"dep:indoc\"]\npassthru_ahk = [\"simulated_input\",\"simulated_output\"]\ngui = [\"win_manifest\",\"kanata-parser/gui\",\n  \"win_sendinput_send_scancodes\",\"win_llhook_read_scancodes\",\n  \"dep:muldiv\",\"dep:strip-ansi-escapes\",\"dep:open\",\n  \"dep:windows-sys\",\n  \"winapi/processthreadsapi\",\n  \"native-windows-gui/tray-notification\",\"native-windows-gui/message-window\",\"native-windows-gui/menu\",\"native-windows-gui/cursor\",\"native-windows-gui/high-dpi\",\"native-windows-gui/embed-resource\",\"native-windows-gui/image-decoder\",\"native-windows-gui/notice\",\"native-windows-gui/animation-timer\",\n]\nzippychord = [\"kanata-parser/zippychord\"]\n\n[profile.release]\nopt-level = \"z\"\nlto = \"fat\"\npanic = \"abort\"\ncodegen-units = 1\n"
  },
  {
    "path": "EnableUIAccess/EnableUIAccess_launch.ahk",
    "content": "#requires AutoHotkey v2.0\n#SingleInstance Off ; Needed for elevation with *runas.\n/* v2 based on EnableUIAccess.ahk v1.01 by Lexikos USE AT YOUR OWN RISK\n  Enables the uiAccess flag in an application's embedded manifest and signs the file with a self-signed digital certificate. If the file is in a trusted location (A_ProgramFiles or A_WinDir), this allows the application to bypass UIPI (User Interface Privilege Isolation, a part of User Account Control in Vista/7). It also enables the journal playback hook (SendPlay).\n  Command line params (mutually exclusive):\n    SkipWarning     - don't display the initial warning\n    \"<in>\" \"<out>\"  - attempt to run silently using the given file(s)\n  This script and the provided Lib files may be used, modified, copied, etc. without restriction.\n*/\n#include <EnableUIAccess>\n\nin_file  := (A_Args.Has(1))?A_Args[1]:'' ; Command line args\nout_file := (A_Args.Has(2))?A_Args[2]:''\n\nif (in_file = \"\"){\n  msgResult := MsgBox(\"Enable the selected EXE to bypass UAC-UIPI security restrictions imposed by modifying 'UIAccess' attribute in the file's embedded manifest and signing the file using a self-signed digital certificate, which is then installed in the local machine's Trusted Root Certification Authorities store.`n`nThe resulting EXE is unusable on a system without this certificate installed!`n`nContinue at your own risk\", \"\", 49)\n  if (msgResult = \"Cancel\"){\n    ExitApp()\n  }\n}\n\nif !A_IsAdmin {\n  if (in_file = \"\") {\n    in_file := \"SkipWarning\"\n  }\n  cmd := \"`\"\" . A_ScriptFullPath . \"`\"\"\n  if !A_IsCompiled {   ; Use A_AhkPath in case the \"runas\" verb isn't registered for ahk files.\n    cmd := \"`\"\" . A_AhkPath . \"`\" \" . cmd\n  }\n  Try Run(\"*RunAs \" cmd \" `\"\" in_file \"`\" `\"\" out_file \"`\"\", , \"\", )\n  ExitApp()\n}\nglobal user_specified_files := false\nif (in_file = \"\" || in_file = \"SkipWarning\") { ; Find AutoHotkey installation.\n  InstallDir := RegRead(\"HKEY_LOCAL_MACHINE\\SOFTWARE\\AutoHotkey\", \"InstallDir\")\n  if A_LastError && A_PtrSize=8 {\n    InstallDir := RegRead(\"HKLM\\SOFTWARE\\Wow6432Node\\AutoHotkey\", \"InstallDir\")\n  }\n  ; Let user confirm or select file(s).\n  in_file := FileSelect(1, InstallDir \"\\AutoHotkey.exe\", \"Select Source File\", \"Executable Files (*.exe)\")\n  if A_LastError {\n    ExitApp()\n  }\n  out_file := FileSelect(\"S16\", in_file, \"Select Destination File\", \"Executable Files (*.exe)\")\n  if A_LastError {\n    ExitApp()\n  }\n  user_specified_files := true\n}\n\nLoop in_file { ; Convert short paths to long paths\n  in_file := A_LoopFileFullPath\n}\nif (out_file = \"\") {   ; i.e. only one file was given via command line\n  out_file := in_file\n} else {\n  Loop out_file {\n    out_file := A_LoopFileFullPath\n  }\n}\nif Crypt.IsSigned(in_file) {\n  msgResult := MsgBox(\"Input file is already signed.  The script will now exit\" in_file,\"\", 48)\n  ExitApp()\n}\n\nif user_specified_files && !IsTrustedLocation(out_file) {\n  msgResult := MsgBox(\"Target path is not a trusted location (Program Files or Windows\\System32), so 'uiAccess'  will have no effect until the file is moved there\",\"\", 49)\n  if (msgResult = \"Cancel\") {\n    ExitApp()\n  }\n}\n\nif (in_file = out_file) { ; The following should typically work even if the file is in use\n  bak_file := in_file \"~\" A_Now \".bak\"\n  FileMove(in_file, bak_file, 1)\n  if A_LastError {\n    Fail(\"Failed to rename selected file.\")\n  }\n  in_file := bak_file\n}\nTry {\n  FileCopy(in_file, out_file, 1)\n} Catch as Err {\n  throw OSError(Err)\n}\nif A_LastError {\n  Fail(\"Failed to copy file to destination.\")\n}\n\nif !EnableUIAccess(out_file) { ; Set the uiAccess attribute in the file's manifest\n  Fail(\"Failed to set uiAccess attribute in manifest\")\n}\n\n\nif (user_specified_files && in_file != out_file) { ; in interactive mode, if not overwriting the original file, offer to create an additional context menu item for AHK files\n  uiAccessVerb := RegRead(\"HKCR\\AutoHotkeyScript\\Shell\\uiAccess\\Command\")\n  if A_LastError {\n    msgResult := MsgBox(\"Register `\"Run Script with UI Access`\" context menu item?\", \"\", 3)\n    if (msgResult = \"Yes\") {\n      RegWrite(\"Run with UI Access\", \"REG_SZ\", \"HKCR\\AutoHotkeyScript\\Shell\\uiAccess\")\n      RegWrite(\"`\"\" out_file \"`\" `\"`%1`\" `%*\", \"REG_SZ\", \"HKCR\\AutoHotkeyScript\\Shell\\uiAccess\\Command\")\n    }\n    if (msgResult = \"Cancel\")\n      ExitApp()\n  }\n}\n\nIsTrustedLocation(path) { ; IsTrustedLocation  →true if path is a valid location for uiAccess=\"true\"\n  ; http://msdn.microsoft.com/en-us/library/bb756929 \"\\Program Files\\ and \\windows\\system32\\ are currently 2 allowable protected locations.\" However, \\Program Files (x86)\\ also appears to be allowed\n  if InStr(path, A_ProgramFiles \"\\\") = 1 {\n    return true\n  }\n  if InStr(path, A_WinDir \"\\System32\\\") = 1 {\n    return true\n  }\n  other := EnvGet(A_PtrSize=8 ? \"ProgramFiles(x86)\" : \"ProgramW6432\") ; On 64-bit systems, if this script is 32-bit, A_ProgramFiles is %ProgramFiles(x86)%, otherwise it is %ProgramW6432%. So check the opposite \"Program Files\" folder:\n  if (other != \"\" && InStr(path, other \"\\\") = 1) {\n    return true\n  }\n  return   false\n}\n\nFail(msg) {\n  ; if (%True% != \"Silent\") { ;???\n    MsgBox(msg \"`nA_LastError: \" A_LastError, \"\", 16)\n  ; }\n  ExitApp()\n}\n\nWarn(msg) {\n  msg .= \" (Err \" A_LastError \")`n\"\n  OutputDebug(msg)\n  FileAppend(msg, \"*\")\n}\n"
  },
  {
    "path": "EnableUIAccess/Lib/EnableUIAccess.ahk",
    "content": "#requires AutoHotkey v2.0\n\nEnableUIAccess(ExePath) {\n  static CertName := \"AutoHotkey\"\n  hStore := DllCall(\"Crypt32\\CertOpenStore\", \"ptr\",10 ; STORE_PROV_SYSTEM_W\n    , \"uint\",0, \"ptr\",0, \"uint\",0x20000 ; SYSTEM_STORE_LOCAL_MACHINE\n    , \"wstr\",\"Root\", \"ptr\")\n  if !hStore {\n    throw OSError()\n  }\n  store := CertStore(hStore)\n  cert := CertContext() ; Find or create certificate for signing.\n  while (cert.ptr := DllCall(\"Crypt32\\CertFindCertificateInStore\", \"ptr\",hStore\n      , \"uint\",0x10001 ; X509_ASN_ENCODING|PKCS_7_ASN_ENCODING\n      , \"uint\",0, \"uint\",0x80007 ; FIND_SUBJECT_STR\n      , \"wstr\", CertName, \"ptr\",cert.ptr, \"ptr\"))\n    && !(DllCall(\"Crypt32\\CryptAcquireCertificatePrivateKey\"\n      , \"ptr\",cert, \"uint\",5 ; CRYPT_ACQUIRE_CACHE_FLAG|CRYPT_ACQUIRE_COMPARE_KEY_FLAG\n      , \"ptr\",0, \"ptr*\", 0, \"uint*\", &keySpec:=0, \"ptr\",0)\n      && (keySpec & 2)) { ; AT_SIGNATURE ; Keep looking for a certificate with a private key.\n  }\n  if !cert.ptr {\n    cert := EnableUIAccess_CreateCert(CertName, hStore)\n  }\n  EnableUIAccess_SetManifest(ExePath)             \t; Set uiAccess attribute in manifest\n  EnableUIAccess_SignFile(ExePath, cert, CertName)\t; Sign the file (otherwise uiAccess attribute is ignored)\n  return true\n}\n\nEnableUIAccess_SetManifest(ExePath) {\n  xml := ComObject(\"Msxml2.DOMDocument\")\n  xml.async := false\n  xml.setProperty(\"SelectionLanguage\", \"XPath\")\n  xml.setProperty(\"SelectionNamespaces\"\n    , \"xmlns:v1='urn:schemas-microsoft-com:asm.v1' \"\n    . \"xmlns:v3='urn:schemas-microsoft-com:asm.v3'\")\n  try {\n    if !xml.loadXML(EnableUIAccess_ReadManifest(ExePath)) {\n      throw Error(\"Invalid manifest\")\n    }\n  } catch as e {\n    throw Error(\"Error loading manifest from \" ExePath,, e.Message \"`n  @ \" e.File \":\" e.Line)\n  }\n\n\n  node := xml.selectSingleNode(\"/v1:assembly/v3:trustInfo/v3:security\"\n    .                          \"/v3:requestedPrivileges/v3:requestedExecutionLevel\")\n  if !node ; Not AutoHotkey?\n    throw Error(\"Manifest is missing required elements\")\n\n  node.setAttribute(\"uiAccess\", \"true\")\n  xml := RTrim(xml.xml, \"`r`n\")\n\n  data := Buffer(StrPut(xml, \"utf-8\") - 1)\n  StrPut(xml, data, \"utf-8\")\n\n  if !(hupd := DllCall(\"BeginUpdateResource\", \"str\",ExePath, \"int\",false))\n    throw OSError()\n  r := DllCall(\"UpdateResource\", \"ptr\",hupd, \"ptr\",24, \"ptr\",1\n          , \"ushort\", 1033, \"ptr\",data, \"uint\",data.size)\n\n  ; Retry loop to work around file locks (especially by antivirus)\n  for delay in [0, 100, 500, 1000, 3500] {\n    Sleep delay\n    if DllCall(\"EndUpdateResource\", \"ptr\",hupd, \"int\",!r) || !r\n      return\n    if !(A_LastError = 5 || A_LastError = 110) ; ERROR_ACCESS_DENIED || ERROR_OPEN_FAILED\n      break\n  }\n  throw OSError(A_LastError, \"EndUpdateResource\")\n}\n\nEnableUIAccess_ReadManifest(ExePath) {\n  if !(hmod := DllCall(\"LoadLibraryEx\", \"str\",ExePath, \"ptr\",0, \"uint\",2, \"ptr\"))\n    throw OSError()\n  try {\n    if !(hres := DllCall(\"FindResource\", \"ptr\",hmod, \"ptr\",1, \"ptr\",24, \"ptr\")) {\n      throw OSError()\n    }\n    size := DllCall(\"SizeofResource\", \"ptr\",hmod, \"ptr\",hres, \"uint\")\n    if !(hglb := DllCall(\"LoadResource\", \"ptr\",hmod, \"ptr\",hres, \"ptr\")) {\n      throw OSError()\n    }\n    if !(pres := DllCall(\"LockResource\", \"ptr\",hglb, \"ptr\")) {\n      throw OSError()\n    }\n    return StrGet(pres, size, \"utf-8\")\n  }\n  finally {\n    DllCall(\"FreeLibrary\", \"ptr\",hmod)\n  }\n}\n\nEnableUIAccess_CreateCert(Name, hStore) {\n  prov := CryptContext() ; Here Name is used as the key container name.\n  if !DllCall(\"Advapi32\\CryptAcquireContext\", \"ptr*\", prov\n    , \"str\",Name, \"ptr\",0, \"uint\",1, \"uint\",0) { ; PROV_RSA_FULL=1, open existing=0\n    if !DllCall(\"Advapi32\\CryptAcquireContext\", \"ptr*\", prov\n      , \"str\",Name, \"ptr\",0, \"uint\",1, \"uint\",8) { ; PROV_RSA_FULL=1, CRYPT_NEWKEYSET=8\n      throw OSError()\n    }\n    if !DllCall(\"Advapi32\\CryptGenKey\", \"ptr\",prov\n        , \"uint\",2, \"uint\",0x4000001, \"ptr*\", CryptKey()) { ; AT_SIGNATURE=2, EXPORTABLE=..01\n      throw OSError()\n    }\n  }\n\n  ; Here Name is used as the certificate subject and name.\n  Loop 2 {\n    if A_Index = 1 {\n      pbName := cbName := 0\n    } else {\n      bName := Buffer(cbName), pbName := bName.ptr\n    }\n    if !DllCall(\"Crypt32\\CertStrToName\", \"uint\",1, \"str\",\"CN=\" Name\n      , \"uint\",3, \"ptr\",0, \"ptr\",pbName, \"uint*\", &cbName, \"ptr\",0) ; X509_ASN_ENCODING=1, CERT_X500_NAME_STR=3\n      throw OSError()\n  }\n  cnb := Buffer(2*A_PtrSize), NumPut(\"ptr\",cbName, \"ptr\",pbName, cnb)\n\n  ; Set expiry to 9999-01-01 12pm +0.\n  NumPut(\"short\", 9999, \"sort\", 1, \"short\", 5, \"short\", 1, \"short\", 12, endTime := Buffer(16, 0))\n\n  StrPut(\"2.5.29.4\", szOID_KEY_USAGE_RESTRICTION := Buffer(9),, \"cp0\")\n  StrPut(\"2.5.29.37\", szOID_ENHANCED_KEY_USAGE := Buffer(10),, \"cp0\")\n  StrPut(\"1.3.6.1.5.5.7.3.3\", szOID_PKIX_KP_CODE_SIGNING := Buffer(18),, \"cp0\")\n\n  ; CERT_KEY_USAGE_RESTRICTION_INFO key_usage;\n  key_usage := Buffer(6*A_PtrSize, 0)\n  NumPut('ptr', 0, 'ptr', 0, 'ptr', 1, 'ptr', key_usage.ptr + 5*A_PtrSize, 'ptr', 0\n    , 'uchar', (CERT_DATA_ENCIPHERMENT_KEY_USAGE := 0x10)\n         | (CERT_DIGITAL_SIGNATURE_KEY_USAGE := 0x80), key_usage)\n\n  ; CERT_ENHKEY_USAGE enh_usage;\n  enh_usage := Buffer(3*A_PtrSize)\n  NumPut(\"ptr\",1, \"ptr\",enh_usage.ptr + 2*A_PtrSize, \"ptr\",szOID_PKIX_KP_CODE_SIGNING.ptr, enh_usage)\n\n  key_usage_data := EncodeObject(szOID_KEY_USAGE_RESTRICTION, key_usage)\n  enh_usage_data := EncodeObject(szOID_ENHANCED_KEY_USAGE, enh_usage)\n\n  EncodeObject(structType, structInfo) {\n    encoder := DllCall.Bind(\"Crypt32\\CryptEncodeObject\", \"uint\",X509_ASN_ENCODING := 1\n      , \"ptr\",structType, \"ptr\",structInfo)\n    if !encoder(\"ptr\",0, \"uint*\", &enc_size := 0)\n      throw OSError()\n    enc_data := Buffer(enc_size)\n    if !encoder(\"ptr\",enc_data, \"uint*\", &enc_size)\n      throw OSError()\n    enc_data.Size := enc_size\n    return enc_data\n  }\n\n  ; CERT_EXTENSION extension[2]; CERT_EXTENSIONS extensions;\n  NumPut(\"ptr\",szOID_KEY_USAGE_RESTRICTION.ptr, \"ptr\",true, \"ptr\",key_usage_data.size, \"ptr\",key_usage_data.ptr\n    ,    \"ptr\",szOID_ENHANCED_KEY_USAGE.ptr   , \"ptr\",true, \"ptr\",enh_usage_data.size, \"ptr\",enh_usage_data.ptr\n    , extension := Buffer(8*A_PtrSize))\n  NumPut(\"ptr\",2, \"ptr\",extension.ptr, extensions := Buffer(2*A_PtrSize))\n\n  if !hCert := DllCall(\"Crypt32\\CertCreateSelfSignCertificate\"\n    , \"ptr\",prov, \"ptr\",cnb, \"uint\",0, \"ptr\",0\n    , \"ptr\",0, \"ptr\",0, \"ptr\",endTime, \"ptr\",extensions, \"ptr\") {\n    throw OSError()\n  }\n  cert := CertContext(hCert)\n\n  if !DllCall(\"Crypt32\\CertAddCertificateContextToStore\", \"ptr\",hStore\n    , \"ptr\",hCert, \"uint\",1, \"ptr\",0) { ; STORE_ADD_NEW=1\n    throw OSError()\n  }\n\n  return cert\n}\n\nEnableUIAccess_DeleteCertAndKey(Name) {\n  ; This first call \"acquires\" the key container but also deletes it.\n  DllCall(\"Advapi32\\CryptAcquireContext\", \"ptr*\", 0, \"str\",Name\n    , \"ptr\",0, \"uint\",1, \"uint\",16) ; PROV_RSA_FULL=1, CRYPT_DELETEKEYSET=16\n  if !hStore := DllCall(\"Crypt32\\CertOpenStore\", \"ptr\",10 ; STORE_PROV_SYSTEM_W\n    , \"uint\",0, \"ptr\",0, \"uint\",0x20000 ; SYSTEM_STORE_LOCAL_MACHINE\n    , \"wstr\", \"Root\", \"ptr\")\n    throw OSError()\n  store := CertStore(hStore)\n  deleted := 0\n  ; Multiple certificates might be created over time as keys become inaccessible\n  while p := DllCall(\"Crypt32\\CertFindCertificateInStore\", \"ptr\",hStore\n    , \"uint\",0x10001 ; X509_ASN_ENCODING|PKCS_7_ASN_ENCODING\n    , \"uint\",0, \"uint\",0x80007 ; FIND_SUBJECT_STR\n    , \"wstr\", Name, \"ptr\",0, \"ptr\") {\n    if !DllCall(\"Crypt32\\CertDeleteCertificateFromStore\", \"ptr\",p) {\n      throw OSError()\n    }\n    deleted++\n  }\n  return deleted\n}\n\nclass Crypt {\n  static IsSigned(FilePath) {\n    return DllCall(\"Crypt32\\CryptQueryObject\"\n      ,\"uint\" \t, CERT_QUERY_OBJECT_FILE := 1\n      ,\"wstr\" \t, FilePath\n      ,\"uint\" \t, CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED := 1<<10\n      ,\"uint\" \t, CERT_QUERY_FORMAT_FLAG_BINARY := 2\n      ,\"uint\" \t, 0\n      ,\"uint*\"\t, &dwEncoding:=0\n      ,\"uint*\"\t, &dwContentType:=0\n      ,\"uint*\"\t, &dwFormatType:=0\n      ,\"ptr\"  \t, 0\n      ,\"ptr\"  \t, 0\n      ,\"ptr\"  \t, 0)\n  }\n}\nclass CryptPtrBase {\n  __new(p:=0) => this.ptr := p\n  __delete() => this.ptr && this.Dispose()\n}\nclass CryptContext extends CryptPtrBase {\n  Dispose() => DllCall(\"Advapi32\\CryptReleaseContext\", \"ptr\",this, \"uint\",0)\n}\nclass CertContext extends CryptPtrBase {\n  Dispose() => DllCall(\"Crypt32\\CertFreeCertificateContext\", \"ptr\",this)\n}\nclass CertStore extends CryptPtrBase {\n  Dispose() => DllCall(\"Crypt32\\CertCloseStore\", \"ptr\",this, \"uint\",0)\n}\nclass CryptKey extends CryptPtrBase {\n  Dispose() => DllCall(\"Advapi32\\CryptDestroyKey\", \"ptr\",this)\n}\n\nEnableUIAccess_SignFile(ExePath, CertCtx, Name) {\n  file_info := struct( ; SIGNER_FILE_INFO\n    \"ptr\",A_PtrSize*3, \"ptr\",StrPtr(ExePath))\n  dwIndex := Buffer(4, 0) ; DWORD\n  subject_info := struct( ; SIGNER_SUBJECT_INFO\n    \"ptr\",A_PtrSize*4, \"ptr\",dwIndex.ptr, \"ptr\",SIGNER_SUBJECT_FILE:=1,\n    \"ptr\",file_info.ptr)\n  cert_store_info := struct( ; SIGNER_CERT_STORE_INFO\n    \"ptr\",A_PtrSize*4, \"ptr\",CertCtx.ptr, \"ptr\",SIGNER_CERT_POLICY_CHAIN:=2)\n  cert_info := struct( ; SIGNER_CERT\n    \"uint\",8+A_PtrSize*2, \"uint\",SIGNER_CERT_STORE:=2,\n    \"ptr\",cert_store_info.ptr)\n  authcode_attr := struct( ; SIGNER_ATTR_AUTHCODE\n    \"uint\",8+A_PtrSize*3, \"int\",false, \"ptr\",true, \"ptr\",StrPtr(Name))\n  sig_info := struct( ; SIGNER_SIGNATURE_INFO\n    \"uint\",8+A_PtrSize*4, \"uint\",CALG_SHA1:=0x8004,\n    \"ptr\",SIGNER_AUTHCODE_ATTR:=1, \"ptr\",authcode_attr.ptr)\n\n  hr := DllCall(\"MSSign32\\SignerSign\"\n    , \"ptr\",subject_info, \"ptr\",cert_info, \"ptr\",sig_info\n    , \"ptr\",0, \"ptr\",0, \"ptr\",0, \"ptr\",0, \"hresult\") ; pProviderInfo pwszHttpTimeStamp psRequest pSipData\n\n  struct(args*) => (\n    args.Push(b := Buffer(args[2], 0)),\n    NumPut(args*),\n    b\n  )\n}\n\nEnableUIAccess_Verify(ExePath) { ; Verifies a signed executable file.  Returns 0 on success, or a standard OS error number.\n  wfi := Buffer(4*A_PtrSize) ; WINTRUST_FILE_INFO\n  NumPut('ptr', wfi.size, 'ptr', StrPtr(ExePath), 'ptr', 0, 'ptr', 0, wfi)\n  NumPut('int64', 0x11d0cd4400aac56b, 'int64', 0xee95c24fc000c28c, actionID := Buffer(16)) ; WINTRUST_ACTION_GENERIC_VERIFY_V2\n\n  wtd := Buffer(9*A_PtrSize+16) ; WINTRUST_DATA\n  NumPut(\n    'ptr', wtd.Size, 'ptr', 0, 'ptr', 0, 'int', WTD_UI_NONE:=2, 'int', WTD_REVOKE_NONE:=0,\n    'ptr', WTD_CHOICE_FILE:=1, 'ptr', wfi.ptr, 'ptr', WTD_STATEACTION_VERIFY:=1,\n    'ptr', 0, 'ptr', 0, 'int', 0, 'int', 0, 'ptr', 0, wtd\n  )\n  return DllCall('wintrust\\WinVerifyTrust', 'ptr', 0, 'ptr', actionID, 'ptr', wtd, 'int')\n}\n"
  },
  {
    "path": "EnableUIAccess/README.md",
    "content": "# EnableUIAccess\n\nSee [the guide documentation for context](https://github.com/jtroo/kanata/blob/main/docs/config.adoc#windows-only-work-elevated).\n"
  },
  {
    "path": "LICENSE",
    "content": "                   GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\n  This version of the GNU Lesser General Public License incorporates\nthe terms and conditions of version 3 of the GNU General Public\nLicense, supplemented by the additional permissions listed below.\n\n  0. Additional Definitions.\n\n  As used herein, \"this License\" refers to version 3 of the GNU Lesser\nGeneral Public License, and the \"GNU GPL\" refers to version 3 of the GNU\nGeneral Public License.\n\n  \"The Library\" refers to a covered work governed by this License,\nother than an Application or a Combined Work as defined below.\n\n  An \"Application\" is any work that makes use of an interface provided\nby the Library, but which is not otherwise based on the Library.\nDefining a subclass of a class defined by the Library is deemed a mode\nof using an interface provided by the Library.\n\n  A \"Combined Work\" is a work produced by combining or linking an\nApplication with the Library.  The particular version of the Library\nwith which the Combined Work was made is also called the \"Linked\nVersion\".\n\n  The \"Minimal Corresponding Source\" for a Combined Work means the\nCorresponding Source for the Combined Work, excluding any source code\nfor portions of the Combined Work that, considered in isolation, are\nbased on the Application, and not on the Linked Version.\n\n  The \"Corresponding Application Code\" for a Combined Work means the\nobject code and/or source code for the Application, including any data\nand utility programs needed for reproducing the Combined Work from the\nApplication, but excluding the System Libraries of the Combined Work.\n\n  1. Exception to Section 3 of the GNU GPL.\n\n  You may convey a covered work under sections 3 and 4 of this License\nwithout being bound by section 3 of the GNU GPL.\n\n  2. Conveying Modified Versions.\n\n  If you modify a copy of the Library, and, in your modifications, a\nfacility refers to a function or data to be supplied by an Application\nthat uses the facility (other than as an argument passed when the\nfacility is invoked), then you may convey a copy of the modified\nversion:\n\n   a) under this License, provided that you make a good faith effort to\n   ensure that, in the event an Application does not supply the\n   function or data, the facility still operates, and performs\n   whatever part of its purpose remains meaningful, or\n\n   b) under the GNU GPL, with none of the additional permissions of\n   this License applicable to that copy.\n\n  3. Object Code Incorporating Material from Library Header Files.\n\n  The object code form of an Application may incorporate material from\na header file that is part of the Library.  You may convey such object\ncode under terms of your choice, provided that, if the incorporated\nmaterial is not limited to numerical parameters, data structure\nlayouts and accessors, or small macros, inline functions and templates\n(ten or fewer lines in length), you do both of the following:\n\n   a) Give prominent notice with each copy of the object code that the\n   Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the object code with a copy of the GNU GPL and this license\n   document.\n\n  4. Combined Works.\n\n  You may convey a Combined Work under terms of your choice that,\ntaken together, effectively do not restrict modification of the\nportions of the Library contained in the Combined Work and reverse\nengineering for debugging such modifications, if you also do each of\nthe following:\n\n   a) Give prominent notice with each copy of the Combined Work that\n   the Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the Combined Work with a copy of the GNU GPL and this license\n   document.\n\n   c) For a Combined Work that displays copyright notices during\n   execution, include the copyright notice for the Library among\n   these notices, as well as a reference directing the user to the\n   copies of the GNU GPL and this license document.\n\n   d) Do one of the following:\n\n       0) Convey the Minimal Corresponding Source under the terms of this\n       License, and the Corresponding Application Code in a form\n       suitable for, and under terms that permit, the user to\n       recombine or relink the Application with a modified version of\n       the Linked Version to produce a modified Combined Work, in the\n       manner specified by section 6 of the GNU GPL for conveying\n       Corresponding Source.\n\n       1) Use a suitable shared library mechanism for linking with the\n       Library.  A suitable mechanism is one that (a) uses at run time\n       a copy of the Library already present on the user's computer\n       system, and (b) will operate properly with a modified version\n       of the Library that is interface-compatible with the Linked\n       Version.\n\n   e) Provide Installation Information, but only if you would otherwise\n   be required to provide such information under section 6 of the\n   GNU GPL, and only to the extent that such information is\n   necessary to install and execute a modified version of the\n   Combined Work produced by recombining or relinking the\n   Application with a modified version of the Linked Version. (If\n   you use option 4d0, the Installation Information must accompany\n   the Minimal Corresponding Source and Corresponding Application\n   Code. If you use option 4d1, you must provide the Installation\n   Information in the manner specified by section 6 of the GNU GPL\n   for conveying Corresponding Source.)\n\n  5. Combined Libraries.\n\n  You may place library facilities that are a work based on the\nLibrary side by side in a single library together with other library\nfacilities that are not Applications and are not covered by this\nLicense, and convey such a combined library under terms of your\nchoice, if you do both of the following:\n\n   a) Accompany the combined library with a copy of the same work based\n   on the Library, uncombined with any other library facilities,\n   conveyed under the terms of this License.\n\n   b) Give prominent notice with the combined library that part of it\n   is a work based on the Library, and explaining where to find the\n   accompanying uncombined form of the same work.\n\n  6. Revised Versions of the GNU Lesser General Public License.\n\n  The Free Software Foundation may publish revised and/or new versions\nof the GNU Lesser General Public License from time to time. Such new\nversions will be similar in spirit to the present version, but may\ndiffer in detail to address new problems or concerns.\n\n  Each version is given a distinguishing version number. If the\nLibrary as you received it specifies that a certain numbered version\nof the GNU Lesser General Public License \"or any later version\"\napplies to it, you have the option of following the terms and\nconditions either of that published version or of any later version\npublished by the Free Software Foundation. If the Library as you\nreceived it does not specify a version number of the GNU Lesser\nGeneral Public License, you may choose any version of the GNU Lesser\nGeneral Public License ever published by the Free Software Foundation.\n\n  If the Library as you received it specifies that a proxy can decide\nwhether future versions of the GNU Lesser General Public License shall\napply, that proxy's public statement of acceptance of any version is\npermanent authorization for you to choose that version for the\nLibrary."
  },
  {
    "path": "README.md",
    "content": "<h1 align=\"center\">Kanata</h1>\n\n<h3 align=\"center\">\n  <img\n    alt=\"Image of a keycap with the letter K on it in pink tones\"\n    title=\"Kanata\"\n    height=\"160\"\n    src=\"assets/kanata-icon.svg\"\n  />\n</h3>\n\n<div align=\"center\">\n  Improve your keyboard comfort\n</div>\n\n## What does this do?\n\nThis is a cross-platform software keyboard remapper for Linux, macOS and Windows.\nA short summary of the features:\n\n- multiple layers of key functionality\n- advanced key behaviour customization (e.g. tap-hold, macros, unicode)\n\nTo see all of the features, see the [configuration guide](./docs/config.adoc).\n\nYou can find pre-built binaries in the [releases page](https://github.com/jtroo/kanata/releases)\nor read on for build instructions.\n\nYou can see a [list of known issues here](./docs/platform-known-issues.adoc).\n\n### Demo\n\n#### Demo video\n\n[Showcase of multi-layer functionality (30s, 1.7 MB)](https://user-images.githubusercontent.com/6634136/183001314-f64a7e26-4129-4f20-bf26-7165a6e02c38.mp4).\n\n#### Online simulator\n\nYou can check out the [online simulator](https://jtroo.github.io)\nto test configuration validity and test input simulation.\n\n## Why is this useful?\n\nImagine if, instead of pressing Shift to type uppercase letters, we had giant\nkeyboards with separate keys for lowercase and uppercase letters. I hope we can\nall agree: that would be a terrible user experience!\n\nA way to think of how Shift keys work is that they switch your input to another\nlayer of functionality where you now type uppercase letters and symbols\ninstead of lowercase letters and numbers.\n\nWhat kanata allows you to do is take this alternate layer concept that Shift\nkeys have and apply it to any key. You can then customize what those layers do\nto suit your exact needs and workflows.\n\n## Usage\n\nRunning kanata currently does not start it in a background process.\nYou will need to keep the window that starts kanata running to keep kanata active.\nSome tips for running kanata in the background:\n\n- Windows: https://github.com/jtroo/kanata/discussions/193\n- Linux: https://github.com/jtroo/kanata/discussions/130#discussioncomment-10227272\n- Run from tray icon: [kanata-tray](https://github.com/rszyma/kanata-tray)\n\n### Pre-built executables\n\nSee the\n[releases page](https://github.com/jtroo/kanata/releases)\nfor executables and instructions.\n\n### Build it yourself\n\nThis project uses the latest Rust stable toolchain. If you installed the\nRust toolchain using `rustup`, e.g. by using the instructions from the\n[official website](https://www.rust-lang.org/learn/get-started),\nyou can get the latest stable toolchain with `rustup update stable`.\n\n<details>\n<summary>Instructions</summary>\n\nUsing `cargo install`:\n\n    cargo install kanata\n\n    # On Linux and macOS, this may not work without `sudo`, see below\n    kanata --cfg <your_configuration_file>\n\nBuild and run yourself in Linux:\n\n    git clone https://github.com/jtroo/kanata && cd kanata\n    cargo build   # --release optional, not really perf sensitive\n\n    # sudo is used because kanata opens /dev/ files\n    #\n    # See below if you want to avoid needing sudo:\n    # https://github.com/jtroo/kanata/wiki/Avoid-using-sudo-on-Linux\n    sudo target/debug/kanata --cfg <your_configuration_file>\n\nBuild and run yourself in Windows.\n\n    git clone https://github.com/jtroo/kanata; cd kanata\n    cargo build   # --release optional, not really perf sensitive\n    target\\debug\\kanata --cfg <your_configuration_file>\n\nBuild and run yourself in macOS:\n\nFirst install the Karabiner driver by following the macOS documentation\nin the [releases page](https://github.com/jtroo/kanata/releases/).\n\nThen you can compile and run with the instructions below:\n\n    git clone https://github.com/jtroo/kanata && cd kanata\n    cargo build   # --release optional, not really perf sensitive\n\n    # sudo is needed to gain permission to intercept the keyboard\n\n    sudo target/debug/kanata --cfg <your_configuration_file>\n\nThe full configuration guide is [found here](./docs/config.adoc).\n\nSample configuration files are found in [cfg_samples](./cfg_samples). The\n[simple.kbd](./cfg_samples/simple.kbd) file contains a basic configuration file\nthat is hopefully easy to understand but does not contain all features. The\n`kanata.kbd` contains an example of all features with documentation. The\nrelease assets also have a `kanata.kbd` file that is tested to work with that\nrelease. All key names can be found in the [keys module](./parser/src/keys/mod.rs),\nand you can also define your own key names.\n\n</details>\n\n### Feature flags\n\nWhen either building yourself or using `cargo install`,\nyou can add feature flags that\nenable functionality that is turned off by default.\n\n<details>\n<summary>Instructions</summary>\n\nIf you want to enable the `cmd` actions,\nadd the flag `--features cmd`.\nFor example:\n\n```\ncargo build --release --features cmd\ncargo install --features cmd\n```\n\nOn Windows,\nif you want to compile a binary that uses the Interception driver,\nyou should add the flag `--features interception_driver`.\nFor example:\n\n```\ncargo build --release --features interception_driver\ncargo install --features interception_driver\n```\n\nTo combine multiple flags,\nuse a single `--features` flag\nand use a comma to separate the features.\nFor example:\n\n```\ncargo build --release --features cmd,interception_driver\ncargo install --features cmd,interception_driver\n```\n\n</details>\n\n## Other installation methods\n\n<details>\n<summary>Repositories for kanata</summary>\n\n[![Packaging status](https://repology.org/badge/vertical-allrepos/kanata.svg)](https://repology.org/project/kanata/versions)\n\n</details>\n\n## Notable features\n\n- Human-readable configuration file.\n  - [Minimal example](./cfg_samples/minimal.kbd)\n  - [Full guide](./docs/config.adoc)\n  - [Simple example with explanations](./cfg_samples/simple.kbd)\n  - [All features showcase](./cfg_samples/kanata.kbd)\n- Live reloading of the configuration for easy testing of your changes.\n- Multiple layers of key functionality\n- Advanced actions such as tap-hold, unicode output, dynamic and static macros\n- Vim-like leader sequences to execute other actions\n- Optionally run a TCP server to interact with other programs\n  - Other programs can respond to [layer changes or trigger layer changes](https://github.com/jtroo/kanata/issues/47)\n- [Interception driver](https://web.archive.org/web/20240209172129/http://www.oblita.com/interception) support (use `kanata_wintercept.exe`)\n  - Note that this issue exists, which is outside the control of this project:\n    https://github.com/oblitum/Interception/issues/25\n\n## Contributing\n\nContributions are welcome!\n\nUnless explicitly stated otherwise, your contributions to kanata will be made\nunder the LGPL-3.0-only[*] license.\n\nSome directories are exceptions:\n\n- [keyberon](./keyberon): MIT License\n- [interception](./interception): MIT or Apache-2.0 Licenses\n\n[Here's a basic low-effort design doc of kanata](./docs/design.md)\n\n[*]: https://www.gnu.org/licenses/identify-licenses-clearly.html\n\n## How you can help\n\n- Try it out and let me know what you think. Feel free to file an issue or\n  start a discussion.\n- Usability issues and unhelpful error messages are considered bugs that should\n  be fixed. If you encounter any, I would be thankful if you file an issue.\n- Browse the open issues and help out if you are able and/or would like to. If\n  you want to try contributing, feel free to ping jtroo for some pointers.\n- If you know anything about writing a keyboard driver for Windows, starting an\n  open-source alternative to the Interception driver would be lovely.\n\n## Community projects related to kanata\n\n- [vscode-kanata](https://github.com/rszyma/vscode-kanata): Language support for kanata configuration files in VS Code\n- [komokana](https://github.com/LGUG2Z/komokana): Automatic application-aware layer switching for [`komorebi`](https://github.com/LGUG2Z/komorebi) (Windows)\n- [kanata-tray](https://github.com/rszyma/kanata-tray): Control kanata from a tray icon\n- [OverKeys](https://github.com/conventoangelo/overkeys): Visual layer display for kanata - see your active layers and keymaps in real-time (Windows)\n- Application-aware layer switching:\n  - [qanata (Linux)](https://github.com/veyxov/qanata)\n  - [kanawin (Windows)](https://github.com/Aqaao/kanawin)\n  - [window_tools (Windows)](https://github.com/reidprichard/window_tools)\n  - [nata (Linux)](https://github.com/mdSlash/nata)\n  - [kanata-vk-agent (macOS)](https://github.com/devsunb/kanata-vk-agent)\n  - [hyprkan (Linux)](https://github.com/mdSlash/hyprkan)\n  - [kanata-switcher (Linux, all DEs)](https://github.com/7mind/kanata-switcher)\n  - [kwanata (Linux-KDE)](https://github.com/jfsicilia/kwanata): A KDE Plasma companion for Kanata - Automatically activates Kanata's layers/virtualkeys and launches or raises apps.\n\n## What does the name mean?\n\nI wanted a \"k\" word since this relates to keyboards. According to Wikipedia,\nkanata is an indigenous Iroquoian word meaning \"village\" or \"settlement\" and is\nthe origin of Canada's name.\n\nThere's also PPT✧.\n\n## Motivation\n\nTLDR: QMK features but for any keyboard, not just fancy mechanical ones.\n\n<details>\n  <summary>Long version</summary>\n\nI have a few keyboards that run [QMK](https://docs.qmk.fm/#/). QMK allows the\nuser to customize the functionality of their keyboard to their heart's content.\n\nOne great use case of QMK is its ability map keys so that they overlap with the\nhome row keys but are accessible on another layer. I won't comment on\nproductivity, but I find this greatly helps with my keyboard comfort.\n\nFor example, these keys are on the right side of the keyboard:\n\n    7 8 9\n    u i o\n    j k l\n    m , .\n\nOn one layer I have arrow keys in the same position, and on another layer I\nhave a numpad.\n\n    arrows:       numpad:\n    - - -         7 8 9\n    - ↑ -         4 5 6\n    ← ↓ →         1 2 3\n    - - -         0 * .\n\nOne could add as many customizations as one likes to improve comfort, speed,\netc. Personally my main motivator is comfort due to a repetitive strain injury\nin the past.\n\nHowever, QMK doesn't run everywhere. In fact, it doesn't run on **most**\nhardware you can get. You can't get it to run on a laptop keyboard or any\nmainstream office keyboard. I believe that the comfort and empowerment QMK\nprovides should be available to anyone with a computer on their existing\nhardware, instead of having to purchase an enthusiast mechanical keyboard\n(which are admittedly very nice — I own a few — but can be costly).\n\nThe best alternative solution that I found for keyboards that don't run QMK was\n[kmonad](https://github.com/kmonad/kmonad). This is an excellent project\nand I recommend it if you want to try something similar.\n\nThe reason for this project's existence is that kmonad is written in Haskell\nand I have no idea how to begin contributing to a Haskell project. From an\noutsider's perspective I think Haskell is a great language but I really can't\nwrap my head around it. And there are a few [outstanding issues](./docs/kmonad_comparison.md)\nat the time of writing that make kmonad suboptimal for my personal workflows.\n\nThis project is written in Rust because Rust is my favourite programming\nlanguage and the prior work of the awesome [keyberon crate](https://github.com/TeXitoi/keyberon)\nexists.\n\n</details>\n\n## Similar Projects\n\nThe most similar project is [kmonad](https://github.com/kmonad/kmonad),\nwhich served as the inspiration for kanata. [Here's a comparison document](./docs/kmonad_comparison.md).\nOther similar projects:\n\n- [QMK](https://docs.qmk.fm/#/): Open source keyboard firmware\n- [keyberon](https://github.com/TeXitoi/keyberon): Rust `#[no_std]` library intended for keyboard firmware\n- [ktrl](https://github.com/ItayGarin/ktrl): Linux-only keyboard customizer with layers, a TCP server, and audio support\n- [kbremap](https://github.com/timokroeger/kbremap): Windows-only keyboard customizer with layers and unicode\n- [xcape](https://github.com/alols/xcape): Linux-only tap-hold modifiers\n- [karabiner-elements](https://karabiner-elements.pqrs.org/): Mac-only keyboard customizer\n- [capsicain](https://github.com/cajhin/capsicain): Windows-only key remapper with driver-level key interception\n- [keyd](https://github.com/rvaiya/keyd): Linux-only key remapper very similar to QMK, kmonad, and kanata\n- [xremap](https://github.com/k0kubun/xremap): Linux-only application-aware key remapper inspired more by Emacs key sequences vs. QMK layers/Vim modes\n- [keymapper](https://github.com/houmain/keymapper): Context-aware cross-platform key remapper with a different transformation model (Linux, Windows, Mac)\n- [mouseless](https://github.com/jbensmann/mouseless): Linux-only mouse-focused key remapper that also has layers, key combo and tap-hold capabilities\n\n### Why the list?\n\nWhile kanata is the best tool for some, it may not be the best tool for\nyou. I'm happy to introduce you to tools that may better suit your needs. This\nlist is also useful as reference/inspiration for functionality that could be\nadded to kanata.\n\n## Donations/Support?\n\nThe author (jtroo) will not accept monetary donations for work on kanata.\nPlease instead donate your time and/or money to charity.\n\nSome links are below. These links are provided for learning and as interesting\nreads. They are **not** an endorsement.\n\n- https://www.effectivealtruism.org/\n- https://www.givewell.org/\n"
  },
  {
    "path": "build.rs",
    "content": "fn main() -> std::io::Result<()> {\n    #[cfg(feature = \"win_manifest\")]\n    {\n        windows::build()?;\n    }\n    Ok(())\n}\n\n#[cfg(feature = \"win_manifest\")]\nmod windows {\n    use indoc::formatdoc;\n    use regex::Regex;\n    use std::fs::File;\n    use std::io::Write;\n    extern crate embed_resource;\n\n    // println! during build\n    macro_rules! pb {\n      ($($tokens:tt)*) => {println!(\"cargo:warning={}\", format!($($tokens)*))}}\n\n    pub(super) fn build() -> std::io::Result<()> {\n        let manifest_path: &str = \"./target/kanata.exe.manifest\";\n\n        // Note about expected version format:\n        // MS says \"Use the four-part version format: mmmmm.nnnnn.ooooo.ppppp\"\n        // https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests\n\n        let re_ver_build = Regex::new(r\"^(?<vpre>(\\d+\\.){2}\\d+)[-a-zA-Z]+(?<vpos>\\d+)$\").unwrap();\n        let re_ver_build2 = Regex::new(r\"^(?<vpre>(\\d+\\.){2}\\d+)[-a-zA-Z]+$\").unwrap();\n        let re_version3 = Regex::new(r\"^(\\d+\\.){2}\\d+$\").unwrap();\n        let mut version: String = env!(\"CARGO_PKG_VERSION\").to_string();\n\n        if re_version3.find(&version).is_some() {\n            version = format!(\"{version}.0\");\n        } else if re_ver_build.find(&version).is_some() {\n            version = re_ver_build\n                .replace_all(&version, r\"$vpre.$vpos\")\n                .to_string();\n        } else if re_ver_build2.find(&version).is_some() {\n            version = re_ver_build2.replace_all(&version, r\"$vpre.0\").to_string();\n        } else {\n            pb!(\"unknown version format '{}', using '0.0.0.0'\", version);\n            version = \"0.0.0.0\".to_string();\n        }\n\n        let manifest_str = formatdoc!(\n            r#\"<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n               <assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\" xmlns:v3=\"urn:schemas-microsoft-com:asm.v3\">\n                 <assemblyIdentity name=\"kanata.exe\" version=\"{}\" type=\"win32\"></assemblyIdentity>\n                 <v3:trustInfo><v3:security>\n                   <v3:requestedPrivileges><v3:requestedExecutionLevel level=\"asInvoker\" uiAccess=\"false\"></v3:requestedExecutionLevel></v3:requestedPrivileges>\n                 </v3:security></v3:trustInfo>\n                 <v3:application>\n                   <v3:windowsSettings>\n                     <dpiAware     xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">true</dpiAware>\n                     <dpiAwareness xmlns=\"http://schemas.microsoft.com/SMI/2016/WindowsSettings\">PerMonitorV2</dpiAwareness>\n                   </v3:windowsSettings>\n                 </v3:application>\n                 <dependency><dependentAssembly>\n                   <assemblyIdentity type=\"win32\" name=\"Microsoft.Windows.Common-Controls\"\n                     version=\"6.0.0.0\" processorArchitecture=\"*\" publicKeyToken=\"6595b64144ccf1df\" language=\"*\"></assemblyIdentity></dependentAssembly>\n                 </dependency>\n               </assembly>\n            \"#,\n            version\n        );\n        let mut manifest_f = File::create(manifest_path)?;\n        write!(manifest_f, \"{manifest_str}\")?;\n        embed_resource::compile(\"./src/kanata.exe.manifest.rc\", embed_resource::NONE);\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "cfg_samples/artsey.kbd",
    "content": ";; ARTSEY MINI 0.2 https://github.com/artseyio/artsey/issues/7\n\n;; Exactly one defcfg entry is required. This is used for configuration key-pairs.\n(defcfg\n  ;; Your keyboard device will likely differ from this.\n  linux-dev /dev/input/event1\n\n  ;; Windows doesn't need any input/output configuration entries; however, there\n  ;; must still be a defcfg entry. You can keep the linux-dev entry or delete\n  ;; it and leave it empty.\n)\n\n(defsrc\n  q    w    e\n  a    s    d\n)\n\n(deflayer base\n  (chord base A) (chord base R) (chord base T)\n  (chord base S) (chord base E) (chord base Y)\n)\n\n(deflayer meta\n  (chord meta A) (chord meta R) (chord meta T)\n  (chord meta S) (chord meta E) (chord meta Y)\n)\n\n(defchords base 5000\n  (A R T S E Y) (layer-switch meta)\n  (A R T      ) (one-shot 2000 lsft)\n  (      S E Y) spc\n  (A          ) a\n  (  R T S    ) b\n  (  R   S    ) c\n  (A       E Y) d\n  (        E  ) e\n  (A R        ) f\n  (A       E  ) g\n  (      S   Y) h\n  (  R     E  ) i\n  (    T S E  ) j\n  (    T   E  ) k\n  (      S E  ) l\n  (  R T      ) m\n  (        E Y) n\n  (A     S    ) o\n  (A R       Y) p\n  (    T     Y) q\n  (  R        ) r\n  (      S    ) s\n  (    T      ) t\n  (A   T      ) u\n  (A   T   E  ) v\n  (    T S    ) w\n  (A         Y) x\n  (          Y) y\n  (  R   S E  ) z\n)\n\n(defchords meta 5000\n  (A R T S E Y) (layer-switch base)\n  (      S E Y) spc\n  (A R T      ) caps ;; should technically be shift lock, probably need to use fake keys for that \n  (A R        ) bspc\n  (  R T      ) del\n  (      S E  ) C-c\n  (        E Y) C-v\n  (A          ) home\n  (  R        ) up\n  (    T      ) end\n  (      S    ) left\n  (        E  ) down\n  (          Y) rght\n)\n"
  },
  {
    "path": "cfg_samples/automousekeys-full-map.kbd",
    "content": "(defcfg\n  ;; F* keys and arrow keys are left unmapped\n  process-unmapped-keys yes\n\n  ;; you may wish to only capture a trackpoint and keyboard\n  ;; but not e.g. a trackpad or external mouse\n  ;;linux-dev-names-include (\n  ;;                         \"AT Translated Set 2 keyboard\"\n  ;;                         \"TPPS/2 Elan TrackPoint\"\n  ;;)\n  ;; optional, but useful with the trackpoint\n  ;;linux-use-trackpoint-property yes\n\n  mouse-movement-key mvmt\n)\n\n;; ANSI layout for eg thinkpad internal or external keyboard\n(defsrc\n  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n  caps a    s    d    f    g    h    j    k    l    ;    '    ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt menu rctl\n  mvmt\n)\n\n(defvirtualkeys\n  mouse (layer-while-held mouse-layer)\n)\n\n(defalias\n  mhld (hold-for-duration 750 mouse)\n\n  moff (on-press release-vkey mouse)\n\n  _ (multi\n     @moff\n     _\n  )\n\n   ;; mouse click extended time out for double tap\n  mdbt (hold-for-duration 500 mouse)\n  mbl (multi\n       mlft\n       @mdbt\n  )\n  mbm (multi\n       mmid\n       @mdbt\n  )\n  mbr (multi\n       mrgt\n       @mdbt\n  )\n)\n\n;; no mappings\n(deflayer qwerty\n  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n  caps a    s    d    f    g    h    j    k    l    ;    '    ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt menu rctl\n  @mhld\n)\n\n;; places mouse keys on the row above the home row.\n;; pressing any other keys exits the mouse layer until mouse movement stops and restarts again.\n(deflayer mouse-layer\n  @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_\n  @_   @_   mrgt mmid @mbl @_   @_   @mbl mmid mrgt @_   @_   @_   @_\n  @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_\n  @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_   @_\n  @_   @_   @_             @_             @_   @_   @_\n  @mhld\n)\n"
  },
  {
    "path": "cfg_samples/automousekeys-only.kbd",
    "content": "(defcfg\n  ;; we are only mapping the keys we want to use for mouse keys\n  process-unmapped-keys yes\n\n  ;; you may wish to only capture a trackpoint and keyboard\n  ;; but not e.g. a trackpad or external mouse\n  ;;linux-dev-names-include (\n  ;;                         \"Lenovo TrackPoint Keyboard II\"\n  ;;)\n  ;; optional, but useful with the trackpoint\n  ;;linux-use-trackpoint-property yes\n\n  mouse-movement-key mvmt\n)\n\n(defsrc)\n\n(defvirtualkeys\n  mouse (layer-while-held mouse-layer)\n)\n\n(defalias\n  mhld (hold-for-duration 750 mouse)\n\n  moff (on-press release-vkey mouse)\n\n  _ (multi\n     @moff\n     _\n  )\n\n  ;; mouse click extended time out for double tap\n  mdbt (hold-for-duration 500 mouse)\n  mbl (multi\n       mlft\n       @mdbt\n  )\n  mbm (multi\n       mmid\n       @mdbt\n  )\n  mbr (multi\n       mrgt\n       @mdbt\n  )\n)\n\n;; no key mappings\n(deflayermap (base)\n             mvmt @mhld\n)\n\n;; places mouse keys on the row above the home row.\n;; pressing any other keys exits the mouse layer until mouse movement stops and restarts again.\n(deflayermap (mouse-layer)\n             w mrgt\n             e mmid\n             r @mbl\n\n             u @mbl\n             i mmid\n             o mrgt\n\n             mvmt @mhld\n             ___ @_\n)\n"
  },
  {
    "path": "cfg_samples/chords.tsv",
    "content": "rus\trust\ncol\tcool\nnice\tnice\nyou\tyou\nth\tthe\n a\ta\n an\tan\nman\tman\nname\tname\nan\tand\nas\tas\nor\tor\nbu\tbut\nif\tif\nso\tso\ndn\tthen\nbc\tbecause\n\nto\tto\nof\tof\nin\tin\n f\tfor\n w\twith\non\ton\nat\tat\nfm\tfrom\nby\tby\nabt\tabout\nup\tup\nio\tinto\nov\tover\naf\tafter\nwo\twithout\n i\tI\n me\tme\n my\tmy\nou\tyou\nur\tyour\nhe\the\nhm\thim\nhis\this\nsh\tshe\nhr\ther\nit\tit\nts\tits\nwe\twe\nus\tus\nour\tour\ndz\tthey\ndr\ttheir\ndm\tthem\nwc\twhich\nwn\twhen\nwt\twhat\nwr\twhere\nho\twho\nhw\thow\nwz\twhy\nis\tis\nar\tare\nwa\twas\ner\twere\nbe\tbe\nhv\thave\nhs\thas\nhd\thad\nnt\tnot\ncn\tcan\ndo\tdo\nwl\twill\ncd\tcould\nwd\twould\nsd\tshould\nli\tlike\nbn\tbeen\nge\tget\nmaz\tmay\nmad\tmade\nmk\tmake\nai\tsaid\nwk\twork\nuz\tuse\nsz\tsay\n g\tgo\nkn\tknow\ntk\ttake\n se\tsee\nlk\tlook\ncm\tcome\nthk\tthink\nwnt\twant\ngi\tgive\nct\tcannot\nde\tdoes\ndi\tdid\nsem\tseem\ncl\tcall\ntha\tthank\n\n im\tI'm\n id\tI'd\ndt\tthat\ndis\tthis\ndes\tthese\ntes\ttest\nal\tall\n o\tone\nmo\tmore\nthe\tthere\nout\tout\nao\talso\ntm\ttime\nsm\tsome\njs\tjust\nne\tnew\nodr\tother\npl\tpeople\n n\tno\ndan\tthan\noz\tonly\n m\tmost\nay\tany\nmay\tmany\nel\twell\nfs\tfirst\nvy\tvery\nmuch\tmuch\nnow\tnow\nev\teven\ngo\tgood\ngrt\tgreat\nway\tway\n t\ttwo\nyr\tyear\nbk\tback\nday\tday\nqn\tquestion\nsc\tsecond\ndg\tthing\n y\tyes\ncn'\tcan't\ndif\tdifferent\ndgh\tthough\ntru\tthrough\nsr\tsorry\nmv\tmove\ndir\tdir\nstop\tstop\ntye\ttype\nnx\tnext\nsam\tsame\ntp\ttop\ncod\tcode\ngit\tgit\n to\tTODO\ncls\tclass\nclus\tcluster\nsure\tsure\nlets\tlet's\nsup\tsuper\nsuch\tsuch\nthig\tthing\nyet\tyet\ndon\tdone\nsem\tseem\nran\tran\njob\tjob\nbot\tbot\nfx\teffect\nnce\tonce\nrad\tread\nltr\tlater\nlot\tlot\nbrw\tbrew\nunst\tuninstall\nrmv\tremove\n ad\tadd\npoe\tproblem\nbuld\tbuild\n tol\ttool\ngot\tgot\nles\tless\n 0\tzero\n 1\tone\n 2\ttwo\n 3\tthree\n 4\tfour\n 5\tfive\n 6\tsix\n 7\tseven\n 8\teight\n 9\tnine\n"
  },
  {
    "path": "cfg_samples/colemak.kbd",
    "content": ";;\n;;  Learn Colemak, a few keys at a time.\n;;\n;;  The \"j\" key moves around the keyboard each step,\n;;  until you reach the full Colemak layout.\n;;\n;;  To select the layout for your current step, press the\n;;  letter \"m\" and the number of your current step, as a chord.\n;;\n;;  Check out:  https://dreymar.colemak.org/tarmak-intro.html\n;;        and:  https://colemak.com\n;;\n\n(defsrc\n  q w e r t y u i o p\n  a s d f g h j k l ;\n  z x c v b n m\n)\n\n(deflayer colemak_j1\n  _ _ j _ _ _ _ _ _ _\n  _ _ _ _ _ _ n e _ _\n  _ _ _ _ _ k _\n)\n\n(deflayer colemak_j2\n  _ _ f _ g _ _ _ _ _\n  _ _ _ t j _ n e _ _\n  _ _ _ _ _ k _\n)\n\n(deflayer colemak_j3\n  _ _ f j g _ _ _ _ _\n  _ r s t d _ n e _ _\n  _ _ _ _ _ k _\n)\n\n(deflayer colemak_j4\n  _ _ f p g j _ _ y ;\n  _ r s t d _ n e _ o\n  _ _ _ _ _ k _\n)\n\n(deflayer colemak\n  _ _ f p g j l u y ;\n  _ r s t d _ n e i o\n  _ _ _ _ _ k _\n)\n\n(defcfg\n  process-unmapped-keys   yes\n  concurrent-tap-hold     yes\n  allow-hardware-repeat   no\n)\n\n(defchordsv2\n  (m 1) (layer-switch colemak_j1) 300 all-released ()\n  (m 2) (layer-switch colemak_j2) 300 all-released ()\n  (m 3) (layer-switch colemak_j3) 300 all-released ()\n  (m 4) (layer-switch colemak_j4) 300 all-released ()\n  (m 5) (layer-switch colemak) 300 all-released ()\n)\n\n"
  },
  {
    "path": "cfg_samples/deflayermap.kbd",
    "content": ";; A configuration showcasing deflayermap.\n;;\n;; The process-unmapped-keys defcfg item is not used\n;; and the lctl and ralt keys are unmapped\n;; because mapping them can cause problems on Windows\n;; with non-US layouts.\n\n(defsrc\n  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n  caps a    s    d    f    g    h    j    k    l    ;    '    ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n       lmet lalt           spc                 rmet rctl\n)\n\n(deflayermap (base)\n  caps (tap-hold 200 200 (caps-word 2000) lctl)\n  spc  (tap-hold 200 200 spc (layer-while-held nav))\n)\n\n(deflayermap (nav)\n  i up\n  j left\n  k down\n  l right\n)\n"
  },
  {
    "path": "cfg_samples/f13_f24.kbd",
    "content": "(defcfg\n  linux-dev /dev/input/by-path/platform-i8042-serio-0-event-kbd\n)\n\n(defsrc\n       f13  f14  f15  f16  f17  f18  f19  f20  f21  f22  f23  f24\n)\n\n(deflayer test\n       f13  f14  f15  f16  f17  f18  f19  f20  f21  f22  f23  f24\n)\n\n"
  },
  {
    "path": "cfg_samples/fancy_symbols.kbd",
    "content": ";; Turns   ⎇›            RightAlt into a symbol key to insert valid kanata unicode symbols for the pressed key\n;; Turns ⇧›⎇› RightShift+RightAlt into a symbol key to insert extra symbols for the same keys\n;; e.g., ⎇›Delete will print ␡\n(defcfg)\n(defalias\n   🔣 (layer-while-held fancy-symbol)\n  ⇧🔣 (layer-while-held ⇧fancy-symbol))\n(defsrc\n  ‹🖰\t🖰›\t🖰3\t🖰4\t🖰5\n  ▶⏸\t◀◀\t▶▶\t🔇 \t🔉\t🔊\t🔅\t🔆\t🎛\t⌨💡+\t⌨💡−\n  ⎋\n  ˋ \t  \t1 \t2\t3\t4\t5\t6\t7\t8 \t9\t0\t- \t=\t␈\t⎀\t⇤\t⇞\t⇭ \t🔢⁄\t🔢∗\t🔢₋\n  ⭾ \t  \tq \tw\te\tr\tt\ty\tu\ti \to\tp\t[ \t]\t\\\t␡\t⇥\t⇟\t🔢₇\t🔢₈\t🔢₉\t🔢₊\n  ⇪ \t  \ta \ts\td\tf\tg\th\tj\tk \tl\t;\t' \t⏎\t \t \t \t \t🔢₄\t🔢₅\t🔢₆\n  ‹⇧\t  \tz \tx\tc\tv\tb\tn\tm\t, \t.\t/\t⇧›\t \t▲\t \t \t \t🔢₁\t🔢₂\t🔢₃\t🔢⏎\n  ‹⎈\t‹◆\t‹⎇\t␠\t \t \t \t \t \t⎇›\t \t☰\t⎈›\t◀\t▼\t▶\t \t \t🔢₀\t🔢⸴           )\n(deflayer qwerty ;; =base with ⎇› as a fancy symbol key\n  ‗\t‗\t‗\t‗\t‗\n  ‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\n  ‗\n  ‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗ \t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\n  ‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗ \t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\n  ‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗ \t‗\t‗\t‗\t \t \t \t \t‗\t‗\t‗\n  ‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗\t‗ \t‗\t‗\t \t‗\t \t \t \t‗\t‗\t‗\t‗\n  ‗\t‗\t‗\t‗\t \t \t \t \t \t@🔣\t \t‗\t‗\t‗\t‗\t‗\t \t‗\t‗           )\n(deflayer  fancy-symbol ;; •block all other keys\n  🔣‹🖰\t🔣🖰›\t🔣🖰3\t🔣🖰4\t🔣🖰5\n  🔣▶⏸\t🔣◀◀\t🔣▶▶\t🔣🔇 \t🔣🔉\t🔣🔊\t🔣🔅\t🔣🔆\t🔣🎛\t🔣⌨💡+\t🔣⌨💡−\n  🔣⎋\n  🔣ˋ\t  \t• \t• \t•\t•\t•\t•\t•\t• \t• \t• \t🔣‐ \t🔣₌\t🔣␈\t🔣⎀\t🔣⇤\t🔣⇞\t🔣⇭   \t🔣🔢⁄\t🔣🔢∗\t🔣🔢₋\n  🔣⭾\t  \t• \t• \t•\t•\t•\t•\t•\t• \t• \t• \t🔣【 \t🔣】\t🔣⧵\t🔣␡\t🔣⇥\t🔣⇟\t🔣🔢₇  \t🔣🔢₈\t🔣🔢₉\t🔣🔢₊\n  🔣⇪\t  \t• \t• \t•\t•\t•\t•\t•\t• \t• \t🔣︔\t'  \t🔣⏎\t  \t  \t  \t  \t  🔣🔢₄\t🔣🔢₅\t🔣🔢₆\n  🔣⇧\t  \t• \t• \t•\t•\t•\t•\t•\t🔣⸴\t🔣．\t🔣⁄\t@⇧🔣\t  \t🔣▲\t  \t  \t  \t  🔣🔢₁\t🔣🔢₂\t🔣🔢₃\t🔣🔢⏎\n  🔣⎈\t🔣◆\t🔣⎇\t🔣␠\t \t \t \t \t \t• \t  \t🔣☰\t•  \t🔣◀\t🔣▼\t🔣▶\t  \t  \t  🔣🔢₀\t🔣🔢⸴  )\n(deflayer ⇧fancy-symbol ;; •block all other keys\n  🔣🖰1\t🔣🖰2\t•\t•    \t•\n  •  \t•  \t•\t🔣🔈⓪⓿₀\t•\t🔣🔈−➖₋⊖\t🔣🔈+➕₊⊕\t•\t•\t🔣⌨💡➕₊⊕\t🔣⌨💡➖₋⊖\n  •\n  🔣˜\t   \t• \t• \t•\t•\t•\t•\t•\t•  \t•\t•\t-   \t=   \t🔣⌫\t• \t🔣⤒↖\t🔣🔢\t•\t•\t•\t•\n  🔣↹\t   \t• \t• \t•\t•\t•\t•\t•\t•  \t•\t•\t🔣「〔⎡\t🔣」〕⎣\t🔣＼\t🔣⌦\t🔣⤓↘\t• \t•\t•\t•\t•\n  • \t   \t• \t• \t•\t•\t•\t•\t•\t•  \t•\t•\t•   \t🔣↩⌤␤\t  \t  \t   \t  \t \t•\t•\t•\n  • \t   \t• \t• \t•\t•\t•\t•\t•\t•  \t•\t/\t•   \t    \t• \t  \t   \t  \t \t•\t•\t•\t🔣🔢↩⌤␤\n  🔣⌃\t🔣❖⌘\t🔣⌥\t🔣␣\t \t \t \t \t \t🔣▤𝌆\t \t•\t•   \t•   \t• \t• \t   \t  \t•\t•   )\n"
  },
  {
    "path": "cfg_samples/home-row-mod-advanced.kbd",
    "content": ";; Home row mods QWERTY example with more complexity.\n;; Some of the changes from the basic example:\n;; - when a home row mod activates tap, the home row mods are disabled\n;;   while continuing to type rapidly\n;; - tap-hold-release helps make the hold action more responsive\n;; - pressing another key on the same half of the keyboard\n;;   as the home row mod will activate an early tap action\n;;\n;; Suggested further reading:\n;;\n;; GitHub discussion on more advanced tap-hold improvements:\n;;\n;;    https://github.com/jtroo/kanata/discussions/1455\n;;\n;; Configuration guide documentation on all tap-hold options:\n;;\n;;    https://github.com/jtroo/kanata/blob/main/docs/config.adoc#tap-hold\n\n(defcfg\n  process-unmapped-keys yes\n)\n(defsrc\n  a   s   d   f   j   k   l   ;\n)\n(defvar\n  ;; Note: consider using different time values for your different fingers.\n  ;; For example, your pinkies might be slower to release keys and index\n  ;; fingers faster.\n  tap-time 200\n  hold-time 150\n\n  left-hand-keys (\n    q w e r t\n    a s d f g\n    z x c v b\n  )\n  right-hand-keys (\n    y u i o p\n    h j k l ;\n    n m , . /\n  )\n)\n(deflayer base\n  @a  @s  @d  @f  @j  @k  @l  @;\n)\n\n(deflayer nomods\n  a   s   d   f   j   k   l   ;\n)\n(deffakekeys\n  to-base (layer-switch base)\n)\n(defalias\n  tap (multi\n    (layer-switch nomods)\n    (on-idle-fakekey to-base tap 20)\n  )\n\n  a (tap-hold-release-keys $tap-time $hold-time (multi a @tap) lmet $left-hand-keys)\n  s (tap-hold-release-keys $tap-time $hold-time (multi s @tap) lalt $left-hand-keys)\n  d (tap-hold-release-keys $tap-time $hold-time (multi d @tap) lctl $left-hand-keys)\n  f (tap-hold-release-keys $tap-time $hold-time (multi f @tap) lsft $left-hand-keys)\n  j (tap-hold-release-keys $tap-time $hold-time (multi j @tap) rsft $right-hand-keys)\n  k (tap-hold-release-keys $tap-time $hold-time (multi k @tap) rctl $right-hand-keys)\n  l (tap-hold-release-keys $tap-time $hold-time (multi l @tap) ralt $right-hand-keys)\n  ; (tap-hold-release-keys $tap-time $hold-time (multi ; @tap) rmet $right-hand-keys)\n)\n"
  },
  {
    "path": "cfg_samples/home-row-mod-basic.kbd",
    "content": ";; Basic home row mods example using QWERTY\n;; For a more complex but perhaps usable configuration,\n;; see home-row-mod-advanced.kbd\n\n(defcfg\n  process-unmapped-keys yes\n)\n(defsrc\n  a   s   d   f   j   k   l   ;\n)\n(defvar\n  ;; Note: consider using different time values for your different fingers.\n  ;; For example, your pinkies might be slower to release keys and index\n  ;; fingers faster.\n  tap-time 200\n  hold-time 150\n)\n(defalias\n  a (tap-hold $tap-time $hold-time a lmet)\n  s (tap-hold $tap-time $hold-time s lalt)\n  d (tap-hold $tap-time $hold-time d lctl)\n  f (tap-hold $tap-time $hold-time f lsft)\n  j (tap-hold $tap-time $hold-time j rsft)\n  k (tap-hold $tap-time $hold-time k rctl)\n  l (tap-hold $tap-time $hold-time l ralt)\n  ; (tap-hold $tap-time $hold-time ; rmet)\n)\n(deflayer base\n  @a  @s  @d  @f  @j  @k  @l  @;\n)"
  },
  {
    "path": "cfg_samples/home-row-mod-prior-idle.kbd",
    "content": ";; Home row mods with tap-hold-require-prior-idle.\n;;\n;; This replaces the layer-switching workaround in home-row-mod-advanced.kbd\n;; with two native features:\n;;\n;;   tap-hold-require-prior-idle  — during fast typing, tap-hold keys resolve as tap\n;;                         immediately (no waiting state, no accidental holds)\n;;\n;;   tap-hold-opposite-hand — hold activates only when the next key is on\n;;                            the opposite hand; same-hand rolls always tap\n;;\n;; The result: no nomods layer, no fakekeys, no multi wrappers.\n;;\n;; Configuration guide:\n;;   https://github.com/jtroo/kanata/blob/main/docs/config.adoc#tap-hold-require-prior-idle\n;;   https://github.com/jtroo/kanata/blob/main/docs/config.adoc#tap-hold\n\n(defcfg\n  process-unmapped-keys yes\n  ;; If any key was pressed within 150ms, resolve tap-hold as tap immediately.\n  ;; Prevents accidental modifier activation during fast typing.\n  tap-hold-require-prior-idle 150\n)\n\n(defsrc\n  a   s   d   f   j   k   l   ;\n)\n\n;; Declare which keys belong to each hand.\n;; tap-hold-opposite-hand uses this to decide hold vs tap.\n(defhands\n  (left  q w e r t a s d f g z x c v b)\n  (right y u i o p h j k l ; n m , . /)\n)\n\n(deflayer base\n  @a  @s  @d  @f  @j  @k  @l  @;\n)\n\n(defalias\n  ;; Note: consider using different time values for your different fingers.\n  ;; For example, your pinkies might be slower to release keys and index\n  ;; fingers faster.\n  a (tap-hold-opposite-hand 150 a lmet)\n  s (tap-hold-opposite-hand 150 s lalt)\n  d (tap-hold-opposite-hand 150 d lctl)\n  f (tap-hold-opposite-hand 150 f lsft)\n  j (tap-hold-opposite-hand 150 j rsft)\n  k (tap-hold-opposite-hand 150 k rctl)\n  l (tap-hold-opposite-hand 150 l ralt)\n  ; (tap-hold-opposite-hand 150 ; rmet)\n)\n"
  },
  {
    "path": "cfg_samples/included-file.kbd",
    "content": "(defalias\n  included-alias (macro i spc a m spc i n c l u d e d)\n)\n"
  },
  {
    "path": "cfg_samples/japanese_mac_eisu_kana.kbd",
    "content": "#|\n  Using meta keys as japanese eisu and kana on Mac with US keyboard.\n\n  | Source  | Tap          | Hold |\n  | ------- | ------------ | ---- |\n  | lmet    | lang2 (eisu) | lmet |\n  | rmet    | lang1 (kana) | rmet |\n\n|#\n\n(defcfg\n  process-unmapped-keys yes\n)\n\n(defsrc\n  lmet  rmet\n)\n\n(deflayer default\n  @lmet @rmet\n)\n\n(defalias\n  lmet (tap-hold-press 200 200 eisu lmet)\n  rmet (tap-hold-press 200 200 kana rmet)\n)\n\n"
  },
  {
    "path": "cfg_samples/jtroo.kbd",
    "content": "(defsrc\n  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n  caps a    s    d    f    g    h    j    k    l    ;    '    ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n\n(defalias\n  ;; toggle layer aliases\n  num (layer-toggle numbers)\n  chr (layer-toggle chords)\n  arr (layer-toggle arrows)\n  msc (layer-toggle misc)\n  lay (layer-toggle layers)\n  mse (layer-toggle mouse)\n\n  ;; change the base layer between qwerty and dvorak\n  dvk (layer-switch dvorak)\n  qwr (layer-switch qwerty)\n\n  ;; tap-hold keys with letters for tap and layer change for hold\n  anm (tap-hold-release 200 200 a @num)\n  oar (tap-hold-release 200 200 o @arr)\n  ech (tap-hold-release 200 200 e @chr)\n  umc (tap-hold-release 200 200 u @msc)\n  grl (tap-hold-release 200 200 grv @lay)\n  .ms (tap-hold-release 200 200 . @mse)\n\n  ;; tap for capslk, hold for lctl\n  cap (tap-hold-release 200 200 caps lctl)\n\n  ;; chords\n  csv C-S-v\n  csc C-S-c\n)\n\n(deflocalkeys-linux\n  pho 445\n)\n\n(defalias\n  ;; shifted keys\n  { S-[\n  } S-]\n  : S-;\n  ral (tap-hold-release 200 200 sldr ralt)\n)\n\n(defalias\n  alp (multi a b c d e f g h i j k l m n o p q r s t u v w x y z)\n  ls (macro l s spc min a l ret)\n)\n\n(deflayer dvorak\n  @grl 1    2    3    4    5    6    7    8    9    0    [    ]    bspc\n  tab  '    ,    @.ms p    y    f    g    c    r    l    /    =    \\\n  @cap @anm @oar @ech @umc i    d    h    t    n    s    -    ret\n  lsft ;    q    j    k    x    b    m    w    v    z    rsft\n  lctl lmet lalt           spc            @ral rmet rctl\n)\n\n(deflayer qwerty\n  @grl 1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n  caps a    s    d    f    g    h    j    k    l    ;    '    ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n\n(deflayer layers\n  _    @qwr @dvk lrld _    _    _    _    _    _    _    _    _    _\n  _    _    _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _              _              _    _    _\n)\n\n(deflayer numbers\n  _    _    _    _    _    _    nlk  kp7  kp8  kp9  _    _    _    _\n  _    _    C-S-tab C-tab _ XX  _    kp4  kp5  kp6  min  _    _    _\n  _    _    C-z  C-y  M-tab XX  _    kp1  kp2  kp3  +    _    _\n  _    C-z  C-x  C-c  C-v  XX   _    kp0  kp0  .    /    _\n  _    _    _              _              _    _    _\n)\n\n(deflayer misc\n  _    _    _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _    _    _    _    ins  @{   @}    [   ]    _    _    _\n  _    _    _    _    C-u  _    C-bspc bspc esc ret _    _    _\n  _    C-z  C-x  C-c  C-v  _    _    del  _    _    _    _\n  _    _    _              _              _    _    _\n)\n\n(deflayer chords\n  _    _    _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _    _    _    _    _    _    @csc _    @ls  _    _    _\n  _    @alp _    _    C-u  _    C-d  _    S-;  _    C-s  _    _\n  _    _    _    _    _    _    C-b  _    _    @csv _    _\n  _    _    _              _              _    _    _\n)\n\n(deflayer arrows\n  _    f1   f2   f3   f4   f5   f6   f7   f8   f9   f10  f11  f12  _\n  _    _    _    _    _    _    C-S-tab pgup up pgdn C-tab _  _    _\n  _    _    _    _    _    _    home left down rght end  _    _\n  _    _    _    _    _    _    _    _    C-w  _    _    _\n  _    _    _              _              _    _    _\n)\n\n(defalias\n  mwu (mwheel-up 50 120)\n  mwd (mwheel-down 50 120)\n  mwl (mwheel-left 50 120)\n  mwr (mwheel-right 50 120)\n)\n\n(deflayer mouse\n  _    _    _    _    _    _    _    _    _    _    _    _    _    _\n  _    @mwu bck  _    fwd  _    _    _    _    _    _    _    _    _\n  _    @mwd mlft _    mrgt mmid _    _    _    _    _    _    _\n  _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _              _              _    _    _\n)\n\n(defseq \"git status\" (g s t)\n  \"git commit --amend --no-edit\" (g c a)\n  \"git rebase -i HEAD~\" (g r e b)\n  \"git log --pretty=oneline --abbrev-commit\" (g l s)\n  \"git diff --ignore-space-change\" (g d f)\n  \"git diff --cached --ignore-space-change\" (g d c)\n  \"git push -f\" (g p f)\n  \"git commit -m 'fix_this_commit_message'\" (g c m)\n)\n\n(deffakekeys\n  \"git status\" (macro g i t spc s t a t u s)\n  \"git commit --amend --no-edit\" (macro g i t spc c o m m i t spc min min a m e n d spc min min n o min e d i t)\n  \"git rebase -i HEAD~\" (macro g i t spc r e b a s e spc min i spc S-h S-e S-a S-d S-grv)\n  \"git log --pretty=oneline --abbrev-commit\" (macro\n    g i t spc l o g spc\n      min min p r e t t y = o n e l i n e spc\n      min min a b b r e v min c o m m i t\n  )\n  \"git diff --ignore-space-change\" (macro\n    g i t spc d i f f spc\n      min min i g n o r e min s p a c e min c h a n g e\n  )\n  \"git diff --cached --ignore-space-change\" (macro\n    g i t spc d i f f spc\n      min min c a c h e d spc\n      min min i g n o r e min s p a c e min c h a n g e\n  )\n  \"git push -f\" (macro g i t spc p u s h spc min f)\n  \"git commit -m 'fix_this_commit_message'\" (macro\n    g i t spc c o m m i t spc\n      min m spc ' f i x S-min t h i s S-min c o m m i t S-min m e s s a g e '\n  )\n)\n"
  },
  {
    "path": "cfg_samples/kanata.kbd",
    "content": "#|\nThis is a sample configuration file that showcases every feature in kanata.\nA more detailed and less terse configuration guide is found here:\n\n    https://github.com/jtroo/kanata/blob/main/docs/config.adoc\n\nOther configuration samples are found here:\n\n    https://github.com/jtroo/kanata/tree/main/cfg_samples\n\nIf anything is confusing or hard to discover, please file an issue or\ncontribute a pull request to help improve the document.\n\nSince it may be important for you to know, pressing and holding all of the\nthree following keys together at the same time will cause kanata to exit:\n\n  Left Control, Space, Escape\n\nThis is for the physical key input rather than after any remappings\nthat are done by kanata.\n\n|#\n\n;; Single-line comments are prefixed by double-semicolon. A single semicolon\n;; is parsed as the keyboard key. Comments are ignored for the configuration\n;; file.\n\n#|\nA multi-line comment block begin with an octothorphe followed by a pipe: `#|`.\nTo end the multi-line comment block, have a pipe followed by an octothorpe,\nlike the following sequence with the colon removed: `|:#`. The actual two\ncharacter sequence is not used because it would end this multi-line comment\nblock and cause a parsing error.\n\nThis configuration language is Lisp-like and uses S-Expression syntax.\nIf you're unfamiliar with Lisp, don't be alarmed. The maintainer jtroo is\nalso unfamiliar with Lisp. You don't need to know Lisp in-depth to\nbe able to configure kanata.\n\nIf you follow along with the examples, you should be fine. Kanata should\nalso hopefully have helpful error messages in case something goes wrong.\nIf you need help, please feel welcome to ask in the GitHub discussions.\n|#\n\n;; One defcfg entry may be added if desired. This is used for configuration\n;; key-value pairs that change kanata's behaviour at a global level.\n;; All defcfg options are optional.\n(defcfg\n  ;; Your keyboard device will likely differ from this. I believe /dev/input/by-id/\n  ;; is preferable; I recall reading that it's less likely to change names on you,\n  ;; but I didn't find any keyboard device in there in my VM. If you are on Linux\n  ;; and omit this entry, kanata will try to attach to every device found on your\n  ;; system that seems like a keyboard.\n  ;; linux-dev /dev/input/by-path/platform-i8042-serio-0-event-kbd\n\n  ;; If you want to read from multiple devices, separate them by `:`.\n  ;; linux-dev /dev/input/<dev1>:/dev/input/<dev2>\n  ;;\n  ;; If you have a colon in your device path, add a backslash before it so that\n  ;; kanata does not parse it as multiple devices.\n  ;; linux-dev /dev/input/path-to\\:device\n\n  ;; Alternatively, you can use list syntax, where both backslashes and colons\n  ;; are parsed literally. List items are separated by spaces or newlines.\n  ;; Using quotation marks for each item is optional, and only required if an\n  ;; item contains spaces.\n  ;; linux-dev (\n  ;;   /dev/input/by-path/pci-0000:00:14.0-usb-0:1:1.0-event\n  ;;   /dev/input/by-id/usb-Dell_Dell_USB_Keyboard-event-kbd\n  ;; )\n\n  ;; The linux-dev-names-include entry is parsed identically to linux-dev. It\n  ;; defines a list of device names that should be included. This is only\n  ;; used if linux-dev is omitted.\n  ;; linux-dev-names-include device-1-name:device\\:2\\:name\n\n  ;; The linux-dev-names-exclude entry is parsed identically to linux-dev. It\n  ;; defines a list of device names that should be excluded. This is only\n  ;; used if linux-dev is omitted. This and linux-dev-names-include are not\n  ;; mutually exclusive but in practice it probably makes sense to only use\n  ;; one of them.\n  ;; linux-dev-names-exclude device-1-name:device\\:2\\:name\n\n  ;; By default, kanata will crash if no input devices are found. You can change\n  ;; this behaviour by setting `linux-continue-if-no-devs-found`.\n  ;;\n  ;; linux-continue-if-no-devs-found yes\n\n  ;; Kanata on Linux automatically detects and grabs input devices\n  ;; when none of the explicit device configurations are in use.\n  ;; In case kanata is undesirably grabbing mouse-like devices,\n  ;; you can use a configuration item to change detection behaviour.\n  ;;\n  ;; linux-device-detect-mode keyboard-only\n\n  ;; On Linux, you can ask kanata to run `xset r rate <delay> <rate>` on startup\n  ;; and on live reload via the config below. The first number is the delay in ms\n  ;; and the second number is the repeat rate in repeats/second.\n  ;;\n  ;; linux-x11-repeat-delay-rate 400,50\n\n  ;; On linux, you can ask kanata to label itself as a trackpoint. This has several\n  ;; effects on libinput including enabling middle mouse button scrolling and using\n  ;; a different acceleration curve. Otherwise, a trackpoint intercepted by kanata\n  ;; may not behave as expected.\n  ;;\n  ;; If using this feature, it is recommended to filter out any non-trackpoint\n  ;; pointing devices using linux-only-linux-dev-names-include,\n  ;; linux-only-linux-dev-names-exclude or linux-only-linux-dev to avoid changing\n  ;; their behavior as well.\n  ;;\n  ;; linux-use-trackpoint-property yes\n\n  ;; Unicode on Linux works by pressing Ctrl+Shift+U, typing the unicode hex value,\n  ;; then pressing Enter. However, if you do remapping in userspace, e.g. via\n  ;; xmodmap/xkb, the keycode \"U\" that kanata outputs may not become a keysym \"u\"\n  ;; after the userspace remapping. This will be likely if you use non-US,\n  ;; non-European keyboards on top of kanata. For unicode to work, kanata needs to\n  ;; use the keycode that outputs the keysym \"u\", which might not be the keycode\n  ;; \"U\".\n  ;;\n  ;; You can use `evtest` or `kanata --debug`, set your userspace key remapping,\n  ;; then press the key that outputs the keysym \"u\" to see which underlying keycode\n  ;; is sent. Then you can use this configuration to change kanata's behaviour.\n  ;;\n  ;; Example:\n  ;;\n  ;;   linux-unicode-u-code v\n\n  ;; Unicode on Linux terminates with the Enter key by default. This may not work in\n  ;; some applications. The termination is configurable with the following options:\n  ;;\n  ;; - `enter`\n  ;; - `space`\n  ;; - `enter-space`\n  ;; - `space-enter`\n  ;;\n  ;; Example:\n  ;;\n  ;;   linux-unicode-termination space\n\n  ;; Kanata on Linux creates an evdev output device named \"kanata\".\n  ;; This name can be changed with this linux-output-device-name.\n  ;;\n  ;; Examples:\n  ;;\n  ;;   linux-output-device-name kanata_laptop\n  ;;   linux-output-device-name \"kanata output device\"\n\n  ;; Kanata on Linux needs to declare a \"bus type\" for its evdev output device.\n  ;; The options are USB and I8042. The default is I8042.\n  ;; Using USB can break disable-touchpad-while-typing on Wayland.\n  ;; But using I8042 appears to break some other scenarios. Thus it is configurable.\n  ;;\n  ;; Examples:\n  ;;\n  ;;   linux-output-device-bus-type USB\n  ;;   linux-output-device-bus-type I8042\n\n  ;; There is an optional configuration entry for Windows to help mitigate strange\n  ;; behaviour of AltGr if your layout uses that. Uncomment one of the items below\n  ;; to change what kanata does with the key.\n  ;;\n  ;; For more context, see: https://github.com/jtroo/kanata/issues/55.\n  ;;\n  ;; windows-altgr cancel-lctl-press ;; remove the lctl press that comes as a combo with ralt\n  ;; windows-altgr add-lctl-release  ;; add an lctl release when ralt is released\n  ;;\n  ;; NOTE: even with these workarounds, putting lctl+ralt in your defsrc may\n  ;; not work too well with other applications that use WH_KEYBOARD_LL.\n  ;; Known applications with issues: GWSL/VcXsrv\n\n  ;; Enable kanata to execute commands.\n  ;;\n  ;; I consider this feature a hazard so it is conditionally compiled out of\n  ;; the default binary.\n  ;;\n  ;; This is dangerous because it allows kanata to execute arbitrary commands.\n  ;; Using a binary compiled with the cmd feature enabled, uncomment below to\n  ;; enable command execution:\n  ;;\n  ;; danger-enable-cmd yes\n\n  ;; Enable processing of keys that are not in defsrc.\n  ;; This is useful if you are only mapping a few keys in defsrc instead of\n  ;; most of the keys on your keyboard. Without this, the tap-hold-release and\n  ;; tap-hold-press actions will not activate for keys that are not in defsrc.\n  ;;\n  ;; The reason this is not enabled by default is because some keys may not\n  ;; work correctly if they are intercepted. E.g. rctl/altgr on Windows; see the\n  ;; windows-altgr configuration item above for context.\n  ;;\n  ;; process-unmapped-keys yes\n\n  ;; We need to set it to yes in this kanata.kbd example config to allow the use of __ and ___\n  ;; in the deflayer-custom-map example below in the file\n\n  ;; This also accepts a list parameter (all-except key1 ... keyN)\n  ;; which behaves like \"yes\" but excludes the keys within the list.\n  process-unmapped-keys (all-except f19)\n\n  ;; Disable all keys not mapped in defsrc.\n  ;; Only works if process-unmapped-keys is also yes.\n  ;; block-unmapped-keys yes\n\n  ;; Intercept mouse buttons for a specific mouse device.\n  ;; The intended use case for this is for laptops such as a Thinkpad, which have\n  ;; mouse buttons that may be useful to activate kanata actions with. This only\n  ;; works with the Interception driver.\n  ;;\n  ;; To know what numbers to put into the string, you can run the\n  ;; kanata-wintercept variant with this defcfg item defined with random numbers.\n  ;; Then when a button is first pressed on the mouse device, kanata will print\n  ;; its hwid in the log; you can then copy-paste that into this configuration\n  ;; entry. If this defcfg item is not defined, the log will not print.\n  ;;\n  ;; windows-interception-mouse-hwid \"70, 0, 90, 0, 20\"\n\n  ;; There is also a list version of windows-interception-mouse-hwid:\n  ;;\n  ;; windows-interception-mouse-hwids (\n  ;;   \"70, 0, 60, 0\"\n  ;;   \"71, 0, 62, 0\"\n  ;; )\n\n  ;; List configuration for kanata-wintercept variants\n  ;; that allows intercepting only some connected keyboards.\n  ;; Use similarly to mouse-hwid above.\n  ;;\n  ;; windows-interception-keyboard-hwids (\n  ;;   \"90, 80, 11, 34\"\n  ;;   \"99, 88, 77, 66\"\n  ;; )\n\n  ;; There are also exclude variants of the wintercept device configurations.\n  ;; These cannot be defined at the same time as the non-exclude variants.\n  ;;\n  ;; windows-interception-keyboard-hwids-exclude (\n  ;;   \"90, 80, 11, 34\"\n  ;;   \"99, 88, 77, 66\"\n  ;; )\n  ;;\n  ;; windows-interception-mouse-hwids-exclude (\n  ;;   \"70, 0, 60, 0\"\n  ;;   \"71, 0, 62, 0\"\n  ;; )\n\n  ;; Transparent keys on layers will delegate to the corresponding defsrc key\n  ;; when found on a layer activated by `layer-switch`. This config entry\n  ;; changes the behaviour to delegate to the action of the first layer,\n  ;; which is the layer active upon startup, that is in the same position.\n  ;;\n  ;; delegate-to-first-layer yes\n\n  ;; This config entry alters the behavior of movemouse-accel actions.\n  ;; By default, this setting is disabled - vertical and horizontal\n  ;; acceleration are independent. Enabling this setting will emulate QMK mouse\n  ;; move acceleration behavior, i.e. the acceleration state of new mouse\n  ;; movement actions are inherited if others are already being pressed.\n  ;;\n  ;; movemouse-inherit-accel-state yes\n\n  ;; This config entry alters the behavior of movemouseaccel actions.\n  ;; This makes diagonal movements simultaneous to mitigate choppiness in\n  ;; drawing apps, if you're using kanata mouse movements to draw for\n  ;; whatever reason.\n  ;;\n  ;; movemouse-smooth-diagonals yes\n\n  ;; This configuration allows you to customize the length limit on dynamic macros.\n  ;; The default limit is 128 keys.\n  ;;\n  ;; dynamic-macro-max-presses 1000\n\n  ;; This configuration makes multiple tap-hold actions that are activated near\n  ;; in time expire their timeout quicker. Without this, the timeout for the 2nd\n  ;; tap-hold onwards will start from 0ms after the previous tap-hold expires.\n  ;;\n  concurrent-tap-hold yes\n\n  ;; This configuration makes the release of one-shot-press and of the tap in a tap-hold\n  ;; by the defined number of milliseconds (approximate).\n  ;; The default value is 5.\n  ;; While the release is delayed, further processing of inputs is also paused.\n  ;; This means that there will be a minor input latency impact in the mentioned scenarios.\n  ;; The reason for this configuration existing is that some environments\n  ;; do not process the scenarios correctly due to the rapidity of the release.\n  ;; Kanata does send the events in the correct order,\n  ;; so the fault is more in the environment, but kanata provides a workaround anyway.\n  rapid-event-delay 5\n\n  ;; This setting defaults to yes but can be configured to no to save on\n  ;; logging. However, if --log-layer-changes is passed as a command line\n  ;; argument, a \"no\" in the configuration file will be overridden and layer\n  ;; changes will be logged.\n  ;;\n  ;; log-layer-changes no\n\n  ;; This configuration will press and then immediately release the non-modifier key\n  ;; as soon as the override activates, meaning you are unlikely as a human to ever\n  ;; release modifiers first, which can result in unintended behaviour.\n  ;;\n  ;; The downside of this configuration is that the non-modifier key\n  ;; does not remain held which is important to consider for your use cases.\n  override-release-on-activation yes\n\n  ;; Accepts a single key name.\n  ;; When configured, whenever a mouse cursor movement is received,\n  ;; the configured key name will be \"tapped\" by Kanata, activating\n  ;; the key's action.\n  ;;\n  ;; This enables reporting of every relative mouse movement, which\n  ;; corresponds to standard mice, trackballs, trackpads and\n  ;; trackpoints. Absolute movements, which can be generated by\n  ;; touchscreens, drawing tablets and some mouse replacement or\n  ;; accessibility software, are ignored. Scrolling events and mouse\n  ;; buttons are also ignored.\n  ;;\n  ;; The intended use of these events is to provide a way to\n  ;; automatically enable a mouse keys layer while mousing, which can\n  ;; be disabled by a timeout or typing on other keys, rather than\n  ;; explicit toggling. see cfg_examples/automousekeys-*.kbd for more.\n  ;;\n  ;; The `mvmt` key name is specially intended for this purpose. It\n  ;; has no output key mapping and cannot be supplied as an action;\n  ;; however, any key may be used.\n  ;;\n  ;; Supports live reload on Linux, but with Windows-interception,\n  ;; this option must be present on startup to enable mouse movement\n  ;; event collection, so restart is required to enable it. Changing\n  ;; the key name is always supported, however.\n  ;;\n  ;; mouse-movement-key mvmt\n)\n\n;; deflocalkeys-* enables you to define and use key names that match your locale\n;; by defining OS code number mappings for that character.\n;;\n;; There are five variants of deflocalkeys-*:\n;; - deflocalkeys-win\n;; - deflocalkeys-winiov2\n;; - deflocalkeys-wintercept\n;; - deflocalkeys-linux\n;; - deflocalkeys-macos\n;;\n;; Only one of each deflocalkeys-* variant is allowed. The variants that are\n;; not applicable will be ignored, e.g. deflocalkeys-linux and deflocalkeys-wintercept\n;; are both ignored when using the default Windows kanata binary.\n;;\n;; The configuration item is parsed similarly to defcfg; it is composed of\n;; pairs of keys and values.\n;;\n;; You can check docs/locales.adoc for the mapping for your locale. If your\n;; locale is not there, please ask for help if needed, and add the mapping for\n;; your locale to the document.\n;;\n;; Web link for locales: https://github.com/jtroo/kanata/blob/main/docs/locales.adoc\n;;\n;; This example is for an Italian keyboard remapping in Linux. The numbers will\n;; unfortunately differ between Linux, Windows-hooks, and Windows-interception.\n;;\n;; To see how you can discover key mappings for yourself, see the \"Non-US keyboards\"\n;; section of docs/config.adoc.\n;;\n;; Web link or config: https://github.com/jtroo/kanata/blob/main/docs/config.adoc\n\n(deflocalkeys-win\n  ì 187\n)\n\n(deflocalkeys-wintercept\n  ì 187\n)\n\n(deflocalkeys-winiov2\n  ì 187\n)\n\n(deflocalkeys-linux\n  ì 13\n)\n\n(deflocalkeys-macos\n  ì 13\n)\n\n;; Only one defsrc is allowed.\n;;\n;; defsrc defines the keys that will be intercepted by kanata. The order of the\n;; keys matches the deflayer declarations and all deflayer declarations must\n;; have the same number of keys as defsrc.\n;;\n;; The visual/spatial positioning is *not* mandatory; it is done by convention\n;; for visual ease. These items are parsed as a long list with newlines being\n;; ignored.\n;;\n;; If you are looking for other keys, the file src/keys/mod.rs should hopefully\n;; provide some insight.\n(defsrc\n  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n  caps a    s    d    f    g    h    j    k    l    ;    '    ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n\n;; The first layer defined is the layer that will be active by default when\n;; kanata starts up. This layer is the standard QWERTY layout except for the\n;; backtick/grave key (@grl) which is an alias for a tap-hold key.\n(deflayer qwerty\n  @grl 1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n  caps a    s    d    f    g    h    j    k    l    ;    '    ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n\n;; The dvorak layer remaps the keys to the dvorak layout. There are several\n;; tap-hold aliases configured on the left side.\n(deflayer dvorak\n  @grl 1    2    3    4    5    6    7    8    9    0    [    ]    bspc\n  tab  '    ,    @.ms p    y    f    g    c    r    l    /    =    \\\n  @cap @anm @oar @ech @umc @ifk d    h    t    n    s    -    ret\n  lsft ;    q    j    k    x    b    m    w    v    z    rsft\n  lctl lmet lalt           spc           @ralt rmet @rcl\n)\n\n;; This is an alternative to deflayer and does not rely on defsrc.\n;; It has the advantage of simpler config if only remapping a few keys.\n;; You might still prefer the standard deflayer for its visual printing in\n;; the log as you are learning a new configuration.\n(deflayermap (custom-map-example)\n  caps esc\n  esc  caps\n\n  ;; You can use _ , __ or ___ instead of specifying a key name to map all\n  ;; keys that are not explicitly mapped in the layer.\n  ;; E.g. esc and caps above will not be overwritten.\n  ;;\n  ;; _    maps only keys that are in defsrc.\n  ;; __   excludes mapping keys that are in defsrc.\n  ;; ___  maps both, keys that are in `defsrc`, and keys that are not.\n  ;;\n  ;; The two- and three-underscore variants require\n  ;; \"process-unmapped-keys yes\" in defcfg to work.\n\n  ;; ___ XX ;; maps all keys that are not mapped explicitly in the layer\n  ;;          ;; (i.e. esc and caps above) to \"no-op\" to disable the key.\n  _ XX   ;; maps all keys that are in defsrc and are not mapped in the layer\n  __ XX  ;; maps all keys that are NOT in defsrc and are not mapped in the layer\n)\n\n;; defvar can be used to declare commonly-used values\n(defvar\n  tap-repress-timeout   100\n  hold-timeout          200\n  tt $tap-repress-timeout\n  ht $hold-timeout\n\n  ;; A list value in defvar that begins with concat behaves in a special manner\n  ;; where strings will be joined together.\n  ;;\n  ;; Below results in 100200\n  a \"hello\"\n  b \"world\"\n  ct (concat $a \" \" $b)\n)\n\n(defalias\n  th1 (tap-hold $tt $ht caps lctl)\n  th2 (tap-hold $tt $ht spc lsft)\n)\n\n;; defalias is used to declare a shortcut for a more complicated action to keep\n;; the deflayer declarations clean and aligned. The alignment in deflayers is not\n;; necessary, but is strongly recommended for ease of understanding visually.\n;;\n;; Aliases are referred to by `@<alias_name>`. Aliases can refer to each other,\n;; e.g. in the `anm` alias. However, an alias can only refer to another alias\n;; that has been defined before it in the file.\n(defalias\n  ;; aliases to change the base layer to qwerty or dvorak\n  dvk (layer-switch dvorak)\n  qwr (layer-switch qwerty)\n\n  ;; Aliases for layer \"toggling\". It's not quite toggling because the layer\n  ;; will be active while the key is held and deactivated when the key is\n  ;; released. An alternate action name you can use is layer-while-held.\n  ;; However, the rest of this document will use The term \"toggle\" for brevity.\n  num (layer-toggle numbers)\n  chr (layer-toggle chords)\n  arr (layer-toggle arrows)\n  msc (layer-toggle misc)\n  lay (layer-toggle layers)\n  mse (layer-toggle mouse)\n  fks (layer-while-held fakekeys)\n\n  ;; tap-hold aliases with tap for dvorak key, and hold for toggle layers\n  ;; WARNING(Linux only): key repeat with tap-hold can behave unexpectedly.\n  ;; For full context, see https://github.com/jtroo/kanata/discussions/422\n  ;;\n  ;; tap-hold parameter order:\n  ;; 1. tap repress timeout\n  ;; 2. hold timeout\n  ;; 3. tap action\n  ;; 4. hold action\n  ;;\n  ;; The hold timeout is the number of milliseconds after which the hold action\n  ;; will activate.\n  ;;\n  ;; The tap repress timeout is best explained in a roundabout way. When you press and\n  ;; hold a standard key on your keyboard (e.g. 'a'), your operating system will\n  ;; read that and keep sending 'a' to the active application. To be able to\n  ;; replicate this behaviour with a tap-hold key, you must press-release-press\n  ;; the key within the tap repress timeout window (number is milliseconds). Simply\n  ;; holding the key results in the hold action activating, which is why you\n  ;; need to double-press for the tap action to stay pressed.\n  ;;\n  ;; There are two additional versions of tap-hold available:\n  ;; 1. tap-hold-press: if there is a key press, the hold action is activated\n  ;; 2. tap-hold-release: if there is a press and release of another key, the\n  ;; hold action is activated\n  ;;\n  ;; These versions are useful if you don't want to wait for the whole hold\n  ;; timeout to activate, but want to activate the hold action immediately\n  ;; based on the next key press or press and release of another key. These\n  ;; versions might be great to implement home row mods.\n  ;;\n  ;; If you come from kmonad, tap-hold-press and tap-hold-release are similar\n  ;; to tap-hold-next and tap-hold-next-release, respectively. If you know\n  ;; the underlying keyberon crate, tap-hold-press is the HoldOnOtherKeyPress\n  ;; and tap-hold-release is the PermissiveHold configuration.\n  anm (tap-hold 200 200 a @num)   ;; tap: a      hold: numbers layer\n  oar (tap-hold 200 200 o @arr)   ;; tap: o      hold: arrows layer\n  ech (tap-hold 200 200 e @chr)   ;; tap: e      hold: chords layer\n  umc (tap-hold 200 200 u @msc)   ;; tap: u      hold: misc layer\n  grl (tap-hold 200 200 grv @lay) ;; tap: grave  hold: layers layer\n  .ms (tap-hold 200 200 . @mse)   ;; tap: .      hold: mouse layer\n  ifk (tap-hold 200 200 i @fks)   ;; tap: i      hold: fake keys layer\n\n  ;; There are additional variants of tap-hold-press and tap-hold-release that\n  ;; activate the timeout action (the 5th parameter) when the action times out\n  ;; as opposed to the hold action being activated by other keys.\n\n  ;; tap: o    hold: arrows layer    timeout: backspace\n  oat (tap-hold-press-timeout   200 200 o @arr bspc)\n  ;; tap: e    hold: chords layer    timeout: esc\n  ect (tap-hold-release-timeout 200 200 e @chr esc)\n  ;; If you add reset-timeout-on-press to tap-hold-release-timeout,\n  ;; the timeout will reset on a press to give you more time to release\n  ;; a key to activate the hold.\n  ect2 (tap-hold-release-timeout 200 200 e @chr esc reset-timeout-on-press)\n\n  ;; There is another variant of `tap-hold-release` that takes a 5th parameter\n  ;; that is a list of keys that will trigger an early tap when pressed.\n\n  ;; tap: u    hold: misc layer      early tap if any of: (a o e) are pressed\n  umk (tap-hold-release-keys 200 200 u @msc (a o e))\n\n  ;; A variant of tap-hold-release-keys accepts another parameter,\n  ;; which is a list of keys that activates the tap\n  ;; on a press->release of a listed key.\n  umk2 (tap-hold-release-tap-keys-release 200 200 u @msc (a o e) (' , .))\n\n  ;; tap: u    hold: misc layer      always tap if any of: (a o e) are pressed\n  uek (tap-hold-except-keys 200 200 u @msc (a o e))\n\n  ;; tap: u    hold: misc layer      early tap if any of: (a o e) are pressed\n  ;; Unlike tap-hold-release-keys, other keys do NOT trigger early hold.\n  ;; This is useful for home row mods where fast typing should not trigger modifiers.\n  utk (tap-hold-tap-keys 200 200 u @msc (a o e))\n\n  ;; tap-hold-order resolves by release order instead of timeout.\n  ;; tap: a    hold: lctl    buffer: 50ms (fast typing grace period)\n  aor (tap-hold-order 200 50 a lctl)\n\n  ;; tap for capslk, hold for lctl\n  cap (tap-hold 200 200 caps lctl)\n\n  ;; Below is an alias for the `multi` action which executes multiple actions\n  ;; in order but at the same time.\n  ;;\n  ;; It may result in some incorrect/unexpected behaviour if combining complex\n  ;; actions, so be reasonable with it. One reasonable use case is this alias:\n  ;; press right-alt while also toggling to the `ralted` layer. The utility of\n  ;; this is better revealed if you go see `ralted` and its aliases.\n  ralt (multi ralt (layer-toggle ralted))\n)\n\n;; Wrapping a top-level configuration item in a list beginning with\n;; (environment (env-var-name env-var-value) ...configuration...)\n;; will make the configuration only active if the environment variable matches.\n(environment (LAPTOP lp1)\n  (defalias met @lp1met)\n)\n\n(environment (LAPTOP lp2)\n  (defalias met @lp2met)\n)\n\n;; NOTE: the configuration below is an older and less general variant\n;; of the environment configuration above.\n;;\n;; The defaliasenvcond variant of defalias is parsed similarly, but there must\n;; be a list parameter first. The list must contain two strings. In order,\n;; these strings are: an environment variable name, and the environment\n;; variable value. When the environment variable defined by name has the\n;; corresponding value when running kanata, the aliases within will be active.\n;; Otherwise, the aliases will be skipped.\n(defaliasenvcond (LAPTOP lp1)\n  met @lp1met\n)\n\n(defaliasenvcond (LAPTOP lp2)\n  met @lp2met\n)\n\n(defalias\n  ;; shifted keys\n  { S-[\n  } S-]\n  : S-;\n\n  ;; alias numbers as themselves for use in macro\n  8 8\n  0 0\n)\n\n(defalias\n  ;; For the multi action, all keys are pressed for the whole sequence\n  ;; but still in the listed order which may be undesirable, particularly\n  ;; for modifiers like shift. You probably want to use macro instead.\n  ;;\n  ;; Chording can be more succinctly described by the modifier prefixes\n  ;; `C-`, `A-`, `S-`, and `M-` for lctrl, lalt, lshift, lmeta, but are possible\n  ;; by using `multi` as well. The lmeta key is also known by some other\n  ;; names: \"Windows\", \"GUI\", \"Command\", \"Super\".\n  ;;\n  ;; For ralt/altgr, you can use either of: `RA-` or `AG-`. They both work the\n  ;; same and only one is allowed in a single chord. This chord can be useful for\n  ;; international layouts.\n  ;;\n  ;; A special behaviour of output chords is that if another key is pressed,\n  ;; all of the chord keys will be released. For the explanation about why\n  ;; this is the case, see the configuration guide.\n  ;;\n  ;; This use case for multi is typing an all-caps string.\n  alp (multi lsft a b c d e f g h i j k l m n o p q r s t u v w x y z)\n\n  ;; Within multi you can also include reverse-release-order to release keys\n  ;; from last-to-first order instead of first-to-last which is the default.\n  S-a-reversed (multi lsft a reverse-release-order)\n\n  ;; Chords using the shortcut syntax. These ones are used for copying/pasting\n  ;; from some Linux terminals.\n  csv C-S-v\n  csc C-S-c\n\n  ;; Windows shortcut for displaying all windows\n  win M-tab\n\n  ;; Accented e characters for France layout using altgr sequence. Showcases\n  ;; both of the shortcuts. You can just use one version of shortcut at your\n  ;; preference.\n  é AG-2\n  è RA-7\n  testmacro (macro AG-2 RA-7)\n  🙃 (unicode 🙃)\n\n  ;; macro accepts keys, chords, and numbers (a delay in ms). Note that numbers\n  ;; will be parsed as delays, so they will need to be aliased to be used.\n  lch (macro h t t p @: / / 100 l o c a l h o s t @: @8 @0 @8 @0)\n  tbm (macro A-(tab 200 tab 200 tab) 200 S-A-(tab 200 tab 200 tab))\n  hpy (macro S-i spc a m spc S-(h a p p y) spc m y S-f r S-i e S-n d @🙃)\n\n  rls (macro-release-cancel Digit1 500 bspc S-1 500 bspc S-2)\n  cop (macro-cancel-on-press Digit1 500 bspc S-1 500 bspc S-2)\n  rlpr (macro-release-cancel-and-cancel-on-press Digit1 500 bspc S-1 500 bspc S-2)\n\n  ;; repeat variants will repeat while held, once ALL macros have ended,\n  ;; including the held macro.\n  mr1 (macro-repeat mltp)\n  mr2 (macro-repeat-release-cancel mltp)\n  mr3 (macro-repeat-cancel-on-press mltp)\n  mr4 (macro-repeat-release-cancel-and-cancel-on-press mltp)\n\n  ;; Kanata also supports dynamic macros. Dynamic macros can be nested, but\n  ;; cannot recurse.\n  dms dynamic-macro-record-stop\n  dst (dynamic-macro-record-stop-truncate 3)\n  dr0 (dynamic-macro-record 0)\n  dr1 (dynamic-macro-record 1)\n  dr2 (dynamic-macro-record 2)\n  dp0 (dynamic-macro-play 0)\n  dp1 (dynamic-macro-play 1)\n  dp2 (dynamic-macro-play 2)\n\n  ;; unmod will release all modifiers temporarily and send the .\n  ;; So for example holding shift and tapping a @um1 key will still output 1.\n  um1 (unmod 1)\n  ;; dead keys é (as opposed to using AltGr) that outputs É when shifted\n  dké (macro (unmod ') e)\n\n  ;; unshift is like unmod but only releases shifts\n  ;; In ISO German QWERTZ, force unshifted symbols even if shift is held\n  de{ (unshift ralt 7)\n  de[ (unshift ralt 8)\n\n  ;; unmod can optionally take a list as the first parameter,\n  ;; and then will only temporarily remove\n  ;; the listed modifiers instead of all modifiers.\n  unalt-a (unmod (lalt ralt) a)\n\n  ;; unicode accepts a single unicode character. The unicode character will\n  ;; not be automatically repeated by holding the key down. The alias name\n  ;; is the unicode character itself and is referenced by @🙁 in deflayer.\n  🙁 (unicode 🙁)\n\n  ;; You may output parentheses or double quotes using unicode\n  ;; by quotes as well as special quoting syntax.\n  lp1 (unicode r#\"(\"#)\n  rp1 (unicode r#\")\"#)\n  dq (unicode r#\"\"\"#)\n  lp2 (unicode \"(\")\n  rp2 (unicode \")\")\n\n  ;; fork accepts two actions and a key list. The first (left) action will\n  ;; activate by default. The second (right) action will activate if any of\n  ;; the keys in the third parameter (right-trigger-keys) are currently active.\n  frk (fork @🙃 @🙁 (lsft rsft))\n\n  ;; switch accepts triples of keys check, action, and fallthrough|break.\n  ;; The default usage of keys check behaves similarly to fork.\n  ;; However, it also accepts boolean operators and|or to allow more\n  ;; complex use cases.\n  ;;\n  ;; The order of cases matters. If two different cases match the\n  ;; currently pressed keys, the case listed earlier in the configuration\n  ;; will activate first. If the early case uses break, the second case will\n  ;; not activate at all. Otherwise if fallthrough is used, the second case\n  ;; will also activate sequentially after the first case.\n  swt (switch\n\n    ;; translating this keys check to some other common languages\n    ;; this might look like:\n    ;;\n    ;;    (a && b && (c || d) && (e || f))\n    ((and a b (or c d) (or e f))) a break\n\n    ;; this case behaves like fork, i.e.\n    ;;\n    ;;    (or a b c)\n    ;;\n    ;; or for some other common languages:\n    ;;\n    ;;    a || b || c\n    (a b c) b fallthrough\n\n    ;; key-history evaluates to true if the n'th most recent typed key,\n    ;; {n | n ∈ [1, 8]}, matches the given key.\n    ((key-history a 1) (key-history b 8)) c break\n\n    ;; key-timing evaluates to true if the n'th most recent typed key,\n    ;; {n | n ∈ [1, 8]}, was typed at a time less-than/greater-than the\n    ;; given number of milliseconds.\n    ((key-timing 1 lt 3000)       (key-timing 2 gt 30000)        ) c break\n    ((key-timing 7 less-than 200) (key-timing 8 greater-than 500)) c break\n\n    ;; not means \"not any of the list constituents\".\n    ;; The example below behaves like:\n    ;;\n    ;;    !(a || b || c)\n    ;;\n    ;; and is equivalent to:\n    ;;\n    ;;    ((not (or a b c)))\n    ((not a b c)) c break\n\n    ;; input logic\n    ((input real lctl)) d break\n    ((input virtual sft)) e break\n    ((input-history real lsft 2)) f break\n    ((input-history virtual ctl 2)) g break\n\n    ;; layer evaluates to `true` if the active layer matches the given name\n    ((layer dvorak)) x break\n    ((layer qwerty)) y break\n\n    ;; base-layer evaluates to `true` if the base layer matches the given name\n    ;; The base layer is the most recent target of layer-switch.\n    ;; The base layer is not always the active layer.\n    ((base-layer dvorak)) x break\n    ((base-layer qwerty)) y break\n\n    ;; default case, empty list always evaluates to true.\n    ;; break vs. fallthrough doesn't matter here\n    () c break\n  )\n\n  ;; Having a cmd action in your configuration without explicitly enabling\n  ;; `danger-enable-cmd` **and** using the cmd-enabled executable will make\n  ;; kanata refuse to load your configuration. The aliases below are commented\n  ;; out since commands aren't allowed by this configuration file.\n  ;;\n  ;; Note that the parameters to `cmd` are executed directly as opposed to\n  ;; passed to a shell. So for example, `~` and `$HOME` would not refer\n  ;; to your home directory on Linux.\n  ;;\n  ;; You can use:\n  ;; `cmd bash -c \"your_stuff_here\"` to run your command inside of bash.\n  ;;\n  ;; cm1 (cmd bash -c \"echo hello world\")\n  ;; cm2 (cmd rm -fr /tmp/testing)\n\n  ;; One variant of `cmd` is `cmd-log`, which lets you control how\n  ;; running command, stdout, stderr, and execution failure are logged.\n  ;;\n  ;; The command takes two extra arguments at the beginning `<log_level>`,\n  ;; and `<error_log_level>`. `<log_level>` controls where the name\n  ;; of the command is logged, as well as the success message and command\n  ;; stdout and stderr.\n  ;;\n  ;; `<error_log_level>` is only used if there is a failure executing the initial\n  ;; command. This can be if there is trouble spawning the command, or\n  ;; the command is not found. This means if you use `bash -c \"thisisntacommand\"`, as\n  ;; long as bash starts up correctly, nothing would be logged to this channel, but\n  ;; something like `thisisntacommand` would be.\n  ;;\n  ;; The log level can be `debug`, `info`, `warn`, `error`, or `none`.\n  ;;\n  ;; cmd-log info error bash -c \"echo these are the default levels\"\n  ;; cmd-log none none bash -c \"echo nothing back in kanata logs\"\n  ;; cmd-log none error bash -c \"only if command fails\"\n  ;; cmd-log debug debug bash -c \"echo log, but require changing verbosity levels\"\n  ;; cmd-log warn warn bash -c \"echo this probably isn't helpful\"\n\n  ;; Another variant of `cmd` is `cmd-output-keys`. This reads the output\n  ;; of the command and treats it as an S-Expression, similarly to `macro`.\n  ;; However, only delays, keys, chords, and chorded lists are supported.\n  ;; Other actions are not.\n  ;;\n  ;; bash: type date-time as YYYY-MM-DD HH:MM\n  ;; cmd-output-keys bash -c \"date +'%F %R' | sed 's/./& /g' | sed 's/:/S-;/g' | sed 's/\\(.\\{20\\}\\)\\(.*\\)/\\(\\1 spc \\2\\)/'\"\n)\n\n;; The underscore _ means transparent. The key on the base layer will be used\n;; instead. XX means no-op. The key will do nothing.\n;;\n;; A similar concept to transparent, use-defsrc means the key will always\n;; behave as the key as defined by defsrc.\n(defalias src use-defsrc)\n(deflayer numbers\n  @src _    _    _    _    _    nlk  kp7  kp8  kp9  _    _    _    _\n  _    _    _    _    _    XX   _    kp4  kp5  kp6  -    _    _    _\n  _    _    C-z  _    _    XX   _    kp1  kp2  kp3  +    _    _\n  _    C-z  C-x  C-c  C-v  XX   _    kp0  kp0  .    /    _\n  _    _    _              _              _    _    _\n)\n\n;; The `lrld` action stands for \"live reload\".\n;;\n;; NOTE: live reload does not read changes to device-related configurations,\n;; such as `linux-dev`, `macos-dev-names-include`,\n;; or `windows-only-windows-interception-keyboard-hwids`.\n;;\n;; The variants `lrpv` and `lrnx` will cycle between multiple configuration files\n;; if they are specified in the startup arguments.\n;; The list action variant `lrld-num` takes a number parameter and\n;; reloads the configuration file specified by the number, according to the\n;; order passed into the arguments on kanata startup.\n;;\n;; Upon a successful reload, the kanata state will begin on the default base layer\n;; in the configuration. E.g. in this example configuration, you would start on\n;; the qwerty layer.\n(deflayer layers\n  _    @qwr @dvk lrld lrpv lrnx (lrld-num 1) _ _    _    _    _    _    _\n  _    _    _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _              _              _    _    _\n)\n\n(defalias\n\n  ;; Alias for one-shot which will activate an action until either the timeout\n  ;; expires or a different key is pressed. The timeout is the first parameter\n  ;; and the action is the second parameter.\n  ;;\n  ;; The intended use cases are pressing a modifier for exactly one key or\n  ;; switching to another layer for exactly one key.\n  ;;\n  ;; If a one-shot key is held then it will act as a regular key. E.g. for os1\n  ;; below, holding an @os1 key will keep lsft held and holding an @os3 key\n  ;; will keep the layer set to misc.\n  os1 (one-shot 500 lsft)\n  os2 (one-shot 500 C-S-lalt)\n  os3 (one-shot 500 (layer-toggle misc))\n\n  ;; Another name for one-shot is one-shot-press, since it ends on the first\n  ;; press of another key.\n  ;;\n  ;; There is another variant one-shot-release which ends on the first release\n  ;; of another key.\n  ;;\n  ;; There are further variants of both of these:\n  ;; - one-shot-press-pcancel\n  ;; - one-shot-release-pcancel\n  ;;\n  ;; These will cancel the one-shot action and all other active one-shot actions\n  ;; if a one-shot key is repressed while already active.\n  osp (one-shot-press 500 lsft)\n  osr (one-shot-release 500 lsft)\n  opp (one-shot-press-pcancel 500 lsft)\n  orp (one-shot-release-pcancel 500 lsft)\n\n  ;; one-shot-pause-processing can be useful in some cases\n  ;; to preserve an activated one-shot state when it otherwise\n  ;; would get deactivated by some action that isn't intended\n  ;; to consume the one-shot.\n  ;; The unit is number of milliseconds.\n  ops (one-shot-pause-processing 5)\n\n  ;; Alias for tap-dance which will activate one of the actions in the action\n  ;; list depending on how many taps were done. Tapping once will output the\n  ;; first action and tapping N times will output the N'th action.\n  ;;\n  ;; The first parameter is a timeout. Tapping the same tap-dance key again\n  ;; within the timeout will reset the timeout and advance the tap-dance to the\n  ;; next key.\n  ;;\n  ;; The action activates either when any of the following happens:\n  ;; - the timeout expires\n  ;; - the tap sequence reaches the end\n  ;; - a different key is pressed\n  td (tap-dance 200 (a b c d spc))\n\n  ;; There is a variant of tap-dance — tap-dance-eager — that will activate\n  ;; every action tapped in the sequence rather than a single one. The example\n  ;; below is rather simple and behaves similarly to the original tap-dance.\n  td2 (tap-dance-eager 500 (\n    (macro a) ;; use macro to prevent auto-repeat of the key\n    (macro bspc b b)\n    (macro bspc bspc c c c)\n  ))\n\n  ;; arbitrary-code allows sending an arbitrary number as an OS code. This is\n  ;; not cross platform! This can be useful for testing keys that are not yet\n  ;; named or mapped in kanata. Please contribute findings with names and/order\n  ;; mappings, either in a GitHub issue or as a pull request! This is currently\n  ;; not supported with Windows using the interception driver.\n  ab1 (arbitrary-code 700)\n)\n\n(defalias\n  ;; caps-word will add an lsft to the active key list for all alphanumeric keys\n  ;; a-z, and the US layout minus key; meaning it will be converted to an\n  ;; underscore.\n  ;;\n  ;; The caps-word state will also be cleared if any key that doesn't get auto-\n  ;; capitalized and also doesn't belong in this list is pressed:\n  ;; - 0-9\n  ;; - kp0-kp9\n  ;; - bspc, del\n  ;; - up, down, left, rght\n  ;;\n  ;; The single parameter is a timeout in milliseconds after which the caps-word\n  ;; state will be cleared and lsft will not be added anymore. The timer is reset\n  ;; any time a capitalizable or extra non-terminating key is active.\n  cw (caps-word 2000)\n\n  ;; Like caps-word, but you get to choose the key lists where lsft gets added.\n  ;; This example is similar to the default caps-word behaviour but it moves the\n  ;; 0-9 keys to capitalized key list from the extra non-terminating key list.\n  cwc (caps-word-custom\n    2000\n    (a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9)\n    (kp0 kp1 kp2 kp3 kp4 kp5 kp6 kp7 kp8 kp9 bspc del up down left rght)\n  )\n)\n\n;; -toggle variants of caps-word will terminate caps-word on repress if it is\n;; currently active, otherwise caps-word will be activated.\n(defalias\n  cwt (caps-word-toggle 2000)\n  cct (caps-word-custom-toggle\n    2000\n    (a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9)\n    (kp0 kp1 kp2 kp3 kp4 kp5 kp6 kp7 kp8 kp9 bspc del up down left rght)\n  )\n)\n\n;; Can see a new action `rpt` in this layer. This repeats the most recently\n;; pressed key. Holding down the `rpt` key will not repeatedly send the key.\n;; The intended use case is to be able to use a different finger to repeat a\n;; double letter, as opposed to double-tapping a letter.\n;;\n;; The `rpt` action only repeats the last key output. For example, it won't\n;; output a chord like `ctrl+c` if the previous key pressed was `C-c` - it\n;; will only output `c`. There is a variant `rpt-any` which will repeat the\n;; previous action and would work for that use case.\n(deflayer misc\n  _    _    _    _    _    _    _    _    _    @é   @è   _    ì #|random custom key for testing|#   _\n  _    _    @ab1 _    _    _    ins  @{   @}   [    ]    _    _    +\n  @cw  _    _    _    C-u  _    del  bspc esc  ret  _    _    _\n  @cwc C-z  C-x  C-c  C-v  _    _    _    @td  @os1 @os2 @os3\n  rpt rpt-any _            _              _    _    _\n)\n\n\n(deflayer chords      ;; you can put list actions directly in deflayer but it's ugly, so prefer aliases.\n  _    _    _    _    _    _    _    _    _    _    @🙁  (unicode 😀) _    _\n  _    _    _    _    _    _    _    _    @csc @hpy @lch @tbm         _    _\n  _    @alp _    _    _    _    _    @ch1 @ch2 @ch4 @ch8 _            _\n  _    _    _    _    _    _    _    _    _    @csv _    _\n  _    _    _              _              _    _    _\n)\n\n(deflayer arrows\n  _    f1   f2   f3   f4   f5   f6   f7   f8   f9   f10  f11  f12  _\n  _    _    _    _    _    _    _    pgup up   pgdn _    _    _    _\n  _    _    _    _    _    _    home left down rght end  _    _\n  _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _              _              _    _    _\n)\n\n;; In Windows, using mouse buttons on the kanata window seems to cause it to hang.\n;; Using the mouse on other windows seems to be fine though.\n;;\n;; The mouse buttons can be clicked using mlft, mrgt, mmid, mfwd and mbck, representing the\n;; left, right, middle, forward and backward mouse buttons respectively. If the key is held, the\n;; button press will also be held.\n;;\n;; If there are multiple mouse click actions within a single multi action, e.g.\n;; (multi mrgt mlft), then all the buttons except the last will be clicked then\n;; unclicked. The last button will remain held until key release. In the example\n;; given, the button sequence will be:\n;; press key->click right->unclick right->click left->release key->release left\n;;\n;; There are variants of the standard mouse buttons which \"tap\" the button.\n;; These are mltp, mrtp, and mmtp. Rather than holding until key release, this\n;; action will click and unclick the button once the key is pressed. Nothing\n;; happens on key release. The action (multi lctl mltp) will result in the\n;; sequence below:\n;; press key->press lctl->click left->unclick left->release key->release lctl\n;;\n;; One can also see mouse movement actions at the lower right side, with the\n;; arrow unicode characters.\n(deflayer mouse\n  _    @mwu @mwd @mwl @mwr _    _    _    _    _    @ma↑ _    _    _\n  _    pgup bck  _    fwd  _    _    _    _    @ma← @ma↓ @ma→ _    _\n  _    pgdn mlft _    mrgt mmid _    mbck mfwd _    @ms↑ _    _\n  @fms _    mltp _    mrtp mmtp _    mbtp mftp @ms← @ms↓ @ms→\n  _    _    _              _              _    _    _\n)\n\n(defalias\n  ;; Mouse wheel actions. The first number is the interval in milliseconds\n  ;; between scroll actions. The second number is the distance in some arbitrary\n  ;; unit. Play with the parameters to see what feels correct. Both numbers\n  ;; must be in the range 1-65535\n  ;;\n  ;; In both Windows and Linux, 120 distance units is equivalent to a single\n  ;; notch movement on a physical wheel. In Linux, not all desktop environments\n  ;; support the REL_WHEEL_HI_RES event so if you experience issues with `mwheel`\n  ;; actions in Linux, using a distance value that is multiple of 120 may help.\n  mwu (mwheel-up 50 120)\n  mwd (mwheel-down 50 120)\n\n  ;; Horizontal mouse wheel actions. Similar story to vertical mouse wheel.\n  mwl (mwheel-left 50 120)\n  mwr (mwheel-right 50 120)\n\n  ;; There are similar wheel actions with `-accel` that have\n  ;; accelerating/inertial scrolling.\n  ;; The parameters are:\n  ;; 1. initial velocity\n  ;; 2. maximum velocity\n  ;; 3. acceleration multiplier\n  ;; 4. deceleration multiplier\n  ;; The units are arbitrary.\n  ;; The author finds the values in the example below\n  ;; to be a decent-feeling starting paint.\n  ;; Experiment to find your preference.\n  mau (mwheel-accel-up   3 1200 1.15 0.93)\n  mad (mwheel-accel-down 3 1200 1.15 0.93)\n\n  ;; Mouse movement actions.The first number is the interval in milliseconds\n  ;; between mouse actions. The second number is the distance traveled per interval\n  ;; in pixels.\n\n  ms↑ (movemouse-up 1 1)\n  ms← (movemouse-left 1 1)\n  ms↓ (movemouse-down 1 1)\n  ms→ (movemouse-right 1 1)\n\n  ;; Mouse movement actions with linear acceleration. The first number is the\n  ;; interval in milliseconds between mouse actions. The second number is the time\n  ;; in milliseconds for the distance to linearly ramp up from the minimum distance\n  ;; to the maximum distance. The third number is the minimum distance traveled\n  ;; per interval in pixels. The fourth number is the maximum distance traveled\n  ;; per interval in pixels.\n\n  ma↑ (movemouse-accel-up 1 1000 1 5)\n  ma← (movemouse-accel-left 1 1000 1 5)\n  ma↓ (movemouse-accel-down 1 1000 1 5)\n  ma→ (movemouse-accel-right 1 1000 1 5)\n\n  ;; setmouse places the cursor at a specific pixel x-y position. This\n  ;; example puts it in the middle of the screen. The coordinates go from 0,0\n  ;; which is the upper-left corner of the screen to 65535,65535 which is the\n  ;; lower-right corner of the screen. If you have multiple monitors, they are\n  ;; treated as one giant screen, which may make it a bit confusing for how to\n  ;; set up the pixels. You will need to experiment.\n  sm (setmouse 32228 32228)\n\n  ;; movemouse-speed takes a percentage by which it then scales all of the\n  ;; mouse movements while held. You can have as many of these active at a\n  ;; given time as you would like, but be warned that some values, such as 33\n  ;; may not have correct pixel distance representations.\n  fms (movemouse-speed 200)\n)\n\n(defalias\n  lft (multi (release-key ralt) left) ;; release ralt if held and also press left\n  rgt (multi (release-key ralt) rght) ;; release ralt if held and also press rght\n  rlr (release-layer ralted)          ;; release layer-toggle of ralted\n)\n\n;; It's not clear what the practical use case is for the @rlr alias, but the\n;; combination of @ralt on the dvorak layer and this layer with @lft and @rgt\n;; results in the physical ralt key behaving mostly as ralt, **except** for\n;; holding it **then** pressing specific keys. These specific keys release the\n;; ralt because it would cause them to have undesired behaviour without the\n;; release.\n;;\n;; E.g. ralt+@lft will result in only left being pressed instead of ralt+left,\n;; while ralt(hold)+tab+tab+tab still works as intended.\n(deflayer ralted\n  _    _    _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _    _    _    _    _    @lft @rlr @rgt _    _    _\n  _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _              _              _    _    _\n)\n\n;; Virtual key actions\n\n(defvirtualkeys\n  ;; Define some virtual keys that perform modifier actions\n  vkctl lctl\n  vksft lsft\n  vkmet lmet\n  vkalt lalt\n\n  ;; A virtual key that toggles all modifier virtual keys above\n  vktal (multi\n        (on-press toggle-virtualkey vkctl)\n        (on-press toggle-virtualkey vksft)\n        (on-press toggle-virtualkey vkmet)\n        (on-press toggle-virtualkey vkalt)\n      )\n\n  ;; Virtual key that activates a macro\n  vkmacro (macro h e l l o spc w o r l d)\n)\n\n(defalias\n  psfvk (on-press press-virtualkey   vksft)\n  rsfvk (on-press release-virtualkey vksft)\n\n  palvk (on-press tap-vkey vktal)\n  macvk (on-press tap-vkey vkmacro)\n\n  isfvk (on-idle 1000 tap-vkey vksft)\n  pisfvk (on-physical-idle 1000 tap-vkey vksft)\n)\n\n;; Press and release fake keys.\n;;\n;; Fake keys can't be pressed by any physical keyboard buttons and can only be\n;; acted upon by the actions:\n;; - on-press-fakekey\n;; - on-release-fakekey\n;; - on-idle-fakekey\n;;\n;; One use case of fake keys is for holding modifier keys\n;; for any number of keypresses and then releasing the modifiers when desired.\n;;\n;; The actions associated with fake keys in deffakekeys are parsed before\n;; aliases, so you can't use aliases within deffakekeys. Other than the lack\n;; of alias support, fake keys can do any action that a normal key can,\n;; including doing operations on previously defined fake keys.\n;;\n;; Operations on fake keys can occur either on press (on-press-fakekey),\n;; on release (on-release-fakekey), or on idle for a specified time\n;; (on-idle-fakekey).\n;;\n;; Fake keys are flexible in usage but can be obscure to discover how they\n;; can be useful to you.\n(deflayer fakekeys\n  _    @fcp @fsp @fmp @pal _    _    _    _    _    _    _    _    _\n  _    @fcr @fsr @fap @ral _    _    _    _    _    _    _    _    _\n  _    @fct @fst @rma _    _    _    _    _    _    _    _    _\n  _    @t1  _    _    _    _    _    _    _    _    _    _\n  _    _    _              _              _    _    _\n)\n\n(deffakekeys\n  ctl lctl\n  sft lsft\n  lsft lsft\n  met lmet\n  alt lalt\n  mmid mmid\n  pal (multi\n        (on-press-fakekey ctl press)\n        (on-press-fakekey sft press)\n        (on-press-fakekey met press)\n        (on-press-fakekey alt press)\n      )\n  ral (multi\n        (on-press-fakekey ctl release)\n        (on-press-fakekey sft release)\n        (on-press-fakekey met release)\n        (on-press-fakekey alt release)\n      )\n)\n\n(defalias\n  fcp (on-press-fakekey ctl press)\n  fcr (on-press-fakekey ctl release)\n  fct (on-press-fakekey ctl tap)\n  fsp (on-release-fakekey sft press)\n  fsr (on-release-fakekey sft release)\n  fst (on-release-fakekey sft tap)\n  fsg (on-release-fakekey sft toggle)\n  fmp (on-press-fakekey met press)\n  fap (on-press-fakekey alt press)\n  rma (multi\n        (on-press-fakekey met release)\n        (on-press-fakekey alt release)\n      )\n  pal (on-press-fakekey pal tap)\n  ral (on-press-fakekey ral tap)\n  rdl (on-idle-fakekey ral tap 1000)\n  hfd (hold-for-duration 1000 met)\n\n  ;; Test of on-press-fakekey and on-release-fakekey in a macro\n  t1 (macro-release-cancel @fsp 5 a b c @fsr 5 c b a)\n\n  ;; If you find that an application isn't registering keypresses correctly\n  ;; with multi, you can try out:\n  ;; - on-press-fakekey-delay\n  ;; - on-release-fakekey-delay\n  ;;\n  ;; Do note that processing a fakekey-delay and even a sequence of delays will\n  ;; delay any other inputs from being processed until the fakekey-delays are\n  ;; all complete, so use with care.\n  stm (multi ;; Shift -> middle mouse with a delay\n    (on-press-fakekey lsft press)\n    (on-press-fakekey-delay 200)\n    (on-press-fakekey mmid press)\n    (on-release-fakekey mmid release)\n    (on-release-fakekey-delay 200)\n    (on-release-fakekey lsft release)\n  )\n)\n\n;; Vim-style leader-key sequences. Activate a fakekey-tap by pressing a \"leader\"\n;; key and then a sequence of characters.\n;; See: https://github.com/jtroo/kanata/issues/97\n;;\n;; You can add an entry to defcfg to change the sequence timeout (default is 1000):\n;;     sequence-timeout <number(ms)>\n;;\n;; If you want multiple timeouts with different leaders, you can also activate the\n;; sequence action:\n;;     (sequence <timeout>)\n;; This acts like `sldr` but uses a different timeout.\n;;\n;; There is also an option to customize the key sequence input mode. Its default\n;; value when not configured is `hidden-suppressed`.\n;;\n;; The options are:\n;;\n;; - `visible-backspaced`: types sequence characters as they are inputted. The\n;;   typed characters will be erased with backspaces for a valid sequence termination.\n;; - `hidden-suppressed`: hides sequence characters as they are typed. Does not\n;;   output the hidden characters for an invalid sequence termination.\n;; - `hidden-delay-type`: hides sequence characters as they are typed. Outputs the\n;;   hidden characters for an invalid sequence termination either after either a\n;;   timeout or after a non-sequence key is typed.\n;;\n;; For `visible-backspaced` and `hidden-delay-type`, a sequence leader input will\n;; be ignored if a sequence is already active. For historical reasons, and in case\n;; it is desired behaviour, a sequence leader input using `hidden-suppressed` will\n;; reset the key sequence.\n;;\n;; Example:\n;;     sequence-input-mode visible-backspaced\n(defseq git-status (g s t))\n(deffakekeys git-status (macro g i t spc s t a t u s))\n(defalias rcl (tap-hold-release 200 200 sldr rctl))\n\n(defseq\n    dotcom (. S-3)\n    dotorg (. S-4)\n)\n(deffakekeys\n    dotcom (macro . c o m)\n    dotorg (macro . o r g)\n)\n;; Enter sequence mode and input .\n(defalias dot-sequence (macro (sequence 250) 10 .))\n(defalias dot-sequence-inputmode (macro (sequence 250 hidden-delay-type) 10 .))\n\n;; There are special keys that you can assign in your actions which will\n;; never output events to your operating system, but which you can use\n;; in sequences. They are named: nop0-nop9.\n(defseq\n    dotcom (nop0 nop1)\n    dotorg (nop8 nop9)\n)\n\n;; A key list within O-(...) signifies simultaneous presses.\n(defseq\n    dotcom (O-(. c m))\n    dotorg (O-(. r g))\n)\n\n;; sequence-noerase\n;;\n;; When you have a keyboard locale that uses dead keys,\n;; you may be pressing two keys that only actually output one symbol.\n;; By default, when visible-backspaced does the backtracking backspace,\n;; it backspaces according to input count.\n;; With dead keys, this may result in too many backspaces.\n;;\n;; The sequence-noerase action is a no-output action\n;; that tells the sequences action to have one fewer backspace\n;; when backtracking with visible-backspaced.\n\n(deflayermap (base)\n  0 sldr\n  u (t! maybe-noerase u)\n)\n(deftemplate maybe-noerase (char)\n  (multi\n    (switch\n      ((key-history ' 1)) (sequence-noerase 1) fallthrough\n      () $char break\n   ))\n)\n(defvirtualkeys seq-output-1 (macro a b c d e f g))\n(defseq seq-output-1 (' u))\n\n;; Input chording.\n;;\n;; Not to be confused with output chords (like C-S-a or the chords layer\n;; defined above), these allow you to perform actions when a combination of\n;; input keys (a \"chord\") are pressed all at once (order does not matter).\n;; Each combination/chord can perform a different action, allowing you to bind\n;; up to `2^n - 1` different actions to just `n` keys.\n;;\n;; Each `defchords` defines a named group of such chord-action pairs.\n;; The 500 is a timeout after which a chord triggers if it isn't triggered by a\n;; key release or press of a non-chord key before the timeout expires.\n;; If a chord is not defined, no action will occur when it is triggered but the\n;; keys used to input it will be consumed regardless.\n;;\n;; Each pair consists of the keys that make up a given chord in the parenthesis\n;; followed by the action that should be executed when the given chord is\n;; pressed.\n;; Note that these keys do not directly correspond to real keys and are merely\n;; arbitrary labels that make sense within the context of the chord.\n;; They are mapped to real keys in layers by configuring the key in the layer to\n;; map to a `(chord name key)` action (like those in the `defalias` below) where\n;; `name` is the name of the chords group (here `binary`) and `key` is one of the\n;; arbitrary labels of the keys in a chord (here `1`, `2`, `4` and `8`).\n;;\n;; Note that it is perfectly valid to nest these `chord` actions that enter\n;; \"chording mode\" within other actions like `tap-dance` and that will work as\n;; one would expect.\n;; However, this only applies to the first key used to enter \"chording mode\".\n;; Once \"chording mode\" is active, all other keys will be directly handled by\n;; \"chording mode\" with no regard for wrapper actions; e.g. if a key is pressed\n;; and it maps to a tap-hold with a chord as the hold action within, that chord\n;; key will immediately activate instead of the key needing to be held for the\n;; timeout period.\n;;\n;; The action executed by a chord (the right side of the chord-action pairs) may\n;; be any regular or advanced action, including aliases. They currently cannot\n;; however contain a `chord` action.\n(defchords binary 500\n  (1      ) 1\n  (  2    ) 2\n  (1 2    ) 3\n  (    4  ) 4\n  (1   4  ) 5\n  (  2 4  ) 6\n  (1 2 4  ) 7\n  (      8) 8\n  (1     8) 9\n  (  2   8) (multi 1 0)\n  (1 2   8) (multi 1 1)\n  (    4 8) (multi 1 2)\n  (1   4 8) (multi 1 3)\n  (  2 4 8) (multi 1 4)\n  (1 2 4 8) (multi 1 5)\n)\n\n(defalias\n  ch1 (chord binary 1)\n  ch2 (chord binary 2)\n  ch4 (chord binary 4)\n  ch8 (chord binary 8)\n)\n\n;; The top-level action `include` will read a configuration from a new file.\n;; At the time of writing, includes can only be placed at the top level. The\n;; included files also cannot contain includes themselves.\n;;\n;; (include included-file.kbd)\n\n\n;; The top-level item `deftemplate` declares a template\n;; which can be expanded multiple times to reduce repetition.\n;;\n;; Expansion of a template is done via `expand-template`.\n\n;; This template defines a chord group and aliases that use the chord group.\n;; The purpose is to easily define the same chord position behaviour\n;; for multiple layers that have different underlying keys.\n(deftemplate left-hand-chords (chordgroupname k1 k2 k3 k4 alias1 alias2 alias3 alias4)\n  (defalias\n    $alias1 (chord $chordgroupname $k1)\n    $alias2 (chord $chordgroupname $k2)\n    $alias3 (chord $chordgroupname $k3)\n    $alias4 (chord $chordgroupname $k4)\n  )\n  (defchords $chordgroupname $chord-timeout\n    ($k1) $k1\n    ($k2) $k2\n    ($k3) $k3\n    ($k4) $k4\n    ($k1 $k2) lctl\n    ($k3 $k4) lsft\n  )\n)\n\n(defvar chord-timeout 200)\n\n(template-expand left-hand-chords qwerty a s d f qwa qws qwd qwf)\n;; You can use t! as a short form of template-expand\n(t! left-hand-chords dvorak a o e u dva dvo dve dvu)\n\n(deflayer template-example\n  _    _    _    _    _    _    _    _    _    _    _    _    _    _\n  _    @qwa @qws @qwd @qwf _    _    _    _    _    _    _    _    _\n  _    @dva @dvo @dve @dvu _    _    _    _    _    _    _    _\n  _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _              _              _    _    _\n)\n\n;; Within a deftemplate you can use if-equal to conditionally insert content\n;; into the template.\n\n(deftemplate home-row (version)\n  a s d f g h\n  (if-equal $version v1 j)\n  (if-equal $version v2 (tap-hold 200 200 j lctl))\n   k l ; '\n)\n\n(deftemplate common-overrides ()\n  (lctl 7) (lctl lsft tab)\n  (lctl 9) (lctl tab)\n  (lalt 7) (lalt lsft tab)\n  (lalt 9) (lalt tab)\n)\n\n;; Wrapping a top-level configuration item in a list beginning with\n;; (platform (applicable-platforms...) ...configuration...)\n;; will make the configuration only active on a specific platform.\n(platform (macos)\n  ;; Only on macos, use command arrows to jump/delete words\n  ;; because command is used for so many other things\n  ;; and it's weird that these cases use alt.\n  (defoverrides\n    (lmet bspc)  (lalt bspc)\n    (lmet left)  (lalt left)\n    (lmet right) (lalt right)\n    (template-expand common-overrides)\n  )\n)\n(platform (win winiov2 wintercept linux)\n  (defoverrides\n    (template-expand common-overrides)\n  )\n)\n\n#|\nThere is a more recent version of defoverrides that offers more customizability.\nInstead of 2 list items per override entry,\n`defoverridesv2` mandates 4, though the extra 2 can be empty.\n\nYou cannot have both a v1 and v2 of `defoverrides` at the same time.\n\nThe 3rd item is an \"exclude modifiers list\" which is composed of modifier key names\n(such as `lctl`, `lalt`) that, if held, will disable the override from activating.\n\nThe 4th item is an \"exclude layers\" list which is composed of layer names\nthat while active as the most recent `layer-switch` or `layer-while-held`,\nwill disable the override from activating.\n\n(defoverridesv2\n  ;; lctl+a will become lalt+9\n  ;; except when lsft is held or other-layer is active.\n  (lctl a) (lalt 9) (lsft) (other-layer))\n\n  ;; lctl+b will always become lalt+0\n  (lctl b) (lalt 0) () ()\n)\n|#\n\n#|\n\nGlobal input chords.\n\nSyntax (5-tuples):\n\n    (defchordsv2\n      (participating-keys1) action1 timeout1 release-behaviour1 (disabled-layers1)\n        ...\n      (participating-keysN) actionN timeoutN release-behaviourN (disabled-layersN)\n    )\n\n|#\n\n(defchordsv2\n  (a b c)   (macro a l p h a b e t)  200 all-released  (qwerty arrows)\n  (h l o)   (macro h e l l o)        250 first-release (qwerty arrows)\n  (g b y e) (macro g o o d b y e)    400 first-release (qwerty arrows)\n)\n\n#|\n\nYet another chording implementation - zippychord:\n\n\n;; This is a sample for US international layout.\n(defzippy\n  zippy.txt\n  on-first-press-chord-deadline 500\n  idle-reactivate-time          500\n  smart-space-punctuation (? ! . , ; :)\n  output-character-mappings (\n    ! S-1\n    ? S-/\n    % S-5\n    \"(\" S-9\n    \")\" S-0\n    : S-;\n    < S-,\n    > S-.\n    r#\"\"\"# S-'\n    | S-\\\n    _ S--\n    ® AG-r\n    ;; In case you use dead keys or compose keys\n    ;; where multiple keys are pressed\n    ;; to produce a single backspaceable symbol,\n    ;; use no-erase or single-output\n    ’ (no-erase `)\n    é (single-output ' e)\n  )\n)\n\nExample file content of zippy.txt:\n---\ndy\tday\ndy 1\tMonday\n abc\tAlphabet\nr df\trecipient\n w  a\tWashington\nrq\trequest\nrqa\trequest assistance\n---\n\nYou can read about zippychord in more detail in the configuration guide.\n\n|#\n\n#|\n\nClipboard actions allow you to manipulate the clipboard.\nTo paste, you should manually output C-v,\nor whatever key output is necessary to paste.\nE.g. S-ins might also work.\n\n|#\n\n(deflayermap (clip)\n a (clipboard-set       clip)\n b (clipboard-save      0)\n c (clipboard-restore   0)\n d (clipboard-save-swap 0 65535)\n #| actions with cmd only works with the compilation flags and defcfg enablement.\n e (clipboard-cmd-set powershell.exe -c \"echo 'hello world'\")\n f (clipboard-save-cmd-set 0 bash -c \"echo 'goodbye'\")\n |#\n)\n"
  },
  {
    "path": "cfg_samples/key-toggle_press-only_release-only.kbd",
    "content": "#|\n\nThis configuration showcases all of:\n\t- key toggle\n\t- press-only\n\t- release-only\n\n|#\n\n(deftemplate toggle-key (vkey-name output-key alias)\n\t(defvirtualkeys $vkey-name $output-key)\n\t(defalias $alias (on-press toggle-vkey $vkey-name))\n)\n\n(deftemplate press-only-release-only-pair\n\t\t(vkey-name output-key press-alias release-alias)\n\t(defvirtualkeys $vkey-name $output-key)\n\t(defalias $press-alias (on-press press-vkey $vkey-name))\n\t(defalias $release-alias (on-press release-vkey $vkey-name))\n)\n\n(template-expand toggle-key v-lctl lctl lcl)\n(template-expand toggle-key v-rctl rctl rcl)\n\n;; t! is a short form of template-expand\n(t! press-only-release-only-pair v-lalt lalt p-a r-a)\n\n(defsrc\n\tlctl rctl lalt ralt\n)\n\n(deflayer base\n\t@lcl @rcl @p-a @r-a\n)\n"
  },
  {
    "path": "cfg_samples/minimal.kbd",
    "content": "#|\nThis minimal config changes Caps Lock to act as Caps Lock on quick tap, but\nif held, it will act as Left Ctrl. It also changes the backtick/grave key to\nact as backtick/grave on quick tap, but change ijkl keys to arrow keys on hold.\n\nThis text between the two pipe+octothorpe sequences is a multi-line comment.\n|#\n\n;; Text after double-semicolons are single-line comments.\n\n#|\nOne defcfg entry may be added, which is used for configuration key-pairs. These\nconfigurations change kanata's behaviour at a more global level than the other\nconfiguration entries.\n|#\n\n(defcfg\n  #|\n  This configuration will process all keys pressed inside of kanata, even if\n  they are not mapped in defsrc. This is so that certain actions can activate\n  at the right time for certain input sequences. By default, unmapped keys are\n  not processed through kanata due to a Windows issue related to AltGr. If you\n  use AltGr in your keyboard, you will likely want to follow the simple.kbd\n  file while unmapping lctl and ralt from defsrc.\n  |#\n  process-unmapped-keys yes\n)\n\n(defsrc\n  caps grv         i\n              j    k    l\n  lsft rsft\n)\n\n(deflayer default\n  @cap @grv        _\n              _    _    _\n  _    _\n)\n\n(deflayer arrows\n  _    _           up\n              left down rght\n  _    _\n)\n\n(defalias\n  cap (tap-hold-press 200 200 caps lctl)\n  grv (tap-hold-press 200 200 grv (layer-toggle arrows))\n)\n"
  },
  {
    "path": "cfg_samples/opposite-hand-hrm.kbd",
    "content": ";; Home row mods using tap-hold-opposite-hand\n;;\n;; Hold activates only when the next key is on the opposite hand,\n;; which substantially reduces misfires during fast same-hand rolls.\n;; Same-hand keys resolve as tap by default.\n;;\n;; Compare with home-row-mod-basic.kbd which uses plain tap-hold.\n\n(defcfg\n  process-unmapped-keys yes\n)\n\n;; Assign physical keys to hands. Keys not listed have no hand\n;; assignment and are governed by (unknown-hand <value>) (default: ignore).\n(defhands\n  (left  q w e r t a s d f g z x c v b)\n  (right y u i o p h j k l ; n m , . /))\n\n(defsrc\n  q   w   e   r   t   y   u   i   o   p\n  a   s   d   f   g   h   j   k   l   ;\n  z   x   c   v   b   n   m   ,   .   /\n                  spc\n)\n\n(defvar\n  tap-time 200\n  hold-time 180\n)\n\n(defalias\n  a (tap-hold-opposite-hand $hold-time a lmet)\n  s (tap-hold-opposite-hand $hold-time s lalt)\n  d (tap-hold-opposite-hand $hold-time d lctl)\n  f (tap-hold-opposite-hand $hold-time f lsft)\n  j (tap-hold-opposite-hand $hold-time j rsft)\n  k (tap-hold-opposite-hand $hold-time k rctl)\n  l (tap-hold-opposite-hand $hold-time l ralt)\n  ; (tap-hold-opposite-hand $hold-time ; rmet)\n)\n\n(deflayer base\n  q   w   e   r   t   y   u   i   o   p\n  @a  @s  @d  @f  g   h   @j  @k  @l  @;\n  z   x   c   v   b   n   m   ,   .   /\n                  spc\n)\n"
  },
  {
    "path": "cfg_samples/push-msg.kbd",
    "content": ";; push-msg Sample Configuration\n;;\n;; This configuration demonstrates the push-msg action for sending\n;; messages to external tools via Kanata's TCP server.\n;;\n;; To use this config:\n;;   kanata -p 7070 -c push-msg.kbd\n;;\n;; Then connect a TCP client to localhost:7070 to receive messages.\n\n(defcfg\n  process-unmapped-keys yes\n)\n\n(defsrc\n  caps a s d f g h j k l ; '\n  z x c v b n m , . /\n)\n\n;; ==========================================================================\n;; Layer Definitions\n;; ==========================================================================\n\n(deflayer base\n  @caps a s d f g h j k l ; '\n  z x c v b n m , . /\n)\n\n(deflayer nav\n  @caps left down up right g h j k l ; '\n  z x c v b n m , . /\n)\n\n;; ==========================================================================\n;; Aliases with push-msg\n;; ==========================================================================\n\n(defalias\n  ;; Caps Lock: tap for Esc (with notification), hold for nav layer (with notification)\n  caps (tap-hold 200 200\n    (multi esc (push-msg \"layer:base\"))\n    (multi (layer-toggle nav) (push-msg \"layer:nav:hold\"))\n  )\n)\n\n;; ==========================================================================\n;; Virtual Keys - Triggerable via TCP ActOnFakeKey\n;; ==========================================================================\n;;\n;; External tools can trigger these using TCP commands:\n;;   {\"ActOnFakeKey\":{\"name\":\"email-sig\",\"action\":\"Tap\"}}\n;;   {\"ActOnFakeKey\":{\"name\":\"switch-nav\",\"action\":\"Tap\"}}\n\n(defvirtualkeys\n  ;; Text expansion macro (S-x for shift+x to get capitals)\n  email-sig (macro\n    S-b e s t spc r e g a r d s , ret ret\n    S-j o h n spc S-d o e\n  )\n\n  ;; Layer switches that also push messages\n  switch-nav (multi\n    (layer-switch nav)\n    (push-msg \"layer:nav:activated\")\n  )\n\n  switch-base (multi\n    (layer-switch base)\n    (push-msg \"layer:base:activated\")\n  )\n\n  ;; Notify external tools (no keyboard action)\n  notify-ready (push-msg \"status:ready\")\n  notify-busy (push-msg \"status:busy\")\n)\n\n;; ==========================================================================\n;; Usage Examples for External Tools\n;; ==========================================================================\n;;\n;; Python TCP client example:\n;;\n;;   import socket\n;;   import json\n;;\n;;   sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n;;   sock.connect(('127.0.0.1', 7070))\n;;   buffer = \"\"\n;;\n;;   while True:\n;;       data = sock.recv(4096).decode()\n;;       buffer += data\n;;       while '\\n' in buffer:\n;;           line, buffer = buffer.split('\\n', 1)\n;;           if line:\n;;               msg = json.loads(line)\n;;               if 'MessagePush' in msg:\n;;                   print(f\"Received: {msg['MessagePush']['message']}\")\n;;               elif 'LayerChange' in msg:\n;;                   print(f\"Layer: {msg['LayerChange']['new']}\")\n;;\n;; Triggering virtual keys from external tools:\n;;\n;;   # Send this JSON to trigger the email-sig virtual key:\n;;   echo '{\"ActOnFakeKey\":{\"name\":\"email-sig\",\"action\":\"Tap\"}}' | nc localhost 7070\n;;\n;;   # Or trigger layer switch:\n;;   echo '{\"ActOnFakeKey\":{\"name\":\"switch-nav\",\"action\":\"Tap\"}}' | nc localhost 7070\n;;\n;; ==========================================================================\n"
  },
  {
    "path": "cfg_samples/simple.kbd",
    "content": ";; Comments are prefixed by double-semicolon. A single semicolon is parsed as the\n;; keyboard key. Comments are ignored for the configuration file.\n;;\n;; This configuration language is Lisp-like. If you're unfamiliar with Lisp,\n;; don't be alarmed. The maintainer jtroo is also unfamiliar with Lisp. You\n;; don't need to know Lisp in-depth to be able to configure kanata.\n;;\n;; If you follow along with the examples, you should be fine. Kanata should\n;; also hopefully have helpful error messages in case something goes wrong.\n;; If you need help, you are welcome to ask.\n\n;; Only one defsrc is allowed.\n;;\n;; defsrc defines the keys that will be intercepted by kanata. The order of the\n;; keys matches with deflayer declarations and all deflayer declarations must\n;; have the same number of keys as defsrc. Any keys not listed in defsrc will\n;; be passed straight to the operating system.\n(defsrc\n  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n  caps a    s    d    f    g    h    j    k    l    ;    '    ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n\n;; The first layer defined is the layer that will be active by default when\n;; kanata starts up. This layer is the standard QWERTY layout except for the\n;; backtick/grave key (@grl) which is an alias for a tap-hold key.\n(deflayer qwerty\n  @grl 1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n  caps a    s    d    f    g    h    j    k    l    ;    '    ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n\n;; The dvorak layer remaps the keys to the dvorak layout. In addition there is\n;; another tap-hold key: @cap. This key retains caps lock functionality when\n;; quickly tapped but is read as left-control when held.\n(deflayer dvorak\n  @grl 1    2    3    4    5    6    7    8    9    0    [    ]    bspc\n  tab  '    ,    .    p    y    f    g    c    r    l    /    =    \\\n  @cap a    o    e    u    i    d    h    t    n    s    -    ret\n  lsft ;    q    j    k    x    b    m    w    v    z    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n\n;; defalias is used to declare a shortcut for a more complicated action to keep\n;; the deflayer declarations clean and aligned. The alignment in deflayers is not\n;; necessary, but is strongly recommended for ease of understanding visually.\n;;\n;; Aliases are referred to by `@<alias_name>`.\n(defalias\n  ;; tap: backtick (grave), hold: toggle layer-switching layer while held\n  grl (tap-hold 200 200 grv (layer-toggle layers))\n\n  ;; layer-switch changes the base layer.\n  dvk (layer-switch dvorak)\n  qwr (layer-switch qwerty)\n\n  ;; tap for capslk, hold for lctl\n  cap (tap-hold 200 200 caps lctl)\n)\n\n;; The `lrld` action stands for \"live reload\". This will re-parse everything\n;; except for linux-dev, meaning you cannot live reload and switch keyboard\n;; devices.\n;;\n;; The keys 1 and 2 switch the base layer to qwerty and dvorak respectively.\n;;\n;; Apart from the layer switching and live reload, all other keys are the\n;; underscore _ which means \"transparent\". Transparent means the base layer\n;; behaviour is used when pressing that key.\n(deflayer layers\n  _    @qwr @dvk lrld _    _    _    _    _    _    _    _    _    _\n  _    _    _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _              _              _    _    _\n)\n"
  },
  {
    "path": "cfg_samples/tray-icon/license_icons.txt",
    "content": "BSD 2-Clause License\n\nCopyright (c) 2024, Fred Vatin\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "cfg_samples/tray-icon/tray-icon.kbd",
    "content": "(defcfg\n  process-unmapped-keys\t\tyes\t;;|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.\n  log-layer-changes    \t\tyes\t;;|no| overhead\n  tray-icon \"./_custom-icons/s.png\" ;; should activate for layers without icons like '5no-icn'\n  ;;opt                   \tval  \t  |≝|\n  icon-match-layer-name   \tyes  \t;;|yes| match layer name to icon files even without an explicit (icon name.ico) config\n  tooltip-layer-changes   \tyes  \t;;|false|\n  tooltip-show-blank      \tyes  \t;;|no|\n  tooltip-duration        \t500  \t;;|500|\n  tooltip-size            \t24,24\t;;|24 24|\n  notify-cfg-reload       \tyes  \t;;|yes|\n  notify-cfg-reload-silent\tno   \t;;|no|\n  notify-error            \tyes  \t;;|yes|\n)\n(defalias l1 (layer-while-held 1emoji))\n(defalias l2 (layer-while-held 2icon-quote))\n(defalias l3 (layer-while-held 3emoji_alt))\n(defalias l4 (layer-while-held 4my-lmap))\n(defalias l5 (layer-while-held 5no-icn))\n(defalias l6 (layer-while-held 6name-match))\n\n(defsrc     \t            \t                            \t1  \t2  \t3  \t4  \t5  \t6)\n(deflayer   \t(⌂          \ticon base.png)              \t@l1\t@l2\t@l3\t@l4\t@l5\t@l6)\t;; find in the 'icon'  subfolder\n(deflayer   \t(1emoji     \t🖻 1symbols.png)             \tq  \tq  \tq  \tq  \tq  \tq)  \t;; find in the 'icons' subfolder\n(deflayer   \t(2icon-quote\t🖻 \"2Nav Num.png\")           \tw  \tw  \tw  \tw  \tw  \tw)  \t;; find in the 'img'   subfolder\n(deflayer   \t(3emoji_alt \t🖼 3trans.parent)            \te  \te  \te  \te  \te  \te)  \t;; find '.png'\n(deflayermap\t(4my-lmap   \t🖻 \"..\\..\\assets\\kanata.ico\")\t1 r\t2 r\t3 r\t4 r\t5 r\t6 r) ;; find in relative path\n(deflayer   \t5no-icn     \t                            \tt  \tt  \tt  \tt  \tt  \tt) ;; 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\n(deflayer   \t6name-match \t                            \ty  \ty  \ty  \ty  \ty  \ty) ;; uses '6name-match' with any valid extension since 'icon-match-layer-name' is set to 'yes'\n"
  },
  {
    "path": "docs/README.md",
    "content": "\n### Converting \".adoc\" to html\n\nTo generate html from the these documentation files, use [\"asciidoctor\"](https://asciidoctor.org)\n(they are not fully compatible with the separate \"asciidoc\" project)\n\n"
  },
  {
    "path": "docs/config-stylesheet.css",
    "content": "html{font-family:sans-serif;-webkit-text-size-adjust:100%}\na{background:none}\na:focus{outline:thin dotted}\na:active,a:hover{outline:0}\nh1{font-size:2em;margin:.67em 0}\nb,strong{font-weight:bold}\nabbr{font-size:.9em}\nabbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}\ndfn{font-style:italic}\nhr{height:0}\nmark{background:#ff0;color:#000}\ncode,kbd,pre,samp{font-family:monospace;font-size:1em}\npre{white-space:pre-wrap}\nq{quotes:\"\\201C\" \"\\201D\" \"\\2018\" \"\\2019\"}\nsmall{font-size:80%}\nsub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}\nsup{top:-.5em}\nsub{bottom:-.25em}\nimg{border:0}\nsvg:not(:root){overflow:hidden}\nfigure{margin:0}\naudio,video{display:inline-block}\naudio:not([controls]){display:none;height:0}\nfieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}\nlegend{border:0;padding:0}\nbutton,input,select,textarea{font-family:inherit;font-size:100%;margin:0}\nbutton,input{line-height:normal}\nbutton,select{text-transform:none}\nbutton,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}\nbutton[disabled],html input[disabled]{cursor:default}\ninput[type=checkbox],input[type=radio]{padding:0}\nbutton::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}\ntextarea{overflow:auto;vertical-align:top}\ntable{border-collapse:collapse;border-spacing:0}\n*,::before,::after{box-sizing:border-box}\nhtml,body{font-size:100%}\nbody{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:\"Noto Serif\",\"DejaVu Serif\",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}\na:hover{cursor:pointer}\nimg,object,embed{max-width:100%;height:auto}\nobject,embed{height:100%}\nimg{-ms-interpolation-mode:bicubic}\n.left{float:left!important}\n.right{float:right!important}\n.text-left{text-align:left!important}\n.text-right{text-align:right!important}\n.text-center{text-align:center!important}\n.text-justify{text-align:justify!important}\n.hide{display:none}\nimg,object,svg{display:inline-block;vertical-align:middle}\ntextarea{height:auto;min-height:50px}\nselect{width:100%}\n.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}\ndiv,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}\na{color:#2156a5;text-decoration:underline;line-height:inherit}\na:hover,a:focus{color:#1d4b8f}\na img{border:0}\np{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}\np aside{font-size:.875em;line-height:1.35;font-style:italic}\nh1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:\"Open Sans\",\"DejaVu Sans\",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}\nh1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}\nh1{font-size:2.125em}\nh2{font-size:1.6875em}\nh3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}\nh4,h5{font-size:1.125em}\nh6{font-size:1em}\nhr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}\nem,i{font-style:italic;line-height:inherit}\nstrong,b{font-weight:bold;line-height:inherit}\nsmall{font-size:60%;line-height:inherit}\ncode{font-family:\"Droid Sans Mono\",\"DejaVu Sans Mono\",monospace;font-weight:400;color:rgba(0,0,0,.9)}\nul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}\nul,ol{margin-left:1.5em}\nul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}\nul.circle{list-style-type:circle}\nul.disc{list-style-type:disc}\nul.square{list-style-type:square}\nul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}\nol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}\ndl dt{margin-bottom:.3125em;font-weight:bold}\ndl dd{margin-bottom:1.25em}\nblockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}\nblockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}\n@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}\nh1{font-size:2.75em}\nh2{font-size:2.3125em}\nh3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}\nh4{font-size:1.4375em}}\ntable{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}\ntable thead,table tfoot{background:#f7f8f7}\ntable thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}\ntable tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}\ntable tr.even,table tr.alt{background:#f8f8f7}\ntable thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}\nh1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}\nh1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}\n.center{margin-left:auto;margin-right:auto}\n.stretch{width:100%}\n.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:\" \";display:table}\n.clearfix::after,.float-group::after{clear:both}\n:not(pre).nobreak{word-wrap:normal}\n:not(pre).nowrap{white-space:nowrap}\n:not(pre).pre-wrap{white-space:pre-wrap}\n:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}\npre{color:rgba(0,0,0,.9);font-family:\"Droid Sans Mono\",\"DejaVu Sans Mono\",monospace;line-height:1.45;text-rendering:optimizeSpeed}\npre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}\npre>code{display:block}\npre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}\nem em{font-style:normal}\nstrong strong{font-weight:400}\n.keyseq{color:rgba(51,51,51,.8)}\nkbd{font-family:\"Droid Sans Mono\",\"DejaVu Sans Mono\",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}\n.keyseq kbd:first-child{margin-left:0}\n.keyseq kbd:last-child{margin-right:0}\n.menuseq,.menuref{color:#000}\n.menuseq b:not(.caret),.menuref{font-weight:inherit}\n.menuseq{word-spacing:-.02em}\n.menuseq b.caret{font-size:1.25em;line-height:.8}\n.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}\nb.button::before,b.button::after{position:relative;top:-1px;font-weight:400}\nb.button::before{content:\"[\";padding:0 3px 0 2px}\nb.button::after{content:\"]\";padding:0 2px 0 3px}\np a>code:hover{color:rgba(0,0,0,.9)}\n#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}\n#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:\" \";display:table}\n#header::after,#content::after,#footnotes::after,#footer::after{clear:both}\n#content{margin-top:1.25em}\n#content::before{content:none}\n#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}\n#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}\n#header>h1:only-child{border-bottom:1px solid #dddddf;padding-bottom:8px}\n#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}\n#header .details span:first-child{margin-left:-.125em}\n#header .details span.email a{color:rgba(0,0,0,.85)}\n#header .details br{display:none}\n#header .details br+span::before{content:\"\\00a0\\2013\\00a0\"}\n#header .details br+span.author::before{content:\"\\00a0\\22c5\\00a0\";color:rgba(0,0,0,.85)}\n#header .details br+span#revremark::before{content:\"\\00a0|\\00a0\"}\n#header #revnumber{text-transform:capitalize}\n#header #revnumber::after{content:\"\\00a0\"}\n#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}\n#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}\n#toc>ul{margin-left:.125em}\n#toc ul.sectlevel0>li>a{font-style:italic}\n#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}\n#toc ul{font-family:\"Open Sans\",\"DejaVu Sans\",sans-serif;list-style-type:none}\n#toc li{line-height:1.3334;margin-top:.3334em}\n#toc a{text-decoration:none}\n#toc a:active{text-decoration:underline}\n#toctitle{color:#7a2518;font-size:1.2em}\n@media screen and (min-width:768px){#toctitle{font-size:1.375em}\nbody.toc2{padding-left:15em;padding-right:0}\nbody.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}\n#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}\n#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}\n#toc.toc2>ul{font-size:.9em;margin-bottom:0}\n#toc.toc2 ul ul{margin-left:0;padding-left:1em}\n#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}\nbody.toc2.toc-right{padding-left:0;padding-right:15em}\nbody.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}\n@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}\n#toc.toc2{width:20em}\n#toc.toc2 #toctitle{font-size:1.375em}\n#toc.toc2>ul{font-size:.95em}\n#toc.toc2 ul ul{padding-left:1.25em}\nbody.toc2.toc-right{padding-left:0;padding-right:20em}}\n#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}\n#content #toc>:first-child{margin-top:0}\n#content #toc>:last-child{margin-bottom:0}\n#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}\n#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}\n#content{margin-bottom:.625em}\n.sect1{padding-bottom:.625em}\n@media screen and (min-width:768px){#content{margin-bottom:1.25em}\n.sect1{padding-bottom:1.25em}}\n.sect1:last-child{padding-bottom:0}\n.sect1+.sect1{border-top:1px solid #e7e7e9}\n#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}\n#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:\"\\00A7\";font-size:.85em;display:block;padding-top:.1em}\n#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}\n#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}\n#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}\ndetails,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}\ndetails{margin-left:1.25rem}\ndetails>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}\ndetails>summary::-webkit-details-marker{display:none}\ndetails>summary::before{content:\"\";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}\ndetails[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}\ndetails>summary::after{content:\"\";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}\n.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:\"Noto Serif\",\"DejaVu Serif\",serif;font-size:1rem;font-style:italic}\ntable.tableblock.fit-content>caption.title{white-space:nowrap;width:0}\n.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}\n.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}\n.admonitionblock>table td.icon{text-align:center;width:80px}\n.admonitionblock>table td.icon img{max-width:none}\n.admonitionblock>table td.icon .title{font-weight:bold;font-family:\"Open Sans\",\"DejaVu Sans\",sans-serif;text-transform:uppercase}\n.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}\n.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}\n.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}\n.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}\n.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}\n.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}\n.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}\n.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}\n@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}\n@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}\n.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^=\"highlight \"]{background:#f7f7f8}\n.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}\n.listingblock>.content{position:relative}\n.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}\n.listingblock:hover code[data-lang]::before{display:block}\n.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}\n.listingblock.terminal pre .command:not([data-prompt])::before{content:\"$\"}\n.listingblock pre.highlightjs{padding:0}\n.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}\n.listingblock pre.prettyprint{border-width:0}\n.prettyprint{background:#f7f7f8}\npre.prettyprint .linenums{line-height:1.45;margin-left:2em}\npre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}\npre.prettyprint li code[data-lang]::before{opacity:1}\npre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}\ntable.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}\ntable.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}\ntable.linenotable td.code{padding-left:.75em}\ntable.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}\npre.pygments span.linenos{display:inline-block;margin-right:.75em}\n.quoteblock{margin:0 1em 1.25em 1.5em;display:table}\n.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}\n.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}\n.quoteblock blockquote{margin:0;padding:0;border:0}\n.quoteblock blockquote::before{content:\"\\201c\";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}\n.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}\n.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}\n.verseblock{margin:0 1em 1.25em}\n.verseblock pre{font-family:\"Open Sans\",\"DejaVu Sans\",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}\n.verseblock pre strong{font-weight:400}\n.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}\n.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}\n.quoteblock .attribution br,.verseblock .attribution br{display:none}\n.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}\n.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}\n.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}\n.quoteblock.abstract{margin:0 1em 1.25em;display:block}\n.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}\n.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}\n.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}\n.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}\n.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}\np.tableblock:last-child{margin-bottom:0}\ntd.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}\ntd.tableblock>.content>:last-child{margin-bottom:-1.25em}\ntable.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}\ntable.grid-all>*>tr>*{border-width:1px}\ntable.grid-cols>*>tr>*{border-width:0 1px}\ntable.grid-rows>*>tr>*{border-width:1px 0}\ntable.frame-all{border-width:1px}\ntable.frame-ends{border-width:1px 0}\ntable.frame-sides{border-width:0 1px}\ntable.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}\ntable.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}\ntable.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}\ntable.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}\ntable.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}\nth.halign-left,td.halign-left{text-align:left}\nth.halign-right,td.halign-right{text-align:right}\nth.halign-center,td.halign-center{text-align:center}\nth.valign-top,td.valign-top{vertical-align:top}\nth.valign-bottom,td.valign-bottom{vertical-align:bottom}\nth.valign-middle,td.valign-middle{vertical-align:middle}\ntable thead th,table tfoot th{font-weight:bold}\ntbody tr th{background:#f7f8f7}\ntbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}\np.tableblock>code:only-child{background:none;padding:0}\np.tableblock{font-size:1em}\nol{margin-left:1.75em}\nul li ol{margin-left:1.5em}\ndl dd{margin-left:1.125em}\ndl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}\nli p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}\nul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}\nul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}\nul.unstyled,ol.unstyled{margin-left:0}\nli>p:empty:only-child::before{content:\"\";display:inline-block}\nul.checklist>li>p:first-child{margin-left:-1em}\nul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}\nul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}\nul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}\nul.inline>li{margin-left:1.25em}\n.unstyled dl dt{font-weight:400;font-style:normal}\nol.arabic{list-style-type:decimal}\nol.decimal{list-style-type:decimal-leading-zero}\nol.loweralpha{list-style-type:lower-alpha}\nol.upperalpha{list-style-type:upper-alpha}\nol.lowerroman{list-style-type:lower-roman}\nol.upperroman{list-style-type:upper-roman}\nol.lowergreek{list-style-type:lower-greek}\n.hdlist>table,.colist>table{border:0;background:none}\n.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}\ntd.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}\ntd.hdlist1{font-weight:bold;padding-bottom:1.25em}\ntd.hdlist2{word-wrap:anywhere}\n.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}\n.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}\n.colist td:not([class]):first-child img{max-width:none}\n.colist td:not([class]):last-child{padding:.25em 0}\n.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}\n.imageblock.left{margin:.25em .625em 1.25em 0}\n.imageblock.right{margin:.25em 0 1.25em .625em}\n.imageblock>.title{margin-bottom:0}\n.imageblock.thumb,.imageblock.th{border-width:6px}\n.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}\n.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}\n.image.left{margin-right:.625em}\n.image.right{margin-left:.625em}\na.image{text-decoration:none;display:inline-block}\na.image object{pointer-events:none}\nsup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}\nsup.footnote a,sup.footnoteref a{text-decoration:none}\nsup.footnote a:active,sup.footnoteref a:active,#footnotes .footnote a:first-of-type:active{text-decoration:underline}\n#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}\n#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}\n#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}\n#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}\n#footnotes .footnote:last-of-type{margin-bottom:0}\n#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}\ndiv.unbreakable{page-break-inside:avoid}\n.big{font-size:larger}\n.small{font-size:smaller}\n.underline{text-decoration:underline}\n.overline{text-decoration:overline}\n.line-through{text-decoration:line-through}\n.aqua{color:#00bfbf}\n.aqua-background{background:#00fafa}\n.black{color:#000}\n.black-background{background:#000}\n.blue{color:#0000bf}\n.blue-background{background:#0000fa}\n.fuchsia{color:#bf00bf}\n.fuchsia-background{background:#fa00fa}\n.gray{color:#606060}\n.gray-background{background:#7d7d7d}\n.green{color:#006000}\n.green-background{background:#007d00}\n.lime{color:#00bf00}\n.lime-background{background:#00fa00}\n.maroon{color:#600000}\n.maroon-background{background:#7d0000}\n.navy{color:#000060}\n.navy-background{background:#00007d}\n.olive{color:#606000}\n.olive-background{background:#7d7d00}\n.purple{color:#600060}\n.purple-background{background:#7d007d}\n.red{color:#bf0000}\n.red-background{background:#fa0000}\n.silver{color:#909090}\n.silver-background{background:#bcbcbc}\n.teal{color:#006060}\n.teal-background{background:#007d7d}\n.white{color:#bfbfbf}\n.white-background{background:#fafafa}\n.yellow{color:#bfbf00}\n.yellow-background{background:#fafa00}\nspan.icon>.fa{cursor:default}\na span.icon>.fa{cursor:inherit}\n.admonitionblock td.icon [class^=\"fa icon-\"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}\n.admonitionblock td.icon .icon-note::before{content:\"\\f05a\";color:#19407c}\n.admonitionblock td.icon .icon-tip::before{content:\"\\f0eb\";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}\n.admonitionblock td.icon .icon-warning::before{content:\"\\f071\";color:#bf6900}\n.admonitionblock td.icon .icon-caution::before{content:\"\\f06d\";color:#bf3400}\n.admonitionblock td.icon .icon-important::before{content:\"\\f06a\";color:#bf0000}\n.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:\"Open Sans\",\"DejaVu Sans\",sans-serif;font-style:normal;font-weight:bold}\n.conum[data-value] *{color:#fff!important}\n.conum[data-value]+b{display:none}\n.conum[data-value]::after{content:attr(data-value)}\npre .conum[data-value]{position:relative;top:-.125em}\nb.conum *{color:inherit!important}\n.conum:not([data-value]):empty{display:none}\ndt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}\nh1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}\np strong,td.content strong,div.footnote strong{letter-spacing:-.005em}\np,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}\np{margin-bottom:1.25rem}\n.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}\n.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}\n.print-only{display:none!important}\n@page{margin:1.25cm .75cm}\n@media print{*{box-shadow:none!important;text-shadow:none!important}\nhtml{font-size:80%}\na{color:inherit!important;text-decoration:underline!important}\na.bare,a[href^=\"#\"],a[href^=\"mailto:\"]{text-decoration:none!important}\na[href^=\"http:\"]:not(.bare)::after,a[href^=\"https:\"]:not(.bare)::after{content:\"(\" attr(href) \")\";display:inline-block;font-size:.875em;padding-left:.25em}\nabbr[title]{border-bottom:1px dotted}\nabbr[title]::after{content:\" (\" attr(title) \")\"}\npre,blockquote,tr,img,object,svg{page-break-inside:avoid}\nthead{display:table-header-group}\nsvg{max-width:100%}\np,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}\nh2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}\n#header,#content,#footnotes,#footer{max-width:none}\n#toc,.sidebarblock,.exampleblock>.content{background:none!important}\n#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}\nbody.book #header{text-align:center}\nbody.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}\nbody.book #header .details{border:0!important;display:block;padding:0!important}\nbody.book #header .details span:first-child{margin-left:0!important}\nbody.book #header .details br{display:block}\nbody.book #header .details br+span::before{content:none!important}\nbody.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}\nbody.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}\n.listingblock code[data-lang]::before{display:block}\n#footer{padding:0 .9375em}\n.hide-on-print{display:none!important}\n.print-only{display:block!important}\n.hide-for-print{display:none!important}\n.show-for-print{display:inherit!important}}\n@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}\n.sect1{padding:0!important}\n.sect1+.sect1{border:0}\n#footer{background:none}\n#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}\n@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}\n\n/* DARK MODE */\n\n@media (prefers-color-scheme: dark) {\nbody,\nbody .btn,\nbody table,\nbody th {\n\tbackground-color: #222; !important\n\tcolor: #e0e0e0; !important\n}\n\nbody .btn {\n\tbox-shadow: 0 0 5px #616161; !important\n\tborder: 1px solid #222; !important\n}\n\nbody .btn:focus {\n\tbox-shadow: 0 0 5px #9e9e9e; !important\n}\n\nbody .theme-switcher {\n\tbackground: url(\"../img/sun.svg\") no-repeat center; !important\n}\n\n.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{\ncolor:#ff8a80;!important\n}\n.quoteblock blockquote::before{\ncolor:#ff8a80;!important\n}\n\nbody h1,\nbody h2,\nbody h3,\nbody h4,\nbody h5,\nbody h6,\nbody #toctitle,\nbody .sidebarblock .title,\nbody .imageblock .title {\n\tcolor: #ff8a80 !important;\n}\n\nbody blockquote::before {\n\tcolor: #d32f2f !important;\n}\n\nbody code,\nbody pre {\n\tbackground-color: #2f2f2f !important;\n\tcolor: #e0e0e0; !important\n}\n\nbody .sectlevel1 li a {\n\tcolor: #ff8a80 !important;\n}\n\nbody a,\nbody a code,\nbody .sectlevel2 li a {\n\tcolor: #90caf9 !important;\n}\n\nbody a:hover,\nbody a:hover code,\nbody a code:hover,\nbody .sectlevel2 li a:hover {\n\tcolor: #42a5f5 !important;\n}\n\nbody #toc,\nbody .pwa-install-div {\n\tbackground-color: #222 !important;\n}\n\nbody #toc {\n\tborder-left-color: #212121; !important\n\tborder-right-color: #212121; !important\n}\n\nbody .pwa-install-div {\n\tbox-shadow: 0 0 5px #2f2f2f; !important\n}\n\nbody #pwa-install-btn {\n\tbox-shadow: 0 0 5px #2f2f2f; !important\n\tbackground-color: #e0e0e0; !important\n\tborder: 1px solid #e0e0e0; !important\n\tcolor: #222; !important\n}\n\nbody li,\nbody p,\nbody .details,\nbody details,\nbody details summary,\nbody td,\nbody blockquote,\nbody .attribution cite {\n\tcolor: #e0e0e0 !important;\n}\n\nbody .sidebarblock {\n\tbackground-color: #222 !important;\n}\n\n* {\n\tscrollbar-color: #818181 #333; !important\n}\n*::-webkit-scrollbar-track:hover {\n\tscrollbar-color: #a1a181 #333; !important\n}\n\n\n/* code style */\n:not(pre):not([class^=L])>code{background:#2f2f2f;!important}\nkbd{background:#2f2f2f;!important}\n.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^=\"highlight \"]{background:#2f2f2f;!important}\n.literalblock.output pre{color:#2f2f2f;!important}\n.prettyprint{background:#2f2f2f;!important}\n}\n"
  },
  {
    "path": "docs/config.adoc",
    "content": "= Kanata Configuration Guide\n:last-update-label!:\nifndef::env-github[]\n:toc: left\nendif::[]\n:stylesheet: config-stylesheet.css\n\nThis document describes how to create a kanata configuration file.\nThe kanata configuration file will determine your keyboard behaviour upon running kanata.\n\n== How to read the guide\n\nifdef::env-github[]\nSee the triple bullet-lines at the upper right\nto open or close a Table of Contents sidebar.\n\nYou may want to view the guide rendered to HTML.\nhttps://jtroo.github.io/config.html[Link to guide].\nendif::[]\n\nThe **Reference** sections are shorter\nand are intended for reviewing how precisely to configure different sections.\nThe **Description** sections are longer\nand contain more details such as advice, motivation, and examples.\n\nThe configuration guide you are reading\nmay have content not applicable to the version you are using.\nSee below for links to specific guide versions.\n\nifdef::env-github[]\n* https://github.com/jtroo/kanata/blob/v1.11.0/docs/config.adoc[v1.11.0]\n* https://github.com/jtroo/kanata/blob/v1.10.1/docs/config.adoc[v1.10.1]\n* https://github.com/jtroo/kanata/blob/v1.10.0/docs/config.adoc[v1.10.0]\n* https://github.com/jtroo/kanata/blob/v1.9.0/docs/config.adoc[v1.9.0]\n* https://github.com/jtroo/kanata/blob/v1.8.1/docs/config.adoc[v1.8.1]\n* https://github.com/jtroo/kanata/blob/v1.8.0/docs/config.adoc[v1.8.0]\nendif::[]\nifndef::env-github[]\n* link:/config-1.11.0.html[v1.11.0]\n* link:/config-1.10.1.html[v1.10.1]\n* link:/config-1.10.0.html[v1.10.0]\n* link:/config-1.9.0.html[v1.9.0]\n* link:/config-1.8.1.html[v1.8.1]\n* link:/config-1.8.0.html[v1.8.0]\nendif::[]\n\n== Preamble\n\nThe configuration file uses S-expression syntax from Lisps. If you are not\nfamiliar with any Lisp-like programming language, do not be too worried. This\ndocument will hopefully be a sufficient guide to help you customize your\nkeyboard behaviour to your exact liking.\n\nUseful terminology to learn early:\n[cols=\"1,5\"]\n|===\n| string\n| A sequence of characters.\nOptionally surrounded by quotes.\nExamples: `backspace`, `\"string with spaces and 1 number\"`.\n\n| list\n| A sequence of strings or nested lists within round brackets.\nList items are separated by any amount of whitespace characters,\nor by round brackets.\nExamples: `(lrld-num 1)`, `(tap-dance 200 (f1(unicode 😀)f2(unicode 🙂)))`.\n|===\n\nIf you have any questions, confusions, suggestions, etc., feel free to\nhttps://github.com/jtroo/kanata/discussions/new/choose[start a discussion]\nor https://github.com/jtroo/kanata/issues/new/choose[file an issue].\nIf you have ideas for how to improve this document or any other part of the project,\nplease be welcome to make a pull request or file an issue.\n\n== Forcefully exit kanata [[force-exit]]\n\nThough this isn't configuration-related,\nit may be important for you to know that pressing and holding all of the\nthree following keys together at the same time will cause kanata to exit:\n\n- Left Control\n- Space\n- Escape\n\nThis mechanism works on the key input **before** any remappings done by kanata.\n\n[[comments]]\n== Comments\n\nYou can add comments to your configuration file. Comments are prefixed with two\nsemicolons. E.g:\n\n[source]\n----\n;; This is a comment in a kanata configuration file.\n;; Comments will be ignored and are intended for you to help understand your\n;; own configuration when reading it later.\n----\n\nYou can begin a multi-line comment block with `+#|+` and end it with `+|#+`:\n\n[source]\n----\n#|\nThis is\na multi-line comment block\n|#\n----\n\n[[required-configuration-entries]]\n== Required configuration entries\n\n[[defsrc]]\n=== defsrc\n\n**Reference**\n\nYour configuration file must have exactly one `defsrc` list.\nThis defines the order of keys that the `+deflayer+` entries will operate on.\n\n.Syntax:\n[source]\n----\n(defsrc $key1 $key2 ... $keyN)\n----\n\n[cols=\"1,6\"]\n|===\n| `$key`\n| The name of a key. This can be a default key name or one defined in <<deflocalkeys>>.\nWhen physically pressing this input key, the action defined\nat the same order position on the active layer will activate.\n|===\n\n**Description**\n\nThe `defsrc` configuration entry defines which of your key inputs\nwill be processed by kanata and how the keys map to defined layers.\nKeys excluded from `defsrc` will not be processed by Kanata\nunless you have `process-unmapped-keys yes` in <<introduction-defcfg,defcfg>>.\n\nKeys not processed by Kanata has implications on various actions.\nFor example:\n\n- Pressing an excluded key will type a letter\nwhile a prior `tap-hold` decision is still pending,\nresulting in potentially incorrect results.\n- Excluded keys do not trigger early activation\nin actions such as `tap-hold-press` or `tap-dance`\n- Excluded keys cannot be read by `fork` or `switch` logic.\n\nThe `defsrc` entry is treated as a long sequence.\nThe amount of whitespace (spaces, tabs, newlines) are not relevant.\nYou may use spaces, tabs, or newlines however you like\nto visually format `defsrc` to your liking.\n\nThe primary source of all key names are the\n`str_to_oscode` and `default_mappings` functions in\nhttps://github.com/jtroo/kanata/blob/main/parser/src/keys/mod.rs[the source].\nPlease feel welcome to file an issue\nif you're unable to find the key you're looking for.\n\nAn example `defsrc` containing the US QWERTY keyboard keys as an\napproximately 60% keyboard layout:\n\n.Example:\n[source]\n----\n(defsrc\n  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n  caps a    s    d    f    g    h    j    k    l    ;    '    ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n----\n\nNote that some keyboards have a Menu key instead of a right Meta key.\nIn this case you can use `menu` instead of `rmet`.\n\nFor non-US keyboards, see <<non-us-keyboards,the non-US keyboards section>>.\n\n[[deflayer]]\n=== deflayer\n\n**Reference**\n\nYour configuration file must have at least one `+deflayer+` entry. This defines\nhow each physical key mapped in `+defsrc+` behaves when kanata runs.\n\n.Syntax:\n[source]\n----\n(deflayer $layer-name $action1 $action2 ... $actionN)\n----\n\n[cols=\"1,5\"]\n|===\n| `$layer-name`\n| A string representing the layer name.\nThis name is used to reference this layer in other actions.\n\n| `$action`\n| The action that activates while this layer is active\nwhen the corresponding `defsrc` input key is pressed.\n|===\n\n**Description**\n\nA `+deflayer+` configuration entry is followed by the layer name then a list of\nkeys or actions. The usable key names are the same as in defsrc. Actions are\nexplained further on in this document. The whitespace story is the same as with\n`+defsrc+`. The order of keys/actions in `+deflayer+` corresponds to the\nphysical key in the same sequence position defined in `+defsrc+`.\n\nThe first layer defined in your configuration file will be the starting layer\nwhen kanata runs. Other layers can be temporarily activated or switched to\nusing actions.\n\nAn example `defsrc` and `deflayer` that remaps QWERTY to the Dvorak layout\nwould be:\n\n.Example:\n[source]\n----\n(defsrc\n  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n  caps a    s    d    f    g    h    j    k    l    ;    '    ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n\n(deflayer dvorak\n  grv  1    2    3    4    5    6    7    8    9    0    [    ]    bspc\n  tab  '    ,    .    p    y    f    g    c    r    l    /    =    \\\n  caps a    o    e    u    i    d    h    t    n    s    -    ret\n  lsft ;    q    j    k    x    b    m    w    v    z    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n----\n\nA <<windows-only-tray-icon,Windows tray menu build>> also allows specifying\nlayer icons in `+deflayer+` and `+deflayermap+` to show in the tray menu on layer activation,\nsee https://github.com/jtroo/kanata/blob/main/cfg_samples/tray-icon/tray-icon.kbd[example config]\n\n==== deflayermap\n\n**Reference**\n\nAn alternative method for defining a layer exists: `deflayermap`.\nThis method maps inputs to actions by defining input-output pairs,\nignoring `defsrc` entirely.\n\nYou will likely want to either enable <<process-unmapped-keys>>\nor define most of your keyboard keys within <<defsrc>> when using `deflayermap`.\nOtherwise many actions do not behave as intended.\nSee one of the links for more context.\n\n.Syntax:\n[source]\n----\n(deflayermap ($layer-name)\n $input1 $action1\n $input2 $action2\n ...\n $inputN $actionN)\n----\n\n[cols=\"1,5\"]\n|===\n| `$layer-name`\n| A string representing the layer name.\nThis name is used to reference this layer in other actions.\n\n| `$input`\n| The input key mapped to the corresponding output.\n\n| `$action`\n| The action that activates while this layer is active\nwhen the corresponding input key is pressed.\n|===\n\n**Description**\n\n\nThe `deflayermap` variant has the advantage of terser configuration\nwhen only a few keys on a layer need to be mapped.\nWhen practicing a new configuration, the standard `deflayer` has an advantage\nof looking more like a physical keyboard layout,\nwhich may be helpful to some.\n\nWithin `deflayermap`, the very first item must be the layer name.\nThe layer name must be in parentheses unlike with `deflayer`.\nAfter the layer name, the layer is configured via pairs of items:\n\n* input key\n* output action\n\nAn example complete configuration that maps Caps Lock to Escape is:\n\n[source]\n----\n;; defsrc is still necessary\n(defsrc)\n(deflayermap (base-layer)\n  caps esc)\n----\n\nThe input key takes the same role as `defsrc` keys.\nThe output action takes the role that items in the normal `deflayer` have.\n\nAs special input names,\nyou can use one of `&#95;`, `&#95;&#95;`, or `&#95;&#95;&#95;` to map all\nthe keys that are not explicitly mapped in the layer,\ne.g. in the example above, these affect keys other than `caps`.\n\n[cols=\"1,6\"]\n|===\n| `&#95;`\n| Map all unmapped keys in this layer that are defined in `defsrc`.\n\n| `&#95;&#95;`\n| Map all unmapped keys in this layer that are not defined in `defsrc`.\n\n| `&#95;&#95;&#95;`\n| Map all unmapped keys in this layer.\n|===\n\nIf a key is not mapped explicitly or through these wildcards, it will be implicitly mapped to a <<transparent-key,transparent key>>.\n\n[[review-of-required-configuration-entries]]\n=== Review of required configuration entries\n\nIf you're reading in order, you have now seen all of the required entries:\n\n* `+defsrc+`\n* `+deflayer+`\n\n[[minimal-config]]\nAn example minimal configuration is:\n\n[source]\n----\n(defsrc a b c)\n\n(deflayer start 1 2 3)\n----\n\nThis will make kanata remap your `a b c` keys to `1 2 3`. This is almost\ncertainly undesirable but is a valid configuration.\n\nNOTE: Please have a read through link:https://github.com/jtroo/kanata/blob/main/docs/platform-known-issues.adoc[the known platform issues]\nbecause they may have implications on what you should include/exclude in `defsrc`.\nThe Windows LLHOOK I/O mechanism has the most issues by far.\n\n[[key-names]]\n== Key names for defsrc and deflayermap\n\nThe source of truth for all default key names are the functions\n`str_to_oscode` and `add_default_str_osc_mappings`\nin the link:https://github.com/jtroo/kanata/blob/main/parser/src/keys/mod.rs[keys/mod.rs file].\n\nhttps://www.toptal.com/developers/keycode[This online tool]\nwill also work for most keys to tell you the key name.\nIt will be shown as the `event.code` field in the web page\nafter you press the key.\n\n[[non-us-keyboards]]\n== Non-US keyboards\n\nFor non-US keyboard users, you may have some keys on your keyboard with characters\nthat are not allowed in `defsrc` by default, at least according to the symbol shown\non the physical keys.\nThe two sections below can help you understand how to remap all your keys.\n\n=== Browser event.code\n\nEnsure kanata and other key remapping programs are **not** running.\nThen you can use https://www.toptal.com/developers/keycode[this online tool]\nand press the key.\nThe `event.code` field tells you the key name to use in Kanata.\nAlternatively, you can read through\nhttps://www.w3.org/TR/uievents-code/[this reference].\nDue to the lengthy key names,\nyou may want to use `deflayermap` if remapping using these key names.\n\nIMPORTANT: On Windows, you should use either `kanata_winIOv2.exe`\nor Interception when using key names according to the browser `event.code`.\nThe default `kanata.exe` does not do mappings according to the browser `event.code`\nkey names.\n\n[[deflocalkeys]]\n=== deflocalkeys\n\n**Reference**\n\nYou can use `deflocalkeys` to define additional key names that can be\nused in `defsrc`, `deflayer`, and anywhere else in the configuration.\n\n.Syntax:\n[source]\n----\n(deflocalkeys-$variant\n  $key-name1 $key-number1\n  $key-name2 $key-number2\n  ...\n  $key-nameN $key-numberN)\n----\n\n[cols=\"1,5\"]\n|===\n| `$variant`\n| One of: `win winiov2 wintercept linux macos`\n\n| `$key-name`\n| A key name of your choice that can be used in the rest of the configuration.\n\n| `$key-number`\n| A key number that varies based on the kanata variant you are using.\n|===\n\nOnly one of each deflocalkeys-* variant is allowed. The variants that are not\napplicable will be ignored, e.g. `deflocalkeys-linux` and `deflocalkeys-wintercept`\nare both ignored when using the default Windows `kanata.exe` binary.\n\n**Description**\n\nThe `deflocalkeys` configurations are not strictly necessary.\nTheir purpose is to help you match your physical keyboard's appearance\nto your kanata configuration,\nin the hopes it will be more readable and less confusing.\nIn the underlying hardware, all keyboard positions send the same scan codes\naccording to their position, regardless of what is printed on the key cap.\nThe scan code names are typically referred to by the corresponding US layout name.\nIt is the job of the operating system to translate the same scan code\nto the correct outputs according to the configured locale and layout.\n\nYou can find configurations that others have made in\nhttps://github.com/jtroo/kanata/blob/main/docs/locales.adoc[this document].\nIf you do not see your keyboard there and are not confident in using\nthe available tools,\nplease feel welcome to ask for help in a discussion or issue.\nPlease contribute to the document if you are able!\n\nThere are five variants of deflocalkeys:\n\n- `deflocalkeys-win`\n- `deflocalkeys-winiov2`\n- `deflocalkeys-wintercept`\n- `deflocalkeys-linux`\n- `deflocalkeys-macos`\n\n.Example:\n[source]\n----\n(deflocalkeys-win\n  ì 187\n)\n\n(deflocalkeys-winiov2\n  ì 187\n)\n\n(deflocalkeys-wintercept\n  ì 187\n)\n\n(deflocalkeys-linux\n  ì 13\n)\n\n(deflocalkeys-macos\n  ì 13\n)\n\n(defsrc\n  grv  1    2    3    4    5    6    7    8    9    0    -    ì    bspc\n)\n----\n\nThe number used for a custom key represents the converted value for an OsCode in\nbase 10. This differs between Windows-hooks, Windows-interception, and Linux.\n\nRunning kanata with the `--debug` flag lets you read the correct number,\nshown in parenthesis of `code` in the `KeyEvent` log lines.\n\nIt also possible to use native tools, as described below.\n\nIn Linux, `evtest` will give the correct number for the physical key you press.\n\nIn Windows using the default hook mechanism, the non-interception version of the\nkeyboard tester in the kanata repository will give the correct number\nin the `code: <number>` section.\n(https://github.com/jtroo/kanata/releases/tag/win-keycode-tester-v0.3.0[prebuilt binary])\n\nIn Windows uning `winIOv2`, the winIOv2 executable variant\nwill give the correct number in the `code: <number>` section.\n\nIn Windows using Interception, the interception version of the keyboard tester\nwill give the correct number i the `num: <number>` section.\nBetween the hook and interception versions, some\nkeys may agree but others may not; do be aware that they are **not** compatible!\n\nHowever, Interception and winIOv2 should generally agree with each other.\n\nIdeas for improving the user-friendliness of this system are welcome! As\nmentioned before, please ask for help in an issue or discussion if needed, and\nhelp with https://github.com/jtroo/kanata/blob/main/docs/locales.adoc[this document]\nis very welcome so that future users can have an easier time 🙂.\n\n[[introduction-defcfg]]\n== Introduction to defcfg\n\nYour configuration file may include a single `defcfg` entry.\nThe `defcfg` can be empty or omitted.\nThere are options that change kanata's behaviour,\nbut this introduction will introduce\nonly the most prevalent entry: `process-unmapped-keys`.\nAll other options can be found later in the <<optional-defcfg-options>> section.\n\n.Example of an empty defcfg:\n[source]\n----\n(defcfg)\n----\n\n[[process-unmapped-keys]]\n=== process-unmapped-keys\n\nThe `process-unmapped-keys` option in `defcfg` is probably the most\ngenerally impactful option.\nEnabling this configuration makes kanata process keys\nthat are not defined in `defsrc`.\nThis might be useful\nif you are only mapping a few keys in defsrc\ninstead of most of the keys on your keyboard.\n\nBy default, keys excluded from `defsrc` will not work in various scenarios.\nSome examples:\n\n- The early hold for prior `+tap-hold-press+` actions will not\n- Prior `+one-shot+` actions will not be released\n- `fork` and `switch` logic will not see the key\n\nThis option is disabled by default.\nThe reason this is not enabled by default\nis because some keys may not work correctly if they are intercepted.\nA known issue being AltGr/ralt/Right Alt; see <<windows-only-windows-altgr>>.\n\n.Example:\n[source]\n----\n(defcfg process-unmapped-keys yes)\n(defcfg process-unmapped-keys (all-except lctl ralt))\n----\n\n== Aliases and variables[[aliases-and-vars]]\n\nBefore learning about actions,\nit will be useful to first learn about aliases and variables.\n\n[[aliases]]\n=== Aliases\n\n**Reference**\n\nUsing the `defalias` configuration entry, you can introduce a shortcut label\nfor an action.\n\n.Syntax:\n[source]\n----\n(defalias\n  $alias-name1 $action1\n  $alias-name2 $action2\n  ...\n  $alias-nameN $actionN)\n----\n\n[cols=\"1,5\"]\n|===\n| `$alias-name`\n| The chosen shortcut label for the action.\nThis shortcut label can be used in the rest of\nthe configuration by prefixing it with the `@`\ncharacter.\n\n| `$action`\n| The ouput action used wherever the alias name is referenced.\n|===\n\n**Description**\n\nThe `defalias` entry reads pairs of items in a sequence\nwhere the first item in the pair is the alias name and the second item is the\naction it can be substituted for.\n\nA list is a sequence of strings\nor nested lists separated by whitespace,\nsurrounded by parentheses.\nAll of the configuration entries we've looked at so far are lists;\n`defalias` is where we'll first see nested lists in this guide.\n\n.Example:\n[source]\n----\n(defalias\n  ;; tap for caps lock, hold for left control\n  cap (tap-hold 200 200 caps lctl)\n)\n----\n\nThis alias can be used in `deflayer` as a substitute for the long action. The\nalias name is prefixed with `@` to signify that it's an alias as opposed to a\nnormal key.\n\n[source]\n----\n(deflayer example\n  @cap a s d f\n)\n----\n\nYou may have multiple `defalias` entries and multiple aliases within a single\n`defalias`. Aliases may also refer to other aliases that were defined earlier\nin the configuration file.\n\n.Example:\n[source]\n----\n(defalias one (tap-hold 200 200 caps lctl))\n(defalias two (tap-hold 200 200 esc lctl))\n(defalias\n  three C-A-del ;; Ctrl+Alt+Del\n  four (tap-hold 200 200 @three ralt)\n)\n----\n\nYou can choose to put actions without aliasing them right into `deflayer`.\nHowever, for long actions it is recommended not to do so to keep a nice visual\nalignment. Visually aligning your `deflayer` entries will hopefully make your\nconfiguration file easier to read.\n\n.Example:\n[source]\n----\n(deflayer example\n  ;; this is equivalent to the previous deflayer example\n  (tap-hold 200 200 caps lctl) a s d f\n)\n----\n\n[[variables]]\n=== Variables\n\n**Reference**\n\nUsing the `defvar` configuration entry,\nyou can introduce a shortcut label for an arbitrary string or list.\n\n.Syntax:\n[source]\n----\n(defvar\n  $var-name1 $var-value1\n  $var-name2 $var-value2\n  ...\n  $var-nameN $var-valueN)\n----\n\n[cols=\"1,5\"]\n|===\n| `$var-name`\n| The chosen shortcut label for the string or list.\nThis shortcut label can be used in the rest of the\nconfiguration by prefixing it with `$`.\n\n| `$var-value`\n| An arbitrary string or list that will be substituted\nwherever the variable is used.\n\n|===\n\n**Description**\n\nUnlike an alias, a variable does not need to be a valid standalone action.\nIn other words,\na variable can be used as components of actions.\n\nThe most common use case is to define common number strings\nfor actions such as `tap-hold`, `tap-dance`, and `one-shot`.\n\nSimilar to how `defalias` works,\n`defvar` reads pairs of items in a sequence\nwhere the first item in the pair is the variable name\nand the second item is a string or list.\nVariables are allowed to refer to previously defined variables.\n\nVariables can be used to substitute most values.\nSome notable exceptions are:\n\n- variables cannot be used in `defcfg`, `defsrc`, or `deflocalkeys`\n- variables cannot be used to substitute an action name\n\nVariables are referred to by prefixing their name with `$`.\n\n.Example:\n[source]\n----\n(defvar\n  tap-repress-timeout   100\n  hold-timeout  200\n  tt $tap-repress-timeout\n  ht $hold-timeout\n)\n\n(defalias\n  th1 (tap-hold $tt $ht caps lctl)\n  th2 (tap-hold $tt $ht spc  lsft)\n)\n----\n\n[[concat-in-defvar]]\n==== concat in defvar\n\nWithin the second item of `defvar`,\na list that begins with the special keyword `concat` will concatenate all\nsubsequent items in the list together into a single string value.\nWithout using `concat`, lists are saved as-is.\n\n.Example:\n[source]\n----\n(defvar\n  rootpath \"/home/myuser/mysubdir\"\n  ;; $otherpath will be the string: /home/myuser/mysubdir/helloworld\n  otherpath (concat $rootpath \"/helloworld\")\n)\n----\n\n[[actions]]\n== Actions\n\nThe actions kanata provides are what make it truly customizable.\nThis section explains the available actions.\n\n[[live-reload]]\n=== Live reload\n\n**Reference**\n\nLive reload variants:\n\n[cols=\"1,5\"]\n|===\n| `lrld`\n| String action that live-reloads the currently-used configuration file.\n\n| `lrld-next`\n| String action that live-reloads the configuration file specified\nconsecutively later in the command line order.\nCycles to the first-specified file\nif currently using the last file specified.\n\n| `lrld-prev`\n| String action that live-reloads the configuration file specified\nconsecutively earlier in the command line order.\nCycles to the last-specified file\nif currently using the first file specified.\n\n| `(lrld-num $n)`\n| List action that live-reloads the n'th file\nas specified in the command line order.\nThe first file specified is `n=1`.\n|===\n\nLive reload does not read or apply changes to device-related configurations.\nExamples of device-related configurations:\n`linux-dev`, `macos-dev-names-include`, `linux-use-trackpoint-property`,\n`windows-only-windows-interception-keyboard-hwids`.\n\n**Description**\n\nYou can put the `+lrld+` action onto a key to live reload your configuration file.\nIf kanata can't parse the file,\nthe previous configuration will continue to be used.\nWhen live reload is activated,\nthe active kanata layer will be the first `deflayer` defined in the configuration.\n\n.Example:\n[source]\n----\n(deflayer has-live-reload\n  lrld a s d f\n)\n----\n\nThere are variants of `lrld`: `lrld-prev` and `lrld-next`. These will cycle\nthrough different configuration files that you specify on kanata's startup.\nThe first configuration file specified will be the one loaded on startup.\nThe prev/next variants can be used with shortened names of `lrpv` and `lrnx` as\nwell.\n\nAnother variant is the list action `lrld-num`.\nThis reloads the configuration file specified by the number,\naccording to the order that the configuration file arguments\nare passed into kanata's startup command.\n\n.Example:\n[source]\n----\n(deflayer has-live-reloads\n  lrld lrpv lrnx (lrld-num 3)\n)\n----\n\nExample specifying multiple config files in the command line:\n\n[source]\n----\nkanata -c startup.cfg -c 2nd.cfg -c 3rd.cfg\n----\n\nGiven the above startup command,\nactivating `(lrld-num 2)` would reload the `2nd.cfg` file.\n\n\n[[layer-switch]]\n=== layer-switch\n\n**Reference**\n\nA list action that changes the active base layer.\n\n.Syntax:\n[source]\n----\n(layer-switch $layer-name)\n----\n\n[cols=\"1,5\"]\n|===\n| `$layer-name`\n| Layer name to switch to.\n|===\n\n\n**Description**\n\nThis action allows you to switch to another \"base\" layer.\nThis is permanent until a `layer-switch` to another layer is activated.\nThe concept of a base layer makes more sense\nwhen looking at the next action: `layer-while-held`.\n\nThis action accepts a single subsequent string which must be a layer name\ndefined in a `deflayer` entry.\n\n.Example:\n[source]\n----\n(defalias dvk (layer-switch dvorak))\n----\n\n[[layer-while-held]]\n=== layer-while-held\n\n**Reference**\n\nA list action that changes the active layer while the key is held.\n\n.Syntax:\n[source]\n----\n(layer-while-held $layer-name)\n----\n\n[cols=\"1,5\"]\n|===\n| `$layer-name`\n| Layer name to activate while key is held.\n|===\n\n**Description**\n\nThis action allows you to temporarily change to another layer while the key\nremains held. When the key is released, you go back to the currently active\n\"base\" layer.\n\nThis action accepts a single subsequent string which must be a layer name\ndefined in a `deflayer` entry.\n\n.Example:\n[source]\n----\n(defalias nav (layer-while-held navigation))\n----\n\nYou may also use `layer-toggle` in place of `layer-while-held`; they behave\nexactly the same. The `layer-toggle` name is slightly shorter but is a bit\ninaccurate with regards to its meaning.\n\n[[transparent-key]]\n=== Transparent key\n\n**Reference**\n\n[cols=\"1,5\"]\n|===\n| `+_+`\n| String action that activates the action of the layer \"underneath\" the active one.\n|===\n\n**Description**\n\nKanata maintains a layer stack consisting in order of:\n\n* a stack of temporary layers, where each `layer-while-held` adds one layer on top\n* the base layer, manipulated by `layer-switch`\n* if `delegate-to-first-layer` is enabled: the first layer defined by `deflayer` or `deflayermap`\n* `defsrc`\n\nA single underscore `+_+` acts as a \"transparent\" key in the current layer.\nIt will invoke the action assigned to the same key position on the next layer in the stack.\n\nNOTE: If `delegate-to-first-layer` is enabled and the base layer is currently set to the first defined layer, a transparent key on the base layer will fall through directly to `defsrc`.\n\n[[use-defsrc]]\n=== use-defsrc\n\n**Reference**\n\n[cols=\"1,6\"]\n|===\n| `use-defsrc`\n| String action that outputs the corresponding `defsrc` input key.\n|===\n\n**Description**\n\nA similar concept to transparent key is the `+use-defsrc+` action.\nWhen activated, the underlying `defsrc` key will be the output action.\n\n.Example:\n[source]\n----\n(defsrc a b c d)\n(defalias src use-defsrc)\n(deflayer remap-only-c-to-d\n  _ _ d @src)\n----\n\n[[no-op]]\n=== No-op\n\n**Reference**\n\n[cols=\"1,6\"]\n|===\n| `XX`\n| String action that will output nothing.\n|===\n\n**Description**\n\nYou may use the action `+XX+` as a \"no operation\" key, meaning pressing the key\nwill do nothing. This might be desirable in place of a transparent key on a\nlayer that is not fully mapped so that a key that is intentionally not mapped\nwill do nothing as opposed to typing a letter.\n\nAlternatively you can use `+✗+` `+∅+` `+•+` to mean no-op.\n\n.Example:\n[source]\n----\n(deflayer contains-no-ops\n  XX ✗ ∅ •)\n----\n\n[[unicode]]\n=== Unicode\n\n**Reference**\n\nList action that outputs a single unicode codepoint.\nThe unicode codepoint will not be repeatedly typed if you hold the key down.\n\n.Syntax:\n[source]\n----\n(unicode $unicode-codepoint)\n----\n\n[cols=\"1,4\"]\n|===\n| `$unicode-codepoint`\n| One unicode codepoint.\nBe warned that many emojis/glyphs/graphemes\nare composed of multiple codepoints.\n|===\n\n**Description**\n\nNOTE: The <<clipboard-actions>> may output unicode characters more consistently.\n\nThe `+unicode+` (or `+🔣+`) action accepts a single unicode character (but not\na composed character, so 🤲, but not 🤲🏿),\nor a single unicode number prefixed with `U+`.\nFor example, both of these actions are the same:\n- `(unicode 🚆)`\n- `(unicode U+1F686)`\n\nIf you want to output a glyph that is composed of multiple codepoints,\nyou can use <<macro>> with multiple `unicode` actions.\n\nYou may use a unicode character as an alias if desired or in its simplified form `+🔣😀+`\n(vs the usual `+(🔣 😀)+`).\n\nNOTE: The unicode action may not be correctly accepted by the active\napplication.\n\nNOTE: If using Linux, make sure to look at the\n<<linux-only-linux-unicode-u-code,unicode behaviour customization>> in defcfg.\nFurthermore, on Wayland the unicode mechanism may be broken entirely.\nOn Wayland you can install then use the `wtype` program,\nusing <<cmd, cmd>> to execute it.\nFor example: `(cmd wtype á)`\n\n.Example:\n[source]\n----\n(defalias\n  sml (unicode 😀)\n  😀 (🔣 😀)\n  🙁 (unicode 🙁)\n)\n(deflayer has-happy-sad\n  @sml @🙁 @😀 🔣😀 d f\n)\n----\n\nIf you want output parentheses `+( )+` via unicode you can quote them.\n\n.Example with parentheses\n[source]\n----\n(defalias\n  lp (unicode \"(\")\n  rp (unicode \")\")\n)\n----\n\nIf you want to output double quotes via unicode\nyou need a special quoting syntax.\n\n.Example use of double-quote within a string\n[source]\n----\n(defalias\n  dq (unicode r#\"\"\"#)\n)\n----\n\n[[output-chordscombos]]\n=== Output chords/combos\n\n**Reference**\n\nPrefixing a known key name with the following strings\nwill output the key alongside the specified modifier.\nMultiple prefixes can be combined to add more modifiers\nto the same key output.\nDuplicate prefixes are not allowed.\n\n[cols=\"1,6\"]\n|===\n| `+C-+`\n| Left Control\n\n| `+RC-+`\n| Right Control\n\n| `+A-+`\n| Left Alt\n\n| `+RA-+`\n| Right Alt, also known as AltGr\n\n| `+AG-+`\n| Also means Right Alt/AltGr\n\n| `+S-+`\n| Left Shift\n\n| `+RS-+`\n| Right Shift\n\n| `+M-+`\n| Left Meta\n\n| `+RM-+`\n| Right Meta\n|===\n\nA special behaviour of output chords is that if another key is pressed,\nall of the chord keys will be released\nbefore the newly pressed key action activates.\nThe modifier keys are often not desired for subsequent actions\nand without this behaviour,\nrapid typing can result in undesired modified key presses.\nIf you want keys to remain pressed, use <<multi>> instead.\n\n**Description**\n\nYou may want to remap a key to automatically be pressed in combination with\nmodifiers such as Control or Shift.\nOutput chords are a way for you to achieve this.\n\nOutput chords are typically used do one-off actions such as:\n\n- type a symbol, e.g. `S-1` to output `!` for the US layout.\n- type an accented character,\ne.g. `RA-a` to output `á` for the US international layout.\n- do a special action like `C-c` to send `SIGTERM` in the terminal\n\nIt should be noted that output chords are not usable in all configuration items.\nIf you get an unknown key error where you expected an output chord to be usable,\nyou must split the output chord into its component keys.\nFor example, `+(unmod C-l)+` is an error;\ninstead you should use `+(unmod lctl l)+`.\n\nThe output chord prefix strings are:\n\n* `+C-+`: Left Control (also `+‹⎈+` `+‹⌃+` or without the `+‹+` side indicator)\n* `+RC-+`: Right Control (also `+⎈›+` `+⌃›+`)\n* `+A-+`: Left Alt (also `+‹⎇+` `+‹⌥+` or without the `+‹+` side indicator))\n* `+RA-+`: Right Alt, a.k.a. AltGr (also `+AG+` `+⎇›+` `+⌥›+`)\n* `+S-+`: Left Shift (also `+‹⇧+` or without the `+‹+` side indicator))\n* `+RS-+`: Right Shift (also `+⇧›+`)\n* `+M-+`: Left Meta, a.k.a. Windows, GUI, Command, Super (also `+‹⌘+` `+‹❖+` `+‹◆+` or without the `+‹+` side indicator))\n* `+RM-+`: Right Meta (also `+⌘›+` `+❖›+` `+◆›+`)\n\n.Example:\n[source]\n----\n(defalias\n  ;; Type exclamation mark (US layout)\n  ex! S-1\n  ;; Ctrl+C: send SIGINT to a Linux terminal program\n  int C-c\n  ;; Win+Tab: open Windows' Task View\n  tsk M-tab\n  ;; Ctrl+Shift+(C|V): copy or paste from certain terminal programs\n  cpy C-S-c\n  pst C-S-v\n)\n----\n\n[[repeat-key]]\n=== Repeat key\n\n**Reference**\n\n[cols=\"1,5\"]\n|===\n| `rpt`\n| String action that outputs the single most-recently typed key.\n\n| `rpt-any`\n| String action that outputs the most-recently outputted action.\n|===\n\n**Description**\n\nThe action `+rpt+` repeats the most recently typed key. Holding down this key\nwill not repeatedly send the key. The intended use case is to be able to use a\ndifferent finger or even thumb key to repeat a typed key, as opposed to\ndouble-tapping a key.\n\n.Example:\n[source]\n----\n(deflayer has-repeat\n  rpt a s d f\n)\n----\n\nThe `rpt` action only repeats the last key output.\nFor example, it won't output a chord like `ctrl+c`\nif the previous key pressed was `C-c`.\nThe `rpt` action will only output `c` in this case.\n\nThere is a variant `rpt-any`\nwhich will repeat any previous action\nand would output `ctrl+c` in the example case.\n\n----\n(deflayer has-repeat-any\n  rpt-any a s d f\n)\n----\n\n[[release-a-key-or-layer]]\n=== Release a key or layer\n\n**Reference**\n\n[cols=\"1,2\"]\n|===\n| `(release-key $key)`\n| List action that releases the defined key from output actions.\nNotably this does not act on key inputs.\n\n| `(release-layer $layer-name)`\n| List action that releases `layer-while-held` activations\nfor the given layer name.\n|===\n\n**Description**\n\nYou can release a held key or layer via these actions:\n\n* `release-key` or `key↑`: release a key, accepts `defsrc` compatible names\n* `release-layer` or `layer↑`: release a while-held layer\n\nA lower-level detail of these actions is that they operate on output states\nas opposed to virtually releasing an input key.\nThis does have some practical significance.\nFor example, if the action `(macro-repeat a 50)` were on the `a` key,\nactivating `(release-key a)` will not stop the repeating macro.\n\nAn example practical use case for `release-key` is seen in the `multi` section\ndirectly below.\n\nThere is currently no known practical use case for\n`release-layer`, but it exists nonetheless.\n\n[[multi]]\n=== multi\n\n**Reference**\n\nActivate multiple actions in sequence.\n\n.Syntax:\n[source]\n----\n(multi $action1 $action2 ... $actionN)\n----\n\n[cols=\"1,3\"]\n|===\n| `$action`\n| An output action.\n|===\n\n**Description**\n\nThe `+multi+` action executes multiple keys or actions in order but also\nsimultaneously. It accepts one or more actions.\n\nAn example use case is to press the \"Alt\" key while also activating another\nlayer.\n\nIn the example below, holding the physical \"Alt\" key will result in a held\nlayer being activated while also holding \"Alt\" itself. The held layer operates\nnearly the same as the standard keyboard, so for example the sequence (hold\nAlt)+(Tab+Tab+Tab) will work as expected. This is in contrast to having a layer\nwhere `tab` is mapped to `A-tab`, which results in repeated press+release of\nthe two keys and has different behaviour than expected. Some special keys will\nrelease the \"Alt\" key and do some other action that requires \"Alt\" to be\nreleased. In other words, the \"Alt\" key serves a dual purpose of still\nfulfilling the \"Alt\" key role for some button presses (e.g. Tab), but also as a\nnew layer for keys that aren't typically used with \"Alt\" to have added useful\nfunctionality.\n\n[source]\n----\n(defalias\n  atl (multi alt (layer-while-held alted-with-exceptions))\n  lft (multi (release-key alt) left) ;; release alt if held and also press left\n  rgt (multi (release-key alt) rght) ;; release alt if held and also press rght\n)\n\n(defsrc\n  alt  a    s    d    f\n)\n\n(deflayer base\n  @atl _    _    _    _\n)\n\n(deflayer alted-with-exceptions\n  _    _    _    @lft @rgt\n)\n----\n\nWARNING: This action can sometimes behave in surprising ways\nwith regards to simultaneity and order of actions.\nFor example, an action like `(multi sldr ')` will not behave as expected.\nDue to implementation details, `sldr` will activate after the `'`\neven though it is listed before.\nThis example could instead be written as `(macro sldr 10 ')`,\nand that would work as intended.\nIt is recommended to avoid `multi` if it can be replaced\nwith a different action like `macro` or an output chord.\n\n==== reverse-release-order\n\n**Reference**\n\nString item that can be used inside of `(multi ...)`\nto reverse the release order of any keys that were pressed\nas part of `multi`.\n\n.Syntax:\n[source]\n----\n(multi ... reverse-release-order)\n----\n\n**Description**\n\nWithin `multi` you can use include `reverse-release-order`\nto do what the action states: reverse the typical release order from\nif you have multiple keys in multi.\n\nFor example, pressing then releasing a key with the action:\n`(multi a b c)` would press a b c in the stated order\nand then release a b c in the stated order.\nChanging it to `(multi a b c reverse-release-order)`\nwould press a b c in the stated order\nand then release c b a in the stated order.\n\n.Example:\n[source]\n----\n(defalias\n  S-a-reversed (multi lsft a reverse-release-order)\n)\n----\n\n[[mouse-actions]]\n=== Mouse actions\n\nYou can click the left, middle, and right buttons using kanata actions, do\nvertical/horizontal scrolling, and move the mouse.\n\n[[mouse-buttons]]\n==== Mouse buttons\n\n**Reference**\n\nYou can activate mouse actions with the string actions below.\n\n[cols=\"1,5\"]\n|===\n| `mlft`\n| Hold left mouse button.\n\n| `mmid`\n| Hold middle mouse button.\n\n| `mrgt`\n| Hold right mouse button.\n\n| `mfwd`\n| Hold forward mouse button.\n\n| `mbck`\n| Hold backward mouse button.\n\n| `mltp`\n| Tap left mouse button.\n\n| `mmtp`\n| Tap middle mouse button.\n\n| `mrtp`\n| Tap right mouse button.\n\n| `mftp`\n| Tap forward mouse button.\n\n| `mbtp`\n| Tap backward mouse button.\n|===\n\nIn Linux and Windows,\nthe hold actions can be used within `defsrc` and `deflayermap`\nto remap mouse buttons like keyboard keys.\n\nNOTE:\nOn Windows, the Kanata process must be restarted\nfor it to begin or to stop handling mouse events;\nchanging defsrc then live-reloading will not\nbegin handling mouse events\nif defsrc previously did not have any mouse events in defsrc.\n\n**Description**\n\nThe mouse button actions are:\n\n* `mlft`: left mouse button\n* `mmid`: middle mouse button\n* `mrgt`: right mouse button\n* `mfwd`: forward mouse button\n* `mbck`: backward mouse button\n\nThe mouse button will be held while the key mapped to it is held.\nOnly on Linux and Windows,\nthe above actions are also usable in `defsrc`\nto enable remapping specified mouse actions in your layers,\nlike you would with keyboard keys.\n\nIf there are multiple mouse click actions within a single multi action, e.g.\n\n`+(multi mrgt mlft)+`\n\nthen all the buttons except the last will be clicked then unclicked. The last\nbutton will remain held until key release. In the example above, pressing then\nreleasing the key mapped to this action will result in the following event\nsequence:\n\n. press key mapped to `+multi+`\n. click right mouse button\n. unclick right mouse button\n. click left mouse button\n. release key mapped to `+multi+`\n. release left mouse button\n\nThere are variants of the standard mouse buttons which \"tap\" the button. Rather\nthan holding the button while the key is held, a mouse click will be\nimmediately followed by the release. Nothing happens when the key is released.\nThe actions are as follows:\n\n* `mltp`: tap left mouse button\n* `mmtp`: tap middle mouse button\n* `mrtp`: tap right mouse button\n* `mftp`: tap forward mouse button\n* `mbtp`: tap bacward mouse button\n\n[[mouse-wheel]]\n==== Mouse wheel\n\n**Reference**\n\nThe `mwheel-*` actions allow you to emulate a mouse wheel.\nHolding the action will repeatedly scroll\naccording to the action configuration.\n\n.Syntax:\n[source]\n----\n(mwheel-$variant $interval $distance)\n----\n\n[cols=\"1,4\"]\n|===\n| `$variant`\n| One of `up down left right` representing the scroll direction to use.\n\n| `$interval`\n| Number of milliseconds between scroll actions.\n\n| `$distance`\n| Distance to travel per activation.\nThe number `120` represents a complete notch on\nstandard resolution mice and in some environments,\n120 or a multiple of it should be what is used.\n|===\n\nYou may use these key names within `defsrc`\nto remap scroll events as if they were keys,\ncorresponding to up, down, left, right respectively:\n`mwu`, `mwd`, `mwl`, `mwr`.\n\nThe remapping of mouse events is only effective on Linux and Windows.\n\nNOTE:\nOn Windows, the Kanata process must be restarted\nfor it to begin or to stop handling mouse events;\nchanging defsrc then live-reloading will not\nbegin handling mouse events\nif defsrc previously did not have any mouse events in defsrc.\n\n**Description**\n\nThe mouse wheel actions are:\n\n* `mwheel-up` or `🖱☸↑`: vertical scroll up\n* `mwheel-down` or `🖱☸↓`: vertical scroll down\n* `mwheel-left` or `🖱☸←`: horizontal scroll left\n* `mwheel-right` or `🖱☸→`: horizontal scroll right\n\nAll of these actions accept two number strings. The first is the interval\n(unit: ms) between scroll actions. The second number is the distance\n(unit: arbitrary). In both Linux and Windows, 120 distance units is equivalent\nto a notch movement on a physical wheel. You can play with the parameters to\nsee what feels correct to you. Both numbers must be in the range [1,65535].\n\nNOTE: In Linux, not all desktop environments support the `REL_WHEEL_HI_RES` event.\nIf this is the case for yours,\nit will likely be a better experience to use a distance value that is a multiple of 120.\n\nOn Linux and Windows, you can also choose to read from a mouse device.\nWhen doing so, using the `mwu`, `mwd`, `mwl`, `mwr` key names in `defsrc`\nallow you to remap the mouse scroll up/down/left/right actions like you would\nwith keyboard keys.\n\nNOTE: If you are using a high-resolution mouse in Linux,\nonly a full \"notch\" of the scroll wheel will activate the action.\n\nNOTE: If you are using a high-resolution mouse with Interception,\nyou will probably get way more events than you intended.\n\n[[mouse-wheel-inertial]]\n===== Mouse wheel: inertial variants\n\n**Reference**\n\nThe `mwheel-accel-*` actions allow you to emulate a mouse wheel,\nwith \"inertial\" properties,\nmeaning scroll speed accelerates while active and decelerates when not.\nYou can adjust factors of acceleration and deceleration.\n\n.Syntax:\n[source]\n----\n(mwheel-accel-$variant $initial-velocity $maximum-velocity $acceleration-multiplier $deceleration-multiplier)\n----\n\n[cols=\"2,3\"]\n|===\n| `$variant`\n| One of `up down left right` representing the scroll direction to use.\n\n| `$initial-velocity`\n| Starting scroll speed, in arbitrary units.\nSuggested starting point: `3`.\n\n| `$maximum-velocity`\n| Starting scroll speed, in arbitrary units.\nSuggested starting point: `1200`.\n\n| `$acceleration-multiplier`\n| Determines acceleration rate of scroll speed.\nSuggested starting point: `1.15`.\n\n| `deceleration-multiplier`\n| Determines deceleration rate of scroll speed.\nSuggested starting point: `0.93`.\nSetting to 0 will make scroll stop abruptly.\n|===\n\n**Description**\n\nAn alternative scrolling action can be used via `mwheel-accel-*`.\nThis action has \"inertial\" behaviour which you may prefer.\n\nThe recommended starting point for the parameters are as below:\n\n.Example configuration:\n[source]\n----\n(defvar\n  mw-initial-v 3\n  mw-maximum-v 1200\n  mw-accel 1.15\n  mw-decel 0.93)\n(defalias\n  mwu (mwheel-accel-up   $mw-initial-v $mw-maximum-v $mw-accel $mw-decel)\n  mwd (mwheel-accel-down $mw-initial-v $mw-maximum-v $mw-accel $mw-decel))\n----\n\n[[mouse-movement]]\n==== Mouse movement\n\n**Reference**\n\nThe `movemouse-*` actions allow you to move the mouse cursor.\nHolding the action will repeatedly move the cursor\naccording to the configuration.\n\n.Syntax:\n[source]\n----\n(movemouse-$variant $interval $distance)\n----\n\n[cols=\"1,4\"]\n|===\n| `$variant`\n| One of `up down left right` representing the direction to move.\n\n| `$interval`\n| Number of milliseconds between move activations.\n\n| `$distance`\n| Distance to travel per activation in unit of pixels.\n|===\n\nThere is a move mouse variant that increases distance per activation\nat a constant rate until a maximum is reached.\n\n.Syntax:\n[source]\n----\n(movemouse-accel-$variant $interval $acceleration-time $min $max)\n----\n\n[cols=\"1,4\"]\n|===\n| `$variant`\n| One of `up down left right` representing the direction to move.\n\n| `$interval`\n| Number of milliseconds between move activations.\n\n| `$acceleration-time`\n| Number of milliseconds until max distance per activation is reached.\n\n| `$min`\n| Initial distance to travel per activation in unit of pixels.\n\n| `$max`\n| Maximum distance to travel per activation in unit of pixels.\n|===\n\n**Description**\n\nThe mouse movement actions are:\n\n* `movemouse-up` or `🖱↑`\n* `movemouse-down` or `🖱↓`\n* `movemouse-left` or `🖱←`\n* `movemouse-right` or `🖱→`\n\nSimilar to the mouse wheel actions, all of these actions accept two number strings.\nThe first is the interval (unit: ms) between movement actions and the second number\nis the distance (unit: pixels) of each movement.\n\nThe following are variants of the above mouse movements that apply linear mouse\nacceleration from the minimum distance to the maximum distance as the mapped key is held.\n\n* `movemouse-accel-up` or `🖱accel↑`\n* `movemouse-accel-down` or `🖱accel↓`\n* `movemouse-accel-left` or `🖱accel←`\n* `movemouse-accel-right` or `🖱accel→`\n\nAll these actions accept four number strings. The first number is the\ninterval (unit: ms) between movement actions. The second number is the time it\ntakes (unit: ms) to linearly ramp up from the minimum distance to the maximum\ndistance. The third and fourth numbers are the minimum and maximum distances\n(unit: pixels) of each movement.\n\nThere is a toggable defcfg option related to `movemouse-accel` - <<movemouse-inherit-accel-state>>. You might want to enable it, especially if you're coming from QMK.\n\n[[set-mouse]]\n==== Set absolute mouse position\n\nThe action `setmouse` or `set🖱` sets the absolute mouse position.\n\nWARNING: This is not supported on Linux.\nFor an interesting keyboard-centric mouse solution in Linux,\ntry looking at\nhttps://github.com/rvaiya/warpd[warpd].\n\nThis list action takes two parameters which are `x` and `y` positions\nof the absolute movement.\n\nThe coordinate system is platform-specific:\n\n* **macOS**: Pixel coordinates. `0,0` is the top-left of the main display,\n  maximum values are your screen resolution (e.g., `1920,1080`).\n* **Windows**: Normalized coordinates from `0,0` (top-left)\n  to `65535,65535` (bottom-right). Multiple monitors are treated as one virtual desktop.\n\nExperimentation will be needed to find the correct values for your setup.\n\n[[mouse-speed]]\n==== Modify the speed of mouse movements\n\nThe action `movemouse-speed` or `🖱speed` modifies the speed at which `movemouse` and\n`movemouse-accel` function at runtime. It does this by expanding or shrinking\n`min_distance` and `max_distance` while the action key is pressed.\n\nThis action accepts one number (unit: percentage) by which the\nmouse movements will be accelerated.\n\nWARNING: Due to the nature of pixels being whole numbers, some values such as\n33 may not result in an exact third of the distance.\n\n.Example:\n[source]\n----\n(defalias\n  fst (movemouse-speed 200)\n  slw (movemouse-speed 50)\n)\n----\n\n[[mouse-all-actions-example]]\n==== Mouse all actions example\n\n[source]\n----\n(defalias\n  mwu (mwheel-up 50 120)\n  mwd (mwheel-down 50 120)\n  mwl (mwheel-left 50 120)\n  mwr (mwheel-right 50 120)\n\n  ms↑ (movemouse-up 1 1)\n  ms← (movemouse-left 1 1)\n  ms↓ (movemouse-down 1 1)\n  ms→ (movemouse-right 1 1)\n\n  ma↑ (movemouse-accel-up 1 1000 1 5)\n  ma← (movemouse-accel-left 1 1000 1 5)\n  ma↓ (movemouse-accel-down 1 1000 1 5)\n  ma→ (movemouse-accel-right 1 1000 1 5)\n\n  sm (setmouse 32228 32228)\n\n  fst (movemouse-speed 200)\n)\n\n(deflayer mouse\n  _    @mwu @mwd @mwl @mwr _    _    _    _    _    @ma↑ _    _    _\n  _    pgup bck  _    fwd  _    _    _    _    @ma← @ma↓ @ma→ _    _\n  _    pgdn mlft _    mrgt mmid _    mbck mfwd _    @ms↑ _    _\n  @fst _    mltp _    mrtp mmtp _    mbtp mftp @ms← @ms↓ @ms→\n  _    _    _              _              _    _    _\n)\n----\n\n[[tap-dance]]\n=== tap-dance\n\n**Reference**\n\nThe `tap-dance` action allows performing different actions\nbased on number of consecutive taps of the same key.\n\n.Syntax:\n[source]\n----\n(tap-dance $timeout $action-list)\n----\n\n[cols=\"1,4\"]\n|===\n| `$timeout`\n| Number of milliseconds after which the tap-dance ends.\n\n| `$action-list`\n| A list of actions that can be selected, ordered by number of taps.\n|===\n\nThe `tap-dance-eager` variant will eagerly perform actions.\nUse of `macro` and `bspc` can help to backtrack for the 2nd tap onwards.\n\n.Syntax:\n[source]\n----\n(tap-dance-eager $timeout $action-list)\n----\n\n**Description**\n\nThe `+tap-dance+` action allows repeated tapping of a key to result in\ndifferent actions. It is followed by a timeout (unit: ms) and a list\nof keys or actions. Each time the key is pressed, its timeout will reset. The\naction will be chosen if one of the following events occur:\n\n* the timeout expires\n* a different key is pressed\n* the key is repeated up to the final action\n\nYou may put normal keys or other actions in `+tap-dance+`.\n\n.Example:\n[source]\n----\n(defalias\n  ;; 1 tap : \"A\" key\n  ;; 2 taps: Control+C\n  ;; 3 taps: Switch to another layer\n  ;; 4 taps: Escape key\n  td (tap-dance 200 (a C-c (layer-switch l2) esc))\n)\n----\n\nThere is a variant of `tap-dance` with the name `tap-dance-eager`. The variant\nis parsed identically but the difference is that it will activate every\naction in the sequence as the taps progress.\n\nIn the example below, repeated taps will, in order:\n\n1. type `a`\n2. erase the `a` and type `bb`\n3. erase the `bb` and type `ccc`\n\n[source]\n----\n(defalias\n  td2 (tap-dance-eager 500 (\n    (macro a) ;; use macro to prevent auto-repeat of the key\n    (macro bspc b b)\n    (macro bspc bspc c c c)\n  ))\n)\n----\n\n[[one-shot]]\n=== one-shot\n\n**Reference**\n\nActivate keys or layers for a time without keeping the input key held,\nfor one subsequent key.\nActivating other one-shot actions,\nwhile one or more are already active,\nwill reset the timeout,\nand overlap the one-shot actions.\n\n\n.Syntax:\n[source]\n----\n($one-shot-variant $timeout $action)\n----\n\nValues for `$variant`:\n[cols=\"1,3\"]\n|===\n| `one-shot-press`\n| End on the first press of another key.\nThis is also the variant selected by the name `one-shot`.\n\n| `+one-shot-release+`\n| End on the first release of a newly pressed key.\n\n| `+one-shot-press-pcancel+`\n| End on the first press of another key\n  or on re-press of this key, or of another active one-shot key\n\n| `+one-shot-release-pcancel+`\n| End on the first release of a newly pressed key\n  or on re-press of this key, or of another active one-shot key.\n|===\n\nOther items:\n[cols=\"1,3\"]\n|===\n| `$timeout`\n| Number of milliseconds after which\nif not deactivated due to user input,\none-shot will deactivate on its own.\n\n| `$action`\n| Layer action, key, or output chord.\n|===\n\n**Description**\n\nThe `+one-shot+` action is similar to \"sticky keys\", if you know what that is.\nThis activates an action or key until either the timeout expires or a different\nkey is used. The `+one-shot+` action must be followed by a timeout (unit:\nms) and another key or action.\n\nSome of the intended use cases are:\n\n* press a modifier for exactly one following key press\n* switch to another layer for exactly one following key press\n\nIf a `+one-shot+` key is held then it will act as the regular key. E.g. holding\na key assigned with `+@os2+` in the example below will keep Left Shift held for\nevery key, not just one, as long as it's still physically pressed.\n\nPressing multiple `+one-shot+` keys in a row within the timeout will combine\nthe actions of those keys and reset the timeout to the value of the most\nrecently pressed `+one-shot+` key.\n\nThere are four variants of the `+one-shot+` action:\n\n- `+one-shot-press+` or `+one-shot↓+`:\n  end on the first press of another key\n- `+one-shot-release+` or `+one-shot↑+`:\n  end on the first release of another key\n- `+one-shot-press-pcancel+` or `+one-shot↓⤫+`:\n  end on the first press of another key\n  or on re-press of another active one-shot key\n- `+one-shot-release-pcancel+` or `+one-shot↑⤫+`:\n  end on the first release of another key\n  or on re-press of another active one-shot key\n\nIt is important to note that the first activation of a one-shot key\ndetermines the behaviour with regards to the 4 variants\nfor all subsequent one-shot key activations,\neven if a following one-shot key has a different configuration\nthan the initial key pressed.\n\nThe default name `+one-shot+` corresponds to `+one-shot-press+`.\n\nNOTE: When using one-shot with keys that will trigger defoverrides,\nyou will likely want to adjust <<override-release-on-activation>> to yes in `defcfg`.\n\n.Example:\n[source]\n----\n(defalias\n  os1 (one-shot 500 (layer-while-held another-layer))\n  os2 (one-shot-press 2000 lsft)\n  os3 (one-shot-release 2000 lctl)\n  os4 (one-shot-press-pcancel 2000 lalt)\n  os5 (one-shot-release-pcancel 2000 lmet)\n)\n----\n\n[[one-shot-pause-processing]]\n==== one-shot-pause-processing\n\n**Reference**\n\nPause `one-shot` processing of new input keypresses for a time,\nto allow actions that are not intended to consume `one-shot`\nto take place.\n\n.Syntax:\n[source]\n----\n(one-shot-pause-processing $time)\n----\n\n[cols=\"1,5\"]\n|===\n| `time`\n| Number of milliseconds to ignore processing.\nSomething notable is that one virtual key press or releas\n(tap is a separate press and subsequent release)\nwill take 1ms to process.\nIf using virtual keys this number must be larger\nthan the number of virtual key events that are taking place.\n|===\n\n**Description**\n\nThe `one-shot-pause-processing` list action allows you to pause the\nkey press processing of one-shot activations.\nAn example of when this is useful the following sequence:\n\n- Activate a layer-while-held\n- Activate a one-shot action on that layer\n- Release the layer-while-held key, which has an `(on-release ...)` action\n  associated with it.\n- The on-release action is not intended to consume one-shot activations\n\nIn the scenario above, by default the on-release activation\nwould trigger deactivation of one-shot;\nthus the pause processing action must be used to stop this from happening.\n\n[[tap-hold]]\n=== tap-hold\n\nWARNING: The `tap-hold` action and all variants can behave unexpectedly on Linux\nwith respect to repeat of antecedent key presses.\nThe full context is in https://github.com/jtroo/kanata/discussions/422[discussion #422].\nIn brief, the workaround is to use `tap-hold` inside of <<multi,multi>>,\ncombined with another key action that behaves as a no-op like `f24`. +\nExample: `(multi f24 (tap-hold ...))`.\nIf multiple `tap-hold` actions may be pressed subsequently,\nall using the `f24` workaround,\nyou may need to release the `f24` within the same `multi`\nto avoid repeats from one double-tapped `tap-hold` action\nfollowed by another, different `tap-hold` action.\nExample:\n`(defvirtualkeys relf24 (release-key f24)) ... (multi f24 (tap-hold ...) (macro 5 (on-press tap-vkey relf24)))`\n\n**Reference**\n\nThe `tap-hold` action lets you activate different actions\ndepending it a key is tapped or held.\n\n.Syntax:\n[source]\n----\n(tap-hold $tap-repress-timeout $hold-timeout $tap-action $hold-action)\n----\n\n[cols=\"1,4\"]\n|===\n| `$tap-repress-timeout`\n| Number of milliseconds for the window that a tap into re-press with hold\nresults in the `$tap-action` being held.\n\n| `$hold-timeout`\n| Number of milliseconds after which the `$hold-action` activates.\nReleasing the key before this elapses\nresults in `$tap-action` activating.\n\n| `$tap-action`\n| Action to activate when the input is determined to be a \"tap\".\n\n| `$hold-action`\n| Action to activate when the input is determined to be a \"hold\".\n|===\n\n.Variants:\n----\n(tap-hold-press $tap-repress-timeout $hold-timeout $tap-action $hold-action)\n(tap-hold-release $tap-repress-timeout $hold-timeout $tap-action $hold-action)\n(tap-hold-press-timeout $tap-repress-timeout $hold-timeout $tap-action $hold-action $timeout-action)\n(tap-hold-release-timeout $tap-repress-timeout $hold-timeout $tap-action $hold-action $timeout-action [?reset-timeout-on-press])\n(tap-hold-release-keys $tap-repress-timeout $hold-timeout $tap-action $hold-action $tap-keys)\n(tap-hold-release-tap-keys-release $tap-repress-timeout $hold-timeout $tap-action $hold-action $tap-trigger-keys-on-press $tap-trigger-keys-on-press-then-release)\n(tap-hold-except-keys $tap-repress-timeout $hold-timeout $tap-action $hold-action $tap-keys)\n(tap-hold-tap-keys $tap-repress-timeout $hold-timeout $tap-action $hold-action $tap-keys)\n(tap-hold-opposite-hand $timeout $tap-action $hold-action [options...])\n(tap-hold-order $tap-repress-timeout $buffer-ms $tap-action $hold-action [options...])\n----\n\n[cols=\"1,2\"]\n|===\n| `tap-hold-press`\n| Activate `$hold-action` early if held and another input key is pressed.\n\n| `tap-hold-release`\n| Activate `$hold-action` early if held and another input key is pressed and released.\n\n| `tap-hold-press-timeout`\n| Activate `$hold-action` if held and another input key is pressed.\nIf the defined timeout elapses, `$timeout-action` will activate.\n\n| `tap-hold-release-timeout`\n| Activate `$hold-action` early if held and another input key is pressed and released.\nIf the defined timeout elapses, `$timeout-action` will activate.\nOptionally include `reset-timeout-on-press` to reset timeout duration on a new press,\ngiving more time for a subsequent release to activate hold instead of timeout.\n\n| `tap-hold-release-keys`\n| Activate `$hold-action` early if held and another input key is pressed and released.\nThe `$tap-keys` parameter is a list of key names.\nActivates `$tap-action` early if a key within `$tap-keys` is pressed before hold activates.\n\n| `tap-hold-release-tap-keys-release`\n| Activate `$hold-action` early if held and another input key is pressed and released.\nThe `$tap-keys-...` parameters are lists of key names.\nActivate `$tap-action` early\nif a key within `$tap-trigger-keys-on-press`\nis pressed before hold activates\nActivate `$tap-action` early\nif a key within `$tap-trigger-keys-on-press-then-release`\nis pressed then released before hold activates.\n\n| `tap-hold-except-keys`\n| The `$tap-keys` parameter is a list of key names.\nActivates `$tap-action` if a key within `$tap-keys` is pressed\nor if the action key is released before hold timeout.\nNo key is ever output until the action key is released\nor another key is pressed,\nwhich differs from the default `tap-hold` behaviour.\n\n| `tap-hold-tap-keys`\n| The `$tap-keys` parameter is a list of key names.\nActivates `$tap-action` early if a key within `$tap-keys` is pressed before hold activates.\nUnlike `tap-hold-release-keys`, does NOT activate `$hold-action` early\nwhen other keys are pressed and released.\nWaits for full `$hold-timeout` before activating `$hold-action`.\nThis is useful for home row mods where fast typing should not trigger modifiers.\n\n| `tap-hold-opposite-hand`\n| Resolves to `$hold-action` when a key from the opposite hand (per `defhands`) is pressed.\nRequires a `defhands` directive. Supports list-form options for fine-grained control.\nSee <<defhands and tap-hold-opposite-hand>> below.\n\n| `tap-hold-order`\n| Resolves purely by key release order, with no timeout.\nIf the tap-hold key is released before the other key, activates `$tap-action`;\nif the other key is released first (while the tap-hold key is still held), activates `$hold-action`.\n`$buffer-ms` is a grace period after the tap-hold key is pressed during which\nrelease-order logic is ignored so that fast typing resolves as tap.\nOptionally, `(require-prior-idle <ms>)` can short-circuit to tap when a different key\nwas pressed within that many milliseconds before the tap-hold key;\n`require-prior-idle` checks prior typing activity before release-order logic begins,\nwhereas `buffer-ms` creates an unconditional tap-only window after the key is pressed.\nThis option is available on all tap-hold variants; see <<tap-hold-require-prior-idle>>.\n|===\n\nAll `tap-hold` variants support an optional trailing `(require-prior-idle <ms>)` option\nto override the global <<tap-hold-require-prior-idle>> setting for that specific action.\nUse `(require-prior-idle 0)` to disable idle detection for a specific key.\n\n**Description**\n\nThe `+tap-hold+` action allows you to have one action/key for a \"tap\" and a\ndifferent action/key for a \"hold\". A tap is a rapid press then release of the\nkey whereas a hold is a long press.\n\nNOTE: for more discussion on tap-hold,\nyou may want to have a look at this GitHub discussion:\nhttps://github.com/jtroo/kanata/discussions/1455[link to discussion].\n\nThe action takes 4 parameters in the listed order:\n\n. tap repress timeout (unit: ms)\n. hold timeout (unit: ms)\n. tap action\n. hold action\n\nThe tap repress timeout is the number of milliseconds within which a rapid\npress+release+press of a key will result in the tap action being held instead\nof the hold action activating.\n\n.Tap repress timeout in more detail\n[%collapsible,indent=4]\n====\nThe way a `tap-hold` action works with respect to the tap repress timeout\nis often unclear to newcomers.\nTo make it concrete, the output event sequence of the `tap-hold` action\n`(tap-hold $tap-repress-timeout 200 a lctl)`\nfor varying values of `$tap-repress-timeout`\nwith a fixed input event sequence will be described.\n\nThe input event sequence is:\n\n- press\n- 50 ms elapses\n- release\n- 50 ms elapses\n- press\n- 300 ms elapses\n- release\n\nWith `(defvar $tap-repress-timeout 0)`, the output event sequence is:\n\n- 50 ms elapses\n- press `a`\n- release `a`\n- 250 ms elapses\n- press `lctl`\n- 100 ms elapses\n- release `lctl`\n\nThe above output sequence is the same for all `$tap-repress-timeout` values\nbetween and including `0` and `99`.\n\nFor a value of `100` or greater for `$tap-repress-timeout`,\nthe output event sequence is instead:\n\n- 50 ms elapses\n- press `a`\n- release `a`\n- 50 ms elapses\n- press `a`\n- 300 ms elapses\n- release `a`\n====\n\nThe hold timeout is the number of milliseconds after which the hold action will\nactivate.\n\nThere are two additional variants of `+tap-hold+`:\n\n* `+tap-hold-press+` or `+tap⬓↓+`\n** If there is a press of a different key, the hold action is activated even if\nthe hold timeout hasn't expired yet\n* `+tap-hold-release+` or `+tap⬓↑+`\n** If there is a press+release of a different key, the hold action is activated\neven if the hold timeout hasn't expired yet\n\nThese variants may be useful if you want more responsive tap-hold keys,\nbut you should be wary of activating the hold action unintentionally.\n\n.Example:\n[source]\n----\n(defalias\n  anm (tap-hold         200 200 a @num) ;; tap: a      hold: numbers layer\n  oar (tap-hold-press   200 200 o @arr) ;; tap: o      hold: arrows layer\n  ech (tap-hold-release 200 200 e @chr) ;; tap: e      hold: chords layer\n)\n----\n\nThere are further additional variants of `tap-hold-press` and `tap-hold-release`:\n\n- `tap-hold-press-timeout` or `tap⬓↓timeout`\n- `tap-hold-release-timeout` or `tap⬓↑timeout`\n\nThese variants take a 5th parameter, in addition to the same 4 as the other\nvariants. The 5th parameter is another action, which will activate if the hold\ntimeout expires as opposed to being triggered by other key actions, whereas the\nnon `-timeout` variants will activate the hold action in both cases.\n\nThe `release` variant also accepts an optional 6th argument,\n`reset-timeout-on-press` which if included changes the timeout behaviour.\nThis flag will make the timeout duration reset if a new key is pressed.\nThis may result in longer timeouts,\nbut can help with more consistent hold activations;\nbecause it may be challenging to release a key in time\nto activate the hold action instead of the timeout action.\n\n- `tap-hold-release-keys` or `tap⬓↑keys`\n\nThis variant takes a 5th parameter which is a list of keys\nthat trigger an early tap\nwhen they are pressed while the `tap-hold-release-keys` action is waiting.\nOtherwise this behaves as `tap-hold-release`.\n\nThe keys in the 5th parameter correspond to the physical input keys,\nor in other words the key that corresponds to `defsrc`.\nThis is in contrast to the `fork` and `switch` actions\nwhich operates on outputted keys, or in other words the outputs\nthat are in `deflayer`, `defalias`, etc. for the corresponding `defsrc` key.\n\n.Example:\n[source]\n----\n(defalias\n  ;; tap: u    hold: misc layer      early tap if any of: (a o e) are pressed\n  umk (tap-hold-release-keys 200 200 u @msc (a o e))\n)\n----\n\n- `tap-hold-release-tap-keys-release`\n\nThis variant behaves nearly the same as `tap-hold-release-keys`,\nbut has another condition with respect to the eager tap.\nIt accepts a second list that activates the eager tap\nif the any key listed within is pressed then released;\nas opposed to only on a press.\n\nThe keys in the 5th and 6th parameters correspond to the physical input keys,\nor in other words the key that corresponds to `defsrc`.\nThis is in contrast to the `fork` and `switch` actions\nwhich operates on outputted keys, or in other words the outputs\nthat are in `deflayer`, `defalias`, etc. for the corresponding `defsrc` key.\n\n.Example:\n[source]\n----\n(defalias\n  ;; tap: u    hold: misc layer      early tap if any of:\n  ;;                                 (z x c v) are pressed, OR\n  ;;                                 (a s d f) are pressed THEN released\n  umk2 (tap-hold-release-tap-keys-release 200 200 u @msc (z x c v) (a s d f))\n)\n----\n\n- `tap-hold-except-keys` or `tap-hold⤫keys`\n\nThis variant takes a 5th parameter which is a list of keys\nthat always trigger a tap\nwhen they are pressed while the `tap-hold-except-keys` action is waiting.\nNo key is ever output until there is either a release of the key or any other\nkey is pressed. This differs from `tap-hold` behaviour.\n\nThe keys in the 5th parameter correspond to the physical input keys,\nor in other words the key that corresponds to `defsrc`.\nThis is in contrast to the `fork` and `switch` actions\nwhich operates on outputted keys, or in other words the outputs\nthat are in `deflayer`, `defalias`, etc. for the corresponding `defsrc` key.\n\n.Example:\n[source]\n----\n(defalias\n  ;; tap: u    hold: misc layer      always tap if any of: (a o e) are pressed\n  umk (tap-hold-except-keys 200 200 u @msc (a o e))\n)\n----\n\n- `tap-hold-tap-keys` or `tap⬓tapkeys`\n\nThis variant takes a 5th parameter which is a list of keys\nthat trigger an early tap when they are pressed.\nUnlike `tap-hold-release-keys`, pressing and releasing other keys\ndoes NOT activate hold early - the full hold timeout is always waited.\nThis is useful for home row mods where fast typing should not trigger modifiers.\n\nThe keys in the 5th parameter correspond to the physical input keys,\nor in other words the key that corresponds to `defsrc`.\n\n.Example:\n[source]\n----\n(defalias\n  ;; tap: a    hold: lsft    early tap if any of: (s d f) are pressed\n  ;; other keys do NOT trigger early hold\n  ath (tap-hold-tap-keys 200 200 a lsft (s d f))\n)\n----\n\n==== defhands and tap-hold-opposite-hand\n\n`defhands` assigns physical keys to `left` / `right` hand groups.\n`tap-hold-opposite-hand` uses this to resolve hold when the next key\nis on the opposite hand — reducing misfires compared to manual key lists.\n\n[source]\n----\n(defhands\n  (left  q w e r t a s d f g z x c v b)\n  (right y u i o p h j k l ; n m , . /))\n\n(defalias\n  fctl (tap-hold-opposite-hand 180 f lctl))\n----\n\n`defhands` rules: at most one block; each group (`(left ...)`/`(right ...)`) at most once;\na key cannot appear in both groups; partial definitions (only one hand) are allowed;\nunlisted keys have no hand assignment.\n\n`tap-hold-opposite-hand` takes `$timeout $tap-action $hold-action` followed by\noptional option lists. Most use `(name value)`, while `neutral-keys` uses\nfollowup key atoms: `(neutral-keys spc tab ...)`.\nIt requires `defhands` to be defined.\nInternally it uses the same `HoldTapConfig::Custom` machinery as `tap-hold-release-keys`.\n\n[cols=\"1,1,1,2\"]\n|===\n| Option | Values | Default | Description\n\n| `(timeout <value>)` | `tap`, `hold` | `tap` | Action when hold timeout elapses (optimized for home-row-mod behavior).\n| `(same-hand <value>)` | `tap`, `hold`, `ignore` | `tap` | Action when a same-hand key is pressed.\n| `(neutral-keys key ...)` | one or more key names | (empty) | Keys treated as neutral, overriding their `defhands` assignment.\n| `(neutral <value>)` | `tap`, `hold`, `ignore` | `ignore` | Action when a `neutral-keys` key is pressed.\n| `(unknown-hand <value>)` | `tap`, `hold`, `ignore` | `ignore` | Action when a key not in `defhands` is pressed.\n|===\n\nWhen a value is `ignore`, that key press is skipped and the action\nwaits for the next key or timeout.\n\nExample with options:\n[source]\n----\n(defalias\n  fctl (tap-hold-opposite-hand 180 f lctl\n    (same-hand ignore)\n    (timeout hold)\n    (neutral-keys spc tab)\n    (neutral tap)))\n----\n\nSee `cfg_samples/opposite-hand-hrm.kbd` for a full working example.\n\n[[macro]]\n=== macro\n\n**Reference**\n\nThe macro action taps the configured sequence of keys or actions.\nNumbers can be used to delay the sequence by the defined number of milliseconds.\n\n.Syntax:\n[source]\n----\n(macro $macro-action1 $macro-action2 ... $macro-actionN)\n----\n\n[cols=\"1,4\"]\n|===\n| `$macro-action`\n| A delay, key, action within the subset allowed within macros,\nor an output-chord-prefixed list of more macro-actions.\n|===\n\n.Variants:\n----\n(macro-release-cancel ...)\n(macro-cancel-on-press ...)\n(macro-release-cancel-and-cancel-on-press ...)\n(macro-repeat ...)\n(macro-repeat-$cancel-variant ...)\n----\n[cols=\"1,2\"]\n|===\n| `macro-release-cancel`\n| Cancel all active macros if the key is released.\n\n| `macro-cancel-on-press`\n| Cancel all active macros if a different key is pressed.\n\n| `macro-release-cancel-and-cancel-on-press`\n| Cancel all active macros if either the key is released or a different key is pressed.\n\n| `macro-repeat`\n| Repeat the macro while held.\n\n| `macro-repeat-$cancel-variant`\n| Repeat the macro while held.\nCancels the final repeat according the behaviour of one of the variants:\n`release-cancel`, `cancel-on-press`, `release-cancel-and-cancel-on-press`.\n|===\n\n**Description**\n\nThe `+macro+` action will tap a sequence of keys with optional\ndelays. This is different from `+multi+` because in the `+multi+` action,\nall keys are held, whereas in `+macro+`, keys are pressed then released.\n\nThis means that with `+macro+` you can have some letters capitalized and others\nnot. This is not possible with `+multi+`.\n\nThe `+macro+` action accepts one or more keys, some actions, chords, and delays\n(unit: ms).  It also accepts a list prefixed with <<output-chordscombos,output chord>>\nmodifiers where the list is subject to the aforementioned restrictions.\n\nIMPORTANT: The number keys `0-9` will be parsed as millisecond delays\nwhereas in other contexts they would be parsed as key names.\nTo use the numbered keys they must be aliased\nor otherwise use the key names `Digit0-Digit9`.\n\nUp to 4 macros can be active at the same time.\n\nThe actions supported in `+macro+` are:\n\n* <<cmd, cmd>>\n* <<unicode, unicode>>\n* <<mouse-actions,mouse actions>>\n* <<repeat-key,repeat>>\n* <<live-reload,live reload>>\n* <<virtual-keys,virtual key actions>>\n* <<sequences,sequence leader>>\n* <<arbitrary-code,arbitrary keycode>>\n* <<dynamic-macro,dynamic macro>>\n* <<unmod,unmod>>\n\nNOTE: Some of these actions may need short delays between.\nFor example, `(macro a (unmod b) 5 (unmod c) d))`\nneeds the delay of `5` to work correctly.\n\n.Example:\n[source]\n----\n(defalias\n  : S-;\n  8 8\n  0 0\n  🙃 (unicode 🙃)\n\n  ;; Type \"http://localhost:8080\"\n  lch (macro h t t p @: / / 100 l o c a l h o s t @: @8 @0 @8 @0)\n\n  ;; Type \"I am HAPPY my FrIeNd 🙃\"\n  hpy (macro S-i spc a m spc S-(h a p p y) spc m y S-f r S-i e S-n d spc @🙃)\n\n  ;; alt-tab(x3) and alt-shift-tab(x3) with macro\n  tfd (macro A-(tab 200 tab 200 tab))\n  tbk (macro A-S-(tab 200 tab 200 tab))\n)\n----\n\n[[macro-release-cancel]]\n==== macro-release-cancel\n\nThe `macro-release-cancel` variant of the `+macro+` action\nwill cancel all active macros\nupon releasing the key.\nShorter unicode variant: `+macro↑⤫+`.\nThis variant is parsed identically to the non-cancelling version.\nAn example use case for this action is holding down\na key to get different outputs,\nsimilar to tap-dance but one can see which keys are being outputted.\n\nE.g. in the example below, when holding the key, first `1` is typed, then\nreplaced by `!` after 500ms, and finally that is replaced by `@` after another\n500ms. However, if the key is released, the last character typed will remain\nand the rest of the macro does not run.\n\n[source]\n----\n(defalias\n  1 1\n\n  ;; macro-release-cancel to output different characters with visual feedback\n  ;; after holding for different amounts of time.\n  1!@ (macro-release-cancel @1 500 bspc S-1 500 bspc S-2)\n)\n----\n\n[[macro-cancel-on-press]]\n==== macro-cancel-on-press\n\nThe `macro-cancel-on-press` variant of the `macro action`\nenables a cancellation trigger for all active macros including itself,\nwhich is activated when a physical press of any other key happens.\nThe trigger is enabled while the macro is in progress.\n\n[source]\n----\n(defalias\n  1 1\n  1!@ (macro-cancel-on-press @1 500 bspc S-1 500 bspc S-2)\n)\n----\n\n[[macro-release-cancel-and-cancel-on-press]]\n==== macro-release-cancel-and-cancel-on-press\n\nThe `macro-release-cancel-and-cancel-on-press` variant\ncombines the cancel behaviours\nof both the release-cancel and cancel-on-press.\n\n[source]\n----\n(defalias\n  1 1\n  1!@ (macro-release-cancel-and-cancel-on-press @1 500 bspc S-1 500 bspc S-2)\n)\n----\n\n\n[[macro-repeat]]\n==== macro-repeat\n\nThere are further `macro-repeat` variants of the three `macro` actions described previously.\nThese variants repeat while held.\nThe repeat will only occur once all macros have completed,\nincluding the held macro key.\nIf multiple repeating macros are being held simulaneously,\nonly the most recently pressed macro will be repeated.\n\n[source]\n----\n(defalias\n  mr1 (macro-repeat mltp)\n  mr2 (macro-repeat-release-cancel mltp)\n  mr3 (macro-repeat-cancel-on-press mltp)\n  mr4 (macro-repeat-release-cancel-and-cancel-on-press mltp)\n)\n----\n\n[[dynamic-macro]]\n=== dynamic-macro\n\n**Reference**\n\nRecord and replay key inputs.\n\n.Syntax:\n[source]\n----\n(dynamic-macro-record $id)\n(dynamic-macro-play   $id)\n(dynamic-macro-record-stop)\n(dynamic-macro-record-stop-truncate $count)\n----\n\n[cols=\"1,3\"]\n|===\n| `dynamic-macro-record`\n| Record a dynamicro macro which will be saved with the defined `$id`.\n\n| `dynamic-macro-play`\n| Play back a macro saved with the defined `$id`.\n\n| `dynamic-macro-record-stop`\n| Stop and save a macro recording.\nThis can also be achieved by recording a new macro\nor re-pressing record with the same `$id`.\n\n| `dynamic-macro-record-stop-truncate`\n| Stop and save a macro recording while truncating `$count` events\nfrom the end of the recording.\nThis can be useful if the record/stop button is on a different layer.\n|===\n\n**Description**\n\nThe dynamic-macro actions allow for recording and playing key presses. The\ndynamic macro records physical key presses, as opposed to kanata's outputs.\nThis allows the dynamic macro to replicate any action, but it means that if\nthe macro starts and ends on different layers, then the macro might not be\nproperly repeatable.\n\nThe action `dynamic-macro-record` accepts one number (0-65535), which represents\nthe macro ID. Activating this action will begin recording physical key inputs.\nIf `dynamic-macro-record` with the same ID is pressed again, the recording will\nend and be saved. If `dynamic-macro-record` with a different ID is pressed then\nthe current recording will end and be saved, then a new recording with the new\nID will begin.\n\nThe action `dynamic-macro-record-stop` will stop and save any active recording.\nThere is a variant of this:\n`dynamic-macro-record-stop-truncate`\nThis is a list action that takes a single parameter:\nthe number of key actions to remove at the end of a dynamic macro.\nThis variant is useful if the macro stop button is on a different layer.\n\nThe action `dynamic-macro-play` accepts one number (0-65535), which represents\nthe macro ID. Activating this action will play the saved recording of physical\nkeys from a previous `dynamic-macro-record` with the same macro ID, if it exists.\n\nOne can nest dynamic macros within each other, e.g. activate\n`(dynamic-macro-play 1)` while recording with `(dynamic-macro-record 0)`.\nHowever, dynamic macros cannot recurse; e.g. activating `(dynamic-macro-play 0)`\nwhile recording with `(dynamic-macro-record 0)` will be ignored.\n\n.Example:\n[source]\n----\n(defalias\n  dr0 (dynamic-macro-record 0)\n  dr1 (dynamic-macro-record 1)\n  dr2 (dynamic-macro-record 2)\n  dp0 (dynamic-macro-play 0)\n  dp1 (dynamic-macro-play 1)\n  dp2 (dynamic-macro-play 2)\n  dms dynamic-macro-record-stop\n  dst (dynamic-macro-record-stop-truncate 1)\n)\n----\n\n[[caps-word]]\n=== caps-word\n\n**Reference**\n\nThe `caps-word` action puts Kanata into a state where\ntyped keys are automatically shifted by `lsft`.\nThe state persists until terminated by timeout\nor by typing a key that ends the state.\nTyping a non-terminating key refreshes the timeout duration.\n\nThe `-toggle` variants will end the caps-word state\nif pressed while caps-word is active,\nwhereas the re-pressing the standard variants\nwill keep the state active and refresh the timeout duration.\n\n.Syntax:\n[source]\n----\n(caps-word $timeout)\n(caps-word-toggle $timeout)\n(caps-word-custom $timeout $shifted-list $non-terminal-list)\n(caps-word-custom-toggle $timeout $shifted-list $non-terminal-list)\n----\n\n[cols=\"1,4\"]\n|===\n| `$timeout`\n| Number of milliseconds after which the caps-word state ends.\nThe duration is refreshed upon typing a non-terminating character.\n\n| `$shifted-list`\n| List of keys that will be automatically shifted.\n\n| `$non-terminal-list`\n| List of keys that are not shifted\nbut which do not terminate the caps-word state.\n|===\n\n**Description**\n\nThe `caps-word` or `word⇪` action triggers a state where the `lsft` key\nwill be added to the active key list\nwhen a set of specific keys are active.\nThe keys are: `a-z` and `-`, which will be outputted as `A-Z` and `_`\nrespectively when using the US layout.\n\nExamples where this is helpful\nis capitalizing a single important word\nlike in `IMPORTANT!`\nor defining a constant in code\nlike `const P99_99_VALUE: ...`.\nThis has an advantage over the regular caps lock\nbecause it automatically ends\nso it doesn't need to be toggled off manually,\nand it also shifts `-` to `_`\nwhich caps lock does not do.\n\nThe `caps-word` state ends when the keyboard is idle\nfor the duration of the defined timeout (1st parameter),\nor a terminating key is pressed.\nEvery key is a terminating key\nexcept the keys which get capitalized\nand the extra keys in this list:\n\n- `0-9`\n- `kp0-kp9`\n- `bspc del`\n- `up down left rght`\n\nYou can use `caps-word-custom` or `word⇪-custom` instead of `caps-word`\nif you want to manually define which keys are capitalized (2nd parameter)\nand what the extra non-terminal+non-capitalized keys should be (3rd parameter).\n\n.Example:\n[source]\n----\n(defalias\n  cw (caps-word 2000)\n\n  ;; This example is similar to the default caps-word behaviour but it moves the\n  ;; 0-9 keys to the capitalized key list from the extra non-terminating key list.\n  cwc (caps-word-custom\n    2000\n    (a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9)\n    (kp0 kp1 kp2 kp3 kp4 kp5 kp6 kp7 kp8 kp9 bspc del up down left rght)\n  )\n)\n----\n\n==== caps-word-toggle[[caps-word-toggle]]\n\nThere are `-toggle` variants of the `caps-word` actions.\nBy default re-pressing `caps-word` will keep `caps-word` active.\nThe `-toggle` variants will end `caps-word` if it is currently active,\notherwise `caps-word` will be activate as normal.\n\n.Example:\n[source]\n----\n(defalias\n  cwt (caps-word-toggle 2000)\n  cct (caps-word-custom-toggle\n    2000\n    (a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9)\n    (kp0 kp1 kp2 kp3 kp4 kp5 kp6 kp7 kp8 kp9 bspc del up down left rght)\n  )\n)\n----\n\n=== unmod[[unmod]]\n\n**Reference**\n\nThe `unmod` action will deactivate modifier keys while outputting\none or more defined keys.\n\n.Syntax:\n[source]\n----\n(unmod $key1 $key2 ... $keyN)\n(unmod ($mod1 $mod2 ... $modN) $key1 $key2 ... $keyN)\n----\n\n[cols=\"1,5\"]\n|===\n| `$key`\n| A key name to output while unmodded.\n\n| `$mod`\n| By default `unmod` will deactivate all modifier keys.\nAn optional list as the first parameter\nallows specfying a subset of modifiers to deactivate during the action.\n|===\n\n**Description**\n\nThe `unmod` action will release all modifiers temporarily\nand send one or more keys.\nAfter the `unmod` key is released, the released modifiers are pressed again.\nThe affected modifiers are: `lsft,rsft,lctl,rctl,lmet,rmet,lalt,ralt`.\n\nA variant of `unmod` is `unshift` or `un⇧`.\nThis action only releases the `lsft,rsft` keys.\nThis can be useful for forcing unshifted keys while AltGr is still held.\n\nNOTE: In case the modifiers to be undone are not part of `defsrc`,\n<<process-unmapped-keys>> needs to be enabled in `defcfg` in order for their\nstates to be tracked correctly.\n\n.Example:\n[source]\n----\n(defalias\n  ;; holding shift and tapping a @um1 key will still output 1.\n  um1 (unmod 1)\n  ;; dead keys é (as opposed to using AltGr) that outputs É when shifted\n  dké (macro (unmod ') e)\n\n  ;; In ISO German QWERTZ, force unshifted symbols even if shift is held\n  { (unshift ralt 7)\n  [ (unshift ralt 8)\n)\n----\n\nA list may optionally be used as the first parameter of `unmod`.\nThe list must be non-empty and must contain only modifier keys,\nwhich are the keys in the affected modifiers list from earlier in this document section.\n\nWhen this list exists, the action will temporarily release only the keys listed\nrather than all modifiers.\n\n.Example:\n[source]\n----\n(defalias\n\t;; only unshift the alt keys\n\tunalt-a (unmod (lalt ralt) a)\n)\n----\n\n[[fork]]\n=== fork\n\n**Reference**\n\nThe `fork` action allows choosing between a default and an alternate action\nbased on whether specific keys are active. `fork` is the equivalent of the\nbasic key checks in `switch`, using none of the list logic items, i.e. virtual\nkeys are not supported.\n\n.Syntax:\n[source]\n----\n(fork $left-action $right-action $right-trigger-keys)\n----\n\n[cols=\"1,3\"]\n|===\n| `$left-action`\n| Action to activate by default.\n\n| `$right-action`\n| Action to activate if any of `$right-trigger-keys` are active.\n\n| `$right-trigger-keys`\n| List of keys that, if active when fork activates,\ncauses `$right-action` to happen in place of `$left-action`.\n|===\n\nTIP: The keys `nop0-nop9` can be used as no-op outputs that\ncan still be checked within `fork`, unlike what `XX` does.\n\n[[switch]]\n=== switch\n\n**Reference**\n\nThe `switch` action allows conditionally activating 0 or more actions,\ndepending on conditional checks.\n\n.Syntax:\n[source]\n----\n(switch\n  $logic-check1 $action1 $post-activate1\n  $logic-check2 $action2 $post-activate2\n  ...\n  $logic-checkN $actionN $post-activateN)\n----\n\n[cols=\"1,4\"]\n|===\n| `$logic-check`\n| The condition, which if it evaluates to true,\nwill trigger the corresponding action.\n\n| `$action`\n| Action to activate when logic evaluates to true.\n\n| `$post-activate`\n| Valid values are `fallthrough` and `break`.\nWith `fallthrough`, when an action activates\nswitch will continue evaluating further logic checks\nand potentially trigger more actions.\nWith `break`, further actions will not activate.\n|===\n\nThe logic check is a list.\nThe items within the list can either be key names or a special list check.\nA key name item evaluates to true if that key name\nis a currently active key output of Kanata upon activating `switch`.\nThe outer-most list evaluates to true\nif any of the items evaluates to true.\n\n.Syntax of logic check:\n[source]\n----\n($item1 $item2 ... $itemN)\n----\n\n.Syntax of special checks:\n[source]\n----\n(or  $item1 $item2 ... $itemN)\n(and $item1 $item2 ... $itemN)\n(not $item1 $item2 ... $itemN)\n(key-history $key-name $key-recency)\n(key-timing  $key-recency $comparator $time)\n(input         $input-type $key-name)\n(input-history $input-type $key-name $input-recency)\n(layer      $layer-name)\n(base-layer $layer-name)\n----\n\n[cols=\"1,4\"]\n|===\n| `or`\n| Evaluates to true if any `$item` is true.\n\n| `and`\n| Evaluates to true if all of `$item` are true.\n\n| `not`\n| Evaluates to true if all of `$item` are false.\n\n| `key-history`\n| Evaluates to true if the key in the recency slot matches `$key-name`.\nA `$key-recency` of 1 is the most recent key pressed according to Kanata processing.\nThe max recency is 8.\n\n| `key-timing`\n| The valid values for `$comparator` are `less-than` and `greater-than`,\nwith `lt` and `gt` as shorthand if desired.\nThis item evaluates to true if the key with the corresponding recency\nwas pressed — for `lt` more recently than, or for `gt` later than —\nthe defined `$time` with unit milliseconds.\n\n| `input`\n| Evaluates to true if the `$key-name` is currently pressed.\nThe `$input-type` must be either `real` or `virtual`.\nIf using `real`, this will check against the defsrc inputs.\nIf using `virtual`, this will check against virtual key activations.\n\n| `input-history`\n| Evaluates to true if the input in the `$input-recency` slot matches `$key-name`.\nTwo input types use the same history with respect to recency slots.\nA recency of 1 is the most recent input i.e. the input activating `switch` itself.\nThe max recency is 8.\n\n| `layer`\n| Evaluates to true if the active layer matches `$layer-name`.\n\n| `base-layer`\n| Evaluates to true if the most-recently-switched-to layer\nfrom a `layer-switch` action matches `$layer-name`.\n|===\n\n**Description**\n\nConceptually, the `switch` action is similar to <<fork>>\nbut has more capabilities as well as more complexity.\nThe `switch` action accepts multiple cases.\nOne case is a triple of:\n\n- logic check\n- action: to activate if logic check evaluates to true\n- `fallthrough|break`: choose to continue vs. stop evaluating cases\n\nThe default use of the logic check behaves similarly to fork.\n\nFor example, the logic check `(a b c)` will activate the corresponding action\nif any of a, b, or c are currently pressed.\n\nTIP: the keys `nop0-nop9` can be used as no-op outputs that\ncan still be checked within `switch`, unlike what `XX` does.\n\nThe logic check also accepts the boolean operators `and|or|not` to allow more\ncomplex use cases.\n\nThe order of cases matters.\nFor example, if two different cases match the currently pressed keys,\nthe case listed earlier in the configuration will activate first.\nIf the early case uses break, the second case will not activate.\nOtherwise if fallthrough is used,\nthe second case will activate sequentially after the first case.\nThis idea generalizes to more than two cases,\nbut the two case example is hopefully simple and effective enough.\n\n.Example:\n[source]\n----\n(defalias\n  swt (switch\n    ;; case 1\n    ((and a b (or c d) (or e f))) @ac1 break\n    ;; case 2\n    (a b c) @ac2 fallthrough\n    ;; case 3\n    () @ac3 break\n  )\n)\n----\n\nBelow is a description of how this example behaves.\n\n==== Case 1\n\n----\n((and a b (or c d) (or e f))) a break\n----\n\nTranslating case 1's logic check to some other common languages\nmight look like:\n\n----\n(a && b && (c || d) && (e || f))\n----\n\nIf the logic check passes, the action `@ac1` will activate.\nNo other action will activate since `break` is used.\n\n==== Cases 2 and 3\n\n----\n(a b c) c fallthrough\n() b break\n----\n\nCase 2's key check behaves like that of `fork`, i.e.\n\n    (or a b c)\n\nor for some other common languages:\n\n    a || b || c\n\nIf this logic check passes and the case 1 does not pass,\nthe action `@ac2` will activate first.\nSince the logic check of case 3 always passes, `@ac3` will activate next.\n\nIf neither case 1 or case 2 pass their logic checks,\ncase 3 will always activate with `@ac3`.\n\n[[key-history-and-key-timing]]\n==== key-history and key-timing\n\nIn addition to simple keys there are two list items\nthat can be used within the case logic check\nthat compare against your typed key history:\n\n* `key-history`\n* `key-timing`\n\nThe `key-history` item compares the order that keys were typed.\nIt accepts, in order:\n\n* a key\n* the key recency\n\nThe key recency must be in the range 1-8,\nwhere 1 is the most recent key that was pressed\nand 8 is 8th most recent key pressed.\n\n.Example:\n[source]\n----\n(defalias\n  swh (switch\n    ((key-history a 1)) S-a break\n    ((key-history b 1)) S-b break\n    ((key-history c 1)) S-c break\n    ((key-history d 8)) (macro d d d) break\n    () XX break\n  )\n)\n----\n\nThe `key-timing` compares how long ago recent key typing events occurred.\nIt accepts, in order,\n\n* the key recency\n* a comparison string, which is one of: `less-than|greater-than|lt|gt`\n* number of milliseconds to compare against\n\nThe key recency must be in the range 1-8,\nwhere 1 is the most recent key that was pressed\nand 8 is 8th most recent key pressed.\nMost use cases are expected to use a value of 1 for this parameter,\nbut perhaps you can find a creative use for the other values.\n\nThe comparison string determines how the actual key event timing\nwill be compared to the provided timing.\n\nThe number of milliseconds must be 0-65535.\n\nWARNING: The maximum milliseconds value of this configuration item\nacross your whole configuration\nwill be a lower bound of how long it takes for kanata to become idle\nand stop processing its state machine every approximately 1ms.\n\n.Example:\n[source]\n----\n(defalias\n  swh (switch\n    ((key-timing 1 less-than 200)) S-a break\n    ((key-timing 1 greater-than 500)) S-b break\n    ((key-timing 2 lt 1000)) S-c break\n    ((key-timing 8 gt 2000)) (macro d d d) break\n    () XX break\n  )\n)\n----\n\n==== not\n\nThe examples presented so far have not included the `not` boolean operator.\nThis operator will now be discussed.\nSyntactically, the `not` operator is used similarly to `or|and`.\nFunctionally, it means \"not **any** of\" the list elements.\n\n.Example:\n[source]\n----\n(defalias\n  swn (switch\n    ((not x y z)) S-a break\n    ;; the above and below cases are equivalent in logic\n    ((not (or x y z))) S-a break\n  )\n)\n----\n\nIn potentially more familiar notation, both cases have the logic:\n\n    !(x || y || z)\n\n==== input\n\nUntil now, all `switch` logic has been associated to key code outputs.\nIt is also possible to operate on inputs.\nInputs can be either real keys or \"virtual\" (fake) keys.\n\n.Example:\n[source]\n----\n(defalias switch-input-example\n  (switch\n    ((input real lctl)) $ac1 break\n    ((input virtual vk1)) $ac2 break\n    () $ac3 break\n  )\n)\n----\n\nSimilar to `key-history` for regular active keys, `input-history` also exists.\nA perhaps surprising, but hopefully logical, behaviour of input-history\nwhen compared to key-history is that, at the time of switch activation,\nthe history of `input-history` for recency `1` will be the just-pressed input.\nIn other words recency `1` is the input activating the `switch` action itself.\nWhereas with `key-history` for example, the key that will be next outputted\nmay be determined by the switch logic itself, so is not in the history.\nThe consequence of this is that you should use a recency of `2`\nwhen referring to the previously pressed input\nbecause the current input is in the recency `1` slot.\n\n.Example:\n[source]\n----\n(defalias switch-input-history-example\n  (switch\n    ((input-history real lsft 2)) $var1 break\n    ((input-history virtual vk2 2)) $var1 break\n    () $ac3 break\n  )\n)\n----\n\n==== layer\n\nThe `layer` list item can be used in `switch` logic to operate on the active layer.\nIt accepts a single layer name\nand evaluates to true if the configured layer name is the active layer,\notherwise it evaluates to false.\n\n.Example:\n[source]\n----\n(defalias switch-layer-example\n  (switch\n     ((layer base)) x break\n    ((layer other)) y break\n                 () z break\n  )\n)\n----\n\n==== base-layer\n\nThe `base-layer` list item evaluates to true\nif the configured layer name is the base layer.\nThe base layer is the most recently switched-to layer\nfrom a `layer-switch` action,\nor the first layer defined in your configuration\nif `layer-switch` has never been activated.\n\n.Example:\n[source]\n----\n(defalias switch-layer-example\n  (switch\n     ((base-layer base)) x break\n    ((base-layer other)) y break\n                 () z break\n  )\n)\n----\n\n\n[[cmd]]\n=== cmd\n\nWARNING: This action does not work unless you use the appropriate binary\nor - if compiling yourself - the appropriate feature flag.\nAdditionally you must add the <<danger-enable-cmd>> `defcfg` option.\n\n**Reference**\n\nThe `cmd` action allows you to execute arbitrary binaries with arbitrary arguments.\nThe `cmd-log` variant behaves similarly but allows customization\nof the stdout and stderr log levels within Kanata's output logging.\nThe `cmd-output-keys` is like `cmd`, but stdout of the command\nwill be parsed as a list of keys, output chords, and delays similar to <<macro>>\nand be typed as kanata outputs.\n\n.Syntax:\n[source]\n----\n(cmd $binary $arg1 $arg2 ... $argN)\n(cmd-log $stdout-log-level $stderr-log-level)\n(cmd-output-keys $binary $arg1 $arg2 ... $argN)\n----\n\n[cols=\"1,3\"]\n|===\n| `$binary`\n| Executable binary to run.\n\n| `$arg`\n| Argument passed into the binary.\n\n| `$stdout-log-level`\n| Log level for stdout of the command. Must be `+debug+`, `+info+`, `+warn+`, `+error+`, or `+none+`.\n\n| `$stderr-log-level`\n| Log level for stderr of the command. Must be `+debug+`, `+info+`, `+warn+`, `+error+`, or `+none+`.\n|===\n\n**Description**\n\nThe `+cmd+` action executes a program with arguments. It accepts one or more\nstrings. The first string is the program that will be run and the following\nstrings are arguments to that program. The arguments are provided to the\nprogram in the order written in the config file.\nLists may also be used within `cmd`\nwhich you may desire to do for reuse via `defvar`.\nLists will be flattened such that arguments are provided to the program\nin the order written in the config file, regardless of list nesting.\nTo be technical, it would be a depth-first flattening (similar to DFS).\n\nCommands are executed directly and not via a shell, so you cannot make\nuse of environment variables or symbols with special meaning.\nFor example `+~+` or `+$HOME+` in Linux will not be\nsubstituted with your home directory.\nIf you want to execute with a shell program\nuse the shell as the first parameter, e.g. `bash` or `powershell.exe`.\n\nThe user executing the command\nis the user that kanata was started with.\nFor example, if kanata was started by root,\nthe command will be run by the root user.\nIf you need to execute as a different user,\non Unix platforms you can use `sudo -u USER`\nbefore the rest of your command to achieve this.\n\n.Example:\n[source]\n----\n(defalias\n  cm1 (cmd rm -fr /tmp/testing)\n\n  ;; You can use bash -c and then a quoted string to execute arbitrary text in\n  ;; bash. All text within double-quotes is treated as a single string.\n  cm2 (cmd bash -c \"echo hello world\")\n\n  ;; You can prefix commands with sudo -u USER\n  ;; to execute commands as a different user.\n  cm3 (cmd sudo -u other_user bash -c \"echo goodbye\")\n)\n----\n\nBy default, `+cmd+` logs start of command, completion of command, stdout, and stderr.\nUsing the variant `+cmd-log+`, these log levels can be changed, and even disabled.\nIt takes two arguments, `+<log_level>+` and `+<error_log_level>+`. `+<log_level>+`\nwill be the level where the command to run, stdout, and stderr are logged.\nThe error channel is logged only if there is a failure with running the\ncommand (typically if the command can't be found, or there is trouble spawning it).\n\nThe valid levels are `+debug+`, `+info+`, `+warn+`, `+error+`, and `+none+`.\n\n.Example:\n[source]\n----\n(defalias\n  ;; The first two arguments are the log levels, then just the normal command\n  ;; This will only error if `bash` is not found or something else goes\n  ;; wrong with the initial execution. Any logs produced by bash will not\n  ;; be shown.\n  noisy-cmd (cmd-log none error bash -c \"echo hello this produces a log\")\n\n  ;; This will only log the output of the command, but it won't start\n  ;; because the command doesn't exist.\n  ignore-failure-cmd (cmd-log info none thiscmddoesnotexist)\n\n  verbose-only-log (cmd-log verbose verbose bash -c \"echo yo\")\n)\n----\n\nThere is a variant of `cmd`: `cmd-output-keys`. This variant reads the output\nof the executed program and reads it as an S-expression, similarly to the\n<<macro, macro action>>. However — unlike macro — only delays, keys, chords, and\nchorded lists are supported. Other actions are not supported.\n\n[source]\n----\n(defalias\n  ;; bash: type date-time as YYYY-MM-DD HH:MM\n  pdb (cmd-output-keys bash -c \"date +'%F %R' | sed 's/./& /g' | sed 's/:/S-;/g' | sed 's/\\(.\\{20\\}\\)\\(.*\\)/\\(\\1 spc \\2\\)/'\")\n\n  ;; powershell: type date-time as YYYY-MM-DD HH:MM\n  pdp (cmd-output-keys powershell.exe \"echo '(' (((Get-Date -Format 'yyyy-MM-dd HH:mm').toCharArray() -join ' ').insert(20, ' spc ') -replace ':','S-;') ')'\")\n)\n----\n\n[[push-msg]]\n=== push-msg\n\nNOTE: This action requires the TCP server to be enabled via the\n<<args-port,`-p` / `--port`>> command line argument.\nIf used without the TCP server enabled, a warning will be logged\nand the action will have no effect.\n\n**Reference**\n\nThe `push-msg` action sends an arbitrary message to all connected TCP clients.\nThis enables communication between your keyboard configuration and external tools\nwithout the security risks or performance overhead of executing shell commands.\n\n.Syntax:\n[source]\n----\n(push-msg $message)\n----\n\n[cols=\"1,3\"]\n|===\n| `$message`\n| A string or S-expression to send to TCP clients.\n  Strings are sent as-is; S-expressions are converted to JSON arrays.\n|===\n\n**Description**\n\nThe `push-msg` action broadcasts a message to all TCP clients connected to Kanata's\nTCP server. This is useful for:\n\n- Triggering external tool actions from keyboard shortcuts\n- Layer change notifications to status bars or tray applications\n- Integration with automation tools like Raycast, Alfred, or Hammerspoon\n- Controlling other applications without spawning shell processes\n\nMessages are sent as newline-terminated JSON in the format:\n\n[source,json]\n----\n{\"MessagePush\":{\"message\":\"your-message-here\"}}\n----\n\nCompared to the <<cmd>> action, `push-msg` offers:\n\n- **Better security**: No shell execution, no privilege escalation risks\n- **Better performance**: Sub-millisecond latency vs ~100ms for command execution\n- **No special flags**: Does not require `danger-enable-cmd` in defcfg\n\n.Example - Basic messages:\n[source]\n----\n(defalias\n  ;; Send a simple string message\n  notify (push-msg \"key-pressed\")\n\n  ;; Send layer change notification\n  nav-on (push-msg \"layer:nav:activated\")\n  nav-off (push-msg \"layer:nav:deactivated\")\n)\n----\n\n.Example - Integration with external tools:\n[source]\n----\n(defalias\n  ;; Trigger a Raycast extension\n  raycast-clip (push-msg \"raycast:clipboard-history\")\n\n  ;; Control media playback via external script\n  play-pause (push-msg \"media:toggle\")\n  next-track (push-msg \"media:next\")\n\n  ;; Switch keyboard layouts via external tool\n  layout-next (push-msg \"xkb:next-layout\")\n)\n----\n\n.Example - Using with defvirtualkeys for external triggering:\n[source]\n----\n;; Define virtual keys that can be triggered via TCP's ActOnFakeKey command\n;; External tools can trigger these using: {\"ActOnFakeKey\":{\"name\":\"email-sig\",\"action\":\"Tap\"}}\n(defvirtualkeys\n  email-sig (macro S-b e s t spc r e g a r d s , ret ret S-j o h n)\n  nav-mode (layer-switch nav)\n)\n\n;; Combine push-msg with other actions\n(defalias\n  launch-term (multi\n    (push-msg \"app:launch:terminal\")\n    (layer-switch base)\n  )\n)\n----\n\nTo receive these messages, connect a TCP client to Kanata's server port.\nSee the https://github.com/jtroo/kanata/blob/main/example_tcp_client/src/main.rs[example TCP client]\nfor implementation guidance.\n\n[[clipboard-actions]]\n=== clipboard actions\n\n**Reference**\n\nClipboard actions can manipulate the operating system clipboard\nalongside \"save ids\".\nTo paste, you would use an action such as `C-v`;\nKanata has no builtin paste action.\n\n.Syntax:\n[source]\n----\n(clipboard-set   $clipboard-string)\n(clipboard-save  $save-id)\n(clipboard-restore    $save-id)\n(clipboard-save-swap  $save-id $save-id)\n(clipboard-cmd-set  $binary $arg1 $arg2 ... $argN)\n(clipboard-save-cmd-set  $save-id $binary $arg1 $arg2 ... $argN)\n----\n\n[cols=\"1,3\"]\n|===\n| `clipboard-set`\n| Sets the clipboard to the specified string.\n\n| `clipboard-save`\n| Saves the current clipboard content with the specified ID.\n\n| `clipboard-restore`\n| Sets the clipboard to content saved with the specified ID.\nIf the save ID content is blank, this will do nothing.\n\n| `clipboard-save-swap`\n| Swaps the content of two save IDs.\n\n| `clipboard-cmd-set`\n| Sets the clipboard to the output of the command.\nThe current content of the clipboard is passed to stdin of the command\nif the current content is text.\nIf the content is an image, nothing is passed to stdin.\n\n| `clipboard-save-cmd-set`\n| Sets the save ID content to the output of the command.\nThe current content of the save ID is passed to stdin of the command\nif the current content is text.\nIf the content is an image, nothing is passed to stdin.\n\n| `$clipboard-string`\n| Fixed string to set the operating system clipboard to.\n\n| `$save-id`\n| A number `0-65535` representing an ID of saved clipboard content.\n\n| `$binary`\n| Executable binary to run.\n\n| `$arg`\n| Argument passed into the binary.\n|===\n\n**Description**\n\nYou can use clipboard actions to save clipboard content,\npaste arbitrary content,\nand then restore the original clipboard content.\nThis functionality is similar to what some text expanders do.\nYou can additionally use shell commands to manipulate\nclipboard content in arbitrary ways.\nThis can all be done in a single command via <<macro>>.\n\nNote that you will likely want to add delays in between components,\nbecause clipboard systems take some time to propagate updates.\n\nThe example below is a macro that pastes the content of the clipboard\ntwice with a space in between,\nwhile restoring the original clipboard content at the end.\nNotable is that the example uses `C-v` but this may not work for you.\nIf you have OS-level remapping, the `v` may be different\nto effectively paste.\nSomething that may also work is `S-ins`\n\n.Example:\n[source]\n----\n(macro\n (clipboard-save 0)\n 20\n (clipboard-cmd-set powershell.exe -c r#\"$v = ($Input | Select-Object -First 1); Write-Host -NoNewLine \"$v $v\"\"#)\n 300\n C-v\n (clipboard-restore 0)\n)\n----\n\nAs another example you can use templates\nwith clipboard actions to have a convenient clipboard-based\ntext output while preserving the old clipboard content.\nThis can fit the use case of \"text expansion\".\n\n.Example:\n[source]\n----\n(deftemplate text-paste (text)\n (macro\n  (clipboard-save 0)\n  20\n  (clipboard-set $text)\n  300\n  C-v\n  (clipboard-restore 0)\n ))\n\n(defalias\n myalias1 (t! text-paste \"Hello world\")\n myalias2 (t! text-paste \"Goodbye my old friend\")\n)\n----\n\n[[arbitrary-code]]\n=== arbitrary-code\n\nThe `arbitrary-code` action allows sending an arbitrary number to kanata's\noutput mechanism. The press is sent when pressed, and the release sent when\nreleased. This action can be useful for testing keys that are not yet named or\nmapped in kanata. Please contribute findings with names and mappings, either in\na GitHub issue or as a pull request!\n\nWARNING: This is not cross platform!\n\nWARNING: When using the Interception driver, this action is still sent over\nSendInput.\n\n[source]\n----\n(defalias ab1 (arbitrary-code 700))\n----\n\n[[global-overrides]]\n== Global overrides\n\nThe `defoverrides` optional configuration item allows you to create global\nkey overrides, irrespective of what actions are used to generate those keys.\nIt accepts pairs of lists:\n\n1. the input key list that gets replaced\n2. the output key list to replace the input keys with\n\nBoth input and output lists accept 0 or more modifier keys (e.g. lctl, rsft)\nand exactly 1 non-modifier key (e.g. 1, bspc).\n\nOnly zero or one `defoverrides` is allowed in a configuration file.\n\nNOTE: Depending on your use case\nyou may want to adjust <<override-release-on-activation>> in `defcfg`.\n\n.Example:\n[source]\n----\n;; Swap numbers and their symbols with respect to shift\n(defoverrides\n  (1) (lsft 1)\n  (2) (lsft 2)\n  ;; repeat for all remaining numbers\n\n  (lsft 1) (1)\n  (lsft 2) (2)\n  ;; repeat for all remaining numbers\n)\n----\n\n[[defoverridesv2]]\n=== defoverridesv2\n\nThere is a more recent version of defoverrides that offers more customizability.\nInstead of 2 list items per override entry,\n`defoverridesv2` mandates 4, though the extra 2 can be empty.\n\nYou cannot have both a v1 and v2 of `defoverrides` at the same time.\n\nThe 3rd item is an \"exclude modifiers list\" which is composed of modifier key names\n(such as `lctl`, `lalt`) that, if held, will disable the override from activating.\n\nNOTE: if all excluded list modifiers are released while the\noverride input is still active, the override will activate.\nYou can avoid this by ensuring to always release the non-modifier key\nbefore releasing any modifiers.\n\nThe 4th item is an \"exclude layers\" list which is composed of layer names\nthat while active as the most recent `layer-switch` or `layer-while-held`,\nwill disable the override from activating.\n\nNOTE: if the layer changes while the override input is still active,\nthe override will activate.\n\n.Example:\n[source]\n----\n(defoverridesv2\n  ;; lctl+a will become lalt+9\n  ;; except when lsft is held or other-layer is active.\n  (lctl a) (lalt 9) (lsft) (other-layer))\n\n  ;; lctl+b will always become lalt+0\n  (lctl b) (lalt 0) () ()\n)\n----\n\n[[templates]]\n== Templates\n\nThe top-level configuration item `deftemplate`\ndeclares a template that can be expanded multiple times\nvia the list item `template-expand`.\nThe short form of `template-expand` is `t!`.\n\nThe parameters to `deftemplate` in order are:\n\n* Template name\n* List of template variables\n* Template content (any combination of lists / strings)\n\nWithin the template content, variable names prefixed with `$`\nwill be substituted with the expression passed into `template-expand`.\n\nThe list item `template-expand` can be placed as a top-level list\nor within another list.\nIts parameters in order are:\n\n* template name\n* parameters to substitute into the template\n\nNOTE: Template expansion happens after file includes and before any other parsing.\nOne consequence of this early parsing is that variables defined in `defvar`\nare **not** substituted when used inside of `template-expand`.\nThis has consequences for condtional content, e.g. with `if-equal`.\nThis is discussed further in Example 5.\n\nExample 1:\n\nIn a simple example, let's say you wanted to set a large group of keys\nto do something different when you're holding alt. Yes, this could also\nbe handled with remapping alt to a layer shift, but there are cases where\nyou wouldn't want this. Rather than retyping the code with `fork` and\n`unmod` (to release alt) a bunch of times, you could template it like so:\n[source]\n----\n(deftemplate alt-fork (original-action new-action)\n  (fork $original-action (multi (unmod (ralt lalt) nop0) $new-action) (lalt ralt))\n)\n(defsrc 1 2 3)\n(defalias fn1 (template-expand alt-fork 1 f1))\n;; Templates are a simple text substitution, so the above is exactly equivalent to:\n;; (defalias fn1 (fork 1 (multi (unmod (ralt lalt) nop0) f1) (lalt ralt)))\n(defalias fn2 (template-expand alt-fork 2 f2))\n;; You can use t! as a short form of template-expand\n(defalias fn3 (t! alt-fork 3 f3))\n(deflayer default @fn1 @fn2 @fn3)\n----\n\n.Example 2:\n[source]\n----\n(defvar chord-timeout 200)\n(defcfg process-unmapped-keys yes)\n\n;; This template defines a chord group and aliases that use the chord group.\n;; The purpose is to easily define the same chord position behaviour\n;; for multiple layers that have different underlying keys.\n(deftemplate left-hand-chords (chordgroupname k1 k2 k3 k4 alias1 alias2 alias3 alias4)\n  (defalias\n    $alias1 (chord $chordgroupname $k1)\n    $alias2 (chord $chordgroupname $k2)\n    $alias3 (chord $chordgroupname $k3)\n    $alias4 (chord $chordgroupname $k4)\n  )\n  (defchords $chordgroupname $chord-timeout\n    ($k1) $k1\n    ($k2) $k2\n    ($k3) $k3\n    ($k4) $k4\n    ($k1 $k2) lctl\n    ($k3 $k4) lsft\n  )\n)\n\n(template-expand left-hand-chords qwerty a s d f qwa qws qwd qwf)\n;; t! is short for template-expand\n(t! left-hand-chords dvorak a o e u dva dvo dve dvu)\n\n(defsrc a s d f)\n(deflayer dvorak @dva @dvo @dve @dvu)\n(deflayer qwerty @qwa @qws @qwd @qwf)\n----\n\n.Example 3:\n[source]\n----\n;; This template defines a home row that customizes a single key's behaviour\n(deftemplate home-row (j-behaviour)\n  a s d f g h $j-behaviour k l ; '\n)\n\n(defsrc\n  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n       ;; usable even inside defsrc\n  caps (t! home-row j)                            ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n\n(deflayer base\n  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n                                 ;; lists can be passed in too!\n  caps (t! home-row (tap-hold 200 200 j lctl))    ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n----\n\n=== if-equal\n\nWithin a template you can use the list item `if-equal`\nto have condiditionally-used items within a template.\n\nIt accepts a minimum of 2 parameters.\nThe first two parameters must be strings and are compared\nagainst each other.\nIf they match, the following parameters are inserted into\nthe template in place of the `if-equal` list.\nOtherwise if the strings do not match\nthen the whole `if-equal` list is removed from the template.\n\n.Example 4:\n----\n(deftemplate home-row (version)\n  a s d f g h\n  (if-equal $version v1 j)\n  (if-equal $version v2 (tap-hold 200 200 j lctl))\n   k l ; '\n)\n\n(defsrc\n  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n  caps (template-expand home-row v1)                            ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n\n(deflayer base\n  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n  caps (template-expand home-row v2)                            ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n----\n\nSimilar to `if-equal` are three more conditional operators for templates:\n\n* `if-not-equal`\n** the content is used if the first two string parameters are not equal\n* `if-in-list`\n** the content is used if the first string parameter exists in\nthe second list-of-strings parameter\n* `if-not-in-list`\n** the content is used if the first string parameter does not exist in\nthe second list-of-strings parameter\n\n.Example 5:\n----\n;; defvar is parsed AFTER template expansion occurs.\n(defvar a hello)\n\n(deftemplate template1 (var1)\n  a (if-equal hello $var1 b) c\n)\n\n;; Below will expand to: `a c` because the string\n;; $a itself is compared against the string hello\n;; and they are not equal.\n(template-expand template1 $a)\n\n(deftemplate template2 (var1)\n  a (if-equal $a $var1 b) c\n)\n\n;; Below will expand to: `a b c` because the string\n;; $a is compared against the string $a and they are equal.\n;; But note that the variable $a is still not substituted\n;; with its defvar value of: hello.\n(template-expand template2 $a)\n----\n\n[[concat-in-deftemplate]]\n=== concat in deftemplate\n\nLike <<concat-in-defvar,concat in defvar>>,\na list beginning with `concat` within the content of `deftemplate`\nwill be replaced with a single string that consists of\nall the subsequent items in the list concatenated to each other.\n\n== Include other files[[include]]\n\nThe `include` optional configuration item\nallows you to include other files into the configuration.\nThis configuration accepts a single string which is a file path.\nThe file path can be an absolute path or a relative path.\nThe path will be relative to the defined configuration file.\n\nAt the time of writing, includes can only be placed at the top level.\nThe included files also cannot contain includes themselves.\n\nNon-existing files will be ignored.\n\n.Example:\n----\n;; This is in the file initially read by kanata, e.g. kanata.kbd\n(include other-file.kbd)\n\n;; This is in the other file\n(defalias\n  included-alias XX\n  ;; ...\n)\n\n;; This is in the other file\n(deflayer included-layer\n  ;; ...\n)\n----\n\n[[platform]]\n== Platform-specific configuration\n\nIf you put any top-level configuration item\nwithin a list beginning with `platform`,\nit will become a platform-specific configuration\nthat is only active for the specified platforms.\n\n.Syntax:\n[source]\n----\n(platform (applicable-platforms) ...)\n----\n\nThe valid values for applicable platforms are:\n\n- `win`\n- `winiov2`\n- `wintercept`\n- `linux`\n- `macos`\n\n.Example:\n[source]\n----\n(platform (macos)\n  ;; Only on macos, use command arrows to jump/delete words\n  ;; because command is used for so many other things\n  ;; and it's weird that these cases use alt.\n  (defoverrides\n    (lmet bspc) (lalt bspc)\n    (lmet left) (lalt left)\n    (lmet right) (lalt right)\n  )\n)\n\n(platform (win winiov2 wintercept)\n  (defalias run-my-script (cmd #| something involving powershell |#))\n)\n\n(platform (macos linux)\n  (defalias run-my-script (cmd #| something involving bash |#))\n)\n----\n\n[[environment]]\n== Environment-conditional configuration\n\n.Syntax:\n[source]\n----\n(environment (env-var-name env-var-value) ...)\n----\n\nThe items `env-var-name` and `env-var-value` can be arbitrary strings.\nThe name is the environment variable that is read\nfor determining if the configuration is used or not.\nIf the value of the environment variable (set only on kanata startup)\nmatches `env-var-value`, the configuration is used; otherwise it is ignored.\nAn empty string for `env-var-value` — `\"\"` — will use the configuration\nif the environment variable is an empty string\nand also if the environment variable is not defined.\n\n.Example:\n[source]\n----\n(environment (LAPTOP lp1)\n  (defalias met @lp1met)\n)\n\n(environment (LAPTOP lp2)\n  (defalias met @lp2met)\n)\n----\n\n.Set environment variables in the current terminal process:\n[source]\n----\n# powershell\n$env:VAR_NAME = \"var_value\"\n\n# bash\nVAR_NAME=var_value\n----\n\n\n[[input-chords-v2]]\n== Input chords / combos (v2)\n\nYou may define a single `+defchordsv2+` configuration item.\nThis enables you to define global input chord behaviour.\nOne might also find this functionality called another name of \"combos\"\nin other projects.\n\nInput chords enables you to press two or more keys in quick succession\nto activate a different action\nthan would normally be associated with those keys.\nWhen activating a chord, the order of presses is not important;\nwhen all keys belonging to a chord are pressed,\nthe action activates regardless of press order.\n\nThe `+defchordsv2+` feature is configured as shown below:\n\n.Syntax example\n[source]\n----\n(defchordsv2\n  (participating-keys1) action1 timeout1 release-behaviour1 (disabled-layers1)\n    ...\n  (participating-keysN) actionN timeoutN release-behaviourN (disabled-layersN)\n)\n----\n\nThe configuration is made up of 5-tuples of:\n\n[cols=\"1,3\"]\n|===\n| `$participating-keys`\n| These are key names you would use in `defsrc`.\nA minimum of two keys must be defined per chord.\nThe list must be unique per chord.\n\n| `$action`\n| These are actions as you would configure in `deflayer` or `defalias`.\nThe action activates if all participating keys are activated\nwithin the timeout.\n\n| `$timeout`\n| The time (unit: milliseconds) within which,\nif all participating keys are pressed,\nthe chord action will activate;\notherwise the key presses are handled by the active layer.\nThe time begins when the first participant is pressed.\n\n| `$release-behaviour`\n| This must be either `first-release` or `all-released`;\n`first-release` means the chord action will be released\nwhen the first participant is released,\nwhile `all-released` means the chord action will be released\nonly when all of the participants have been released.\n\n| `$disabled-layers`\n| A list of layer names on which this chord is disabled.\n|===\n\nInput chords have a related `defcfg` item: <<chords-v2-min-idle>>.\nWhen any non-chord activation happens,\na timeout begins with duration configured by\n`chords-v2-min-idle` (unit: milliseconds).\nUntil this timeout expires, all inputs will immediately skip\nchords processing and be processed by the active layer.\n\nIMPORTANT: When opting into input chords v2,\nyou must enable `concurrent-tap-hold`.\nThis is enforced for a more responsive `tap-hold` experience when\nactivated by a chord.\n\n.Example:\n[source]\n----\n(defcfg concurrent-tap-hold yes)\n(defchordsv2\n  (a s)    c                200 all-released  (non-chord-layer)\n  (a s d) (macro h e l l o) 250 first-release (non-chord-layer)\n  (s d f) (macro b y e)     400 first-release (non-chord-layer)\n)\n----\n\nNOTE: Also see <<input-chords,v1 chords>>,\nwhich are configured differently and can be defined per-layer.\n\n[[chordsv2-processing-order]]\n=== Action processing order\n\nThe chordsv2 system handles keys before any layer action handling.\nThis has a consequence of not entering the delay and interruption flow\nof actions such as `tap-hold-release`.\n\nIf you want a chordsv2 action activation to be processed\nby the aforementioned category of actions\nyou can move the action into a virtual key\n(see <<virtual-keys>>) and have the chordsv2 action\npress and release the virtual key.\n\nSee a sample below that showcases a reusable template.\n\n.Example:\n[source]\n----\n(defcfg concurrent-tap-hold yes)\n(defsrc)\n(deflayermap (base)\n  caps (tap-hold-release 0 200 esc lctl))\n\n;; defines a vkey named v-$key, example v-bspc\n;; and an alias @v-bspc that press and and releases the v-key\n;; within on-press and on-release respectively.\n(deftemplate v- (key)\n  (defvirtualkeys (concat v- $key) $key)\n  (defalias (concat v- $key)\n    (multi (on-press press-vkey (concat v- $key)) (on-release release-vkey (concat v- $key))))\n)\n\n(t! v- bspc)\n(t! v- del)\n(defchordsv2\n  (j k) @v-bspc 75 first-release ()\n  (s d) @v-del 75 first-release ()\n)\n----\n\n[[optional-defcfg-options]]\n== defcfg options\n\n[[danger-enable-cmd]]\n=== danger-enable-cmd\n\nThis option can be used to enable the `cmd` action in your configuration. The\n`+cmd+` action allows kanata to execute programs with arguments passed to them.\n\nThis requires using a kanata program that is compiled with the `cmd` action\nenabled. The reason for this is so that if you choose to, there is no way for\nkanata to execute arbitrary programs even if you download some random\nconfiguration from the internet.\n\nThis configuration is disabled by default and can be enabled by giving it the\nvalue `yes`.\n\n.Example:\n[source]\n----\n(defcfg\n  danger-enable-cmd yes\n)\n----\n\n[[sequence-timeout]]\n=== sequence-timeout\n\nThis option customizes the key sequence timeout (unit: ms). Its default value\nis 1000. The purpose of this item is explained in <<sequences>>.\n\n.Example:\n[source]\n----\n(defcfg\n  sequence-timeout 2000\n)\n----\n\n[[sequence-input-mode]]\n=== sequence-input-mode\n\nThis option customizes the key sequence input mode. Its default value when not\nconfigured is `hidden-suppressed`.\n\nThe options are:\n\n- `visible-backspaced`: types sequence characters as they are inputted. The\n  typed characters will be erased with backspaces for a valid sequence termination.\n- `hidden-suppressed`: hides sequence characters as they are typed. Does not\n  output the hidden characters for an invalid sequence termination.\n- `hidden-delay-type`: hides sequence characters as they are typed. Outputs the\n  hidden characters for an invalid sequence termination either after a\n  timeout or after a non-sequence key is typed.\n\nFor `visible-backspaced` and `hidden-delay-type`, a sequence leader input will\nbe ignored if a sequence is already active. For historical reasons, and in case\nit is desired behaviour, a sequence leader input using `hidden-suppressed` will\nreset the key sequence.\n\nSee <<sequences>> for more about sequences.\n\n.Example:\n[source]\n----\n(defcfg\n  sequence-input-mode visible-backspaced\n)\n----\n\n\n[[sequence-backtrack-modcancel]]\n=== sequence-backtrack-modcancel\n\nThis option customizes the behaviour of key sequences\nwhen modifiers are used.\nThe default is `yes` and can be overridden to `no` if desired.\n\nSetting it to `yes` allows both `fk1` and `fk2` to be activated\nin the following configuration, but with `no`,\n`fk1` will be impossible to activate\n\n----\n(defseq\n  fk1 (lsft a b)\n  fk2 (S-(c d))\n)\n----\n\nSee <<sequences>> for more about sequences and\nhttps://github.com/jtroo/kanata/blob/main/docs/sequence-adding-chords-ideas.md[this document]\nfor more context about this specific configuration.\n\n.Example:\n[source]\n----\n(defcfg\n  sequence-backtrack-modcancel no\n)\n----\n\n[[log-layer-changes]]\n=== log-layer-changes\n\nBy default, kanata will log layer changes. However, logging has some processing\noverhead. If you do not care for the logging, you can choose to disable it.\n\n.Example:\n[source]\n----\n(defcfg\n  log-layer-changes no\n)\n----\n\nIf `+--log-layer-changes+` is passed as a command line argument,\na `no` in the configuration file will be overridden\nand layer changes will again be logged.\nThis flag can be helpful when testing new configuration changes\nwhile keeping the default behaviour as \"no logging\" to save on processing,\nso that the `defcfg` item does not need to be adjusted back and forth\nwhen experimenting vs. stable usage.\n\n[[delegate-to-first-layer]]\n=== delegate-to-first-layer\n\n\nBy default, transparent keys on layers\nwill delegate to the corresponding defsrc key\nwhen found on a layer activated by `layer-switch`.\n\nThis config entry changes the behaviour\nto delegate to the action in the same position on the first layer defined\nin the configuration, which is the active layer on startup.\n\nFor more context, see https://github.com/jtroo/kanata/issues/435.\n\n.Example:\n[source]\n----\n(defcfg\n  delegate-to-first-layer yes\n)\n----\n\n\n[[movemouse-inherit-accel-state]]\n=== movemouse-inherit-accel-state\n\nBy default `movemouse-accel` actions will track the acceleration\nstate for vertical and horizontal axes separately.\n\nWhen this setting is enabled, `movemouse-accel` will behave exactly like mouse movements in https://qmk.fm[QMK],\ni.e. the acceleration state of new mouse\nmovement actions will be inherited if others are already being pressed.\n\n.Example:\n[source]\n----\n(defcfg\n  movemouse-inherit-accel-state yes\n)\n----\n\n[[movemouse-smooth-diagonals]]\n=== movemouse-smooth-diagonals\n\nBy default, mouse movements move one direction at a time\nand vertical/horizontal movements are on independent timers.\n\nThis can result in non-smooth diagonals when drawing a line in some app.\nThis option adds a small imperceptible amount of latency to\nsynchronize the mouse movements.\n\n.Example:\n[source]\n----\n(defcfg\n  movemouse-smooth-diagonals yes\n)\n----\n\n=== dynamic-macro-max-presses [[dynamic-macro-max-presses]]\n\nThis configuration allows you to customize the length limit on dynamic macros.\nThe default length limit is 128 keys.\n\n.Example:\n[source]\n----\n(defcfg\n  dynamic-macro-max-presses 1000\n)\n----\n\n=== concurrent-tap-hold [[concurrent-tap-hold]]\nThis configuration makes multiple tap-hold actions\nthat are activated near in time expire their timeout quicker.\nBy default this is disabled.\nWhen disabled, the timeout for a following tap-hold\nwill start from 0ms **after** the previous tap-hold expires.\nWhen enabled, the timeout will start\nas soon as the tap-hold action is pressed\neven if a previous tap-hold action is still held and has not expired.\n\n.Example:\n[source]\n----\n(defcfg\n  concurrent-tap-hold yes\n)\n----\n\n[[block-unmapped-keys]]\n=== block-unmapped-keys\n\nIf you desire to use only a subset of your keyboard\nyou can use `block-unmapped-keys` to make every key\nother than those that exist in `defsrc` a no-op.\n\nNOTE: this only functions correctly if you also set\n<<process-unmapped-keys>> to yes.\n\n.Example:\n[source]\n----\n(defcfg\n  block-unmapped-keys yes\n)\n----\n\n[[rapid-event-delay]]\n=== rapid-event-delay\n\nThis configuration applies to the following events:\n\n* the release of one-shot-press activation\n* the release of the tapped key in a tap-hold activation\n* a non-eager tap-dance activation from interruption by another key\n* input chord activations, both v1 and v2\n\nKey event processing is paused the defined number of milliseconds (approximate).\nThe default value is 5.\n\nThere will be a minor input latency impact in the mentioned scenarios.\nSince 5ms is 1 frame at 200 Hz refresh rate,\nin most scenarios this will not be perceptible.\n\nThe reason for this configuration existing is that some environments\ndo not process the scenarios correctly due to the rapidity of key events.\nKanata does send the events in the correct order,\nso the fault is more in the environment,\nbut kanata provides a workaround anyway.\n\nIf you are negatively impacted by the latency increase of these events\nand your environment is not impacted by increased rapidity,\nyou can reduce the value to a number between 0 and 4.\n\n.Example:\n[source]\n----\n(defcfg\n  ;; If your environment is particularly buggy, might need to delay even more\n  rapid-event-delay 20\n)\n----\n\n[[chords-v2-min-idle]]\n=== chords-v2-min-idle\n\nThis configuration affects the timer during which chords processing is disabled.\nNOTE: For more info, see <<input-chords-v2>>.\n\nThe default (and minimum) value is `5` and the unit is milliseconds.\n\n.Example:\n[source]\n----\n(defcfg\n  chords-v2-min-idle 200\n)\n----\n\n[[tap-hold-require-prior-idle]]\n=== tap-hold-require-prior-idle\n\nThis configuration applies to all `tap-hold` variants.\nWhen a different physical key was pressed within the configured number of\nmilliseconds before a `tap-hold` key is pressed, the `tap-hold` key will\nimmediately resolve as its tap action without entering the waiting state.\nThis prevents accidental modifier activations during fast typing.\nThe concept is equivalent to ZMK's `require-prior-idle-ms`.\n\nNOTE: For more info, see <<tap-hold>>.\n\nThe default value is `0` (disabled) and the unit is milliseconds.\n\n.Example:\n[source]\n----\n(defcfg\n  tap-hold-require-prior-idle 150\n)\n----\n\n- If you type a normal key and then press a `tap-hold` key within the threshold,\n  the `tap-hold` key immediately outputs its tap action.\n- If the keyboard is idle for longer than the threshold before pressing a `tap-hold` key,\n  normal `tap-hold` behavior applies (waiting state, hold on timeout, etc.).\n- When `tap-hold-require-prior-idle` triggers, the tap action is chosen before any other\n  `tap-hold` configuration (e.g., `tap-hold-opposite-hand`, `concurrent-tap-hold`)\n  is consulted.\n\n==== Per-action override\n\nThe global `tap-hold-require-prior-idle` value can be overridden on individual\n`tap-hold` actions using the `(require-prior-idle <ms>)` option.\nThis is useful when most keys benefit from idle detection (e.g., home-row mods)\nbut specific keys (e.g., a layer-tap key) need immediate activation even during\nfast typing.\n\nThe option can be appended to any `tap-hold` variant as a trailing\n`(keyword value)` list:\n\n.Example: disable for a specific key\n[source]\n----\n(defcfg tap-hold-require-prior-idle 150)\n(defalias\n  ;; HRMs use the global 150ms threshold\n  a (tap-hold 200 200 a lmet)\n  s (tap-hold 200 200 s lalt)\n  ;; Layer key disables idle detection for immediate activation\n  nav (tap-hold 200 200 tab (layer-toggle nav) (require-prior-idle 0))\n)\n----\n\n.Example: enable for a specific key without a global setting\n[source]\n----\n(defalias\n  a (tap-hold 200 200 a lmet (require-prior-idle 150))\n)\n----\n\n- `(require-prior-idle 0)` disables the feature for that action,\n  even when a global threshold is set.\n- A non-zero value enables or changes the threshold for that action only.\n- When no per-action override is specified, the global `defcfg` value is used.\n- Works with all `tap-hold` variants: `tap-hold`, `tap-hold-press`,\n  `tap-hold-release`, `tap-hold-press-timeout`, `tap-hold-release-timeout`,\n  `tap-hold-release-keys`, `tap-hold-except-keys`, `tap-hold-tap-keys`,\n  and `tap-hold-opposite-hand`.\n\n[[override-release-on-activation]]\n=== override-release-on-activation\n\nThis configuration item changes activation behaviour from `defoverrides`.\n\nTake this example override:\n\n[source]\n----\n(defoverrides (lsft a) (lsft 9))\n----\n\nThe default behaviour is that if `lsft` is released **before** releasing `a`,\nkanata's behaviour would be to send `a`.\n\nA future improvement could be to make the `9` continue to be the key held,\nbut that is not implemented today.\n\nThe workaround in case the above behaviour negatively impacts your workflow\nis to enable this configuration.\nThis configuration will press and then immediately release the `9` output\nas soon as the override activates, meaning you are unlikely as a human to ever\nrelease `lsft` first.\n\nThe effect of this configuration is that the `9` key cannot remain held\nwhen activated by the override which is important to consider for your use cases.\n\n.Example:\n[source]\n----\n(defcfg\n  override-release-on-activation yes\n)\n----\n\n[[allow-hardware-repeat]]\n=== allow-hardware-repeat\n\nBy default, any repeat-key events generated by the physical keyboard (or operating system)\nwill be passed through to the application.  On Linux, under Wayland, this is wasted effort\nsince the DE handles key-repeat on its own.  Such events can also be distracting when\ndebugging your configuration with evtest, etc.\n\nSetting this option to \"false\" will cause such events to be dropped, and not passed through.\nThis is primarily meant for Linux, but may find some use on Mac.  It is not implemented on\nWindows, and will be silently ignored.\n\n.Example:\n[source]\n----\n(defcfg\n   allow-hardware-repeat false\n)\n----\n\n[[alias-to-trigger-on-load]]\n=== alias-to-trigger-on-load\n\nSelect an alias to execute when first starting, and after each\nlive-reload of the config. You can use this to run external\ncommands, or to stack layers (with layer-while-held).\n\nThe name of an alias, without a leading \"@\", is expected as a\nparameter.  The example below will beep at startup (assuming\nyour system has a beep command), and will already be blocking\nthe swapped \"i\" and \"o\" keys.\n\n.Example:\n[source]\n----\n(defcfg\n  alias-to-trigger-on-load S\n  danger-enable-cmd yes\n)\n\n(deffakekeys B (layer-while-held block))\n\n(defalias\n  P (on-press toggle-vkey B)\n  S (macro @P (cmd beep))\n)\n\n(defsrc i o p )\n(deflayer base o i @P )\n(deflayer block • • _ )\n----\n\n[[mouse-movement-key]]\n=== Linux or Windows-interception only: mouse-movement-key\n\nAccepts a single key name.\nWhen configured, whenever a mouse cursor movement is received,\nthe configured key name will be \"tapped\" by Kanata,\nactivating the key's action.\n\nThis enables reporting of every relative mouse movement, which\ncorresponds to standard mice, trackballs, trackpads and\ntrackpoints. Absolute movements, which can be generated by\ntouchscreens, drawing tablets and some mouse replacement or\naccessibility software, are ignored. Scrolling events and mouse\nbuttons are also ignored.\n\nThe intended use of these events is to provide a way to automatically\nenable a mouse keys layer while mousing, which can be disabled by a\ntimeout or typing on other keys, rather than explicit toggling. see\ncfg_examples/automousekeys-*.kbd for more.\n\nThe `mvmt` key name is specially intended for this purpose.  It has no\noutput key mapping and cannot be supplied as an action; however, any\nkey may be used.\n\nSupports live reload on Linux, but with Windows-interception, this\noption must be present on startup to enable mouse movement event\ncollection, so restart is required to enable it. Changing the key name\nis always supported, however.\n\n.Example:\n[source]\n----\n(defcfg\n  process-unmapped-keys yes\n  mouse-movement-key mvmt\n)\n\n(defsrc\n  k     l     ;\n  mvmt\n)\n\n(defvirtualkeys\n  mouse (layer-while-held mouse-layer)\n)\n\n(defalias\n  mhld (hold-for-duration 750 mouse)\n)\n\n(deflayer qwerty\n  k     l     ;\n  @mhld\n)\n\n(deflayer mouse-layer\n  mlft  mmid  mrgt\n  @mhld\n)\n----\n\n[[linux-only-linux-dev]]\n=== Linux only: linux-dev\n\nBy default, kanata will try to detect which input devices are keyboards and try\nto intercept them all. However, you may specify exact keyboard devices from the\n`/dev/input` directories using the `linux-dev` configuration.\n\n.Example:\n[source]\n----\n(defcfg\n  linux-dev /dev/input/by-path/platform-i8042-serio-0-event-kbd\n)\n----\n\nIf you want to specify multiple keyboards, you can separate the paths with a\ncolon `+:+`.\n\n.Example:\n[source]\n----\n(defcfg\n  linux-dev /dev/input/dev1:/dev/input/dev2\n)\n----\n\nDue to using the colon to separate devices, if you have a device with colons in\nits file name, you must escape those colons with backslashes:\n\n[source]\n----\n(defcfg\n  linux-dev /dev/input/path-to\\:device\n)\n----\n\nAlternatively, you can use list syntax, where both backslashes and colons\nare parsed literally. List items are separated by spaces or newlines.\nUsing quotation marks for each item is optional, and only required if an\nitem contains spaces.\n\n[source]\n----\n(defcfg\n  linux-dev (\n    /dev/input/path:to:device\n    \"/dev/input/path to device\"\n  )\n)\n----\n\nFor devices that do not have an easily identifiable device path like Bluetooth\nkeyboards using the `linux-dev-names-include` option below is recommended.\n\n[[linux-only-linux-dev-names-include]]\n=== Linux only: linux-dev-names-include\n\nIn the case that `linux-dev` is omitted,\nthis option defines a list of device names that should be included.\nDevice names that do not exist in the list will be ignored.\n\nDevice paths are not supported by this option; instead use the device names\nas output by Kanata during startup.  Launch Kanata with a\n<<minimal-configuration,minimal configuration>> (any use of a `linux-dev*`\noption may hide devices) and look for lines beginning with \"registering\":\n\n----\nregistering /dev/input/eventX: \"Device name 1\"\nregistering /dev/input/eventY: \"Device name 2\"\n----\n\nThe entire name within quotes must be used, partial matches and regex's are not supported.\n\n.Example:\n[source]\n----\n(defcfg\n  linux-dev-names-include (\n    \"Device name 1\"\n    \"Device name 2\"\n  )\n)\n----\n\n[[linux-only-linux-dev-names-exclude]]\n=== Linux only: linux-dev-names-exclude\n\nIn the case that `linux-dev` is omitted,\nthis option defines a list of device names that should be excluded.\nThis option is parsed identically to `linux-dev-names-include`.\n\nThe `linux-dev-names-include` and `linux-dev-names-exclude` options\nare not mutually exclusive\nbut in practice it probably only makes sense to use one and not both.\n\n.Example:\n[source]\n----\n(defcfg\n  linux-dev-names-exclude (\n    \"Device Name 1\"\n    \"Device Name 2\"\n  )\n)\n----\n\n[[linux-only-linux-continue-if-no-devs-found]]\n=== Linux only: linux-continue-if-no-devs-found\n\nBy default, kanata will crash if no input devices are found. You can change\nthis behaviour by setting `linux-continue-if-no-devs-found`.\n\n.Example:\n[source]\n----\n(defcfg\n  linux-continue-if-no-devs-found yes\n)\n----\n\n[[linux-only-linux-device-detect-mode]]\n=== Linux only: linux-device-detect-mode\n\nKanata on Linux automatically detects and grabs input devices\nwhen none of the explicit device configurations are in use.\nIn case kanata is undesirably grabbing mouse-like devices,\nyou can use a configuration item to change detection behaviour.\n\nThe configuration is `linux-device-detect-mode` and it has the options:\n\n[cols=\"1,4\"]\n|===\n| `keyboard-only`\n| Grab devices that seem to be a keyboard only.\n\n| `keyboard-mice`\n| Grab devices that seem to be a keyboard only\nand devices that declare **both** keyboard and mouse functionality.\n\n| `any`\n| Grab all keyboard-like and mouse-like devices.\n|===\n\nThe default behaviour is:\n\n[cols=\"1,4\"]\n|===\n| `keyboard-mice`\n| When no mouse events are in `defsrc`.\n\n| `any`\n| When any mouse buttons or mouse scroll events are in `defsrc`.\n|===\n\n[[linux-only-linux-unicode-u-code]]\n=== Linux only: linux-unicode-u-code\n\nUnicode on Linux works by pressing Ctrl+Shift+U, typing the unicode hex value,\nthen pressing Enter. However, if you do remapping in userspace, e.g. via\nxmodmap/xkb, the keycode \"U\" that kanata outputs may not become a keysym \"u\"\nafter the userspace remapping. This will be likely if you use non-US,\nnon-European keyboards on top of kanata. For unicode to work, kanata needs to\nuse the keycode that outputs the keysym \"u\", which might not be the keycode\n\"U\".\n\nYou can use `evtest` or `kanata --debug`, set your userspace key remapping,\nthen press the key that outputs the keysym \"u\" to see which underlying keycode\nis sent. Then you can use this configuration to change kanata's behaviour.\n\n.Example:\n[source]\n----\n(defcfg\n  linux-unicode-u-code v\n)\n----\n\n[[linux-only-linux-unicode-termination]]\n=== Linux only: linux-unicode-termination\n\nUnicode on Linux terminates with the Enter key by default. This may not work in\nsome applications. The termination is configurable with the following options:\n\n- `enter`\n- `space`\n- `enter-space`\n- `space-enter`\n\n.Example:\n[source]\n----\n(defcfg\n  linux-unicode-termination space\n)\n----\n\n=== Linux only: linux-x11-repeat-delay-rate[[linux-only-x11-repeat-rate]]\n\nOn Linux, you can tell kanata to run `xset r rate <delay> <rate>`\non startup and on live reload\nvia the configuration item `linux-only-x11-repeat-rate`.\nThis takes two numbers separated by a comma.\nThe first number is the delay in ms\nand the second number is the repeat rate in repeats/second.\n\nThis configuration item does not affect Wayland or no-desktop environments.\n\n.Example:\n[source]\n----\n(defcfg\n  linux-x11-repeat-delay-rate 400,50\n)\n----\n\n[[linux-only-linux-use-trackpoint-property]]\n=== Linux only: linux-use-trackpoint-property\n\nOn linux, you can ask kanata to label itself as a trackpoint. This has several\neffects on libinput including enabling middle mouse button scrolling and using a\ndifferent acceleration curve. Otherwise, a trackpoint intercepted by kanata may\nnot behave as expected.\n\nIf using this feature, it is recommended to filter out any non-trackpoint\npointing devices using <<linux-only-linux-dev-names-include>>,\n<<linux-only-linux-dev-names-exclude>> or <<linux-only-linux-dev>> to avoid\nchanging their behavior as well.\n\n.Example:\n[source]\n----\n(defcfg\n  linux-use-trackpoint-property yes\n)\n----\n\n[[linux-only-linux-output-device-name]]\n=== Linux only: linux-output-device-name\n\nThis option defines the name of the evdev output device.\nThe default value is kanata.\n\n.Example:\n[source]\n----\n(defcfg\n   linux-output-device-name \"kanata output\"\n)\n----\n\n[[linux-only-linux-output-device-bus-type]]\n=== Linux only: linux-output-device-bus-type\n\nKanata on Linux needs to declare a \"bus type\" for its evdev output device.\nThe options are `USB`, `I8042`, and `virtual`, with the default as `I8042`.\nUsing USB can https://github.com/jtroo/kanata/pull/661[break disable-touchpad-while-typing on Wayland].\nBut using I8042 appears to break https://github.com/jtroo/kanata/issues/1131[some other scenarios].\nThus the output bus type is configurable.\n\n.Example:\n[source]\n----\n(defcfg\n   linux-output-device-bus-type USB\n)\n----\n\n[[macos-only-macos-dev-names-include]]\n=== macOS only: macos-dev-names-include\n\nThis option defines a list of device names that should be included.\nBy default, kanata will try to detect which input devices are keyboards and try\nto intercept them all. However, you may specify exact keyboard devices to intercept\nusing the `macos-dev-names-include` configuration.\nDevice names that do not exist in the list will be ignored.\nThis option is parsed identically to `linux-dev`.\n\nUse `kanata -l` or `kanata --list` to list the available keyboards.\n\n.Example:\n[source]\n----\n(defcfg\n  macos-dev-names-include (\n    \"Device name 1\"\n    \"Device name 2\"\n  )\n)\n----\n\n[[macos-only-macos-dev-names-exclude]]\n=== macOS only: macos-dev-names-exclude\n\nThis option defines a list of device names that should be excluded.\nBy default, kanata will try to detect which input devices are keyboards and try\nto intercept them all. However, you may specify certain keyboard devices to be ignored\nusing the `macos-dev-names-exclude` configuration.\nDevice names that do not exist in the list will be included.\nThis option is parsed identically to `linux-dev`.\n\nUse `kanata -l` or `kanata --list` to list the available keyboards.\n\n.Example:\n[source]\n----\n(defcfg\n  macos-dev-names-exclude (\n    \"Device name 1\"\n    \"Device name 2\"\n  )\n)\n----\n[[windows-only-windows-altgr]]\n=== Windows only: windows-altgr\n\nThere is an option for Windows to mitigate the strange behaviour of AltGr (ralt)\nif you're using `process-unmapped-keys yes` or have the key in your defsrc.\nThis is applicable for many non-US layouts.\nYou can use one of the listed values to change what kanata does with the key:\n\n* `cancel-lctl-press`\n** This will remove the `lctl` press that is generated alonside `ralt`\n* `add-lctl-release`\n** This adds an `lctl` release when `ralt` is released\n\nWithout these workarounds,\nyou should use `process-unmapped-keys (all-except lctl ralt))`,\nor use `process-unmapped-keys no`\n**and** also omit both `ralt` and `lctl` from `defsrc`.\n\n.Example:\n[source]\n----\n(defcfg\n  windows-altgr add-lctl-release\n)\n----\n\nFor more context, see: https://github.com/jtroo/kanata/issues/55.\n\nNOTE: Even with these workarounds, putting `+lctl+` and `+ralt+` in your defsrc may not\nwork properly with other applications that also use keyboard interception.\nKnown application with issues: GWSL/VcXsrv\n\n=== Windows only: windows-interception-mouse-hwid[[windows-only-windows-interception-mouse-hwid]]\n\nThis defcfg item allows you to intercept mouse buttons for a specific mouse device.\nThis only works with the Interception driver\n(the -wintercept variants of the release binaries).\n\nThe original use case for this is for laptops such as a Thinkpad,\nwhich have mouse buttons that may be desirable to activate kanata actions with.\n\nTo know what numbers to put into the string, you can run the variant with this\ndefcfg item defined with any numbers. Then when a button is first pressed on\nthe mouse device, kanata will print its hwid in the log; you can then\ncopy-paste that into this configuration entry. If this defcfg item is not\ndefined, the log will not print.\n\nHwids in Kanata are byte array representations of a concatenation of the\nASCII hardware ids, which can be seen in Device Manager on Windows. As such,\nthey are an arbitrary length and can be very long.\n\nhttps://github.com/jtroo/kanata/issues/108[Relevant issue].\n\n.Example:\n[source]\n----\n(defcfg\n  windows-interception-mouse-hwid \"70, 0, 60, 0\"\n)\n----\n\n=== Windows only: windows-interception-mouse-hwids[[windows-only-windows-interception-mouse-hwids]]\n\nThis item has a similar purpose as the singular version documented above,\nbut is instead a list of strings that allows multiple mice to be intercepted.\n\nIf both the singular and list items are used,\nthe singular version will behave as if added to the list.\n\n.Example:\n[source]\n----\n(defcfg\n  windows-interception-mouse-hwids (\n    \"70, 0, 60, 0\"\n    \"71, 0, 62, 0\"\n  )\n)\n----\n\n=== Windows only: windows-interception-keyboard-hwids[[windows-only-windows-interception-keyboard-hwids]]\n\nThis defcfg item allows you to intercept only specific keyboards.\nIts value must be a list of strings\nwith each string representing one hardware ID.\n\nTo know what numbers to put into the string,\nyou can run the variant with this defcfg item empty.\nThen when a button is first pressed on the keyboard,\nkanata will print its hwid in the log.\nYou can then copy-paste that into this configuration entry.\nIf this defcfg item is not defined, the log will not print.\n\nHwids in Kanata are byte array representations of a concatenation of the\nASCII hardware ids, which can be seen in Device Manager on Windows. As such,\nthey are an arbitrary length and can be very long.\n\n.Example:\n[source]\n----\n(defcfg\n  windows-interception-keyboard-hwids (\n    \"70, 0, 60, 0\"\n    \"71, 72, 73, 74\"\n  )\n)\n----\n\n=== Windows only: windows-interception-keyboard-hwids-exclude[[windows-only-windows-interception-keyboard-hwids-exclude]]\n\nThis defcfg item allows you to exclude certain keyboards from being intercepted.\nYou cannot define this alongside the inclusive keyboard configuration.\n\nIt is parsed identically to the inclusive configuration.\n\n.Example:\n[source]\n----\n(defcfg\n  windows-interception-keyboard-hwids-exclude (\n    \"70, 0, 60, 0\"\n    \"71, 72, 73, 74\"\n  )\n)\n----\n\n=== Windows only: windows-interception-mouse-hwids-exclude[[windows-only-windows-interception-mouse-hwids-exclude]]\n\nThis defcfg item allows you to exclude certain mice from being intercepted.\nYou cannot define this alongside the inclusive mouse configuration.\n\nIt is parsed identically to the inclusive configuration.\n\n.Example:\n[source]\n----\n(defcfg\n  windows-interception-mouse-hwids-exclude (\n    \"70, 0, 60, 0\"\n    \"71, 0, 62, 0\"\n  )\n)\n----\n\n[[windows-only-tray-icon]]\n=== Windows only: tray-icon\n\nShow a custom tray icon file for a <<windows-only-win-tray>> gui-enabled build of kanata on Windows.\nAccepts either the full path (including the file name with an extension) to the icon file\nor just the file name, which is then searched in the following locations:\n\n* Default parent folders:\n** config file's, executable's\n** env vars: `XDG_CONFIG_HOME`, `APPDATA` (`C:\\Users\\<Name>\\AppData\\Roaming`), `USERPROFILE` `/.config` (`C:\\Users\\<Name>\\.config`)\n* Default config subfolders: `kanata` `kanata-tray`\n* Default image subfolders (optional): `icon` `img` `icons`\n* Supported image file formats: `ico` `jpg` `jpeg` `png` `bmp` `dds` `tiff`\n\nIf not specified, tries to load any icon file from the same locations with the name matching\nconfig name with extension replaced by one of the supported ones.\nSee https://github.com/jtroo/kanata/blob/main/cfg_samples/tray-icon/tray-icon.kbd[example config] for more details.\n\n.Example:\n[source]\n----\n;; in a config file C:\\Users\\<U>\\AppData\\Roaming\\kanata\\kanata.kbd\n(defcfg\n  tray-icon base.png ;; will load C:\\Users\\<U>\\AppData\\Roaming\\kanata\\base.png\n)\n----\n\n[[windows-only-icon-match-layer-name]]\n=== Windows only: icon-match-layer-name\n\nWhen enabled, attempt to switch to a custom tray icon that matches the name of the active layer\nif the layer doesn't specify an explicit icon. If no icon file is found, the default icon will be used (see <<windows-only-tray-icon>>).\nFile search rules are the same as in <<windows-only-tray-icon>>. Defaults to true.\nSee https://github.com/jtroo/kanata/blob/main/cfg_samples/tray-icon/tray-icon.kbd[example config] for more details.\n\n[[windows-only-tooltip-layer-changes]]\n=== Windows only: tooltip-layer-changes\n\nShow a custom layer icon near the mouse pointer position. Defaults to false. Requires <<windows-only-win-tray>> gui-enabled build.\n\n[[windows-only-tooltip-show-blank]]\n=== Windows only: tooltip-show-blank\n\nShow a blank square when instead of an icon if a layer isn't configured to have one. Defaults to false. Requires <<windows-only-win-tray>> gui-enabled build.\n\n[[windows-only-tooltip-no-base]]\n=== Windows only: tooltip-no-base\n\nDon't show a tooltip layer icon for the base layer (1st deflayer). Defaults to true. Requires <<windows-only-win-tray>> gui-enabled build.\n\n[[windows-only-tooltip-duration]]\n=== Windows only: tooltip-duration\n\nSet duration (in ms) for showing a custom layer icon near the mouse pointer position. 0 to never hide. Defaults to 500. Requires <<windows-only-win-tray>> gui-enabled build.\n\n[[windows-only-tooltip-size]]\n=== Windows only: tooltip-size\n\nSet the size (comma-separated Width,Height without spaces) for a custom layer icon near the mouse pointer position. Defaults to 24,24. Requires <<windows-only-win-tray>> gui-enabled build.\n\n[[windows-only-notify-cfg-reload]]\n=== Windows only: notify-cfg-reload\n\nShow system notification message on config reload. Defaults to true. Requires <<windows-only-win-tray>> gui-enabled build.\n\n[[windows-only-notify-cfg-reload-silent]]\n=== Windows only: notify-cfg-reload-silent\n\nDisable sound for the system notification message on config reload. Defaults to false. Requires <<windows-only-win-tray>> gui-enabled build.\n\n[[windows-only-notify-error]]\n=== Windows only: notify-error\n\nShow system notification message on kanata errors. Defaults to true. Requires <<windows-only-win-tray>> gui-enabled build.\n\n[[using-multiple-defcfg-options]]\n=== Using multiple defcfg options\n\nThe `defcfg` entry is treated as a list with pairs of strings. For example:\n\n[source]\n----\n(defcfg a 1 b 2)\n----\n\nThis will be treated as configuration `a` having value `1` and configuration\n`b` having value `2`.\n\nAn example defcfg containing many of the options is shown below. It should be\nnoted options that are Linux-only, Windows-only, or macOS-only will be ignored when used on\na non-applicable operating system.\n\n[source]\n----\n;; Don't actually use this exact configuration,\n;; it's almost certainly not what you want.\n(defcfg\n  process-unmapped-keys yes\n  danger-enable-cmd yes\n  sequence-timeout 2000\n  sequence-input-mode visible-backspaced\n  sequence-backtrack-modcancel no\n  log-layer-changes no\n  delegate-to-first-layer yes\n  movemouse-inherit-accel-state yes\n  movemouse-smooth-diagonals yes\n  dynamic-macro-max-presses 1000\n  linux-dev (/dev/input/dev1 /dev/input/dev2)\n  linux-dev-names-include (\"Name 1\" \"Name 2\")\n  linux-dev-names-exclude (\"Name 3\" \"Name 4\")\n  linux-continue-if-no-devs-found yes\n  linux-unicode-u-code v\n  linux-unicode-termination space\n  linux-x11-repeat-delay-rate 400,50\n  windows-altgr add-lctl-release\n  windows-interception-mouse-hwid \"70, 0, 60, 0\"\n)\n----\n\n== Command line arguments[[cli-args]]\n\nWhen executing Kanata, you can pass a variety of arguments to the program\nto change Kanata's behaviour.\nThis section describes the purpose and usage of these arguments.\n\n[[args-help]]\n=== Show help text: `-h`, `--help`\n\nOnly show help text that describes these arguments,\nthen exit the program.\n\n[[args-config-file]]\n=== Configuration file(s): `-c`, `--cfg`\n\nConfiguration file(s) to use with Kanata.\nWhen not specified, default file locations are checked.\nThe default files checked depend on your operating system\nand are described by `--help`.\n\nThis argument can be used more than once;\nsee <<live-reload>> to understand the behaviour of this.\nThe startup configuration will be the first file listed.\n\n[[args-tcp]]\n=== TCP server address: `-p`, `--port`\n\nEnable the TCP server capability and listen on either:\n- a specified port, on all IP addresses\n- a specific `IP:PORT`\n\nThis enables use cases such as\nhttps://github.com/jtroo/kanata?tab=readme-ov-file#community-projects-related-to-kanata[application aware switching].\n\nThe protocol is plaintext newline-terminated JSON.\nThe source of truth is the\nhttps://github.com/jtroo/kanata/blob/main/tcp_protocol/src/lib.rs[TCP protocol code].\n\nYou may also be interested in the\nhttps://github.com/jtroo/kanata/blob/main/example_tcp_client/src/main.rs[example client].\n\n==== TCP Protocol Overview\n\nThe TCP server uses a simple request/response model with JSON messages.\nEach message is a single line of JSON terminated by a newline (`\\n`).\n\n- **Client → Server**: Commands to control Kanata (reload config, switch layers, etc.)\n- **Server → Client**: Responses to commands and event notifications (layer changes, config reloads, etc.)\n\n==== Client Commands\n\nThese JSON messages can be sent from a TCP client to control Kanata:\n\n===== Layer Control\n\n[cols=\"1,2\"]\n|===\n| Command | Description\n\n| `{\"ChangeLayer\":{\"new\":\"layer-name\"}}`\n| Switch to the specified layer. Equivalent to the `layer-switch` keyboard action.\n\n| `{\"RequestLayerNames\":{}}`\n| Request a list of all defined layer names. Server responds with `LayerNames`.\n\n| `{\"RequestCurrentLayerName\":{}}`\n| Request the name of the currently active layer. Server responds with `CurrentLayerName`.\n\n| `{\"RequestCurrentLayerInfo\":{}}`\n| Request the current layer's name and full configuration text. Server responds with `CurrentLayerInfo`.\n|===\n\n.Example - Query and switch layers:\n[source,bash]\n----\n# Get all layer names\necho '{\"RequestLayerNames\":{}}' | nc localhost 7070\n\n# Get current layer\necho '{\"RequestCurrentLayerName\":{}}' | nc localhost 7070\n\n# Switch to nav layer\necho '{\"ChangeLayer\":{\"new\":\"nav\"}}' | nc localhost 7070\n----\n\n===== Virtual Key Actions\n\n[cols=\"1,2\"]\n|===\n| Command | Description\n\n| `{\"RequestFakeKeyNames\":{}}`\n| Request a list of all defined virtual key names. Server responds with `FakeKeyNames`.\n\n| `{\"ActOnFakeKey\":{\"name\":\"key-name\",\"action\":\"Tap\"}}`\n| Trigger a virtual key defined in `defvirtualkeys`. Actions: `Press`, `Release`, `Tap`, `Toggle`.\n|===\n\nVirtual keys must be defined in your configuration using `defvirtualkeys`.\nSee the <<virtual-keys>> section for details.\n\n.Example - Trigger a text expansion macro:\n[source]\n----\n;; In your config:\n(defvirtualkeys\n  email-sig (macro S-b e s t spc r e g a r d s)\n)\n\n;; From TCP client:\necho '{\"ActOnFakeKey\":{\"name\":\"email-sig\",\"action\":\"Tap\"}}' | nc localhost 7070\n----\n\n===== Mouse Control\n\n[cols=\"1,2\"]\n|===\n| Command | Description\n\n| `{\"SetMouse\":{\"x\":100,\"y\":200}}`\n| Set the mouse cursor position to absolute screen coordinates.\n|===\n\nThis is the TCP equivalent of the <<set-mouse>> keyboard action.\n\n===== Configuration Reload\n\n[cols=\"1,2\"]\n|===\n| Command | Description\n\n| `{\"Reload\":{}}`\n| Reload the current configuration file. Equivalent to `lrld` keyboard action.\n\n| `{\"ReloadNext\":{}}`\n| Load the next configuration file in the list. Equivalent to `lrnx` keyboard action.\n\n| `{\"ReloadPrev\":{}}`\n| Load the previous configuration file in the list. Equivalent to `lrpv` keyboard action.\n\n| `{\"ReloadNum\":{\"index\":0}}`\n| Load configuration file at the specified index (0-based).\n\n| `{\"ReloadFile\":{\"path\":\"/path/to/config.kbd\"}}`\n| Load a specific configuration file by path.\n|===\n\nAll reload commands support optional `wait` and `timeout_ms` fields for synchronous confirmation:\n\n[source,json]\n----\n{\"Reload\":{\"wait\":true,\"timeout_ms\":5000}}\n----\n\nWhen `wait` is `true`, the server blocks until the reload completes or times out, then sends a `ReloadResult` message. The `timeout_ms` field specifies the maximum wait time in milliseconds (default: 5000).\n\n===== Server Information\n\n[cols=\"1,2\"]\n|===\n| Command | Description\n\n| `{\"Hello\":{}}`\n| Request server version and capabilities. Server responds with `HelloOk`.\n|===\n\n==== Server Messages\n\nThese JSON messages are sent from Kanata to connected TCP clients:\n\n===== Event Notifications\n\nThese are sent automatically when events occur:\n\n[cols=\"1,2\"]\n|===\n| Message | Description\n\n| `{\"LayerChange\":{\"new\":\"layer-name\"}}`\n| Sent when the active layer changes.\n\n| `{\"ConfigFileReload\":{\"new\":\"/path/to/config.kbd\"}}`\n| Sent when a configuration file is reloaded.\n\n| `{\"MessagePush\":{\"message\":\"your-message\"}}`\n| Sent when a `push-msg` action is triggered from the keyboard configuration.\n\n| `{\"Error\":{\"msg\":\"error description\"}}`\n| Sent when an error occurs processing a command.\n\n| `{\"HoldActivated\":{\"key\":\"caps\"}}`\n| Sent when a tap-hold key transitions to hold state. The `key` field is the physical key name.\n\n| `{\"TapActivated\":{\"key\":\"a\"}}`\n| Sent when a tap-hold key triggers its tap action. The `key` field is the physical key name.\n|===\n\n===== Query Responses\n\nThese are sent in response to client queries:\n\n[cols=\"1,2\"]\n|===\n| Message | Description\n\n| `{\"LayerNames\":{\"names\":[\"base\",\"nav\",\"num\"]}}`\n| Response to `RequestLayerNames`. Contains all defined layer names.\n\n| `{\"FakeKeyNames\":{\"names\":[\"email-sig\",\"nav-mode\"]}}`\n| Response to `RequestFakeKeyNames`. Contains all defined virtual key names.\n\n| `{\"CurrentLayerName\":{\"name\":\"base\"}}`\n| Response to `RequestCurrentLayerName`. Contains the active layer name.\n\n| `{\"CurrentLayerInfo\":{\"name\":\"base\",\"cfg_text\":\"...\"}}`\n| Response to `RequestCurrentLayerInfo`. Contains the layer name and its full configuration text.\n\n| `{\"HelloOk\":{\"version\":\"1.11.0\",\"protocol\":1,\"capabilities\":[...]}}`\n| Response to `Hello`. Contains server version, protocol version, and supported capabilities. Includes `hold-activated` and `tap-activated`.\n\n| `{\"ReloadResult\":{\"ok\":true}}`\n| Response to reload commands when `wait` was `true`. Indicates whether the config reload succeeded. If timed out, includes `timeout_ms`.\n|===\n\nFor a complete implementation example, see the\nhttps://github.com/jtroo/kanata/blob/main/example_tcp_client/src/main.rs[example TCP client].\n\n[[args-quiet]]\n=== Disable logs other than errors: `-q`, `--quiet`\n\nSilence info and warning logs so that only errors are logged.\nThis might be desirable when running in the background as a system service.\n\n[[args-debug]]\n=== Enable debug logs: `-d`, `--debug`\n\nAdd extra debug logging.\nThis is helpful when diagnosing an issue\nor discovering key names.\n\n[[args-trace]]\n=== Enable trace logs: `-t`, `--trace`\n\nAdd extra trace logging.\nAlso includes debug logging.\nThis is sometimes helpful when diagnosing an issue,\nbut is very verbose.\n\n[[args-nodelay]]\n=== Remove startup delay: `-n`, `--nodelay`\n\nBy default, Kanata adds a delay before reading keyboard inputs.\nThis helps protect against stale states\nwhen started from a terminal.\n\nHowever, this may be undesirable\nwhen running as a background service.\n\n[[args-check]]\n=== Only check configuration: `--check`\n\nCheck the configuration file validity and then exit.\n\n[[args-log-layer-changes]]\n=== Force log changes: `--log-layer-changes`\n\nYour final desired `defcfg` could be to have logging of layer changes as false.\nHowever, for testing purposes you may want to temporarily log the layers.\nThis configuration forces logging to happen for this use case.\n\n[[args-no-wait]]\n=== Skip exit prompt: `--no-wait`\n\nBy default, kanata displays \"Press enter to exit\" and waits for user input\nbefore exiting. This flag skips that prompt and exits immediately.\n\nThis is useful when running kanata as a background service (e.g., systemd)\nwhere you want automatic restart on failure. Without this flag,\nthe service would hang waiting for stdin input that never comes.\n\n[[args-linux-dev-symlink]]\n=== Linux only - Device symlink path: `-s`, `--symlink-path`\n\nIf you want another program to consume the Kanata device output,\nyou can use this flag to have a consistent device filesystem path.\n\n[[args-wait-device]]\n=== Linux only - Wait before device grab attempt: `-w`, `--wait-device-ms`\n\nSome devices take a while to become ready and can fail to be grabbed.\nThis configuration adds a delay before trying to grab devices\nin case this is an issue impacting you.\n\n[[args-macos-list-devices]]\n=== macOS only - Only list keyboards: `-l`, `--list`\n\nList keyboard names that can be used\nwithin defcfg and then exit.\n\n== Advanced features[[advanced-features]]\n[[virtual-keys]]\n=== Virtual keys\n\nYou can define up to 767 virtual keys.\nThese keys are not directly mapped to any physical key presses or releases.\nVirtual keys can be activated via special actions:\n\n* `(on-press    <action> <virtual key name>)` or `on↓`:\nActivate a virtual key action when pressing the associated input key.\n* `(on-release  <action> <virtual key name>)` or `on↑`:\nActivate a virtual key action when releasing the associated input key.\n* `(on-idle <idle time> <action> <virtual key name>)`:\nActivate a virtual key action when kanata has been idle\nfor at least `idle time` milliseconds.\n* `(on-physical-idle <idle time> <action> <virtual key name>)`:\nActivate a virtual key action when the physical keyboard has\nhad all keys released\nfor at least `idle time` milliseconds.\n* `(hold-for-duration <hold time> <virtual key name>`):\nPress a virtual key for `hold time` milliseconds.\nIf `hold-for-duration` retriggered on a virtual key before release,\nthe time will be reset with no additional press/release events.\n\nThe `<action>` parameter can be one of:\n\n* `tap-virtualkey     | tap-vkey`:\nPress and release the virtual key. If the key is already pressed, this only releases it.\n* `press-virtualkey   | press-vkey`:\nPress the virtual key. It will not be released until another action triggers a release or tap.\nIf the key is already pressed, this does nothing.\n* `release-virtualkey | release-vkey`:\nRelease the virtual key. If it is not already pressed, this does nothing.\n* `toggle-virtualkey  | toggle-vkey`:\nPress the virtual key if it is not already pressed, otherwise release it.\n\nA virtual key can be defined in a `defvirtualkeys` configuration entry.\nConfiguring this entry is similar to `+defalias+`,\nbut you cannot make use of aliases inside to shorten an action.\nYou can refer to previously defined virtual keys.\n\nExpanding on the `on-idle` action some more,\nthe wording that \"kanata\" has been idle is important.\nEven if the keyboard is idle, kanata may not yet be idle.\nFor example, if a long-running macro is playing,\nor kanata is waiting for the timeout of actions such as `caps-word` or `tap-dance`,\nkanata is not yet idle, and the tick count for the `<idle time>` parameter\nwill not yet be counting even if you no longer have any keyboard keys pressed.\nThe variant `on-physical-idle` may be more desirable if you want the timer\nto only start based on having all keyboard keys be released.\n\n.Example:\n[source]\n----\n(defvirtualkeys\n  ;; Define some virtual keys that perform modifier actions\n  ctl lctl\n  sft lsft\n  met lmet\n  alt lalt\n\n  ;; A virtual key that toggles all modifier virtual keys above\n  tal (multi\n        (on-press toggle-virtualkey ctl)\n        (on-press toggle-virtualkey sft)\n        (on-press toggle-virtualkey met)\n        (on-press toggle-virtualkey alt)\n      )\n\n  ;; Virtual key that activates a macro\n  vkmacro (macro h e l l o spc w o r l d)\n)\n\n(defalias\n  psf (on-press press-virtualkey   sft)\n  rsf (on-press release-virtualkey sft)\n\n  tal (on-press tap-vkey tal)\n  mac (on-press tap-vkey vkmacro)\n\n  isf (on-idle 1000 tap-vkey sft)\n  hfd (hold-for-duration 1000 met)\n)\n\n(deflayer use-virtual-keys\n  @psf @rsf @tal @mac a s d f @isf @hfd\n)\n----\n\n.Older fake keys documentation\n[%collapsible]\n====\nThe older configuration style of fake keys are still supported\nbut the new style is preferred due to (hopefully) clearer naming.\n\nFake keys can be defined inside of `deffakekeys`.\n\nThe actions are:\n\n* `+(on-press-fakekey <fake key name> <action>)+` or `on↓fakekey`: Activate a fake key\n  action when pressing the key mapped to this action.\n* `+(on-release-fakekey <fake key name> <action>)+` or `on↑fakekey`: Activate a fake key\n  action when releasing the key mapped to this action.\n* `+(on-idle-fakekey <fake key name> <action> <idle time>)+`:\n  Activate a fake key action when kanata has been idle\n  for at least `idle time` milliseconds.\n\nThe aforementioned `+<key action>+` can be one of four values:\n\n* `+press+`: Press the fake key. It will not be released until another action\n  triggers a release or tap.\n* `+release+`: Release the fake key. If it's not already pressed, this does nothing.\n* `+tap+`: Press and release the fake key. If it's already pressed, this only releases it.\n* `+toggle+`: Press the fake key if not already pressed, otherwise release it.\n\n.Example:\n[source]\n----\n(deffakekeys\n  ctl lctl\n  sft lsft\n  met lmet\n  alt lalt\n\n  ;; Press all modifiers\n  pal (multi\n        (on-press fakekey ctl press)\n        (on-press-fakekey sft press)\n        (on-press-fakekey met press)\n        (on-press-fakekey alt press)\n      )\n\n  ;; Release all modifiers\n  ral (multi\n        (on-press-fakekey ctl release)\n        (on-press-fakekey sft release)\n        (on-press-fakekey met release)\n        (on-press-fakekey alt release)\n      )\n)\n\n(defalias\n  psf (on-press-fakekey sft press)\n  rsf (on-press-fakekey sft release)\n\n  pal (on-press-fakekey pal tap)\n  ral (on-press-fakekey ral tap)\n\n  isf (on-idle-fakekey sft tap 1000)\n)\n\n(deflayer use-virtual-keys\n  @psf @rsf @pal @ral a s d f @isf\n)\n----\n\n====\n\nFor more context, you can read the\nhttps://github.com/jtroo/kanata/issues/80[issue that sparked the creation of virtual keys].\n\nSomething notable about virtual keys is that they don't always interrupt the state\nof an active `+tap-dance-eager+`. If a `macro` action is assigned to a virtual\nkey, this won't interrupt a tap dance. However, most other action types,\nnotably a \"normal\" key action like `+rsft+` will still interrupt a tap dance.\n\n[[sequences]]\n=== Sequences\n\nThe `+sldr+` action makes kanata go into \"sequence\" mode. The action name is\nshort for \"sequence leader\". This comes from Vim which has the concept of a configurable\nsequence leader key. When in sequence mode, keys are not typed\n(<<sequence-input-mode,by default>>)\nbut are saved until one of the following happens:\n\n* A key is typed that does not match any sequence\n* `+sequence-timeout+` milliseconds elapses since the most recent key press\n\nSequences are configured similarly to `+defvirtualkeys+`. The first parameter of a\npair must be a defined virtual key name. The second parameter is a list of keys\nthat will activate a virtual key tap when typed in the defined order. More\nprecisely, the action triggered is:\n\n`+(on-press tap-vkey <virtual key name>)+`\n\n.Example:\n[source]\n----\n(defseq git-status (g s t))\n(defvirtualkeys git-status (macro g i t spc s t a t u s))\n(defalias rcl (tap-hold-release 200 200 sldr rctl))\n\n(defseq\n    dotcom (. S-3)\n    dotorg (. S-4)\n\n    ;;     The shifted letters in parentheses means a single press of lsft\n    ;;     must remain held while both h and then s are pressed.\n    ;;     This is not the same as S-h S-s, which means that the lsft key\n    ;;     must be released and repressed between the h and s presses.\n    https (S-(h s))\n)\n(defvirtualkeys\n    dotcom (macro . c o m)\n    dotorg (macro . o r g)\n    https (macro h t t p s S-; / /)\n)\n----\n\nThere are 10 special keys with names `nop0-nop9` which kanata treats specially.\nKanata will never send OS events for these keys\nbut they can still participate in sequences.\n\nSee an example of using the nop keys\nalongside templates to define sequences below.\n\n.Example:\n[source]\n----\n(defsrc f7   f8   f9   f10)\n(deflayer base\n        sldr nop0 nop1 nop2)\n(deftemplate seq (vk-name input-keys output-action)\n    (defvirtualkeys $vk-name $output-action)\n    (defseq $vk-name $input-keys)\n)\n;; template-expand has a shortened form: t!\n(t! seq dotcom (nop0 nop1) (macro . c o m))\n(t! seq dotorg (nop0 nop2) (macro . o r g))\n----\n\nIf 10 special nop keys do not seem sufficient,\nyou can get creative with your sequences and treat some as a prefix modifier.\nFor example, you can get 24 \"keys\" by treating `nop0-nop5` as normal\nwhile treating `nop6-nop9` as prefixes that are always followed by a second nop key.\n\n.Example:\n[source]\n----\n(defalias\n  nop0 nop0\n  ;; ...\n  nop5 nop5\n  nop6 (macro nop6 nop0)\n  ;; ...\n  nop11 (macro nop6 nop5)\n  ;; ...\n  nop18 (macro nop9 nop0)\n  ;; ...\n  nop23 (macro nop9 nop5)\n)\n----\n\n==== Overlapping keys in any order\n\nWithin the key list of `defseq` configuration items,\nthe special `O-` list prefix can be used to denote a set of keys that must\nall be pressed before any are released in order to match the sequence.\n\nFor an example, `O-(a b c)` is equivalent to `O-(c b a)`.\n\n.Example:\n[source]\n----\n(defvirtualkey hello (macro h (unshift e l) 5 (unshift l o)))\n(defseq hello (O-(h l o)))\n----\n\nWARNING: The way that sequences implements this functionality behind the scenes\nis by generating a sequence for every permutation of the overlapping keys.\nThis can make kanata use up a lot of memory.\nDue to this, the maximum keys allowed in a given `O-(...)` list is 6,\nbut you are still permitted to add more to the sequence,\nincluding more `O-(...)` lists.\nDoing the above can balloon kanata's memory consumption.\n\n.Sample of more advanced usage\n[%collapsible]\n====\n\nThe configuration below showcases context-dependent chording\nwith auto-space and auto-deleted spaces from typing punctuation.\n\nFor example, chording `(d a y)` and then `(t u e)` will output\n`Tuesday`, while chording `(t u e)` by itself does nothing.\n\n.Example configuration:\n[source]\n----\n(defsrc f1)\n(deflayer base lrld)\n(defcfg process-unmapped-keys yes\n\tsequence-input-mode visible-backspaced\n\tconcurrent-tap-hold true)\n(deftemplate seq (vk-name in out)\n\t(defvirtualkeys $vk-name $out)\n\t(defseq $vk-name $in))\n\n(defvirtualkeys rls-sft (multi (release-key lsft)(release-key rsft)))\n(defvar rls-sft (on-press tap-vkey rls-sft))\n(deftemplate rls-sft () $rls-sft 5)\n\n(defchordsv2\n\t(d a y) (macro sldr d (t! rls-sft) a y spc nop0) 200 first-release ()\n\t(h l o) (macro h (t! rls-sft) e l l o sldr spc nop0) 200 first-release ()\n)\n(t! seq Monday (d a y spc nop0 O-(m o n)) (macro S-m $rls-sft o n d a y nop9 sldr spc nop0))\n(t! seq Tuesday (d a y spc nop0 O-(t u e)) (macro S-t $rls-sft u e s d a y nop9 sldr spc nop0))\n(t! seq DelSpace_. (spc nop0 .) (macro .))\n(t! seq DelSpace_; (spc nop0 ;) (macro ;))\n----\n\n.Try using the above configuration to type the text:\n[source]\n----\nday;\nDay;\nTuesday.\nday hello\nhello day\nHello day.\nhello Tuesday\nHello Monday;\n----\n\n====\n\n==== Override the global timeout and input mode\n\nAn alternative to using `sldr` is the `sequence` action.\nThe syntax is `(sequence <timeout>)`.\nThis enters sequence mode with a sequence timeout\ndifferent from the globally configured one.\n\nThe `sequence` action can also be called with a second parameter.\nThe second parameter is an override for `sequence-input-mode`:\n\n----\n(sequence <timeout> <input-mode>)\n----\n\n\n.Example:\n[source]\n----\n;; Enter sequence mode and input . with a timeout of 250\n(defalias dot-sequence (macro (sequence 250) 10 .))\n\n;; Enter sequence mode and input . with a timeout of 250 and using hidden-delay-type\n(defalias dot-sequence (macro (sequence 250 hidden-delay-type) 10 .))\n----\n\n==== sequence-noerase\n\nWhen you have a keyboard locale that uses dead keys,\nyou may be pressing two keys that only actually output one symbol.\nBy default, when the `visible-backspaced` input mode does the backtracking backspaces,\nit backspaces according to input count.\nWith dead keys, this may result in too many backspaces.\n\nThe `sequence-noerase` action is a no-output action\nthat tells the sequences action to have one fewer backspace\nwhen backtracking with visible-backspaced.\n\n.Example:\n[source]\n----\n(deflayermap (base)\n  0 sldr\n  u (t! maybe-noerase u)\n)\n(deftemplate maybe-noerase (char)\n  (multi\n    (switch\n      ((key-history ' 1)) (sequence-noerase 1) fallthrough\n      () $char break\n   ))\n)\n(defvirtualkeys seq-output-1 (macro a b c d e f g))\n(defseq seq-output-1 (' u))\n\n----\n\n==== More about sequences\n\nFor more context about sequences, you can read the\nhttps://github.com/jtroo/kanata/issues/97[design and motivation of sequences].\nYou may also be interested in\nhttps://github.com/jtroo/kanata/blob/main/docs/sequence-adding-chords-ideas.md[the document describing chords in sequences]\nto read about how chords in sequences behave.\n\n[[input-chords]]\n=== Input chords\n\nNot to be confused with <<output-chords-combos,output chords>>, `+chord+`\nactions allow you to perform various actions based on which specific combination\nof input keys are pressed together. Such an unordered combination of keys\nis called a \"chord\". Each chord can perform a different action, allowing you\nto bind up to `+2^n - 1+` different actions to just `+n+` keys.\n\nInput chords are configured similarly to `+defalias+` with two extra parameters\nat the beginning of each `+defchords+` group: the name of the group and a\ntimeout value after which a chord triggers if it isn't triggered by a key release\nor press of a non-chord key before the timeout expires.\n\n[source]\n----\n(defsrc a b c)\n(deflayer default\n  @cha @chb @chc\n)\n\n(defalias\n  cha (chord example a)\n  chb (chord example b)\n  chc (chord example c)\n)\n\n(defchords example 500\n  (a      ) a\n  (   b   ) b\n  (a     c) C-v\n  (a  b  c) @three\n)\n----\n\nThe first item of each pair specifies the keys that make up a given chord.\nThe second item of each pair is the action to be executed when the given chord\nis pressed and may be any regular or advanced action, including aliases. It\ncurrently cannot however contain another `+chord+` action.\n\nNote that unlike with `+defseq+`, these keys do not directly correspond to real\nkeys and are merely arbitrary labels that make sense within the context of the\nchord.\nThey are mapped to real keys in layers by configuring the key in the layer to\nmap to a `+(chord name key)+` action where `+name+` is the name of the chords\ngroup (above `+example+`) and `+key+` is one of these arbitrary labels.\n\nIt is perfectly valid to nest these `+chord+` actions that enter \"chording mode\"\nwithin other actions like `+tap-dance+` and that will work as one would expect.\nHowever, this only applies to the first key used to enter \"chording mode\".\nOnce \"chording mode\" is active, all other keys will be directly handled by\n\"chording mode\" with no regard for wrapper actions; e.g. if a key is pressed\nand it maps to a tap-hold with a chord as the hold action within, that chord\nkey will immediately activate instead of the key needing to be held for the\ntimeout period.\n\n**Release behaviour**\n\nFor single key actions and output chords — like `lctl` or `S-tab` —\nand for `layer-while-held`,\nan input chord will release the action only when all keys that are part of\nthe input chord have been released.\nIn other words, if even one key is held for the input chord\nthen the output action will be continued to be held,\nbut only for the mentioned action categories.\nThe behaviour also applies to the actions mentioned above\nwhen used inside of `multi` but not within any other action.\n\nAn exception to the behaviour described above\nfor the action categories that would normally apply\nis if a chord decomposition occurs.\nA chord decomposition occurs when you input a chord\nthat does not correspond to any action.\nWhen this happens, kanata splits up the key presses to activate\nother actions from the components of the input chord.\nIn this scenario, the behaviour described in the next paragraph will occur.\n\nFor chord decompositions and all other action categories,\nthe release behaviour is more confusing:\nthe output action will end when any key is released during the timeout,\nor if the timeout expires, the output action ends when the *first* key\nthat was pressed in the chord gets released.\nThis inconsistency is a limitation of the current implementation.\nIn these scenarios it is recommended\nto hold down all keys if you want to keep holding\nand to release all keys if you want to do a release.\nThis is because it will probably be difficult\nto know which key was pressed first.\n\nIf you want to bypass the behaviour of keys being held for chord outputs,\nyou could change the chord output actions to be <<macro,macros>> instead.\nUsing a macro will guarantee a rapid press+release for the output keys.\n\n[[defaliasenvcond]]\n=== defaliasenvcond\n\nNOTE: this configuration item is older and instead you may want to use\nthe newer and more generalized <<environment>> configuration.\n\nThere is a variant of `defalias`: `defaliasenvcond`.\nThis variant is parsed similarly,\nbut there must be an extra list parameter\nthat comes before all of the name-action pairs.\n\nThe list must contain two strings.\nIn order, these strings are:\nan environment variable name,\nand the environment variable value.\nWhen the environment variable defined by the name\nhas the corresponding value when starting kanata,\nthe aliases within will be active.\nOtherwise, the aliases will be skipped.\n\nA use case for `defaliasenvcond` is when one has multiple devices\nwhich vary in layout of keys,\ne.g. different special keys on the bottom row.\nUsing environment variables, one can use the same kanata\nconfiguration across those multiple devices\nwhile changing key behaviours to keep consistent behaviour\nof specific key positions across the multiple devices,\nwhen the hardware keys at those physical key positions are not\nthe same.\n\n\n.Example:\n[source]\n----\n(defaliasenvcond (LAPTOP lp1)\n  met @lp1met\n)\n\n(defaliasenvcond (LAPTOP lp2)\n  met @lp2met\n)\n----\n\n.Set environment variables in the current terminal process:\n[source]\n----\n# powershell\n$env:VAR_NAME = \"var_value\"\n\n# bash\nVAR_NAME=var_value\n----\n\n[[custom-tap-hold-behaviour]]\n=== Custom tap-hold behaviour\n\nThis is not currently configurable without modifying the source code, but if\nyou're willing and/or capable, there is a tap-hold behaviour that is currently\nnot exposed. Using this behaviour, one can be very particular about when and how\ntap vs. hold will activate by using extra information. The available\ninformation that can be used is exactly which keys have been pressed or\nreleased as well as the timing in milliseconds of those key presses.\nThe action `+tap-hold-release-keys+` makes use of some of this capability, but\ndoesn't make full use of the power of this functionality.\n\nFor more context, you can read the\nhttps://github.com/jtroo/kanata/issues/128[motivation for custom tap-hold behaviour].\n\n\n[[fancy-key-symbols]]\n=== Fancy key symbols\n\nInstead of using the same `+a-z+` letters for special keys, e.g., `+lsft+` for `+LeftShift+`\nyou can use much shorter, yet more visible, key symbols like `+‹⇧+`.\n\nFor more details see\nhttps://github.com/jtroo/kanata/blob/main/docs/fancy_symbols.md[symbol list] and\nhttps://github.com/jtroo/kanata/blob/main/cfg_samples/fancy_symbols.kbd[example config], which not only uses these symbols in layer definitions, but also repurposes `+⎇›+` and `+⇧›+` `+⎇›+` keys into \"symbol\" keys that allow you to insert these fancy symbols by pressing the key, e.g.,\n\n* hold `+⎇›+` and tap `+Delete+` would insert `+␡+`\n\n[[windows-only-work-elevated]]\n=== Windows only: enable in elevated windows\n\nThe default `kanata.exe` binary doesn't work in elevated windows (run with administrative privileges),\ne.g., `Control Panel`. However, you can use AutoHotkey's \"EnableUIAccess\" script to self-sign the binary,\nmove it to \"Program Files\", then launching kanata from there will also work in these elevated windows.\nSee https://github.com/jtroo/kanata/blob/main/EnableUIAccess[EnableUIAccess] folder with the script\nand its required libraries (needs https://www.autohotkey.com/download/[AutoHotkey v2] installed)\n\nIf compiling yourself, you should add the feature flag `win_manifest`\nto enable the use of the `EnableUIAccess` script:\n\n```\ncargo build --win_manifest\n```\n\n[[windows-only-win-tray]]\n=== Windows only: win-tray\n\nKanata can be compiled as a Windows GUI tray app with the feature flag `gui`.\nThis can simplify launching the app on user login by placing a `.lnk`\nat `%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup`, show custom icon indicator per config\n\n\nimage:https://github.com/jtroo/kanata/blob/main/docs/win-tray/win-tray-screen.png[icon indicator per config,477,129]\n\nas well as dynamic icon indicator per layer (might need to click on the gif below to play)\n\nimage:https://github.com/jtroo/kanata/blob/main/docs/win-tray/win-tray-layer-change.gif[icon indicator per layer,33,35,opts=autoplay]\n\n(see <<windows-only-win-tray>>). It also supports (re)loading configs.\n\nCurrently the only configuration supported is tray icon per profile, all other configuration should\nbe done by passing cli flags in the `Target` field of `.lnk`, e.g., `\"C:\\Program Files\\kanata\\kanata.exe\" -d -n`\nto launch kanata without a delay in a debug mode\n\nWhen launched from a command line, the app outputs log to the console, but otherwise the logs are currently\nonly available via an app capable of viewing `OutputDebugString` debugs, e.g., https://github.com/smourier/TraceSpy[TraceSpy].\n\n[[test-your-config]]\n=== Test your config\n\nKanata has a `+kanata_simulated_input+` tool\nto help test your configuration in a predictable manner.\n\nYou can try it out on https://jtroo.github.io/[GitHub pages].\n\nCode for the CLI tool can be found under link:https://github.com/jtroo/kanata/blob/main/simulated_input/[simulated_input].\n\nInstead of physically typing to test something\nand wondering whether you didn't get the expected result because your config is wrong or\nbecause you mistyped something,\nyou can write a sequence of key presses in a `+sim.txt+` file,\nrun the tool with your config\nand get a \"timeline\" view of input/output events that can help understand how kanata translates\nyour input into various key/mouse presses.\n\nWARNING: The format of this view may change. Emoji output may break vertical alignment.\n\nFor more details download the files below and run `kanata_simulated_input -c sim.kbd -s sim.txt` +\n  - https://github.com/jtroo/kanata/blob/main/docs/simulated_output/sim.kbd[example config] with simple home row mod bindings +\n  - https://github.com/jtroo/kanata/blob/main/docs/simulated_output/sim.txt[example input sequence] +\n  - https://github.com/jtroo/kanata/blob/main/docs/simulated_output/sim_out.txt[example output sequence] +\n\nInput sequence file format: whitespace insensitive list of `prefix:key` pairs where prefix is one of: +\n  - `🕐`, `t`, or `tick` to add time between key events in `ms` +\n  - `↓`, `d`, `down`, or `press` +\n  - `↑`, `u`, `up`, or `release` +\n  - `⟳`, `r`, or `repeat` +\n  - `🎭`, `fakekey`, `vk`, or `virtualkey` to activate virtual keys +\n  - `🔀`, `ls`, or `layer-switch` to switch layers +\n\nVirtual key format is `vk:name[:action]`, `fakekey:name[:action]`, or `virtualkey:name[:action]`. +\nWhen using the emoji prefix, the format is `🎭name[:action]`. Action is one of: +\n  - `press` or `p` (default if omitted) +\n  - `release` +\n  - `tap` or `t` +\n  - `toggle` or `g` +\nExample: `vk:vk_bear:tap` or `🎭vk_bear:tap`. +\n\nLayer switch format is `ls:name` or `layer-switch:name`. +\nWhen using the emoji prefix, the format is `🔀name`. +\nThis switches to the specified layer as the new default layer. +\nExample: `ls:nav` or `🔀nav`. +\n\nAnd key names are defined in the https://github.com/jtroo/kanata/blob/main/parser/src/keys/mod.rs[str_to_oscode function],\nfor example, `1` for the numeric key 1 or `kp1`/`🔢₁` for the keypad numeric key 1\n\nUsing unicode symbols `🕐`,`↓`,`↑`,`⟳`,`🎭`,`🔀` allows skipping the `:` separator, e.g., `↓k` ≝ `↓:k` ≝ `d:k`\n\n[[zippychord]]\n=== Zippychord\n\n**Reference**\n\nYou may define a single `+defzippy+` configuration item.\nThis configuration enables chorded text expansion.\n\n.Configuration syntax within the kanata configuration\n[source]\n----\n(defzippy\n  $zippy-filename ;; required\n  on-first-press-chord-deadline $deadline-millis  ;; optional\n  idle-reactivate-time          $idle-time-millis ;; optional\n  smart-space                   $smart-space-cfg  ;; optional\n  smart-space-punctuation (                       ;; optional\n    $punc1 $punc2 ... $puncN)\n  output-character-mappings (    ;; optional\n    $character1 $output-mapping1\n    $character2 $output-mapping2\n    ;; ...\n    $characterN $output-mappingN\n  )\n)\n----\n\n[cols=\"1,4\"]\n|===\n| `$zippy-filename`\n| Relative or absolute file path.\nIf relative, its path is relative to\nthe directory containing the kanata configuration file.\nThis must be the first item following `defzippy`.\n\n| `$deadline-millis`\n| Number of milliseconds.\nAfter the first press while zippy is enabled,\nif no chord activates within this configured time,\nzippy is temporarily disabled.\n\n| `$idle-time-millis`\n| Number of milliseconds.\nAfter typing ends and this configured number of milliseconds elapses,\nzippy will be re-enabled from being temporarily disabled.\n\n| `$smart-space-cfg`\n| Determines the smart space behaviour.\nThe options are `none`, `add-space-only`, and `full`.\nWith `none`, outputs are typed as-is.\nWith `add-space-only`, spaces are automatically added after outputs\nwhich end with neither a space or a backspace ⌫.\nWith `full`, the `add-space-only` behaviour applies\nalongside additional behaviour:\ntyping punctuation (default characters: `, . ;`)\nafter a zippy activation will delete a prior automatically-added space.\n\n| `$punc`\n| A character defined in `output-character-mappings`\nor a known key name, which shall be considered as punctuation.\nThe `smart-space-punctuation` configuration\nwill overwrite the default punctuation list\nconsidered by smart-space;\nif you want to include the default characters,\nyou must include them in this configuration.\n\n| `$character`\n| A single unicode codepoint for use\nin the output column of the zippy configuration file.\n\n| `$output-mapping`\n| Key or output chord to tell kanata how to type `$character`\nwhen seen in the zippy file output column.\nMust be a single key or output chord.\nThe output chord may contain `AG-` to tell kanata to press with AltGr\nand may contain `S-` to tell kanata to press with Shift.\n\nThe list items `no-erase` and `single-output` are also usable in this position.\n\n| `no-erase`\n| Accepts a single key or output chord as a parameter.\nThe zippy system will not backspace this character\nin case of auto-erasure by a superset chord or followup chord.\nUse for dead keys or compose keys.\n\n| `single-output`\n| Accepts one or more keys or output chords as a parameter.\nThe zippy system send only one backspace\nin case of auto-erasure by a superset chord or followup chord.\nUse for a dead key or compose key sequence with one output symbol.\n|===\n\nRegarding output mappings,\nyou can configure the output of the special-lisp-syntax characters\n`+) ( \"+` via these lines:\n\n[source]\n----\n    \")\"    $right-paren-output\n    \"(\"    $left-paren-output\n    r#\"\"\"# $double-quote-output\n----\n\nAs an example, for the US layout these should be the correct lines:\n\n[source]\n----\n    \")\"     S-0\n    \"(\"     S-9\n    r#\"\"\"#  S-'\n----\n\n.Configuration syntax within the zippy configuration file\n[source]\n----\n// This is a comment.\n//          inputs            ↹  outputs\n$chord1 $follow-chord1.1...1.M\t$output1\n$chord2 $follow-chord2.1...2.M\t$output2\n// ...\n$chordN $follow-chordN.1...N.M\t$outputN\n----\n\nThe format is two columns separated by a single Tab character.\nThe first column is input and the second is output.\n\n[cols=\"1,4\"]\n|===\n|`$chord`\n| A set of characters.\nYou can use space by including it as the first character in the chord;\nfor an example see `Alphabet` in the sample below.\nWith 0 optional follow chords,\nthe corresponding output on the same line (`$output`)\nwill activate when zippy is enabled\nand all the defined chord keys are pressed simultaneously.\nThe order of presses is not important.\n\n| `$follow-chord`\n| 0 or more chords, used the same way as `$chord`.\nHaving follow chords means the `$output` on the same line\nwill activate upon first activating the earlier chord(s) in the same line,\nreleasing all keys, and pressing the keys in `$follow-chordN.M`.\nFollow chords are separated from the previous chord by a space.\nIf using a space in the follow chord, use two spaces;\nfor an example see `Washington` in the sample below.\n\n| `$output`\n| The characters to type when the chord and optional follow chord(s)\nare all pressed by the user.\nThis is separated from the input chord column\nby a single Tab character.\nThe characters are typed in sequence\nand must all be singular-name key names\nas one would configure in `defsrc`.\nA capitalized single-character key name\nwill be parsed successfully\nand these will be outputted alongside Shift to output the capitalized key.\nAdditionally, `output-character-mappings` configuration can be used\nto inform kanata of additional mappings that may use Shift or AltGr.\n|===\n\n**Examples**\n\n.Sample kanata configuration\n[source]\n----\n(defzippy\n  zippy.txt\n  on-first-press-chord-deadline 500\n  idle-reactivate-time          500\n  smart-space-punctuation (? ! . , ; :)\n  output-character-mappings (\n    ;; This should work for US international.\n    ! S-1\n    ? S-/\n    % S-5\n    \"(\" S-9\n    \")\" S-0\n    : S-;\n    < S-,\n    > S-.\n    r#\"\"\"# S-'\n    | S-\\\n    _ S--\n    ® AG-r\n    ’ (no-erase `)\n    é (single-output ' e)\n  )\n)\n----\n\n.Sample zippy file content\n\n[source]\n----\ndy\tday\ndy 1\tMonday\ndy 2\tTuesday\n abc\talphabet\n w  a\tWashington\ngi\tgit\ngi f p\tgit fetch -p\n----\n\n**Description**\n\nZippychord is yet another chording mechanism in Kanata.\nThe inspiration behind it is primarily the\nhttps://github.com/psoukie/zipchord[zipchord project].\nThe name is similar; it is named \"zippy\" instead of \"zip\" because\nKanata's implementation is not a port and does not aim for 100%\nbehavioural compatibility.\n\nThe intended use case is shorthands, or accelerating character output.\nWithin zippychord, inputs are keycode chords or sequences,\nand the outputs are also purely keycodes.\nIn other words, all other actions are unsupported;\ne.g. layers, switch, one-shot.\n\nZippychord behaves on outputted keycodes, i.e. the key outputs\nafter kanata has finished processing your\ninputs, layers, switch logic and other configurations.\nThis is similar to how sequences operate\nand is unlike chords(v1) and chordsv2.\nFurthermore, outputs are all eager like `visible-backspaced` on sequences.\nIf a zippychord activation occurs, typed keys are backspaced.\n\nTo give an example, if one configures zippychord with a line like:\n\n[source]\n----\ngi\tgit\n----\n\nthen either of the following typing event sequences\nwill erase the input characters\nand then proceed to type the output \"git\"\nlike if it was `(macro bspc bspc g i t)`.\n\n[source]\n----\n(press g) (press i)\n(press i) (press g)\n----\n\nNote that there aren't any release events listed.\nTo contrast, the following event sequence would not result in an activation:\n\n[source]\n----\n(press g) (release g) (press i)\n----\n\nZippychord supports fully overlapping chords and sequences.\nFor example, this configuration is allowed:\n\n[source]\n----\ngi\tgit␣\ngi s\tgit␣status\ngi c\tgit checkout␣\ngi c b\tgit checkout -b␣\ngi c a\tgit commit --amend␣\ngi c n\tgit commit --amend --no-edit\ngi c a m\tgit commit --amend -m 'FIX_THIS_COMMIT_MESSAGE'\n----\n\nWhen you begin with the `(g i)` chord, you can follow up\nwith various character sequences to output different git commands.\nThis use case is quite similar to git aliases.\nOne advantage of zippychord is that it eagerly shows you\nthe true underlying command as you type.\n"
  },
  {
    "path": "docs/design.md",
    "content": "# Design doc\n\n## Obligatory diagram\n\n<img src=\"./kanata-basic-diagram.svg\">\n\n## main\n\n- read args\n- read config\n- start event loops\n\n## event loop\n\n- read key events\n- send events to processing loop on channel\n\n## processing loop\n\n- check for events on mpsc\n- if event: send event to layout\n- tick() the keyberon layout, send any events needed\n- if no event: sleep for 1ms\n- separate monotonic time checks, because can't rely on sleep to be\n  fine-grained or accurate\n- send `ServerMessage`s to the TCP server\n\n## TCP server\n\n- listen for `ClientMessage`s and act on them\n- recv `ServerMessage`s from processing loop and forward to all connected\n  clients\n\n## layout\n\n- uses keyberon\n- indices of `kanata_keyberon::layout::Event::{Press, Release}(x,y)`:\n\n      x = 0 or 1 (0 is for physical key presses, 1 is for fake keys)\n      y = OS code of key used as an index\n\n## OS-specific code\n\nMost of the OS specific code is in `oskbd/` and `keys/`. There's a bit of it in\n`kanata/` since the event loops to receive OS events are different.\n"
  },
  {
    "path": "docs/fancy_symbols.md",
    "content": "### Supported key symbols\n\n  |Symbol(s)[^1] \t|Key `name`                                \t|\n  |---------     \t|--------                                  \t|\n  |‹x x›         \t| Left/Right modifiers (e.g., ‹⎈ LCtrl)    \t|\n  |⇧             \t| Shift                                    \t|\n  |⎈ ⌃           \t| Control                                  \t|\n  |⌘ ◆ ❖         \t| Windows/Command                          \t|\n  |⎇ ⌥           \t| Alt                                      \t|\n  |⇪             \t| capslock                                 \t|\n  |⎋             \t|`escape`                                  \t|\n  |⭾ ↹           \t|`tab`                                     \t|\n  |␠ ␣           \t| `spc` spacebar                             \t|\n  |␈ ⌫           \t|`bspc` backspace (delete backward)         |\n  |␡ ⌦           \t|`del` delete forward                      \t|\n  |⏎ ↩ ⌤ ␤       \t|`ret` return or enter                     \t|\n  |︔ ⸴ ．⁄        \t|semicolon `;` / comma `,` / period `.` / slash `/`\t|\n  |⧵ ＼           \t| backslash `\\`                            \t|\n  |﹨ <           \t|`non_us_backslash`                        \t|\n  |【 「 〔 ⎡       \t|`open_bracket`                            \t|\n  |】 」 〕 ⎣       \t|`close_bracket`                           \t|\n  |ˋ ˜           \t|`grave_accent_and_tilde`                  \t|\n  |‐ ₌           \t|`hyphen` `equal_sign`                     \t|\n  |▲ ▼ ◀ ▶       \t|`up`/`down`/`left`/`right` (arrows)        |\n  |⇞ ⇟           \t|`pgup`/`pgdn` (page up, page down)\t      |\n  |⎀             \t|`insert`                               \t|\n  |⇤ ⤒ ↖         \t|`home`                                 \t|\n  |⇥ ⤓ ↘         \t|`end`                                  \t|\n  |⇭             \t|`numlock`                              \t|\n  |🔢₁ 🔢₂ 🔢₃ 🔢₄ 🔢₅\t|`keypad_` `1`–`5`                      \t|\n  |🔢₆ 🔢₇ 🔢₈ 🔢₉ 🔢₀\t|`keypad_` `6`–`0`                      \t|\n  |🔢₋ 🔢₌ 🔢₊      \t|`keypad_` `hyphen`/`equal_sign`/`plus` \t|\n  |🔢⁄ 🔢．🔢∗       \t|`keypad_` `slash`/`period`/`asterisk`  \t|\n  |◀◀ ▶⏸ ▶▶      \t|`vk_consumer_` `previous`/`play`/`next`\t|\n  |🔊 🔈+ or ➕₊⊕   \t|`volume_up`                            \t|\n  |🔉 🔈− or ➖₋⊖   \t|`volume_down`                          \t|\n  |🔇 🔈⓪ or ⓿ ₀   \t|`mute`                                 \t|\n  |🔆 🔅           \t|`vk_consumer_brightness_` `up`/`down`  \t|\n  |⌨💡+ or ➕₊⊕    \t|`vk_consumer_illumination_up`          \t|\n  |⌨💡− or ➖₋⊖    \t|`vk_consumer_illumination_down`        \t|\n  |🎛             \t|`vk_dashboard`                         \t|\n  |▤ ☰ 𝌆         \t|`application`                          \t|\n  |🖰1 🖰2 ... 🖰5  \t|`button` `1`–`5`                       \t|\n  |‹🖰 🖰›         \t|`button` `1` `2`                       \t|\n\n[^1]: space-separated list of keys; `or` means only last symbol in a pair changes\n"
  },
  {
    "path": "docs/interception.md",
    "content": "# Windows Interception driver implementation notes\n\n- Interception handle is `!Send` and `!Sync`\n  - means a single thread should own both input and output\n  - `KbdOut` will need to send keyboard output events to that thread as opposed\n    to Linux using `uinput` and the original Windows code using `SendInput`\n    which are independent of the input devices.\n  - Maybe save channel in kanata struct as part of new kanata\n- Interception can filter for only keyboard events\n  - should use this filter feature; don't want to intercept mouse\n- Need to save previous device for sending to, in case wait/receive (with\n  timeout) don't return anything so that sending stuff can be sent to some\n  device.\n- Input `ScanCode` maps to the keyberon `KeyCode`; they both use the USB\n  standard codes.\n  - For ease of integration will probably need to unfortunately convert it to\n    an `OsCode` even though the processing loop will soon after just convert it\n    back to `KeyCode`. Oh well.\n"
  },
  {
    "path": "docs/kmonad_comparison.md",
    "content": "# Comparison with kmonad\n\nThe kmonad project is the closest alternative for this project.\n\n## Benefits of kmonad over kanata\n\n- ~MacOS support~ (this is implemented now)\n- Different features\n\n## Why I built and use kanata\n\n- [Double-tapping a tap-hold key](https://github.com/kmonad/kmonad/issues/163) did not behave\n  [how I want it to](https://docs.qmk.fm/#/tap_hold?id=tapping-force-hold)\n- Some key sequences with tap-hold keys [didn't behave how I want](https://github.com/kmonad/kmonad/issues/466):\n  - `(press lsft) (press a) (release lsft) (release a)` (a is a tap-hold key)\n  - The above outputs `a` in kmonad, but I want it to output `A`\n- kmonad was missing [mouse buttons](https://github.com/kmonad/kmonad/issues/150)\n\nThe issues listed are all fixable in kmonad and I hope they are one day! For me\nthough, I didn't and still don't know Haskell well enough to contribute to\nkmonad. That's why I instead built kanata based off of the excellent work that\nhad already gone into the\n[keyberon](https://github.com/TeXitoi/keyberon),\n[ktrl](https://github.com/ItayGarin/ktrl), and\n[kbremap](https://github.com/timokroeger/kbremap) projects.\n\nIf you want to see the features that kanata offers, the\n[configuration guide](./config.adoc) is a good starting point.\n\nI dogfood kanata myself and it works great for my use cases. Though kanata is a\nyounger project than kmonad, it now has more features. If you give kanata a\ntry, feel free to ask for help in an issue or discussion, or let me know how it\nwent 🙂.\n"
  },
  {
    "path": "docs/locales.adoc",
    "content": "////\nCommented out since it doesn't seem to add anything for now, but maybe in the future\n:sectlinks:\n:sectanchors:\n////\n\nifdef::env-github[]\n:tip-caption: :bulb:\n:note-caption: :information_source:\n:important-caption: :heavy_exclamation_mark:\n:caution-caption: :fire:\n:warning-caption: :warning:\nendif::[]\n\n= Keyboard locales\n\n////\nCommented out since doc is short enough without a ToC for the time being.\n:toc:\n:toc-title: pass:[<b>TABLE OF CONTENTS</b>]\n:toclevels: 3\n////\n\n== ISO 100% Keyboard (event.code)\n\nNOTE: Tested on Linux only\n\n[%collapsible]\n====\n----\n(defsrc\n  Escape                   F1      F2     F3     F4         F5     F6     F7     F8             F9         F10            F11  F12      PrintScreen  ScrollLock  Pause\n  Backquote        Digit1 Digit2 Digit3 Digit4 Digit5 Digit6 Digit7 Digit8 Digit9 Digit0       Minus       Equal         Backspace      Insert       Home        PageUp        NumLock NumpadDivide NumpadMultiply NumpadSubtract\n  Tab                   KeyQ   KeyW   KeyE   KeyR   KeyT   KeyY   KeyU   KeyI   KeyO    KeyP       BracketLeft BracketRight  Enter      Delete       End         PageDown      Numpad7 Numpad8      Numpad9        NumpadAdd\n  CapsLock              KeyA   KeyS   KeyD   KeyF   KeyG   KeyH   KeyJ   KeyK   KeyL   Semicolon     Quote      Backslash                                                      Numpad4 Numpad5      Numpad6\n  ShiftLeft IntlBackslash  KeyZ   KeyX   KeyC   KeyV   KeyB   KeyN   KeyM   Comma  Period       Slash                   ShiftRight                   ArrowUp                   Numpad3 Numpad2      Numpad1        NumpadEnter\n  ControlLeft       MetaLeft AltLeft                   Space                   AltRight       MetaRight   ContextMenu ControlRight      ArrowLeft    ArrowDown   ArrowRight    Numpad0              NumpadDecimal  \n)\n----\n====\n\n== ISO German QWERTZ (Windows, non-interception)[[german]]\n\n=== Using `deflocalkeys-win`:[[german-defwin]]\n\n[%collapsible]\n====\n----\n(defcustomkeys\n  ü    186\n  +    187\n  #    191\n  ö    192\n  ß    219\n  ^    220\n  ´    221\n  ä    222\n  <    226\n)\n\n(defsrc\n  ^         1    2    3    4    5    6    7    8    9    0    ß    ´    bspc\n  tab       q    w    e    r    t    z    u    i    o    p    ü    +\n  caps      a    s    d    f    g    h    j    k    l    ö    ä    #    ret\n  lsft <    y    x    c    v    b    n    m    ,    .    -    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n----\n====\n\n=== Without using `deflocalkeys`:[[german-nodeflocalkeys]]\n\n[%collapsible]\n====\n----\n(defsrc\n  \\         1    2    3    4    5    6    7    8    9    0    [    ]    bspc\n  tab       q    w    e    r    t    z    u    i    o    p    ;    =\n  caps      a    s    d    f    g    h    j    k    l    grv  '    /    ret\n  lsft 102d y    x    c    v    b    n    m    ,    .    -    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n----\n====\n\n=== Example aliases[[german-aliases]]\n\n[%collapsible]\n====\n----\n(defalias\n  ;; shifted german keys\n  ! S-1\n  ˝ S-2  ;; unicode 02DD ˝ look-a-like is used because @\" is no valid alias, to be displayed correctly\n         ;; in console requires a font that can - e.g. cascadia\n  §\tS-3\n  $\tS-4\n  %\tS-5\n  &\tS-6\n  /\tS-7\n  ﴾\tS-8  ;; unicode FD3E ﴾ look-a-like is used because @( is no valid alias, to be displayed correctly...\n  ﴿\tS-9  ;; unicode FD3F ﴿ look-a-like is used because @) is no valid alias, to be displayed correctly ...\n  =\tS-0\n  ? S-ß\n  * S-+\n  ' S-#\n  ; S-,\n  : S-.\n  _ S--\n  > S-<\n  < <   ;; not really needed but having @< and @> looks consistent\n\n  ;; change dead keys in normal keys\n  ´ (macro ´ spc )\t  ;; ´ \n  ` (macro S-´ spc )  ;; `\n  ^ (macro ^ spc )    ;; ^ = \\ - shifting @^ will produce an incorrect space now\n  ° S-^\n  \n  ;; AltGr german keys\n  ~ A-C-+\n  \\ A-C-ß\n  ẞ A-C-S-ß\n  | A-C-<\n  } A-C-0\n  { A-C-7\n  ] A-C-9\n  [ A-C-8\t\n  € A-C-e\n  @ A-C-q\n  ² A-C-2\n  ³ A-C-3\n  µ A-C-m\n)\n----\n====\n\n== ISO German QWERTZ (MacOS)[[german]]\n\n=== Using `deflocalkeys-macos`:[[german-defmac]]\n\n[%collapsible]\n====\n----\n(deflocalkeys-macos\n  ß    12\n  ´    13\n  z    21\n  ü    26\n  +    27\n  ö    39\n  ä    40\n  <    41\n  #    43\n  y    44\n  -    53\n  ^    86\n)\n\n(defsrc\n  ⎋         f1   f2   f3   f4   f5   f6   f7   f8   f9   f10  f11  f12\n  ^         1    2    3    4    5    6    7    8    9    0    ß    ´    ⌫\n  ↹         q    w    e    r    t    z    u    i    o    p    ü    +\n  ⇪         a    s    d    f    g    h    j    k    l    ö    ä    #    ↩\n ‹⇧   <     y    x    c    v    b    n    m    ,    .    -         ▲    ⇧›\n  fn       ‹⌃   ‹⌥   ‹⌘              ␣              ⌘›   ⌥›   ◀    ▼    ▶\n)\n----\n====\n\n== ISO French Azerty (MacOS)[[french]]\n\n=== Using `deflocalkeys-macos`:[[french-defmac]]\n\n[%collapsible]\n====\n----\n(deflocalkeys-macos\n  @    50\n  par  12 ;; Close parentheses\n  -    13\n  ^    73\n  $    164\n  ù    85\n  `    192\n  <    41\n  /    191\n  =    53\n  a    16\n  q    30\n  z    17\n  w    44\n  m    39\n)\n\n(defsrc\n  ⎋         f1   f2   f3   f4   f5   f6   f7   f8   f9  f10   f11  f12\n  @         1    2    3    4    5    6    7    8    9    0    par   -    ⌫\n  ↹         a    z    e    r    t    y    u    i    o    p     ^    $\n  ⇪         q    s    d    f    g    h    j    k    l    m     ù    `    ↩\n ‹⇧   <     w    x    c    v    b    n    ,    .    /    =          ▲    ⇧›\n  fn       ‹⌃   ‹⌥   ‹⌘              ␣              ⌘›   ⌥›    ◀    ▼    ▶\n)\n----\n====\n\n== ISO French AZERTY (Windows, non-interception)[[french]]\n\nNOTE: This is for the https://kbdlayout.info/kbdfr?arrangement=ISO105[French AZERTY layout] (ISO105 arrangement). Tested on Windows only.\n\n[%collapsible]\n====\n----\n(deflocalkeys-win\n\tk252 223 ;; ref to the key [!] (VK_OEM_8)\n)\n\n(defsrc ;; french\n  '        1     2     3     4     5     6     7     8     9     0      [    eql        bspc\n  tab       a     z     e     r     t     y     u     i     o     p      ]     ;\n  caps       q     s     d     f     g     h     j     k     l     m      `     bksl     ret\n  lsft nubs   w     x     c     v     b     n     comm  .     /     k252                rsft\n  lctl    lmet   lalt           spc                             ralt                    rctl\n)\n----\n====\n\n== ISO Turkish QWERTY (Linux)[[turkish]]\n\nNOTE: This is for the https://kbdlayout.info/kbdtuq?arrangement=ISO105[Turkish QWERTY layout] (ISO105 arrangement). Tested on Linux only.\n\n[%collapsible]\n====\n----\n(deflocalkeys-linux\n\t* \t12\n\t- \t13\n\tı \t23\n\tğ \t26\n\tü \t27\n\tş \t39\n\tİ \t40\n\t, \t43\n\t< \t86\n\tö \t51\n\tç \t52\n\t. \t53\n)\n\n(defsrc ;; turkish-iso105\n\tgrv  \t1\t2\t3\t4\t5\t6\t7\t8\t9\t0\t*\t-\tbspc\n\ttab  \tq\tw\te\tr\tt\ty\tu\tı\to\tp\tğ\tü\n\tcaps \ta\ts\td\tf\tg\th\tj\tk\tl\tş\tİ\t,\tret\n\tlsft \t<\tz\tx\tc\tv\tb\tn\tm\tö\tç\t.\t\trsft\n\tlctl\tlmet\tlalt\t\t\t\tspc\t\t\t\t\tralt\trmet\trctl\n)\n\n;; We use İ instead of i because kanata doesn't allow using i in deflocalkeys, as it is a default key name.\n----\n====\n\n== ABNT2 Brazillian Portuguese QWERTY (Linux)[[portuguese]]\n\nNOTE: This is for the https://kbdlayout.info/kbdbr[ABNT2 QWERTY layout]. Tested on Linux only.\n\n[%collapsible]\n====\n----\n(deflocalkeys-linux\n  ´ 26\n  [ 27\n  ç 39\n  ~ 40\n  ' 41\n  ] 43\n  ; 53\n  \\ 86\n  / 89\n)\n\n(defsrc ;; brazillian-abnt2\n  esc  f1    f2   f3   f4   f5   f6   f7   f8   f9   f10  f11  f12 \n  '    1     2    3    4    5    6    7    8    9    0    -    =   bspc\n  tab  q     w    e    r    t    y    u    i    o    p    ´    [   ret\n  caps a     s    d    f    g    h    j    k    l    ç    ~    ]  \n  lsft \\     z    x    c    v    b    n    m    ,    .    ;    rsft\n  lctl lmet  lalt           spc            ralt      /\n)\n----\n====\n\n== ISO Swedish QWERTY (Linux)[[swedish]]\n\n[%collapsible]\n====\n----\n;; Swedish ISO105\n(deflocalkeys-linux\n  §   41\n  +   12\n  ´   13 ;; Acute accent. Opposite to the grave accent (grv).\n  å   26\n  ¨   27\n  ö   39\n  ä   40\n  '   43\n  <   86\n  ,   51\n  .   52\n  -   53\n)\n\n(defsrc ;; Swedish ISO105\n  §    1    2    3    4    5    6    7    8    9    0    +    ´    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    å    ¨\n  caps a    s    d    f    g    h    j    k    l    ö    ä    '    ret\n  lsft <    z    x    c    v    b    n    m    ,    .    -         rsft\n  lctl lmet lalt                spc                 ralt rmet menu rctl\n)\n\n;; Empty layer that matches the Swedish layout\n(deflayer default\n  _    _    _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _    _    _    _    _    _    _    _    _    _    _    _\n  _    _    _    _    _    _    _    _    _    _    _    _         _\n  _    _    _                   _                   _    _    _    _\n)\n----\n====\n\n\n== Swedish QWERTY Localkeys (Windows)[[swedish]]\n\n[%collapsible]\n====\n----\n(deflocalkeys-win\n  §   220\n  +   187\n  ´   219\n  å   221\n  ¨   186\n  ö   192\n  ä   222\n  '   191\n  <   226\n  ,   188\n  .   190\n  -   189\n)\n----\n====\n"
  },
  {
    "path": "docs/platform-known-issues.adoc",
    "content": "= Hardware known issues\n\nAt the electric circuit layer of many keyboards,\ncost-saving measures can lead to key presses not registering\nwhen pressing multiple keys simultaneously.\nUsually this happens with at least 3 key presses.\nKanata cannot fix this issue.\nYou can work around it by avoiding\nthe problem key combination,\nor using a different keyboard.\n\n= Platform-dependent known issues\n\n== Preface\n\nThis document contains a list of known issues\nwhich are unique to a given platform.\nThe platform supported by the core maintainer (jtroo)\nare Windows 11 and Linux.\nWindows 10 is expected to work fine,\nbut as Windows 10 end-of-support is approaching in 2025,\njtroo no longer has any devices with it installed.\n\nOn Windows, there are two backing mechanisms that can be used\nfor keyboard input/output and they have different issues.\nThese will be differentiated by the words \"LLHOOK\" and \"Interception\",\nwhich map to the binaries\n`kanata.exe` and `kanata_wintercept.exe` respectively.\n\n== Windows 11 LLHOOK\n\n* Some input key combinations (e.g. Win+L) cannot be intercepted before\n  running their default action\n** https://github.com/jtroo/kanata/issues/192\n** https://github.com/jtroo/kanata/discussions/428\n* OS-level key remapping behaves differently vs. Linux or Interception\n** Does not affect winiov2 variant\n** https://github.com/jtroo/kanata/issues/152\n* Certain applications that also use the LLHOOK mechanism may not behave correctly\n** https://github.com/jtroo/kanata/issues/55\n** https://github.com/jtroo/kanata/issues/250\n** https://github.com/jtroo/kanata/issues/430\n** https://github.com/espanso/espanso/issues/1488\n* AltGr / ralt / Right Alt can misbehave\n** https://github.com/jtroo/kanata/blob/main/docs/config.adoc#windows-only-windows-altgr\n* NumLock state can mess with arrow keys in unexpected ways\n** Does not affect winiov2 variant\n** https://github.com/jtroo/kanata/issues/78\n** https://github.com/jtroo/kanata/issues/667\n** Workaround: use the correct https://github.com/jtroo/kanata/discussions/354[numlock state]\n* Without `process-unmapped-keys yes`, using arrow keys\nwithout also having the shift keys in `defsrc` will break shift highlighting\n** Does not affect winiov2 variant\n** https://github.com/jtroo/kanata/issues/858\n** Workaround: add shift keys to `defsrc` or use `process-unmapped-keys yes` in `defcfg`\n\n== Windows 11 Interception\n\n* Sleeping your system or unplugging/replugging devices enough times causes\n  inputs to stop working\n** https://github.com/oblitum/Interception/issues/25\n* Some less-frequently used keys are not supported or handled correctly\n** https://github.com/jtroo/kanata/issues/127\n** https://github.com/jtroo/kanata/issues/164\n** https://github.com/jtroo/kanata/issues/425\n** https://github.com/jtroo/kanata/issues/532\n\n== Linux\n\n* Key repeats can occur when they normally wouldn't in some cases\n** https://github.com/jtroo/kanata/discussions/422\n** https://github.com/jtroo/kanata/issues/450\n** https://github.com/jtroo/kanata/issues/1441\n* Unicode support has varying success due to many applications\n  not supporting the `ibus` input mechanism.\n  Using xkb to map keys to unicode\n  or using clipboard actions are more consistent solutions\n** https://github.com/jtroo/kanata/discussions/703\n* Key actions can behave incorrectly due to the rapidity of key events\n** https://github.com/jtroo/kanata/discussions/733\n** https://github.com/jtroo/kanata/issues/740\n** adjusting https://github.com/jtroo/kanata/blob/main/docs/config.adoc#rapid-event-delay[rapid-event-delay] can potentially be a workaround\n* Macro keys on certain gaming keyboards might stop being processed\n** Context: search for `POTENTIAL PROBLEM - G-keys` in\nlink:../src/kanata/mod.rs[the code].\n** Workaround: leave `process-unmapped-keys` disabled\nand explicitly map keys in `defsrc` instead\n\n== MacOS\n\n* Only left, right, and middle mouse buttons are implemented for clicking\n* Mouse input processing is not implemented, e.g. putting `mlft` into `defsrc` does nothing\n"
  },
  {
    "path": "docs/release-template.md",
    "content": "## Configuration guide\n\n<!-- NOTE: GitHub release doc seems to not support multiline paragraph joining as opposed to other places markdown is used in GitHub. Keep paragraphs on one line in this file, as ugly as it is to do so. -->\n\nLink to the appropriate configuration guide version: [guide link TODO: FIX LINK](https://github.com/jtroo/kanata/blob/FIXME/docs/config.adoc).\n\n## Changelog (since <TODO: previous_version_here>)\n\n<details>\n<summary>Change log</summary>\n* TODO: fill this out\n</details>\n\n## Sample configuration file\n\nThe attached `kanata.kbd` file is tested to work with the current version. The one in the `main` branch of the repository may have extra features that are not supported in this release.\n\n## Windows\n\n<details>\n<summary>Instructions</summary>\n\nDownload the appropriate `kanata-windows-variant.zip` file for your machine CPU. Extract and move the desired binary variant to its intended location. Optionally, download `kanata.kbd`. With the two files in the same directory, you can double-click the extracted `.exe` file to start kanata. Kanata does not start a background process, so the window needs to stay open after startup. See [this discussion](https://github.com/jtroo/kanata/discussions/193) for tips to run kanata in the background.\n\nYou need to run via `cmd` or `powershell` to use a different configuration file:\n\n`kanata_windows_binaryvariant.exe --cfg <cfg_file>`\n\n### Binary variants\n\nExplanation of items in the binary variant:\n\n- x64 vs. arm64:\n  - Select x64 if your machine's CPU is Intel or AMD. If ARM, use arm64.\n- tty vs gui:\n  - tty runs in a terminal, gui runs as a system tray application\n- cmd\\_allowed vs. not\n  - cmd\\_allowed allows the `cmd` actions; otherwise, they are compiled out of the application\n- winIOv2 vs. wintercept\n  - winIOv2 uses the LLHOOK and SendInput Windows mechanisms to intercept and send events.\n  - wintercept uses the [Interception driver](https://github.com/oblitum/Interception). Beware of its known issue that disables keyboards and mice until system reboot: [Link to issue](https://github.com/oblitum/Interception/issues/25).\n    - you will need to install the driver using the release or from the [copy in this repo](https://github.com/jtroo/kanata/tree/main/assets).\n    - the benefit of using this driver is that it is a lower-level mechanism than Windows hooks, and `kanata` will work in more applications.\n\n### wintercept installation\n\n#### Steps to install the driver\n\n- extract the `.zip`\n- run a shell with administrator privilege\n- run the script `\"command line installer/install-interception.exe\"`\n- reboot\n\n#### Additional installation steps\n\nThe above steps are those recommended by the interception driver author. However, I have found that those steps work inconsistently and sometimes the dll stops being able to be loaded. I suspect it has something to do with being installed in the privileged location of `system32\\drivers`.\n\nTo help with the dll issue, you can copy the following file in the zip archive to the directory that kanata starts from: `Interception\\library\\x64\\interception.dll`.\n\nE.g. if you start kanata from your `Documents` folder, put the file there:\n\n**Example:**\n\n```\nC:\\Users\\my_user\\Documents\\\n    kanata_windows_wintercept_x64.exe\n    kanata.kbd\n    interception.dll\n```\n\n### kanata\\_passthru_x64.dll\n\nThe Windows `kanata_passthru_x64.dll` file allows using Kanata as a library within AutoHotkey to avoid conflicts between keyboard hooks installed by both. You can channel keyboard input events received by AutoHotkey into Kanata's keyboard engine and get the transformed keyboard output events (per your Kanata config) that AutoHotkey can then send to the OS.\n\nTo make use of this, take `kanata_passthru_x64.dll`, then the [simulated\\_passthru\\_ahk](https://github.com/jtroo/kanata/blob/main/docs/simulated_passthru_ahk) folder with a brief example, place the dll there, open `kanata_passthru.ahk` to read what the example does and then double-click to launch it.\n\n</details>\n\n## Linux\n\n<details>\n<summary>Instructions</summary>\n\nDownload the `kanata-linux-x64.zip` file.\n\n Extract and move the desired binary variant to its intended location. Run the binary in a terminal and point it to a valid configuration file. Kanata does not start a background process, so the window needs to stay open after startup. See [this discussion](https://github.com/jtroo/kanata/discussions/130) for how to set up kanata with systemd.\n\n**Example:**\n\n```\nchmod +x kanata   # may be downloaded without executable permissions\nsudo ./kanata_linux_x64 --cfg <cfg_file>`\n```\n\nTo avoid requiring `sudo`, [follow the instructions here](https://github.com/jtroo/kanata/wiki/Avoid-using-sudo-on-Linux).\n\n### Binary variants\n\nExplanation of items in the binary variant:\n\n- cmd\\_allowed vs. not\n  - cmd\\_allowed allows the `cmd` actions; otherwise, they are compiled out of the application\n\n</details>\n\n## macOS\n\n<details>\n<summary>Instructions</summary>\n\nThe supported Karabiner driver version in this release is `v6.2.0`.\n\n**WARNING**: macOS does not support mouse as input. The `mbck` and `mfwd` mouse button actions are also not operational.\n\n### Binary variants\n\nExplanation of items in the binary variant:\n\n- x64 vs. arm64:\n  - Select x64 if your machine's CPU is Intel. If ARM, use arm64.\n- cmd\\_allowed vs. not\n  - cmd\\_allowed allows the `cmd` actions; otherwise, they are compiled out of the application\n\n### Instructions for macOS 11 and newer\n\nYou must use the Karabiner driver version `v6.2.0`.\n\nPlease read through this issue comment:\n\nhttps://github.com/jtroo/kanata/issues/1264#issuecomment-2763085239\n\nAlso have a read through this discussion:\n\nhttps://github.com/jtroo/kanata/discussions/1537\n\nAt some point it may be beneficial to provide concise and accurate instructions within this documentation. The maintainer (jtroo) does not own macOS devices to validate; please contribute the instructions to the file `docs/release-template.md` if you are able.\n\n### Install Karabiner driver for macOS 10 and older:\n\n- Install the [Karabiner kernel extension](https://github.com/pqrs-org/Karabiner-VirtualHIDDevice).\n\n### After installing the appropriate driver for your OS (both macOS <=10 and >=11)\n\nDownload the appropriate `kanata-macos-variant.zip` for your machine CPU.\n\nExtract and move the desired binary variant to its intended location. Run the binary in a terminal and point it to a valid configuration file. Kanata does not start a background process, so the window needs to stay open after startup.\n\n**Example:**\n\n```\nchmod +x kanata_macos_arm64   # may be downloaded without executable permissions\nsudo ./kanata_macos_arm64 --cfg <cfg_file>`\n```\n\n### Add permissions\n\nIf Kanata is not behaving correctly, you may need to add permissions. Please see this issue: [link to macOS permissions issue](https://github.com/jtroo/kanata/issues/1211).\n\n</details>\n\n## sha256 checksums\n\n<details>\n<summary>Sums</summary>\n\n```\nTODO: fill this out\n```\n\n</details>\n"
  },
  {
    "path": "docs/sequence-adding-chords-ideas.md",
    "content": "# Sequence improvement: sequence chords\n\n## Preface\n\nThis document is a record of designing/braindumping\nfor the improvement to the sequences feature to add chord support.\nIt is left in an informal and disorganized state \n— as opposed to a being presentable design doc — out of laziness.\nApologies ahead of time if you read this and it's hard to follow,\nfeel free to contribute a PR to create a new and more polished doc.\n\n## Motivation\n\nThe desire is to be able to add output chords to a sequence.\nThe effect of this is that: `(S-(a b))` can be differentiated from `(S-a b)`.\nToday, chords are not supported at all.\nThe two sequences above could be written as `(lsft a b)`;\nhowever, the code today has no way to decide the difference\nbetween `lsft` being applied to only `a` or to both `a` and `b`.\n\nThe feature will codenamed \"seqchords\" for brevity in this document.\n\n## An exploration of an idea: track releases?\n\nToday, the sequence `(lsft a b c)` doesn't care when the `lsft`,\nor even `a` or `b` are released relative to when the subsequent keys\nare pressed. However, with seqchords, the code could be potentially changed to\nmake sequences release-aware.\n\nIt seems a little difficult to integrate\nthis into the trie structure used to track sequences though.\nWith an implementation that is release-aware,\nit seems like the code would need to figure out how to conditionally\nadd release events to the trie, depending if the seq was\n`(lsft a)` or `(S-a)`\n\nFor now, I think a different approach would be better.\n\n## A different idea: modify presses held with mod keys.\n\nThe current sequence type is `Vec<u16>` since keys don't fit into `u8`.\nHowever, there are fewer than 1024 (2^10) keys total.\nThat means there are 6 bits to play with.\n6 bits are enough for the types of modifiers (of which there are 4),\nbut differentiating both sides (increases to 8).\nPerhaps one only cares to use both left and right shifts though, and maybe\nboth left and right alts.\nOne could also use a `u32` instead, but that seems unnecessary for now.\nI see no backwards compatibility issues if one\ndesired that change in the future.\n\nWith this in mind, while modifiers are held, set the upper unused bits of\nthe stored `u16` values.\n\n### Backwards compatibility?\n\nThis does mean that `(lsft a b)` behaves differently\nwith vs. without seqchords.\nUnless maybe the code automatically generates the various permutations\nof this type of sequence, but that seems complicated.\nOr maybe have a `u16` with a special bit pattern that could be used\nto differentiate between `(S-(a b))` and `(lsft a b)`.\nFor now, let's say that the bit pattern is `0xFFFF`.\nIf a modifier is pressed and the sequence `[..., <mod>, 0xFFFF]`\nexists in the trie: continue processing the sequence in mod-aware mode.\n\nOR for simplicity, just say \"screw backwards compatibility\" and force users\nto be clear about what they mean and define the extra permutations, if they\nwant them. I prefer this.\n\n### Data format examples\n\nLet's begin the description of the new data format.\nSince shifted keys seem like they will be the main use case for seqchords,\nonly that will be described in this document for now.\nHere are the numerical key values relevant to the examples.\n\n- `a:    0x001E`\n- `b:    0x0030`.\n- `lsft: 0x002A`.\n\nThis differs by OS, but that's not important.\n\nThe transformation of `(lsft a b)` to a sequence in the trie today\nlooks like:\n\n- `[0x002A, 0x001E, 0x0030]`\n\nThis will remain unchanged with seqchords.\nLet's say that chorded keys using `lsft`\nwill have the otherwise-unused MSB (bit 15) set.\n\nThe transformation of some sequences using chords will be:\n\n1. `(S-(a b)) => [0x802A, 0x801E, 0x8030]`\n2. `  (S-a b) => [0x802A, 0x801E, 0x0030]`\n3. `(S-a S-b) => [0x802A, 0x801E, 0x802A, 0x8030]`\n\nNotably, `lsft` is modifying its own upper bits.\nThis should simplify the implementation logic\nso that the code does not need to add a special-case check\nthat the newly-pressed key is itself a modifier.\n\nOne may need to define different sequences if one wishes to use both\nleft and right shifts to be able to trigger these shifted sequences.\nThe syntax does not exist today, but maybe `(S-(a b))` and `(RS-(a b))`\nas an example for left and right shifts.\nThe reason different sequences would be required is because the\nsequence->trie check operates on the integers that correspond to the keycodes.\n\nConsideration: maybe there could be transformations for the right modifier\nkeys to ensure they get translated to the left modifier keys.\nThis seems like it could be a sensible default to start with.\nIf a change is desired in the future to **not** do this transformation,\nit doesn't seem too difficult to add a configuration item to do so.\nFor now that will be left out, deferring to the YAGNI principle.\n\n### Backwards compatibility revisited\n\nThinking back on the topic of backwards compatibility,\nI'm scrapping that idea of special bit patterns.\nI thought of a probably-better way:\nbacktracking with modifier cancellation.\n\nBy default when seqchords gets added,\nthe modified bit patterns will be used\nto check in the trie for valid sequences.\nHowever, with a `defcfg` item `sequence-backtrack-modcancel`\n— which should be `yes` by default for back-compat reasons —\nif the code encounters an invalid sequence with the modded bit pattern,\nit will try again with the unmodded bit pattern, and only if that does not\nmatch will sequence-mode end with an invalid termination.\nThis backtracking can be turned off if desired,\ne.g. if it behaves badly in some future seqchords use cases.\n"
  },
  {
    "path": "docs/setup-linux.md",
    "content": "# Instructions\n\nIn 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.\n\n### 1. Create the uinput group (if it doesn’t exist)\n\n```bash\nsudo groupdel uinput 2>/dev/null\nsudo groupadd --system uinput\n```\n\n### 2. Add your user to the `input` and `uinput` group\n\n```sh\nsudo usermod -aG input $USER\nsudo usermod -aG uinput $USER\n```\n\nVerify:\n\n```sh\ngroups\n```\n\nYou may need to log out and back in for it to take effect.\n\n### 3. Load the uinput kernel module\n\n```sh\nsudo modprobe uinput\n```\n\nThis ensures `/dev/uinput` exists.\n\n### 4. Make sure the uinput device file has the right permissions.\n\nCreate the udev rule:\n\n```bash\nsudo tee /etc/udev/rules.d/99-input.rules > /dev/null <<EOF\nKERNEL==\"uinput\", MODE=\"0660\", GROUP=\"uinput\", OPTIONS+=\"static_node=uinput\"\nEOF\n```\n\nReload udev rules:\n\n```bash\nsudo udevadm control --reload-rules && sudo udevadm trigger\n```\n\nVerify:\n\n```bash\nls -l /dev/uinput\n```\n\nExpected output:\n\n```bash\ncrw-rw---- 1 root uinput 10, <minor> <MMM DD HH:MM> /dev/uinput\n```\n\n## 5. (Optional) Run Kanata immediately if the group change isn’t active\n\nIf `uinput` is not listed in `groups` even after adding your user:\n\n```bash\nnewgrp uinput -c kanata\n```\n\nThis temporarily gives the current shell the `uinput` group so kanata can access `/dev/uinput` until the next login.\n\n### 6a. (Optional) Create and enable a systemd user service\n\nFirst, create the directory for user services:\n\n```bash\nmkdir -p ~/.config/systemd/user\n```\n\nThen add this to: `~/.config/systemd/user/kanata.service`:\n\n```bash\n[Unit]\nDescription=Kanata keyboard remapper\nDocumentation=https://github.com/jtroo/kanata\n\n[Service]\nEnvironment=PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/bin\n#   Uncomment the 4 lines beneath this to increase process priority\n#   of Kanata in case you encounter lagginess when resource constrained.\n#   WARNING: doing so will require the service to run as an elevated user such as root.\n#   Implementing least privilege access is an exercise left to the reader.\n#\n# CPUSchedulingPolicy=rr\n# CPUSchedulingPriority=99\n# IOSchedulingClass=realtime\n# Nice=-20\nType=simple\nExecStart=/usr/bin/sh -c 'exec $$(which kanata) --cfg $${HOME}/.config/kanata/config.kbd --no-wait'\nRestart=on-failure\nRestartSec=3\n\n[Install]\nWantedBy=default.target\n```\n\nNote: The `--no-wait` flag is required for `Restart=on-failure` to work.\nWithout it, kanata waits for user input on exit, which blocks automatic restart.\n\nMake sure to update the executable location for sh in the snippet above.\nThis would be the line starting with `ExecStart=/usr/bin/sh -c`.\nYou can check the executable path with:\n\n```bash\nwhich sh\n```\n\nAlso, verify if the path to kanata is included in the line `Environment=PATH=[...]`.\nFor 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`.\n`%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\n\nThen run:\n\n```bash\nsystemctl --user daemon-reload\nsystemctl --user enable kanata.service\nsystemctl --user start kanata.service\nsystemctl --user status kanata.service   # check whether the service is running\n```\n\n### 6b. To create and enable an OpenRC daemon service\n\nEdit new file `/etc/init.d/kanata` as root, replacing \\<username\\> as appropriate:\n\n```bash\n#!/sbin/openrc-run\n\ncommand=\"/home/<username>/.cargo/bin/kanata\"\n#command_args=\"--config=/home/<username>/.config/kanata/kanata.kbd\"\n\ncommand_background=true\npidfile=\"/run/${RC_SVCNAME}.pid\"\n\ncommand_user=\"<username>\"\n```\n\nThen run:\n\n```\nsudo chmod +x /etc/init.d/kanata # script must be executable\nsudo rc-service kanata start\nrc-status # check that kanata isn't listed as [ crashed ]\nsudo rc-update add kanata default # start the service automatically at boot\n```\n\n# Credits\n\nThe original text was taken and adapted from: https://github.com/kmonad/kmonad/blob/master/doc/faq.md#linux\n"
  },
  {
    "path": "docs/simulated_output/sim.kbd",
    "content": "(defcfg\n  process-unmapped-keys\tyes\t;;|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.\n  log-layer-changes    \tyes\t;;|no| overhead\n)\n(defvar ;; declare commonly-used values. prefix with $ to call them. They are refered with `$<var name>`\n  tap-repress-timeout \t1000 ;;|500|\n  hold-timeout\t        1500 ;;|500|\n  🕐↕          \t        $tap-repress-timeout\n  🕐🠿          \t        $hold-timeout\n)\n(defalias\n  ;; home row mods ↕tap 🠿hold\n  ;; pinky ring middle index | index middle ring pinky\n  ;; \t          timeout ↕tap 🠿hold¦↕tap 🠿hold action\n  ⌂‹◆\t(tap-hold-release $🕐↕ $🕐🠿 a ‹◆)\t;;\n  ⌂‹⎇\t(tap-hold-release $🕐↕ $🕐🠿 s ‹⎇)\t;;\n  ⌂‹⎈\t(tap-hold-release $🕐↕ $🕐🠿 d ‹⎈)\t;;\n  ⌂‹⇧\t(tap-hold-release $🕐↕ $🕐🠿 f ‹⇧)\t;;\n  ⌂⇧›\t(tap-hold-release $🕐↕ $🕐🠿 j ⇧›)\t;; same actions for the right side\n  ⌂⎈›\t(tap-hold-release $🕐↕ $🕐🠿 k ⎈›)\t;;\n  ⌂⎇›\t(tap-hold-release $🕐↕ $🕐🠿 l ⎇›)\t;;\n  ⌂◆›\t(tap-hold-release $🕐↕ $🕐🠿 ; ◆›)\t;;\n)\n\n(defsrc\n` 1 2\na\ts\td\tf\t\t\tj\tk\tl\t;)\n(deflayer ⌂ ;; modtap layer for home row mods and 1 printing a 🤲🏿 char (will appear as 🤲 until kanata's unicode feature is extended)\n  ‗ 🔣🤲🏿 ‗\n  @⌂‹◆ @⌂‹⎇ @⌂‹⎈ @⌂‹⇧        @⌂⇧› @⌂⎈› @⌂⎇› @⌂◆›)\n"
  },
  {
    "path": "docs/simulated_output/sim.txt",
    "content": "↓j 🕐1600 ↓l 🕐5000 ↓1 🕐50 ↑1 🕐50 ↓1 🕐50 ↑1 🕐50 ↑j 🕐50 ↑l 🕐50\n"
  },
  {
    "path": "docs/simulated_output/sim_out.txt",
    "content": "🕐Δms│           1500    100        1500    3500            50      50            50      50              50\nIn───┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n k↑  │                                                      1                     1       J               L\n k↓  │  J                L                  1                       1\n k⟳  │\nΣin  │ ↓J 🕐1600         ↓L   🕐5000         ↓1    🕐50       ↑1  🕐50 ↓1  🕐50       ↑1  🕐50 ↑J  🕐50         ↑L  🕐50\nOut──┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n k↑  │                                                                                            ⇧›              ⎇›\n k↓  │           ⇧›                 ⎇›\n 🖰↑  │\n 🖰↓  │\n 🖰   │\n 🔣  │                                                🤲                     🤲\n code│\n raw↑│\n raw↓│\nΣout │          ↓⇧›                ↓⎇›                🤲                     🤲                    ↑⇧›             ↑⎇›\n"
  },
  {
    "path": "docs/simulated_passthru_ahk/[COPY HERE] kanata_passthru.dll _",
    "content": ""
  },
  {
    "path": "docs/simulated_passthru_ahk/kanata_dll.kbd",
    "content": ";;Test config for kanata.dll use by AutoHotkey, only maps two keys (f,j) to left/right modtap home row mod Shifts\n(defcfg\n  process-unmapped-keys\tyes\t;;|no| enable processing of keys that are not in defsrc\n  log-layer-changes    \tno \t;;|no| overhead\n)\n\n(defvar\n  🕐↕\t1000 ;;|500| tap-repress-timeout\n  🕐🠿\t1500 ;;|500| hold-timeout\n  )\n(defalias\t;;      timeout→ \ttap\t  hold   ¦\t tap\t hold ←action\n  f⌂‹⇧   \t(tap-hold-release\t$🕐↕\t$🕐🠿       \tf   \t‹⇧)\n  j⌂⇧›   \t(tap-hold-release\t$🕐↕\t$🕐🠿       \tj   \t⇧›)\n  )\n\n(defsrc      f     j   )\n(deflayer ⌂ @f⌂‹⇧ @j⌂⇧›)\n"
  },
  {
    "path": "docs/simulated_passthru_ahk/kanata_passthru.ahk",
    "content": "#Requires AutoHotKey 2.1-alpha.4\n/*\nA short example of using Kanata as a library with AutoHotkey, first F8 press will load the library, second F8 press will activate Kanata for a few (ihDuration) seconds with 'f'/'j' turned into home row Shift mods.\nThe more useful script would not use F8, but f/j directly as hotkeys, but this owl hasn't been drawn yet...\nBoth Kanata and this script mainly output to Windows debug log, use github.com/smourier/TraceSpy to view it\nDependencies and config:\n*/\n  libPath   \t:= \"./\"              \t; kanata_passthru.dll @ this folder\n  kanata_cfg\t:= \"./kanata_dll.kbd\"\t; kanata config @ this file location\n  ihDuration\t:= 10                \t; seconds of activity after pressing F8\n  dbg       \t:= 1                 \t; script's debug level (0 to silence some of its output)\n  dbg_dll   \t:= 1                  ; kanata's debug level (Err=1 Warn=2 Inf=3 Dbg=4 Trace=5)\n/*\nBrief overview of the architecture:\nSetup:\n  - AHK: configures cbKanataOut callback to Send keys out and shares its address with Kanata\n  - Kanata: exports 4 functions\n    - fnKanata_main: set up paths to config and initialize\n    - fnKanata_in_ev: get input key events\n    - K_output_ev_check: check if output key events exist\n    - fnKanata_reset: reset Kanata's state without exiting\n! AHK enables inputhook, so intercepts all keyboard input\n← Redirects all intercepted input to Kanata, where we hit 2 limitations\n  ✗ Kanata can't send keys out itself as it'll be intercepted by AHK's inputhook\n  ✗ Kanata's thread that processes input can't call AHK function in the main thread since AHK is single-threaded\n✓ Kanata opens an async channel from the input processing thread (with Keyberon state machine) to its main\n  ← sends key out data back to the main thread\n  → our script after sending input keys calls Kanata to read this channel until it's empty, and then Sends these keys out\n*/\n\nget_thread_id() {\n  return DllCall(\"GetCurrentThreadId\", \"UInt\")\n}\n\nF8::kanata_dll('vk77')\nkanata_dll(vkC) {\n  ; static K\t:= keyConstant , vk := K._map, sc := K._mapsc  ; various key name constants, gets vk code to avoid issues with another layout\n   ; , s    \t:= helperString ; K.▼ = vk['▼']\n  static is_init := false\n   ,lErr:=1, lWarn:=2, lInf:=3, lDbg:=4, lTrace:=5, log_lvl := dbg_dll ; Kanata's\n   ,last↓ := [0,0]\n   ,id_thread := get_thread_id()\n   ,Cvk_d := GetKeyVK(vkC), Csc_d := GetKeySC(vkC), token1 := 1, ih0 := 0 ; decimal value\n   ,C↑ := false, cleanup := false ; track whether the trigger key has been released to not release it twice on kanata cleanup\n  ; set up machinery for AHK and Kanata to communicate\n  ,libNm            \t:= \"kanata_passthru\"\n  ,lib𝑓             \t:= libNm '\\' 'lib_kanata_passthru' ; receives AHK's address of AHK's cb KanataOut that accepts simulated output events\n  ,lib𝑓input_ev     \t:= libNm '\\' 'input_ev_listener' ; receives key input and uses event_loop's input event handler callback (which will in turn communicate via the internal kanata's channels to keyberon state machine etc.)\n  ,lib𝑓output_ev    \t:= libNm '\\' 'output_ev_check' ; checks if output event is ready (it's sent to our callback if it is)\n  ,lib𝑓reset        \t:= libNm '\\' 'reset_kanata_state' ; reset kanata's state\n  ,hModule          \t:= DllCall(\"LoadLibrary\", \"Str\",libPath libNm '.dll', \"Ptr\")  ; Avoids the need for DllCall in the loop to load the library\n  ,fnKanata_main    \t:= DllCall.Bind(lib𝑓, 'Ptr',unset, 'Str',unset, 'Int',unset)\n  ,fnKanata_in_ev   \t:= DllCall.Bind(lib𝑓input_ev, 'Int',unset , 'Int',unset,  'Int',unset)\n  ,K_output_ev_check\t:= DllCall.Bind(lib𝑓output_ev)\n  ,fnKanata_reset   \t:= DllCall.Bind(lib𝑓reset, 'Int',unset)\n  static ih := InputHook(\"T\" ihDuration \" I1\")\n  , 🕐k_pre := A_TickCount\n  , 🕐k_now := A_TickCount\n  hooks := \"hooks#: \" gethookcount()\n\n  addr_cbKanataOut := CallbackCreate(cbKanataOut)\n  if not is_init {\n    is_init := true\n\n    ; setup inputhook callback functions\n    ih.KeyOpt(  \t'{All}','NSI')  ; N: Notify. OnKeyDown/OnKeyUp callbacks to be called each time the key is pressed\n    ih.OnKeyDown\t:= cbK↓.Bind(1)\t;\n    ih.OnKeyUp  \t:= cbK↑.Bind(0)\t;\n\n    OutputDebug('¦' id_thread \"¦registered inputhook with VisibleText=\" ih.VisibleText \" VisibleNonText=\" ih.VisibleNonText \"`nIlevel=\" ih.MinSendLevel ' hooks#: ' gethookcount() ' →kanata addr#' addr_cbKanataOut)\n    fnKanata_main(addr_cbKanataOut,kanata_cfg,log_lvl) ; setup kanata, passign ahk callback to accept out key events\n    return\n  }\n\n  cbK↓(token,  ih,vk,sc) {\n    static _d := 1, isUp := false, dir := (isUp?'↑':'↓')\n    🕐k_pre := 🕐k_now\n    🕐k_now := A_TickCount\n    if (dbg>=_d) {\n      dbgtxt := ''\n      vk_hex := Format(\"vk{:x}\",vk)\n      key_name := GetKeyName(Format(\"vk{:x}\",vk)) ; bugs with layouts, not english even if english is active\n      dbgtxt .= \"ih\" dir (isSet(key_name)?key_name:'') \"      🢥🄺: vk=\" vk \"¦\" vk_hex \" sc=\" sc ' l' A_SendLevel \" ¦\" id_thread \"¦\"\n      OutputDebug(dbgtxt)\n    }\n    isH := fnKanata_in_ev(vk,sc,isUp)\n    dbgOut := ''\n    for i in [4,4,4,5,5,5] { ; poll a key out channel@kanata) a few times to see if there are key events\n      sleep(i)\n      isOut := K_output_ev_check(), dbgOut.=isOut\n      if (isOut < 0) { ; get as many keys as are available untill reception errors out\n        break\n      }\n    } ;🔚∎🏁\n    (dbg<_d+1)?'':(dbgtxt:='🏁ih' dir ' pos isH=' isH ' isOut=' dbgOut ' ' format(\" 🕐Δ{:.3f}\",A_TickCount - 🕐k_now) ' ' A_ThisFunc ' ¦' id_thread '¦', OutputDebug(dbgtxt))\n  }\n  cbK↑(token,  ih,vk,sc) {\n    static _d := 1, isUp := true, dir := (isUp?'↑':'↓')\n    🕐k_pre := 🕐k_now\n    🕐k_now := A_TickCount\n    if (dbg>=_d) {\n      dbgtxt := ''\n      vk_hex := Format(\"vk{:x}\",vk)\n      key_name := GetKeyName(Format(\"vk{:x}\",vk)) ; bugs with layouts, not english even if english is active\n      dbgtxt .= \"ih\" dir (isSet(key_name)?key_name:'') \"      🢥🄺: vk=\" vk \"¦\" vk_hex \" sc=\" sc ' l' A_SendLevel \" ¦\" id_thread \"¦\"\n      OutputDebug(dbgtxt)\n    }\n    isH := fnKanata_in_ev(vk,sc,isUp)\n    dbgOut := ''\n    for i in [4,4,4,5,5,5] { ; poll a key out channel@kanata) a few times to see if there are key events\n      sleep(i)\n      isOut := K_output_ev_check(), dbgOut.=isOut\n      if (isOut < 0) { ; get as many keys as are available until reception errors out\n        break\n      }\n    }\n    (dbg<_d+1)?'':(dbgtxt:='🏁ih' dir ' pos isH=' isH ' isOut=' dbgOut ' ' format(\" 🕐Δ{:.3f}\",A_TickCount - 🕐k_now) ' ' A_ThisFunc ' ¦' id_thread '¦', OutputDebug(dbgtxt))\n  }\n  ; set up machinery for AHK to receive data from kanata\n  cbKanataOut(kvk,ksc,up) {\n    ; static K\t:= keyConstant, vk:=K._map, vkr:=K._mapr, vkl:=K._maplng, vkrl:=K._maprlng, vk→en:=vkrl['en'], sc:=K._mapsc  ; various key name constants, gets vk code to avoid issues with another layout\n    static _d := 1, lvl_to := 0\n    🕐1 := preciseTΔ()\n    vk_hex := Format(\"vk{:x}\",kvk)\n    if not C↑ && up && (kvk=Cvk_d) {\n      C↑ := true , (dbg<_d)?'':(OutputDebug('trigger key released'))\n    }\n    if cleanup && C↑ && up && (kvk=Cvk_d) { ; todo: check for physical position before excluding?\n      (dbg<_d)?'':(OutputDebug(\"dupe release of trigger key on kanata's cleanup, ignore\"))\n      C↑ := false\n      return\n    }\n    ; Critical ; todo: needed??? avoid being interrupted by itself (or any other thread)\n    if (dbg>=_d) {\n      dbgtxt := ''\n      dir := (up?'↑':'↓')\n      key_name := GetKeyName(vk_hex) ; bugs with layouts, not english even if english is active\n      ; key_name := vk→en.Get(vk_hex,key_name_cur)\n      hooks := \"hooks#: \" gethookcount()\n      dbgtxt .= dir\n    }\n    if isSet(vk_hex) {\n      (dbg<_d)?'':(dbgtxt .= key_name \"       🄷🢦 : vk=\" kvk '¦' vk_hex ' @l' A_SendLevel ' → ' lvl_to ' ' hooks ' ¦' id_thread '¦ ' A_ThisFunc, OutputDebug(dbgtxt))\n      if up {\n        ; SendEvent('{' vk_hex ' up}')\n        SendInput('{' vk_hex ' up}')\n      } else {\n        ; SendEvent('{' vk_hex ' down}')\n        SendInput('{' vk_hex ' down}')\n      }\n    } else {\n      (dbg<_d)?'':(dbgtxt .= '✗name' \"       🄷🢦 : vk=\" kvk '¦' vk_hex ' @l' A_SendLevel ' → ' lvl_to ' ' hooks ' ¦' id_thread '¦ ' A_ThisFunc, OutputDebug(dbgtxt))\n    }\n    🕐2 := preciseTΔ(), 🕐Δ := 🕐2-🕐1\n    if 🕐Δ > 0.5 {\n      (dbg<_d+1)?'':(OutputDebug('🐢🏁 ' format(\" 🕐Δ{:.3f}\",🕐Δ) ' ¦' id_thread '¦ ' A_ThisFunc))\n    } else {\n      (dbg<_d+1)?'':(OutputDebug('🐇🏁 ' format(\" 🕐Δ{:.3f}\",🕐Δ) ' ¦' id_thread '¦ ' A_ThisFunc))\n    }\n    return 1\n  }\n  ; CallbackFree(cbKanataOut)\n\n  if (Cvk_d) { ; modtap; send the activating hotkey to Kanata so it can take it into acount\n    cbK↓(token1,ih0,Cvk_d,Csc_d)\n  }\n  ih.Start()\t\t;\n  ih.Wait() \t\t; Waits until the Input is terminated (InProgress is false)\n  if (ih.EndReason  = \"Timeout\") { ; cleanup kanata's state\n    ; key_name := GetKeyName(Format(\"vk{:x}\",last↓[1]))\n    OutputDebug('—`n`n——————————————— Timeout')\n    🕐k_now := A_TickCount, 🕐Δ := 🕐k_now - 🕐k_pre\n    cleanup := true\n    res := fnKanata_reset(🕐Δ) ; reset kanata's state, progressing time to catch up, release held keys (even those physically held since reset is reset, so from kanata's perspective they should be released)\n    cleanup := false\n    dbgtxt := ''\n    dbgtxt .= 'ih¦' 🕐Δ '🕐Δ timeout A_TimeSinceThisHotkey ' A_TimeSinceThisHotkey\n    dbgOut := ''\n    loop 10 { ; get the remaining out keys from kanata\n      isOut := K_output_ev_check(), dbgOut.=isOut\n      if (isOut < 0) {\n        break\n      }\n    }\n    OutputDebug(dbgtxt '`n`n——————————————— isOut=' dbgOut ' ')\n  }\n\n  ; DllCall(\"FreeLibrary\", \"Ptr\",hModule)  ; to conserve memory, the DLL may be unloaded after using it\n  hModule:=0\n}\n\ngethookcount() {\n  if        (A_KeybdHookInstalled = 0) {\n    return \"_¦_\"\n  } else if (A_KeybdHookInstalled = 3) {\n    return \"✓¦✓\"\n  } else if (A_KeybdHookInstalled = 2) {\n    return \"_¦✓\"\n  } else if (A_KeybdHookInstalled = 1) {\n    return \"✓¦_\"\n  } else {\n    return \"?\"\n  }\n}\n\npreciseTΔ(n:=3) {\n  static start := nativeFunc.GetSystemTimePreciseAsFileTime()\n  t := round(     nativeFunc.GetSystemTimePreciseAsFileTime() - start,n)\n  return t\n}\nclass nativeFunc {\n  static GetSystemTimePreciseAsFileTime() {\n    /* learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime\n    retrieves the current system date and time with the highest possible level of precision (<1us)\n    FILETIME structure contains a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC)\n    100 ns  ->  0.1 µs  ->  0.001 ms  ->  0.00001 s\n    1     sec  ->  1000 ms  ->  1000000 µs\n    0.1   sec  ->   100 ms  ->   100000 µs\n    0.001 sec  ->    10 ms  ->    10000 µs\n    */\n    static interval2sec := (10 * 1000 * 1000) ; 100ns * 10 → µs * 1000 → ms * 1000 → sec\n    DllCall(\"GetSystemTimePreciseAsFileTime\", \"int64*\",&ft:=0)\n    return ft / interval2sec\n  }\n}\n"
  },
  {
    "path": "docs/switch-design",
    "content": "# Preface:\n\nThis document is a scratch space for the design of the switch action.\nIt may be out of date and is kept around for posterity.\n\n.syntax:\n----\n(switch\n  (or a b c) (cmd <cmd1>) break\n  (and a b (or c d)) (cmd <cmd2>) fallthrough\n  (and a b (or c d) (or e f)) fallthrough\n  ()\n)\n----\n\n\n.opcode format examples:\n----\n(or a b c)\nOR-4 a b c\n\n(and a b (or c d))\nAND-6 a b OR-6 c d\n\n(and a b (or c d) (or e f))\nAND-9 a b OR-6 c d OR-9 e f\n----\n\n.opcodes:\n----\nkey: all values < 1024\nOR/AND: OP & 0xF000\n  OR : 0x1000\n  AND: 0x2000\n  length: OP & 0x0FFF\n----\n\n.Rough algorithm for opcodes:\n----\nvalue=true\npush first opcode\nWHILE stack is not empty\n  WHILE index <= ending_index\n    switch\n      opcode:\n        push, continue\n      key(OR):\n        value=true: skip to index, pop\n        value=false: continue\n      key(AND):\n        value=true: continue\n        value=false: skip to index, pop\n  pop\n  switch\n    current_value(OR):\n      value=true: skip to index, pop\n      value=false: continue\n    current_value(AND):\n      value=true: continue\n      value=false: skip to index, pop\nreturn value\n----\n\n.statestruct:\n----\n  value\n  current_index\n  current_end_index\n  current_op\n  stack (op, ending_index)\n----\n\n.rough sequence 1:\n----\npressed:       y y      y y      y y\nopcodes: AND-9 a b OR-6 c d OR-9 e f\n\nindex: 0\n  push: AND-9\n  stack: AND-9\n\nindex: 1\n  val: true\n\nindex: 2\n  val: true\n\nindex: 3\n  push: OR-6\n  stack: AND-9 OR-6\n\nindex: 4\n  val: true\n  skip to 6\n  pop\n  stack: AND-9\n\nindex: 6\n  push: OR-9\n  stack: AND-9 OR-9\n\nindex: 7\n  val: true\n  skip to 9\n  pop\n  stack: AND-9-true\n\nindex 9:\n  pop\n  stack: empty\n  return val: true\n----\n\n.rough sequence 2:\n----\npressed:       y y      n n      y y\nopcodes: AND-9 a b OR-6 c d OR-9 e f\n\nindex: 0\n  push: AND-9\n  stack: AND-9\n\nindex: 1\n  val: true\n\nindex: 2\n  val: true\n\nindex: 3\n  push: OR-6\n  stack: AND-9 OR-6\n  val: true\n\nindex: 4\n  val: false\n\nindex: 5\n  val: false\n\nindex: 6\n  val: false\n  pop\n  stack: AND-9\n  skip to 9\n  pop\n  stack: empty\n  return val: false\n----\n\n.rough sequence 3:\n----\npressed:       n y      n n      y y\nopcodes: AND-9 a b OR-6 c d OR-9 e f\n\nindex: 0\n  push: AND-9\n  stack: AND-9\n\nindex: 1\n  val: false\n  skip to 9\n  pop\n  stack: empty\n  return val: false\n----\n\n\n.pseudo code again:\n----\nlet mut value = true\nlet mut current_index = 1\nlet mut current_end_index = first_opcode - end_index\nlet mut current_op = OR\nwhile current_index < slice_length {\n  if index >= current_end_index:\n    if stack is empty:\n      break\n    else:\n      pop stack to current_op and current_end_index\n      switch\n        current_value(OR):\n          value=true: skip to current_end_index; continue\n        current_value(AND):\n          value=false: skip to current_end_index; continue\n  switch\n    opcode:\n      push (current_end_index,current_op)\n      update (current_end_index,current_op) with opcode\n    key(OR):\n      value=true: skip to current_end_index; continue\n      value=false\n    key(AND):\n      value=true\n      value=false: skip to current_end_index; continue\n  current_index++;\n}\nreturn value\n----\n"
  },
  {
    "path": "example_tcp_client/.gitignore",
    "content": "target\n"
  },
  {
    "path": "example_tcp_client/Cargo.toml",
    "content": "[package]\nname = \"kanata_example_tcp_client\"\ndescription = \"Example kanata TCP client\"\nversion = \"1.1.0\"\nedition = \"2021\"\nlicense = \"LGPL-3.0\"\nauthors = [\"jtroo <j.andreitabs@gmail.com>\"]\n\n[dependencies]\nanyhow = \"1\"\nclap = { version = \"4\", features = [ \"derive\" ] }\nkanata-tcp-protocol = { path = \"../tcp_protocol\" }\nlog = \"0.4.8\"\nsimplelog = \"0.12\"\nserde_json = \"1\""
  },
  {
    "path": "example_tcp_client/src/main.rs",
    "content": "use clap::Parser;\nuse kanata_tcp_protocol::*;\nuse simplelog::*;\nuse std::io::{BufRead, BufReader, Write, stdin};\nuse std::net::{SocketAddr, TcpStream};\nuse std::process::exit;\nuse std::time::Duration;\n\n#[derive(Parser, Debug)]\n#[clap(author, version, about, long_about = None)]\nstruct Args {\n    /// Port that kanata's TCP server is listening on\n    #[clap(short, long)]\n    port: Option<u16>,\n\n    /// Enable debug logging\n    #[clap(short, long)]\n    debug: bool,\n\n    /// Enable trace logging (implies --debug as well)\n    #[clap(short, long)]\n    trace: bool,\n}\n\nfn main() {\n    let args = Args::parse();\n    init_logger(&args);\n    print_usage();\n\n    let port = match args.port {\n        Some(p) => p,\n        None => {\n            log::error!(\"no port provided via the -p|--port flag; exiting\");\n            exit(1);\n        }\n    };\n    log::info!(\"attempting to connect to kanata\");\n    let kanata_conn = TcpStream::connect_timeout(\n        &SocketAddr::from(([127, 0, 0, 1], port)),\n        Duration::from_secs(5),\n    )\n    .expect(\"connect to kanata\");\n    log::info!(\"successfully connected\");\n    let writer_stream = kanata_conn.try_clone().expect(\"clone writer\");\n    let reader_stream = kanata_conn;\n    std::thread::spawn(move || write_to_kanata(writer_stream));\n    read_from_kanata(reader_stream);\n}\n\nfn print_usage() {\n    log::info!(\n        \"\\n\\\n    You can also use any other software to connect to kanata over TCP.\\n\\\n    The protocol is plaintext JSON with newline terminated messages.\n\\n\\\n    Layer change notifications from kanata look like:\\n\\\n    {}\n\\n\\\n    Requests to change kanata's layer look like:\\n\\\n    {}\n\\n\\\n    Configuration reload commands:\\n\\\n    - reload: {}\\n\\\n    - reload next: {}\\n\\\n    - reload previous: {}\\n\\\n    - reload specific index: {}\\n\\\n    - reload specific file: {}\n\\n\\\n    Server responses for commands look like:\\n\\\n    - Success: {}\\n\\\n    - Error: {}\n    \",\n        serde_json::to_string(&ServerMessage::LayerChange {\n            new: \"newly-changed-to-layer\".into()\n        })\n        .expect(\"deserializable\"),\n        serde_json::to_string(&ClientMessage::ChangeLayer {\n            new: \"requested-layer\".into()\n        })\n        .expect(\"deserializable\"),\n        serde_json::to_string(&ClientMessage::Reload {\n            wait: None,\n            timeout_ms: None\n        })\n        .expect(\"deserializable\"),\n        serde_json::to_string(&ClientMessage::ReloadNext {\n            wait: None,\n            timeout_ms: None\n        })\n        .expect(\"deserializable\"),\n        serde_json::to_string(&ClientMessage::ReloadPrev {\n            wait: None,\n            timeout_ms: None\n        })\n        .expect(\"deserializable\"),\n        serde_json::to_string(&ClientMessage::ReloadNum {\n            index: 1,\n            wait: None,\n            timeout_ms: None\n        })\n        .expect(\"deserializable\"),\n        serde_json::to_string(&ClientMessage::ReloadFile {\n            path: \"/path/to/config.kbd\".to_string(),\n            wait: None,\n            timeout_ms: None,\n        })\n        .expect(\"deserializable\"),\n        serde_json::to_string(&ServerResponse::Ok).expect(\"deserializable\"),\n        serde_json::to_string(&ServerResponse::Error {\n            msg: \"Invalid config index: 5. Only 2 configs are available (0-1).\".to_string()\n        })\n        .expect(\"deserializable\"),\n    )\n}\n\nfn init_logger(args: &Args) {\n    let log_lvl = match (args.debug, args.trace) {\n        (_, true) => LevelFilter::Trace,\n        (true, false) => LevelFilter::Debug,\n        (false, false) => LevelFilter::Info,\n    };\n    let mut log_cfg = ConfigBuilder::new();\n    if let Err(e) = log_cfg.set_time_offset_to_local() {\n        eprintln!(\"WARNING: could not set log TZ to local: {e:?}\");\n    };\n    CombinedLogger::init(vec![TermLogger::new(\n        log_lvl,\n        log_cfg.build(),\n        TerminalMode::Mixed,\n        ColorChoice::AlwaysAnsi,\n    )])\n    .expect(\"init logger\");\n    log::info!(\n        \"kanata_example_tcp_client v{} starting\",\n        env!(\"CARGO_PKG_VERSION\")\n    );\n}\n\nfn write_to_kanata(mut s: TcpStream) {\n    log::info!(\"writer starting\");\n    log::info!(\"writer: enter commands to send to kanata:\");\n    log::info!(\"  - layer name: change to that layer\");\n    log::info!(\"  - fk:KEYNAME: tap fake key\");\n    log::info!(\"  - reload: reload current config\");\n    log::info!(\"  - reload-next: reload next config\");\n    log::info!(\"  - reload-prev: reload previous config\");\n    log::info!(\"  - reload-num:N: reload config at index N\");\n    log::info!(\"  - reload-file:PATH: reload config file at PATH\");\n    let mut input = String::new();\n    loop {\n        stdin().read_line(&mut input).expect(\"stdin is readable\");\n        let command = input.trim_end().to_owned();\n\n        let msg = if command.starts_with(\"fk:\") {\n            let fkname = command.trim_start_matches(\"fk:\").into();\n            log::info!(\"writer: telling kanata to tap fake key \\\"{fkname}\\\"\");\n            serde_json::to_string(&ClientMessage::ActOnFakeKey {\n                name: fkname,\n                action: FakeKeyActionMessage::Tap,\n            })\n            .expect(\"deserializable\")\n        } else if command == \"reload\" {\n            log::info!(\"writer: telling kanata to reload current config\");\n            serde_json::to_string(&ClientMessage::Reload {\n                wait: None,\n                timeout_ms: None,\n            })\n            .expect(\"deserializable\")\n        } else if command == \"reload-next\" {\n            log::info!(\"writer: telling kanata to reload next config\");\n            serde_json::to_string(&ClientMessage::ReloadNext {\n                wait: None,\n                timeout_ms: None,\n            })\n            .expect(\"deserializable\")\n        } else if command == \"reload-prev\" {\n            log::info!(\"writer: telling kanata to reload previous config\");\n            serde_json::to_string(&ClientMessage::ReloadPrev {\n                wait: None,\n                timeout_ms: None,\n            })\n            .expect(\"deserializable\")\n        } else if command.starts_with(\"reload-num:\") {\n            let index_str = command.trim_start_matches(\"reload-num:\");\n            match index_str.parse::<usize>() {\n                Ok(index) => {\n                    log::info!(\"writer: telling kanata to reload config at index {index}\");\n                    serde_json::to_string(&ClientMessage::ReloadNum {\n                        index,\n                        wait: None,\n                        timeout_ms: None,\n                    })\n                    .expect(\"deserializable\")\n                }\n                Err(_) => {\n                    log::error!(\"Invalid number format for reload-num: {index_str}\");\n                    input.clear();\n                    continue;\n                }\n            }\n        } else if command.starts_with(\"reload-file:\") {\n            let path = command.trim_start_matches(\"reload-file:\").to_string();\n            log::info!(\"writer: telling kanata to reload config file \\\"{path}\\\"\");\n            serde_json::to_string(&ClientMessage::ReloadFile {\n                path,\n                wait: None,\n                timeout_ms: None,\n            })\n            .expect(\"deserializable\")\n        } else {\n            log::info!(\"writer: telling kanata to change layer to \\\"{command}\\\"\");\n            serde_json::to_string(&ClientMessage::ChangeLayer { new: command })\n                .expect(\"deserializable\")\n        };\n\n        s.write_all(msg.as_bytes()).expect(\"stream writable\");\n        input.clear();\n    }\n}\n\nfn read_from_kanata(s: TcpStream) {\n    log::info!(\"reader starting\");\n    let mut reader = BufReader::new(s);\n    let mut msg = String::new();\n    loop {\n        msg.clear();\n        reader.read_line(&mut msg).expect(\"stream readable\");\n\n        // Try to parse as ServerResponse first (for command responses)\n        if let Ok(response) = serde_json::from_str::<ServerResponse>(&msg) {\n            match response {\n                ServerResponse::Ok => {\n                    log::info!(\"✓ Command executed successfully\");\n                }\n                ServerResponse::Error { msg } => {\n                    log::error!(\"✗ Command failed: {}\", msg);\n                }\n            }\n            continue;\n        }\n\n        // Fall back to parsing as ServerMessage (for notifications)\n        let parsed_msg: ServerMessage = match serde_json::from_str(&msg) {\n            Ok(msg) => msg,\n            Err(e) => {\n                log::warn!(\"could not parse server message {msg}: {e:?}\");\n                std::process::exit(1);\n            }\n        };\n        match parsed_msg {\n            ServerMessage::LayerChange { new } => {\n                log::info!(\"reader: kanata changed layers to \\\"{new}\\\"\");\n            }\n            msg => {\n                log::info!(\"got msg: {msg:?}\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "interception/Cargo.toml",
    "content": "[workspace]\nmembers = [\".\"]\n\n[package]\nname = \"kanata-interception\"\ndescription = \"Safe wrapper for Interception. Forked for use with kanata.\"\nversion = \"0.3.0\"\nauthors = [\"Joe Kaushal <joe.kaushal@gmail.com>\"]\nedition = \"2018\"\nrepository = \"https://github.com/jtroo/kanata\"\nlicense = \"MIT OR Apache-2.0\"\n\n[dependencies]\ninterception-sys = \"0.1.3\"\nbitflags = \"1.2.1\"\nnum_enum = \"0.6.0\"\nserde = { version = \"1.0.114\", features = [\"derive\"] }\n"
  },
  {
    "path": "interception/src/lib.rs",
    "content": "pub extern crate interception_sys;\n\n#[macro_use]\nextern crate bitflags;\n\npub use interception_sys as raw;\npub mod scancode;\n\npub use scancode::ScanCode;\n\nuse std::convert::{TryFrom, TryInto};\nuse std::default::Default;\nuse std::time::Duration;\nuse std::vec::Vec;\n\npub type Device = i32;\npub type Precedence = i32;\n\npub enum Filter {\n    MouseFilter(MouseFilter),\n    KeyFilter(KeyFilter),\n}\n\npub type Predicate = extern \"C\" fn(device: Device) -> bool;\n\nbitflags! {\n    pub struct MouseState: u16 {\n        const LEFT_BUTTON_DOWN = 1;\n        const LEFT_BUTTON_UP = 2;\n\n        const RIGHT_BUTTON_DOWN = 4;\n        const RIGHT_BUTTON_UP = 8;\n\n        const MIDDLE_BUTTON_DOWN = 16;\n        const MIDDLE_BUTTON_UP = 32;\n\n        const BUTTON_4_DOWN = 64;\n        const BUTTON_4_UP = 128;\n\n        const BUTTON_5_DOWN = 256;\n        const BUTTON_5_UP = 512;\n\n        const WHEEL = 1024;\n        const HWHEEL = 2048;\n\n        // MouseFilter only\n        const MOVE = 4096;\n    }\n}\n\npub type MouseFilter = MouseState;\n\nbitflags! {\n    pub struct MouseFlags: u16 {\n        const MOVE_RELATIVE = 0;\n        const MOVE_ABSOLUTE = 1;\n\n        const VIRTUAL_DESKTOP = 2;\n        const ATTRIBUTES_CHANGED = 4;\n\n        const MOVE_NO_COALESCE = 8;\n\n        const TERMSRV_SRC_SHADOW = 256;\n    }\n}\n\nbitflags! {\n    pub struct KeyState: u16 {\n        const DOWN = 0;\n        const UP = 1;\n\n        const E0 = 2;\n        const E1 = 3;\n\n        const TERMSRV_SET_LED = 8;\n        const TERMSRV_SHADOW = 16;\n        const TERMSRV_VKPACKET = 32;\n    }\n}\n\nbitflags! {\n    pub struct KeyFilter: u16 {\n        const DOWN = 1;\n        const UP = 2;\n\n        const E0 = 4;\n        const E1 = 8;\n\n        const TERMSRV_SET_LED = 16;\n        const TERMSRV_SHADOW = 32;\n        const TERMSRV_VKPACKET = 64;\n    }\n}\n\n#[derive(Debug, Copy, Clone)]\npub enum Stroke {\n    Mouse {\n        state: MouseState,\n        flags: MouseFlags,\n        rolling: i16,\n        x: i32,\n        y: i32,\n        information: u32,\n    },\n\n    Keyboard {\n        code: ScanCode,\n        state: KeyState,\n        information: u32,\n    },\n}\n\nimpl TryFrom<raw::InterceptionMouseStroke> for Stroke {\n    type Error = &'static str;\n\n    fn try_from(raw_stroke: raw::InterceptionMouseStroke) -> Result<Self, Self::Error> {\n        let state = match MouseState::from_bits(raw_stroke.state) {\n            Some(state) => state,\n            None => return Err(\"Extra bits in raw mouse state\"),\n        };\n\n        let flags = match MouseFlags::from_bits(raw_stroke.flags) {\n            Some(flags) => flags,\n            None => return Err(\"Extra bits in raw mouse flags\"),\n        };\n\n        Ok(Stroke::Mouse {\n            state: state,\n            flags: flags,\n            rolling: raw_stroke.rolling,\n            x: raw_stroke.x,\n            y: raw_stroke.y,\n            information: raw_stroke.information,\n        })\n    }\n}\n\nimpl TryFrom<raw::InterceptionKeyStroke> for Stroke {\n    type Error = &'static str;\n\n    fn try_from(raw_stroke: raw::InterceptionKeyStroke) -> Result<Self, Self::Error> {\n        let state = match KeyState::from_bits(raw_stroke.state) {\n            Some(state) => state,\n            None => return Err(\"Extra bits in raw keyboard state\"),\n        };\n\n        let code = match ScanCode::try_from(raw_stroke.code) {\n            Ok(code) => code,\n            Err(_) => ScanCode::Esc,\n        };\n\n        Ok(Stroke::Keyboard {\n            code: code,\n            state: state,\n            information: raw_stroke.information,\n        })\n    }\n}\n\nimpl TryFrom<Stroke> for raw::InterceptionMouseStroke {\n    type Error = &'static str;\n\n    fn try_from(stroke: Stroke) -> Result<Self, Self::Error> {\n        if let Stroke::Mouse {\n            state,\n            flags,\n            rolling,\n            x,\n            y,\n            information,\n        } = stroke\n        {\n            Ok(raw::InterceptionMouseStroke {\n                state: state.bits(),\n                flags: flags.bits(),\n                rolling: rolling,\n                x: x,\n                y: y,\n                information: information,\n            })\n        } else {\n            Err(\"Stroke must be a mouse stroke\")\n        }\n    }\n}\n\nimpl TryFrom<Stroke> for raw::InterceptionKeyStroke {\n    type Error = &'static str;\n\n    fn try_from(stroke: Stroke) -> Result<Self, Self::Error> {\n        if let Stroke::Keyboard {\n            code,\n            state,\n            information,\n        } = stroke\n        {\n            Ok(raw::InterceptionKeyStroke {\n                code: code as u16,\n                state: state.bits(),\n                information: information,\n            })\n        } else {\n            Err(\"Stroke must be a keyboard stroke\")\n        }\n    }\n}\n\npub struct Interception {\n    ctx: raw::InterceptionContext,\n}\n\nimpl Interception {\n    pub fn new() -> Option<Self> {\n        let ctx = unsafe { raw::interception_create_context() };\n\n        if ctx == std::ptr::null_mut() {\n            return None;\n        }\n\n        Some(Interception { ctx: ctx })\n    }\n\n    pub fn get_precedence(&self, device: Device) -> Precedence {\n        unsafe { raw::interception_get_precedence(self.ctx, device) }\n    }\n\n    pub fn set_precedence(&self, device: Device, precedence: Precedence) {\n        unsafe { raw::interception_set_precedence(self.ctx, device, precedence) }\n    }\n\n    pub fn get_filter(&self, device: Device) -> Filter {\n        if is_invalid(device) {\n            return Filter::KeyFilter(KeyFilter::empty());\n        }\n\n        let raw_filter = unsafe { raw::interception_get_filter(self.ctx, device) };\n        if is_mouse(device) {\n            let filter = match MouseFilter::from_bits(raw_filter) {\n                Some(filter) => filter,\n                None => MouseFilter::empty(),\n            };\n\n            Filter::MouseFilter(filter)\n        } else {\n            let filter = match KeyFilter::from_bits(raw_filter) {\n                Some(filter) => filter,\n                None => KeyFilter::empty(),\n            };\n\n            Filter::KeyFilter(filter)\n        }\n    }\n\n    pub fn set_filter(&self, predicate: Predicate, filter: Filter) {\n        let filter = match filter {\n            Filter::MouseFilter(filter) => filter.bits(),\n            Filter::KeyFilter(filter) => filter.bits(),\n        };\n\n        unsafe {\n            let predicate = std::mem::transmute(Some(predicate));\n            raw::interception_set_filter(self.ctx, predicate, filter)\n        }\n    }\n\n    pub fn wait(&self) -> Device {\n        unsafe { raw::interception_wait(self.ctx) }\n    }\n\n    pub fn wait_with_timeout(&self, duration: Duration) -> Device {\n        let millis = match u32::try_from(duration.as_millis()) {\n            Ok(m) => m,\n            Err(_) => u32::MAX,\n        };\n\n        unsafe { raw::interception_wait_with_timeout(self.ctx, millis) }\n    }\n\n    pub fn send(&self, device: Device, strokes: &[Stroke]) -> i32 {\n        if is_mouse(device) {\n            self.send_internal::<raw::InterceptionMouseStroke>(device, strokes)\n        } else if is_keyboard(device) {\n            self.send_internal::<raw::InterceptionKeyStroke>(device, strokes)\n        } else {\n            0\n        }\n    }\n\n    fn send_internal<T: TryFrom<Stroke>>(&self, device: Device, strokes: &[Stroke]) -> i32 {\n        let mut raw_strokes = Vec::new();\n\n        for stroke in strokes {\n            if let Ok(raw_stroke) = T::try_from(*stroke) {\n                raw_strokes.push(raw_stroke)\n            }\n        }\n\n        let ptr = raw_strokes.as_ptr();\n        let len = match u32::try_from(raw_strokes.len()) {\n            Ok(l) => l,\n            Err(_) => u32::MAX,\n        };\n\n        unsafe { raw::interception_send(self.ctx, device, std::mem::transmute(ptr), len) }\n    }\n\n    pub fn receive(&self, device: Device, strokes: &mut [Stroke]) -> i32 {\n        if is_mouse(device) {\n            self.receive_internal::<raw::InterceptionMouseStroke>(device, strokes)\n        } else if is_keyboard(device) {\n            self.receive_internal::<raw::InterceptionKeyStroke>(device, strokes)\n        } else {\n            0\n        }\n    }\n\n    fn receive_internal<T: TryInto<Stroke> + Default + Copy>(\n        &self,\n        device: Device,\n        strokes: &mut [Stroke],\n    ) -> i32 {\n        let mut raw_strokes: Vec<T> = Vec::with_capacity(strokes.len());\n        raw_strokes.resize_with(strokes.len(), Default::default);\n\n        let ptr = raw_strokes.as_ptr();\n        let len = match u32::try_from(raw_strokes.len()) {\n            Ok(l) => l,\n            Err(_) => u32::MAX,\n        };\n\n        let num_read =\n            unsafe { raw::interception_receive(self.ctx, device, std::mem::transmute(ptr), len) };\n\n        let mut num_valid: i32 = 0;\n        for i in 0..num_read {\n            if let Ok(stroke) = raw_strokes[i as usize].try_into() {\n                strokes[num_valid as usize] = stroke;\n                num_valid += 1;\n            }\n        }\n\n        num_valid\n    }\n\n    pub fn get_hardware_id(&self, device: Device, buffer: &mut [u8]) -> u32 {\n        let ptr = buffer.as_mut_ptr();\n        let len = match u32::try_from(buffer.len()) {\n            Ok(l) => l,\n            Err(_) => u32::MAX,\n        };\n\n        unsafe {\n            raw::interception_get_hardware_id(self.ctx, device, std::mem::transmute(ptr), len)\n        }\n    }\n}\n\nimpl Drop for Interception {\n    fn drop(&mut self) {\n        unsafe { raw::interception_destroy_context(self.ctx) }\n    }\n}\n\npub extern \"C\" fn is_invalid(device: Device) -> bool {\n    unsafe { raw::interception_is_invalid(device) != 0 }\n}\n\npub extern \"C\" fn is_keyboard(device: Device) -> bool {\n    unsafe { raw::interception_is_keyboard(device) != 0 }\n}\n\npub extern \"C\" fn is_mouse(device: Device) -> bool {\n    unsafe { raw::interception_is_mouse(device) != 0 }\n}\n"
  },
  {
    "path": "interception/src/scancode.rs",
    "content": "use num_enum::TryFromPrimitive;\nuse serde::{Deserialize, Serialize};\n\n// ref: https://handmade.network/wiki/2823-keyboard_inputs_-_scancodes,_raw_input,_text_input,_key_names\n#[derive(Serialize, Deserialize, Hash, Debug, Eq, PartialEq, Copy, Clone, TryFromPrimitive)]\n#[repr(u16)]\npub enum ScanCode {\n    Esc = 0x01,\n\n    Num1 = 0x02,\n    Num2 = 0x03,\n    Num3 = 0x04,\n    Num4 = 0x05,\n    Num5 = 0x06,\n    Num6 = 0x07,\n    Num7 = 0x08,\n    Num8 = 0x09,\n    Num9 = 0x0A,\n    Num0 = 0x0B,\n\n    Minus = 0x0C,\n    Equals = 0x0D,\n    Backspace = 0x0E,\n\n    Tab = 0x0F,\n\n    Q = 0x10,\n    W = 0x11,\n    E = 0x12,\n    R = 0x13,\n    T = 0x14,\n    Y = 0x15,\n    U = 0x16,\n    I = 0x17,\n    O = 0x18,\n    P = 0x19,\n    LeftBracket = 0x1A,\n    RightBracket = 0x1B,\n    Enter = 0x1C,\n    LeftControl = 0x1D,\n    A = 0x1E,\n    S = 0x1F,\n    D = 0x20,\n    F = 0x21,\n    G = 0x22,\n    H = 0x23,\n    J = 0x24,\n    K = 0x25,\n    L = 0x26,\n    SemiColon = 0x27,\n    Apostrophe = 0x28,\n    Grave = 0x29,\n    LeftShift = 0x2A,\n    BackSlash = 0x2B,\n    Z = 0x2C,\n    X = 0x2D,\n    C = 0x2E,\n    V = 0x2F,\n    B = 0x30,\n    N = 0x31,\n    M = 0x32,\n    Comma = 0x33,\n    Period = 0x34,\n    Slash = 0x35,\n    RightShift = 0x36,\n    NumpadMultiply = 0x37,\n    LeftAlt = 0x38,\n    Space = 0x39,\n    CapsLock = 0x3A,\n    F1 = 0x3B,\n    F2 = 0x3C,\n    F3 = 0x3D,\n    F4 = 0x3E,\n    F5 = 0x3F,\n    F6 = 0x40,\n    F7 = 0x41,\n    F8 = 0x42,\n    F9 = 0x43,\n    F10 = 0x44,\n    NumLock = 0x45,\n    ScrollLock = 0x46,\n    Numpad7 = 0x47,\n    Numpad8 = 0x48,\n    Numpad9 = 0x49,\n    NumpadMinus = 0x4A,\n    Numpad4 = 0x4B,\n    Numpad5 = 0x4C,\n    Numpad6 = 0x4D,\n    NumpadPlus = 0x4E,\n    Numpad1 = 0x4F,\n    Numpad2 = 0x50,\n    Numpad3 = 0x51,\n    Numpad0 = 0x52,\n    NumpadPeriod = 0x53,\n    AltPrintScreen = 0x54,\n    SC_55 = 0x55,\n    Int1 = 0x56,\n    F11 = 0x57,\n    F12 = 0x58,\n    SC_59 = 0x59,\n    Oem1 = 0x5A,\n    Oem2 = 0x5B,\n    Oem3 = 0x5C,\n    EraseEOF = 0x5D,\n    Oem4 = 0x5E,\n    Oem5 = 0x5F,\n    SC_60 = 0x60,\n    SC_61 = 0x61,\n    Zoom = 0x62,\n    Help = 0x63,\n    F13 = 0x64,\n    F14 = 0x65,\n    F15 = 0x66,\n    F16 = 0x67,\n    F17 = 0x68,\n    F18 = 0x69,\n    F19 = 0x6A,\n    F20 = 0x6B,\n    F21 = 0x6C,\n    F22 = 0x6D,\n    F23 = 0x6E,\n    Oem6 = 0x6F,\n    Katakana = 0x70,\n    Oem7 = 0x71,\n    SC_72 = 0x72,\n    SC_73 = 0x73,\n    SC_74 = 0x74,\n    SC_75 = 0x75,\n    F24 = 0x76,\n    SBCSChar = 0x77,\n    SC_78 = 0x78,\n    Convert = 0x79,\n    SC_7A = 0x7A,\n    NonConvert = 0x7B,\n    SC_7C = 0x7C,\n    SC_7D = 0x7D,\n    SC_7E = 0x7E,\n    SC_7F = 0x7F,\n    SC_80 = 0x80,\n    SC_81 = 0x81,\n    SC_82 = 0x82,\n    SC_83 = 0x83,\n    SC_84 = 0x84,\n    SC_85 = 0x85,\n    SC_86 = 0x86,\n    SC_87 = 0x87,\n    SC_88 = 0x88,\n    SC_89 = 0x89,\n    SC_8A = 0x8A,\n    SC_8B = 0x8B,\n    SC_8C = 0x8C,\n    SC_8D = 0x8D,\n    SC_8E = 0x8E,\n    SC_8F = 0x8F,\n    SC_90 = 0x90,\n    SC_91 = 0x91,\n    SC_92 = 0x92,\n    SC_93 = 0x93,\n    SC_94 = 0x94,\n    SC_95 = 0x95,\n    SC_96 = 0x96,\n    SC_97 = 0x97,\n    SC_98 = 0x98,\n    SC_99 = 0x99,\n    SC_9A = 0x9A,\n    SC_9B = 0x9B,\n    SC_9C = 0x9C,\n    SC_9D = 0x9D,\n    SC_9E = 0x9E,\n    SC_9F = 0x9F,\n    SC_A0 = 0xA0,\n    SC_A1 = 0xA1,\n    SC_A2 = 0xA2,\n    SC_A3 = 0xA3,\n    SC_A4 = 0xA4,\n    SC_A5 = 0xA5,\n    SC_A6 = 0xA6,\n    SC_A7 = 0xA7,\n    SC_A8 = 0xA8,\n    SC_A9 = 0xA9,\n    SC_AA = 0xAA,\n    SC_AB = 0xAB,\n    SC_AC = 0xAC,\n    SC_AD = 0xAD,\n    SC_AE = 0xAE,\n    SC_AF = 0xAF,\n    SC_B0 = 0xB0,\n    SC_B1 = 0xB1,\n    SC_B2 = 0xB2,\n    SC_B3 = 0xB3,\n    SC_B4 = 0xB4,\n    SC_B5 = 0xB5,\n    SC_B6 = 0xB6,\n    SC_B7 = 0xB7,\n    SC_B8 = 0xB8,\n    SC_B9 = 0xB9,\n    SC_BA = 0xBA,\n    SC_BB = 0xBB,\n    SC_BC = 0xBC,\n    SC_BD = 0xBD,\n    SC_BE = 0xBE,\n    SC_BF = 0xBF,\n    SC_C0 = 0xC0,\n    SC_C1 = 0xC1,\n    SC_C2 = 0xC2,\n    SC_C3 = 0xC3,\n    SC_C4 = 0xC4,\n    SC_C5 = 0xC5,\n    SC_C6 = 0xC6,\n    SC_C7 = 0xC7,\n    SC_C8 = 0xC8,\n    SC_C9 = 0xC9,\n    SC_CA = 0xCA,\n    SC_CB = 0xCB,\n    SC_CC = 0xCC,\n    SC_CD = 0xCD,\n    SC_CE = 0xCE,\n    SC_CF = 0xCF,\n    SC_D0 = 0xD0,\n    SC_D1 = 0xD1,\n    SC_D2 = 0xD2,\n    SC_D3 = 0xD3,\n    SC_D4 = 0xD4,\n    SC_D5 = 0xD5,\n    SC_D6 = 0xD6,\n    SC_D7 = 0xD7,\n    SC_D8 = 0xD8,\n    SC_D9 = 0xD9,\n    SC_DA = 0xDA,\n    SC_DB = 0xDB,\n    SC_DC = 0xDC,\n    SC_DD = 0xDD,\n    SC_DE = 0xDE,\n    SC_DF = 0xDF,\n    SC_E0 = 0xE0,\n    SC_E1 = 0xE1,\n    SC_E2 = 0xE2,\n    SC_E3 = 0xE3,\n    SC_E4 = 0xE4,\n    SC_E5 = 0xE5,\n    SC_E6 = 0xE6,\n    SC_E7 = 0xE7,\n    SC_E8 = 0xE8,\n    SC_E9 = 0xE9,\n    SC_EA = 0xEA,\n    SC_EB = 0xEB,\n    SC_EC = 0xEC,\n    SC_ED = 0xED,\n    SC_EE = 0xEE,\n    SC_EF = 0xEF,\n    SC_F0 = 0xF0,\n    SC_F1 = 0xF1,\n    SC_F2 = 0xF2,\n    SC_F3 = 0xF3,\n    SC_F4 = 0xF4,\n    SC_F5 = 0xF5,\n    SC_F6 = 0xF6,\n    SC_F7 = 0xF7,\n    SC_F8 = 0xF8,\n    SC_F9 = 0xF9,\n    SC_FA = 0xFA,\n    SC_FB = 0xFB,\n    SC_FC = 0xFC,\n    SC_FD = 0xFD,\n    SC_FE = 0xFE,\n    SC_NonExtendMax = 0xFF,\n}\n"
  },
  {
    "path": "justfile",
    "content": "set windows-shell := [\"powershell.exe\", \"-NoLogo\", \"-Command\"]\n\n# Build the release binaries for Linux and put the binaries+cfg in the output directory\nbuild_release_linux output_dir:\n  cargo build --release\n  cp target/release/kanata \"{{output_dir}}/kanata\"\n  strip \"{{output_dir}}/kanata\"\n  cargo build --release --features cmd\n  cp target/release/kanata \"{{output_dir}}/kanata_cmd_allowed\"\n  strip \"{{output_dir}}/kanata_cmd_allowed\"\n  cp cfg_samples/kanata.kbd \"{{output_dir}}\"\n\n# Build the release binaries for Windows and put the binaries+cfg in the output directory.\nbuild_release_windows output_dir:\n  cargo build --release --no-default-features --features tcp_server,win_manifest; cp target/release/kanata.exe \"{{output_dir}}\\kanata_legacy_output.exe\"\n  cargo build --release --features win_manifest,interception_driver; cp target/release/kanata.exe \"{{output_dir}}\\kanata_wintercept.exe\"\n  cargo build --release --features win_manifest,win_sendinput_send_scancodes; cp target/release/kanata.exe \"{{output_dir}}\\kanata.exe\"\n  cargo build --release --features win_manifest,win_sendinput_send_scancodes,win_llhook_read_scancodes; cp target/release/kanata.exe \"{{output_dir}}\\kanata_winIOv2.exe\"\n  cargo build --release --features win_manifest,cmd,win_sendinput_send_scancodes; cp target/release/kanata.exe \"{{output_dir}}\\kanata_cmd_allowed.exe\"\n  cargo build --release --features win_manifest,cmd,interception_driver; cp target/release/kanata.exe \"{{output_dir}}\\kanata_wintercept_cmd_allowed.exe\"\n  cargo build --release --features passthru_ahk --package=simulated_passthru; cp target/release/kanata_passthru.dll \"{{output_dir}}\\kanata_passthru.dll\"\n  cargo build --release --features win_manifest,gui    ; cp target/release/kanata.exe \"{{output_dir}}\\kanata_gui.exe\"\n  cargo build --release --features win_manifest,gui,cmd; cp target/release/kanata.exe \"{{output_dir}}\\kanata_gui_cmd_allowed.exe\"\n  cargo build --release --features win_manifest,gui,interception_driver    ; cp target/release/kanata.exe \"{{output_dir}}\\kanata_gui_wintercept.exe\"\n  cargo build --release --features win_manifest,gui,cmd,interception_driver; cp target/release/kanata.exe \"{{output_dir}}\\kanata_gui_wintercept_cmd_allowed.exe\"\n  cp cfg_samples/kanata.kbd \"{{output_dir}}\"\n\n# Generate the sha256sums for all files in the output directory\nsha256sums output_dir:\n  rm -f {{output_dir}}/sha256sums\n  cd {{output_dir}}; sha256sum * > sha256sums\n\ntest:\n  cargo test -p kanata -p kanata-parser -p kanata-keyberon -p kanata-wasm -p kanata-tcp-protocol -- --nocapture\n  cargo test --features=simulated_output sim_tests\n  cargo test --features=simulated_output -- must_be_single_threaded --ignored --test-threads=1\n  cargo clippy --all\n\nfmt:\n  cargo fmt --all\n\n[doc('Run fmt, check, and clippy')]\ncheck:\n  cargo fmt --all\n  cargo check\n  cargo clippy --all\n\nguic:\n  cargo check              --features=gui\nguif:\n  cargo fmt    --all\n  cargo clippy --all --fix --features=gui -- -D warnings\n\nahkc:\n  cargo check              --features=passthru_ahk\nahkf:\n  cargo fmt    --all\n  cargo clippy --all --fix --features=passthru_ahk -- -D warnings\n\nchange_subcrate_versions version:\n  sed -i 's/^version = \".*\"$/version = \"{{version}}\"/' parser/Cargo.toml tcp_protocol/Cargo.toml keyberon/Cargo.toml\n  sed -i 's/^\\(#\\? \\?kanata-\\(keyberon\\|parser\\|tcp-protocol\\).*version\\) = \"[0-9.]*\"/\\1 = \"{{version}}\"/' Cargo.toml parser/Cargo.toml\n\ncov:\n  cargo llvm-cov clean --workspace\n  cargo llvm-cov --no-report --workspace --no-default-features\n  cargo llvm-cov --no-report --workspace\n  cargo llvm-cov --no-report --workspace --features=cmd,win_llhook_read_scancodes,win_sendinput_send_scancodes\n  cargo llvm-cov --no-report --workspace --features=cmd,interception_driver,win_sendinput_send_scancodes\n  cargo llvm-cov --no-report --features=simulated_output -- sim_tests\n  cargo llvm-cov report --html\n\npublish:\n  cd keyberon; cargo publish\n  cd tcp_protocol; cargo publish\n  cd parser; cargo publish\n  cargo publish\n\n# Include the trailing `\\` or `/` in the output_dir parameter. The parameter should be an absolute path.\ncfg_to_html output_dir:\n  cd docs ; asciidoctor config.adoc\n  cd docs ; cp config.html \"{{output_dir}}config.html\"; rm config.html\n\n[doc('Deprecated. The wasm-pack project is no longer maintained; prefer wasm-build instead.\nInclude the trailing `\\` or `/` in the output_dir parameter. The parameter should be an absolute path.\n')]\nwasm_pack output_dir:\n  cd wasm; wasm-pack build --target web; cd pkg; cp kanata_wasm_bg.wasm \"{{output_dir}}\"; cp kanata_wasm.js \"{{output_dir}}\"\n\n[doc('Include the trailing `\\` or `/` in the output_dir parameter. The parameter should be an absolute path.')]\nwasm-build output_dir:\n  cd wasm; echo \"*\" > pkg/.gitignore\n  cd wasm; cargo build --lib --release --target wasm32-unknown-unknown\n  cd wasm; wasm-bindgen target/wasm32-unknown-unknown/release/kanata_wasm.wasm --out-dir pkg --typescript --target web\n  wasm-opt wasm/pkg/kanata_wasm_bg.wasm -o wasm/pkg/kanata_wasm.wasm-opt.wasm -Oz\n  rm wasm/pkg/kanata_wasm_bg.wasm\n  mv wasm/pkg/kanata_wasm.wasm-opt.wasm wasm/pkg/kanata_wasm_bg.wasm\n  cp wasm/pkg/kanata_wasm_bg.wasm \"{{output_dir}}\"\n  cp wasm/pkg/kanata_wasm.js \"{{output_dir}}\"\n"
  },
  {
    "path": "key-sort-add/Cargo.toml",
    "content": "[workspace]\nmembers = [\".\"]\n\n[package]\nname = \"key-sort-add\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\n"
  },
  {
    "path": "key-sort-add/README.md",
    "content": "# key-sort-add\n\nA small program that was used to sort and fill in OsCode mappings.\n"
  },
  {
    "path": "key-sort-add/mapping.txt",
    "content": "=== kc to osc\nKeyCode::Escape => OsCode::KEY_ESC,\nKeyCode::Kb1 => OsCode::KEY_1,\nKeyCode::Kb2 => OsCode::KEY_2,\nKeyCode::Kb3 => OsCode::KEY_3,\nKeyCode::Kb4 => OsCode::KEY_4,\nKeyCode::Kb5 => OsCode::KEY_5,\nKeyCode::Kb6 => OsCode::KEY_6,\nKeyCode::Kb7 => OsCode::KEY_7,\nKeyCode::Kb8 => OsCode::KEY_8,\nKeyCode::Kb9 => OsCode::KEY_9,\nKeyCode::Kb0 => OsCode::KEY_0,\nKeyCode::Minus => OsCode::KEY_MINUS,\nKeyCode::Equal => OsCode::KEY_EQUAL,\nKeyCode::BSpace => OsCode::KEY_BACKSPACE,\nKeyCode::Tab => OsCode::KEY_TAB,\nKeyCode::Q => OsCode::KEY_Q,\nKeyCode::W => OsCode::KEY_W,\nKeyCode::E => OsCode::KEY_E,\nKeyCode::R => OsCode::KEY_R,\nKeyCode::T => OsCode::KEY_T,\nKeyCode::Y => OsCode::KEY_Y,\nKeyCode::U => OsCode::KEY_U,\nKeyCode::I => OsCode::KEY_I,\nKeyCode::O => OsCode::KEY_O,\nKeyCode::P => OsCode::KEY_P,\nKeyCode::LBracket => OsCode::KEY_LEFTBRACE,\nKeyCode::RBracket => OsCode::KEY_RIGHTBRACE,\nKeyCode::Enter => OsCode::KEY_ENTER,\nKeyCode::LCtrl => OsCode::KEY_LEFTCTRL,\nKeyCode::A => OsCode::KEY_A,\nKeyCode::S => OsCode::KEY_S,\nKeyCode::D => OsCode::KEY_D,\nKeyCode::F => OsCode::KEY_F,\nKeyCode::G => OsCode::KEY_G,\nKeyCode::H => OsCode::KEY_H,\nKeyCode::J => OsCode::KEY_J,\nKeyCode::K => OsCode::KEY_K,\nKeyCode::L => OsCode::KEY_L,\nKeyCode::SColon => OsCode::KEY_SEMICOLON,\nKeyCode::Quote => OsCode::KEY_APOSTROPHE,\nKeyCode::Grave => OsCode::KEY_GRAVE,\nKeyCode::LShift => OsCode::KEY_LEFTSHIFT,\nKeyCode::Bslash => OsCode::KEY_BACKSLASH,\nKeyCode::Z => OsCode::KEY_Z,\nKeyCode::X => OsCode::KEY_X,\nKeyCode::C => OsCode::KEY_C,\nKeyCode::V => OsCode::KEY_V,\nKeyCode::B => OsCode::KEY_B,\nKeyCode::N => OsCode::KEY_N,\nKeyCode::M => OsCode::KEY_M,\nKeyCode::Comma => OsCode::KEY_COMMA,\nKeyCode::Dot => OsCode::KEY_DOT,\nKeyCode::Slash => OsCode::KEY_SLASH,\nKeyCode::RShift => OsCode::KEY_RIGHTSHIFT,\nKeyCode::KpAsterisk => OsCode::KEY_KPASTERISK,\nKeyCode::LAlt => OsCode::KEY_LEFTALT,\nKeyCode::Space => OsCode::KEY_SPACE,\nKeyCode::CapsLock => OsCode::KEY_CAPSLOCK,\nKeyCode::F1 => OsCode::KEY_F1,\nKeyCode::F2 => OsCode::KEY_F2,\nKeyCode::F3 => OsCode::KEY_F3,\nKeyCode::F4 => OsCode::KEY_F4,\nKeyCode::F5 => OsCode::KEY_F5,\nKeyCode::F6 => OsCode::KEY_F6,\nKeyCode::F7 => OsCode::KEY_F7,\nKeyCode::F8 => OsCode::KEY_F8,\nKeyCode::F9 => OsCode::KEY_F9,\nKeyCode::F10 => OsCode::KEY_F10,\nKeyCode::NumLock => OsCode::KEY_NUMLOCK,\nKeyCode::Clear => OsCode::KEY_CLEAR,\nKeyCode::ScrollLock => OsCode::KEY_SCROLLLOCK,\nKeyCode::Kp7 => OsCode::KEY_KP7,\nKeyCode::Kp8 => OsCode::KEY_KP8,\nKeyCode::Kp9 => OsCode::KEY_KP9,\nKeyCode::KpMinus => OsCode::KEY_KPMINUS,\nKeyCode::Kp4 => OsCode::KEY_KP4,\nKeyCode::Kp5 => OsCode::KEY_KP5,\nKeyCode::Kp6 => OsCode::KEY_KP6,\nKeyCode::KpPlus => OsCode::KEY_KPPLUS,\nKeyCode::Kp1 => OsCode::KEY_KP1,\nKeyCode::Kp2 => OsCode::KEY_KP2,\nKeyCode::Kp3 => OsCode::KEY_KP3,\nKeyCode::Kp0 => OsCode::KEY_KP0,\nKeyCode::KpDot => OsCode::KEY_KPDOT,\nKeyCode::F11 => OsCode::KEY_F11,\nKeyCode::F12 => OsCode::KEY_F12,\nKeyCode::KpEnter => OsCode::KEY_KPENTER,\nKeyCode::RCtrl => OsCode::KEY_RIGHTCTRL,\nKeyCode::KpSlash => OsCode::KEY_KPSLASH,\nKeyCode::SysReq => OsCode::KEY_SYSRQ,\nKeyCode::RAlt => OsCode::KEY_RIGHTALT,\nKeyCode::Home => OsCode::KEY_HOME,\nKeyCode::Up => OsCode::KEY_UP,\nKeyCode::PgUp => OsCode::KEY_PAGEUP,\nKeyCode::Left => OsCode::KEY_LEFT,\nKeyCode::Right => OsCode::KEY_RIGHT,\nKeyCode::End => OsCode::KEY_END,\nKeyCode::Down => OsCode::KEY_DOWN,\nKeyCode::PgDown => OsCode::KEY_PAGEDOWN,\nKeyCode::Insert => OsCode::KEY_INSERT,\nKeyCode::Delete => OsCode::KEY_DELETE,\nKeyCode::Mute => OsCode::KEY_MUTE,\nKeyCode::VolDown => OsCode::KEY_VOLUMEDOWN,\nKeyCode::VolUp => OsCode::KEY_VOLUMEUP,\nKeyCode::Power => OsCode::KEY_POWER,\nKeyCode::KpEqual => OsCode::KEY_KPEQUAL,\nKeyCode::Pause => OsCode::KEY_PAUSE,\nKeyCode::KpComma => OsCode::KEY_KPCOMMA,\nKeyCode::LGui => OsCode::KEY_LEFTMETA,\nKeyCode::RGui => OsCode::KEY_RIGHTMETA,\nKeyCode::Stop => OsCode::KEY_STOP,\nKeyCode::Again => OsCode::KEY_AGAIN,\nKeyCode::Undo => OsCode::KEY_UNDO,\nKeyCode::Copy => OsCode::KEY_COPY,\nKeyCode::Paste => OsCode::KEY_PASTE,\nKeyCode::Find => OsCode::KEY_FIND,\nKeyCode::Cut => OsCode::KEY_CUT,\nKeyCode::Help => OsCode::KEY_HELP,\nKeyCode::Menu => OsCode::KEY_MENU,\nKeyCode::MediaCalc => OsCode::KEY_CALC,\nKeyCode::MediaSleep => OsCode::KEY_SLEEP,\nKeyCode::MediaWWW => OsCode::KEY_WWW,\nKeyCode::MediaCoffee => OsCode::KEY_COFFEE,\nKeyCode::MediaBack => OsCode::KEY_BACK,\nKeyCode::MediaForward => OsCode::KEY_FORWARD,\nKeyCode::MediaEjectCD => OsCode::KEY_EJECTCD,\nKeyCode::MediaNextSong => OsCode::KEY_NEXTSONG,\nKeyCode::MediaPlayPause => OsCode::KEY_PLAYPAUSE,\nKeyCode::MediaPreviousSong => OsCode::KEY_PREVIOUSSONG,\nKeyCode::MediaStopCD => OsCode::KEY_STOPCD,\nKeyCode::MediaRefresh => OsCode::KEY_REFRESH,\nKeyCode::MediaEdit => OsCode::KEY_EDIT,\nKeyCode::MediaScrollUp => OsCode::KEY_SCROLLUP,\nKeyCode::MediaScrollDown => OsCode::KEY_SCROLLDOWN,\nKeyCode::F13 => OsCode::KEY_F13,\nKeyCode::F14 => OsCode::KEY_F14,\nKeyCode::F15 => OsCode::KEY_F15,\nKeyCode::F16 => OsCode::KEY_F16,\nKeyCode::F17 => OsCode::KEY_F17,\nKeyCode::F18 => OsCode::KEY_F18,\nKeyCode::F19 => OsCode::KEY_F19,\nKeyCode::F20 => OsCode::KEY_F20,\nKeyCode::F21 => OsCode::KEY_F21,\nKeyCode::F22 => OsCode::KEY_F22,\nKeyCode::F23 => OsCode::KEY_F23,\nKeyCode::F24 => OsCode::KEY_F24,\nKeyCode::Wakeup => OsCode::KEY_WAKEUP,\nKeyCode::BrightnessUp => OsCode::KEY_BRIGHTNESSUP,\nKeyCode::BrightnessDown => OsCode::KEY_BRIGHTNESSDOWN,\nKeyCode::KbdIllumUp => OsCode::KEY_KBDILLUMUP,\nKeyCode::KbdIllumDown => OsCode::KEY_KBDILLUMDOWN,\nKeyCode::Lang1 => OsCode::KEY_HANGEUL,\nKeyCode::Lang2 => OsCode::KEY_HANJA,\nKeyCode::NonUsBslash => OsCode::KEY_102ND,\nKeyCode::PScreen => OsCode::KEY_PRINT,\nKeyCode::Application => OsCode::KEY_COMPOSE,\nKeyCode::AltErase => OsCode::KEY_ALTERASE,\nKeyCode::Cancel => OsCode::KEY_CANCEL,\nKeyCode::MediaMute => OsCode::KEY_MICMUTE,\nKeyCode::Intl1 => OsCode::KEY_RO,\nKeyCode::Intl3 => OsCode::KEY_YEN,\nKeyCode::K0xAA => OsCode::KEY_MEDIA,\nKeyCode::K0xAB => OsCode::KEY_EMAIL,\nKeyCode::K0xAC => OsCode::KEY_PLAYER,\nKeyCode::K0xAD => OsCode::KEY_HOMEPAGE,\nKeyCode::K0xAE => OsCode::KEY_MAIL,\nKeyCode::K0xAF => OsCode::KEY_MUHENKAN,\nKeyCode::K0xB0 => OsCode::KEY_HENKAN,\nKeyCode::K0xB1 => OsCode::KEY_KATAKANA,\nKeyCode::K0xB2 => OsCode::KEY_KATAKANAHIRAGANA,\nKeyCode::K0xB3 => OsCode::KEY_HIRAGANA,\nKeyCode::K252 => OsCode::KEY_252,\nKeyCode::K253 => OsCode::KEY_253,\nKeyCode::K254 => OsCode::KEY_254,\nKeyCode::K255 => OsCode::KEY_255,\nKeyCode::K256 => OsCode::BTN_0,\nKeyCode::K257 => OsCode::BTN_1,\nKeyCode::K258 => OsCode::BTN_2,\nKeyCode::K259 => OsCode::BTN_3,\nKeyCode::K260 => OsCode::BTN_4,\nKeyCode::K261 => OsCode::BTN_5,\nKeyCode::K262 => OsCode::BTN_6,\nKeyCode::K263 => OsCode::BTN_7,\nKeyCode::K264 => OsCode::BTN_8,\nKeyCode::K265 => OsCode::BTN_9,\nKeyCode::K266 => OsCode::KEY_266,\nKeyCode::K267 => OsCode::KEY_267,\nKeyCode::K268 => OsCode::KEY_268,\nKeyCode::K269 => OsCode::KEY_269,\nKeyCode::K270 => OsCode::KEY_270,\nKeyCode::K271 => OsCode::KEY_271,\nKeyCode::K272 => OsCode::BTN_LEFT,\nKeyCode::K273 => OsCode::BTN_RIGHT,\nKeyCode::K274 => OsCode::BTN_MIDDLE,\nKeyCode::K275 => OsCode::BTN_SIDE,\nKeyCode::K276 => OsCode::BTN_EXTRA,\nKeyCode::K277 => OsCode::BTN_FORWARD,\nKeyCode::K278 => OsCode::BTN_BACK,\nKeyCode::K279 => OsCode::BTN_TASK,\nKeyCode::K280 => OsCode::KEY_280,\nKeyCode::K281 => OsCode::KEY_281,\nKeyCode::K282 => OsCode::KEY_282,\nKeyCode::K283 => OsCode::KEY_283,\nKeyCode::K284 => OsCode::KEY_284,\nKeyCode::K285 => OsCode::KEY_285,\nKeyCode::K286 => OsCode::KEY_286,\nKeyCode::K287 => OsCode::KEY_287,\nKeyCode::K288 => OsCode::BTN_TRIGGER,\nKeyCode::K289 => OsCode::BTN_THUMB,\nKeyCode::K290 => OsCode::BTN_THUMB2,\nKeyCode::K291 => OsCode::BTN_TOP,\nKeyCode::K292 => OsCode::BTN_TOP2,\nKeyCode::K293 => OsCode::BTN_PINKIE,\nKeyCode::K294 => OsCode::BTN_BASE,\nKeyCode::K295 => OsCode::BTN_BASE2,\nKeyCode::K296 => OsCode::BTN_BASE3,\nKeyCode::K297 => OsCode::BTN_BASE4,\nKeyCode::K298 => OsCode::BTN_BASE5,\nKeyCode::K299 => OsCode::BTN_BASE6,\nKeyCode::K300 => OsCode::KEY_300,\nKeyCode::K301 => OsCode::KEY_301,\nKeyCode::K302 => OsCode::KEY_302,\nKeyCode::K303 => OsCode::BTN_DEAD,\nKeyCode::K304 => OsCode::BTN_SOUTH,\nKeyCode::K305 => OsCode::BTN_EAST,\nKeyCode::K306 => OsCode::BTN_C,\nKeyCode::K307 => OsCode::BTN_NORTH,\nKeyCode::K308 => OsCode::BTN_WEST,\nKeyCode::K309 => OsCode::BTN_Z,\nKeyCode::K310 => OsCode::BTN_TL,\nKeyCode::K311 => OsCode::BTN_TR,\nKeyCode::K312 => OsCode::BTN_TL2,\nKeyCode::K313 => OsCode::BTN_TR2,\nKeyCode::K314 => OsCode::BTN_SELECT,\nKeyCode::K315 => OsCode::BTN_START,\nKeyCode::K316 => OsCode::BTN_MODE,\nKeyCode::K317 => OsCode::BTN_THUMBL,\nKeyCode::K318 => OsCode::BTN_THUMBR,\nKeyCode::K319 => OsCode::KEY_319,\nKeyCode::K320 => OsCode::BTN_TOOL_PEN,\nKeyCode::K321 => OsCode::BTN_TOOL_RUBBER,\nKeyCode::K322 => OsCode::BTN_TOOL_BRUSH,\nKeyCode::K323 => OsCode::BTN_TOOL_PENCIL,\nKeyCode::K324 => OsCode::BTN_TOOL_AIRBRUSH,\nKeyCode::K325 => OsCode::BTN_TOOL_FINGER,\nKeyCode::K326 => OsCode::BTN_TOOL_MOUSE,\nKeyCode::K327 => OsCode::BTN_TOOL_LENS,\nKeyCode::K328 => OsCode::BTN_TOOL_QUINTTAP,\nKeyCode::K329 => OsCode::BTN_STYLUS3,\nKeyCode::K330 => OsCode::BTN_TOUCH,\nKeyCode::K331 => OsCode::BTN_STYLUS,\nKeyCode::K332 => OsCode::BTN_STYLUS2,\nKeyCode::K333 => OsCode::BTN_TOOL_DOUBLETAP,\nKeyCode::K334 => OsCode::BTN_TOOL_TRIPLETAP,\nKeyCode::K335 => OsCode::BTN_TOOL_QUADTAP,\nKeyCode::K336 => OsCode::BTN_GEAR_DOWN,\nKeyCode::K337 => OsCode::BTN_GEAR_UP,\nKeyCode::K338 => OsCode::KEY_338,\nKeyCode::K339 => OsCode::KEY_339,\nKeyCode::K340 => OsCode::KEY_340,\nKeyCode::K341 => OsCode::KEY_341,\nKeyCode::K342 => OsCode::KEY_342,\nKeyCode::K343 => OsCode::KEY_343,\nKeyCode::K344 => OsCode::KEY_344,\nKeyCode::K345 => OsCode::KEY_345,\nKeyCode::K346 => OsCode::KEY_346,\nKeyCode::K347 => OsCode::KEY_347,\nKeyCode::K348 => OsCode::KEY_348,\nKeyCode::K349 => OsCode::KEY_349,\nKeyCode::K350 => OsCode::KEY_350,\nKeyCode::K351 => OsCode::KEY_351,\nKeyCode::K352 => OsCode::KEY_OK,\nKeyCode::K353 => OsCode::KEY_SELECT,\nKeyCode::K354 => OsCode::KEY_GOTO,\nKeyCode::K355 => OsCode::KEY_CLEAR,\nKeyCode::K356 => OsCode::KEY_POWER2,\nKeyCode::K357 => OsCode::KEY_OPTION,\nKeyCode::K358 => OsCode::KEY_INFO,\nKeyCode::K359 => OsCode::KEY_TIME,\nKeyCode::K360 => OsCode::KEY_VENDOR,\nKeyCode::K361 => OsCode::KEY_ARCHIVE,\nKeyCode::K362 => OsCode::KEY_PROGRAM,\nKeyCode::K363 => OsCode::KEY_CHANNEL,\nKeyCode::K364 => OsCode::KEY_FAVORITES,\nKeyCode::K365 => OsCode::KEY_EPG,\nKeyCode::K366 => OsCode::KEY_PVR,\nKeyCode::K367 => OsCode::KEY_MHP,\nKeyCode::K368 => OsCode::KEY_LANGUAGE,\nKeyCode::K369 => OsCode::KEY_TITLE,\nKeyCode::K370 => OsCode::KEY_SUBTITLE,\nKeyCode::K371 => OsCode::KEY_ANGLE,\nKeyCode::K372 => OsCode::KEY_FULL_SCREEN,\nKeyCode::K373 => OsCode::KEY_MODE,\nKeyCode::K374 => OsCode::KEY_KEYBOARD,\nKeyCode::K375 => OsCode::KEY_ASPECT_RATIO,\nKeyCode::K376 => OsCode::KEY_PC,\nKeyCode::K377 => OsCode::KEY_TV,\nKeyCode::K378 => OsCode::KEY_TV2,\nKeyCode::K379 => OsCode::KEY_VCR,\nKeyCode::K380 => OsCode::KEY_VCR2,\nKeyCode::K381 => OsCode::KEY_SAT,\nKeyCode::K382 => OsCode::KEY_SAT2,\nKeyCode::K383 => OsCode::KEY_CD,\nKeyCode::K384 => OsCode::KEY_TAPE,\nKeyCode::K385 => OsCode::KEY_RADIO,\nKeyCode::K386 => OsCode::KEY_TUNER,\nKeyCode::K387 => OsCode::KEY_PLAYER,\nKeyCode::K388 => OsCode::KEY_TEXT,\nKeyCode::K389 => OsCode::KEY_DVD,\nKeyCode::K390 => OsCode::KEY_AUX,\nKeyCode::K391 => OsCode::KEY_MP3,\nKeyCode::K392 => OsCode::KEY_AUDIO,\nKeyCode::K393 => OsCode::KEY_VIDEO,\nKeyCode::K394 => OsCode::KEY_DIRECTORY,\nKeyCode::K395 => OsCode::KEY_LIST,\nKeyCode::K396 => OsCode::KEY_MEMO,\nKeyCode::K397 => OsCode::KEY_CALENDAR,\nKeyCode::K398 => OsCode::KEY_RED,\nKeyCode::K399 => OsCode::KEY_GREEN,\nKeyCode::K400 => OsCode::KEY_YELLOW,\nKeyCode::K401 => OsCode::KEY_BLUE,\nKeyCode::K402 => OsCode::KEY_CHANNELUP,\nKeyCode::K403 => OsCode::KEY_CHANNELDOWN,\nKeyCode::K404 => OsCode::KEY_FIRST,\nKeyCode::K405 => OsCode::KEY_LAST,\nKeyCode::K406 => OsCode::KEY_AB,\nKeyCode::K407 => OsCode::KEY_NEXT,\nKeyCode::K408 => OsCode::KEY_RESTART,\nKeyCode::K409 => OsCode::KEY_SLOW,\nKeyCode::K410 => OsCode::KEY_SHUFFLE,\nKeyCode::K411 => OsCode::KEY_BREAK,\nKeyCode::K412 => OsCode::KEY_PREVIOUS,\nKeyCode::K413 => OsCode::KEY_DIGITS,\nKeyCode::K414 => OsCode::KEY_TEEN,\nKeyCode::K415 => OsCode::KEY_TWEN,\nKeyCode::K416 => OsCode::KEY_VIDEOPHONE,\nKeyCode::K417 => OsCode::KEY_GAMES,\nKeyCode::K418 => OsCode::KEY_ZOOMIN,\nKeyCode::K419 => OsCode::KEY_ZOOMOUT,\nKeyCode::K420 => OsCode::KEY_ZOOMRESET,\nKeyCode::K421 => OsCode::KEY_WORDPROCESSOR,\nKeyCode::K422 => OsCode::KEY_EDITOR,\nKeyCode::K423 => OsCode::KEY_SPREADSHEET,\nKeyCode::K424 => OsCode::KEY_GRAPHICSEDITOR,\nKeyCode::K425 => OsCode::KEY_PRESENTATION,\nKeyCode::K426 => OsCode::KEY_DATABASE,\nKeyCode::K427 => OsCode::KEY_NEWS,\nKeyCode::K428 => OsCode::KEY_VOICEMAIL,\nKeyCode::K429 => OsCode::KEY_ADDRESSBOOK,\nKeyCode::K430 => OsCode::KEY_MESSENGER,\nKeyCode::K431 => OsCode::KEY_DISPLAYTOGGLE,\nKeyCode::K432 => OsCode::KEY_SPELLCHECK,\nKeyCode::K433 => OsCode::KEY_LOGOFF,\nKeyCode::K434 => OsCode::KEY_DOLLAR,\nKeyCode::K435 => OsCode::KEY_EURO,\nKeyCode::K436 => OsCode::KEY_FRAMEBACK,\nKeyCode::K437 => OsCode::KEY_FRAMEFORWARD,\nKeyCode::K438 => OsCode::KEY_CONTEXT_MENU,\nKeyCode::K439 => OsCode::KEY_MEDIA_REPEAT,\nKeyCode::K440 => OsCode::KEY_10CHANNELSUP,\nKeyCode::K441 => OsCode::KEY_10CHANNELSDOWN,\nKeyCode::K442 => OsCode::KEY_IMAGES,\nKeyCode::K443 => OsCode::KEY_443,\nKeyCode::K444 => OsCode::KEY_444,\nKeyCode::K445 => OsCode::KEY_445,\nKeyCode::K446 => OsCode::KEY_446,\nKeyCode::K447 => OsCode::KEY_447,\nKeyCode::K448 => OsCode::KEY_DEL_EOL,\nKeyCode::K449 => OsCode::KEY_DEL_EOS,\nKeyCode::K450 => OsCode::KEY_INS_LINE,\nKeyCode::K451 => OsCode::KEY_DEL_LINE,\nKeyCode::K452 => OsCode::KEY_452,\nKeyCode::K453 => OsCode::KEY_453,\nKeyCode::K454 => OsCode::KEY_454,\nKeyCode::K455 => OsCode::KEY_455,\nKeyCode::K456 => OsCode::KEY_456,\nKeyCode::K457 => OsCode::KEY_457,\nKeyCode::K458 => OsCode::KEY_458,\nKeyCode::K459 => OsCode::KEY_459,\nKeyCode::K460 => OsCode::KEY_460,\nKeyCode::K461 => OsCode::KEY_461,\nKeyCode::K462 => OsCode::KEY_462,\nKeyCode::K463 => OsCode::KEY_463,\nKeyCode::K464 => OsCode::KEY_FN,\nKeyCode::K465 => OsCode::KEY_FN_ESC,\nKeyCode::K466 => OsCode::KEY_FN_F1,\nKeyCode::K467 => OsCode::KEY_FN_F2,\nKeyCode::K468 => OsCode::KEY_FN_F3,\nKeyCode::K469 => OsCode::KEY_FN_F4,\nKeyCode::K470 => OsCode::KEY_FN_F5,\nKeyCode::K471 => OsCode::KEY_FN_F6,\nKeyCode::K472 => OsCode::KEY_FN_F7,\nKeyCode::K473 => OsCode::KEY_FN_F8,\nKeyCode::K474 => OsCode::KEY_FN_F9,\nKeyCode::K475 => OsCode::KEY_FN_F10,\nKeyCode::K476 => OsCode::KEY_FN_F11,\nKeyCode::K477 => OsCode::KEY_FN_F12,\nKeyCode::K478 => OsCode::KEY_FN_1,\nKeyCode::K479 => OsCode::KEY_FN_2,\nKeyCode::K480 => OsCode::KEY_FN_D,\nKeyCode::K481 => OsCode::KEY_FN_E,\nKeyCode::K482 => OsCode::KEY_FN_F,\nKeyCode::K483 => OsCode::KEY_FN_S,\nKeyCode::K484 => OsCode::KEY_FN_B,\nKeyCode::K485 => OsCode::KEY_485,\nKeyCode::K486 => OsCode::KEY_486,\nKeyCode::K487 => OsCode::KEY_487,\nKeyCode::K488 => OsCode::KEY_488,\nKeyCode::K489 => OsCode::KEY_489,\nKeyCode::K490 => OsCode::KEY_490,\nKeyCode::K491 => OsCode::KEY_491,\nKeyCode::K492 => OsCode::KEY_492,\nKeyCode::K493 => OsCode::KEY_493,\nKeyCode::K494 => OsCode::KEY_494,\nKeyCode::K495 => OsCode::KEY_495,\nKeyCode::K496 => OsCode::KEY_496,\nKeyCode::K497 => OsCode::KEY_BRL_DOT1,\nKeyCode::K498 => OsCode::KEY_BRL_DOT2,\nKeyCode::K499 => OsCode::KEY_BRL_DOT3,\nKeyCode::K500 => OsCode::KEY_BRL_DOT4,\nKeyCode::K501 => OsCode::KEY_BRL_DOT5,\nKeyCode::K502 => OsCode::KEY_BRL_DOT6,\nKeyCode::K503 => OsCode::KEY_BRL_DOT7,\nKeyCode::K504 => OsCode::KEY_BRL_DOT8,\nKeyCode::K505 => OsCode::KEY_BRL_DOT9,\nKeyCode::K506 => OsCode::KEY_BRL_DOT10,\nKeyCode::K507 => OsCode::KEY_507,\nKeyCode::K508 => OsCode::KEY_508,\nKeyCode::K509 => OsCode::KEY_509,\nKeyCode::K510 => OsCode::KEY_510,\nKeyCode::K511 => OsCode::KEY_511,\nKeyCode::K512 => OsCode::KEY_NUMERIC_0,\nKeyCode::K513 => OsCode::KEY_NUMERIC_1,\nKeyCode::K514 => OsCode::KEY_NUMERIC_2,\nKeyCode::K515 => OsCode::KEY_NUMERIC_3,\nKeyCode::K516 => OsCode::KEY_NUMERIC_4,\nKeyCode::K517 => OsCode::KEY_NUMERIC_5,\nKeyCode::K518 => OsCode::KEY_NUMERIC_6,\nKeyCode::K519 => OsCode::KEY_NUMERIC_7,\nKeyCode::K520 => OsCode::KEY_NUMERIC_8,\nKeyCode::K521 => OsCode::KEY_NUMERIC_9,\nKeyCode::K522 => OsCode::KEY_NUMERIC_STAR,\nKeyCode::K523 => OsCode::KEY_NUMERIC_POUND,\nKeyCode::K524 => OsCode::KEY_NUMERIC_A,\nKeyCode::K525 => OsCode::KEY_NUMERIC_B,\nKeyCode::K526 => OsCode::KEY_NUMERIC_C,\nKeyCode::K527 => OsCode::KEY_NUMERIC_D,\nKeyCode::K528 => OsCode::KEY_CAMERA_FOCUS,\nKeyCode::K529 => OsCode::KEY_WPS_BUTTON,\nKeyCode::K530 => OsCode::KEY_TOUCHPAD_TOGGLE,\nKeyCode::K531 => OsCode::KEY_TOUCHPAD_ON,\nKeyCode::K532 => OsCode::KEY_TOUCHPAD_OFF,\nKeyCode::K533 => OsCode::KEY_CAMERA_ZOOMIN,\nKeyCode::K534 => OsCode::KEY_CAMERA_ZOOMOUT,\nKeyCode::K535 => OsCode::KEY_CAMERA_UP,\nKeyCode::K536 => OsCode::KEY_CAMERA_DOWN,\nKeyCode::K537 => OsCode::KEY_CAMERA_LEFT,\nKeyCode::K538 => OsCode::KEY_CAMERA_RIGHT,\nKeyCode::K539 => OsCode::KEY_ATTENDANT_ON,\nKeyCode::K540 => OsCode::KEY_ATTENDANT_OFF,\nKeyCode::K541 => OsCode::KEY_ATTENDANT_TOGGLE,\nKeyCode::K542 => OsCode::KEY_LIGHTS_TOGGLE,\nKeyCode::K543 => OsCode::KEY_543,\nKeyCode::K544 => OsCode::BTN_DPAD_UP,\nKeyCode::K545 => OsCode::BTN_DPAD_DOWN,\nKeyCode::K546 => OsCode::BTN_DPAD_LEFT,\nKeyCode::K547 => OsCode::BTN_DPAD_RIGHT,\nKeyCode::K548 => OsCode::KEY_548,\nKeyCode::K549 => OsCode::KEY_549,\nKeyCode::K550 => OsCode::KEY_550,\nKeyCode::K551 => OsCode::KEY_551,\nKeyCode::K552 => OsCode::KEY_552,\nKeyCode::K553 => OsCode::KEY_553,\nKeyCode::K554 => OsCode::KEY_554,\nKeyCode::K555 => OsCode::KEY_555,\nKeyCode::K556 => OsCode::KEY_556,\nKeyCode::K557 => OsCode::KEY_557,\nKeyCode::K558 => OsCode::KEY_558,\nKeyCode::K559 => OsCode::KEY_559,\nKeyCode::K560 => OsCode::KEY_ALS_TOGGLE,\nKeyCode::K561 => OsCode::KEY_ROTATE_LOCK_TOGGLE,\nKeyCode::K562 => OsCode::KEY_562,\nKeyCode::K563 => OsCode::KEY_563,\nKeyCode::K564 => OsCode::KEY_564,\nKeyCode::K565 => OsCode::KEY_565,\nKeyCode::K566 => OsCode::KEY_566,\nKeyCode::K567 => OsCode::KEY_567,\nKeyCode::K568 => OsCode::KEY_568,\nKeyCode::K569 => OsCode::KEY_569,\nKeyCode::K570 => OsCode::KEY_570,\nKeyCode::K571 => OsCode::KEY_571,\nKeyCode::K572 => OsCode::KEY_572,\nKeyCode::K573 => OsCode::KEY_573,\nKeyCode::K574 => OsCode::KEY_574,\nKeyCode::K575 => OsCode::KEY_575,\nKeyCode::K576 => OsCode::KEY_BUTTONCONFIG,\nKeyCode::K577 => OsCode::KEY_TASKMANAGER,\nKeyCode::K578 => OsCode::KEY_JOURNAL,\nKeyCode::K579 => OsCode::KEY_CONTROLPANEL,\nKeyCode::K580 => OsCode::KEY_APPSELECT,\nKeyCode::K581 => OsCode::KEY_SCREENSAVER,\nKeyCode::K582 => OsCode::KEY_VOICECOMMAND,\nKeyCode::K583 => OsCode::KEY_ASSISTANT,\nKeyCode::K584 => OsCode::KEY_KBD_LAYOUT_NEXT,\nKeyCode::K585 => OsCode::KEY_585,\nKeyCode::K586 => OsCode::KEY_586,\nKeyCode::K587 => OsCode::KEY_587,\nKeyCode::K588 => OsCode::KEY_588,\nKeyCode::K589 => OsCode::KEY_589,\nKeyCode::K590 => OsCode::KEY_590,\nKeyCode::K591 => OsCode::KEY_591,\nKeyCode::K592 => OsCode::KEY_BRIGHTNESS_MIN,\nKeyCode::K593 => OsCode::KEY_BRIGHTNESS_MAX,\nKeyCode::K594 => OsCode::KEY_594,\nKeyCode::K595 => OsCode::KEY_595,\nKeyCode::K596 => OsCode::KEY_596,\nKeyCode::K597 => OsCode::KEY_597,\nKeyCode::K598 => OsCode::KEY_598,\nKeyCode::K599 => OsCode::KEY_599,\nKeyCode::K600 => OsCode::KEY_600,\nKeyCode::K601 => OsCode::KEY_601,\nKeyCode::K602 => OsCode::KEY_602,\nKeyCode::K603 => OsCode::KEY_603,\nKeyCode::K604 => OsCode::KEY_604,\nKeyCode::K605 => OsCode::KEY_605,\nKeyCode::K606 => OsCode::KEY_606,\nKeyCode::K607 => OsCode::KEY_607,\nKeyCode::K608 => OsCode::KEY_KBDINPUTASSIST_PREV,\nKeyCode::K609 => OsCode::KEY_KBDINPUTASSIST_NEXT,\nKeyCode::K610 => OsCode::KEY_KBDINPUTASSIST_PREVGROUP,\nKeyCode::K611 => OsCode::KEY_KBDINPUTASSIST_NEXTGROUP,\nKeyCode::K612 => OsCode::KEY_KBDINPUTASSIST_ACCEPT,\nKeyCode::K613 => OsCode::KEY_KBDINPUTASSIST_CANCEL,\nKeyCode::K614 => OsCode::KEY_RIGHT_UP,\nKeyCode::K615 => OsCode::KEY_RIGHT_DOWN,\nKeyCode::K616 => OsCode::KEY_LEFT_UP,\nKeyCode::K617 => OsCode::KEY_LEFT_DOWN,\nKeyCode::K618 => OsCode::KEY_ROOT_MENU,\nKeyCode::K619 => OsCode::KEY_MEDIA_TOP_MENU,\nKeyCode::K620 => OsCode::KEY_NUMERIC_11,\nKeyCode::K621 => OsCode::KEY_NUMERIC_12,\nKeyCode::K622 => OsCode::KEY_AUDIO_DESC,\nKeyCode::K623 => OsCode::KEY_3D_MODE,\nKeyCode::K624 => OsCode::KEY_NEXT_FAVORITE,\nKeyCode::K625 => OsCode::KEY_STOP_RECORD,\nKeyCode::K626 => OsCode::KEY_PAUSE_RECORD,\nKeyCode::K627 => OsCode::KEY_VOD,\nKeyCode::K628 => OsCode::KEY_UNMUTE,\nKeyCode::K629 => OsCode::KEY_FASTREVERSE,\nKeyCode::K630 => OsCode::KEY_SLOWREVERSE,\nKeyCode::K631 => OsCode::KEY_DATA,\nKeyCode::K632 => OsCode::KEY_ONSCREEN_KEYBOARD,\nKeyCode::K633 => OsCode::KEY_633,\nKeyCode::K634 => OsCode::KEY_634,\nKeyCode::K635 => OsCode::KEY_635,\nKeyCode::K636 => OsCode::KEY_636,\nKeyCode::K637 => OsCode::KEY_637,\nKeyCode::K638 => OsCode::KEY_638,\nKeyCode::K639 => OsCode::KEY_639,\nKeyCode::K640 => OsCode::KEY_640,\nKeyCode::K641 => OsCode::KEY_641,\nKeyCode::K642 => OsCode::KEY_642,\nKeyCode::K643 => OsCode::KEY_643,\nKeyCode::K644 => OsCode::KEY_644,\nKeyCode::K645 => OsCode::KEY_645,\nKeyCode::K646 => OsCode::KEY_646,\nKeyCode::K647 => OsCode::KEY_647,\nKeyCode::K648 => OsCode::KEY_648,\nKeyCode::K649 => OsCode::KEY_649,\nKeyCode::K650 => OsCode::KEY_650,\nKeyCode::K651 => OsCode::KEY_651,\nKeyCode::K652 => OsCode::KEY_652,\nKeyCode::K653 => OsCode::KEY_653,\nKeyCode::K654 => OsCode::KEY_654,\nKeyCode::K655 => OsCode::KEY_655,\nKeyCode::K656 => OsCode::KEY_656,\nKeyCode::K657 => OsCode::KEY_657,\nKeyCode::K658 => OsCode::KEY_658,\nKeyCode::K659 => OsCode::KEY_659,\nKeyCode::K660 => OsCode::KEY_660,\nKeyCode::K661 => OsCode::KEY_661,\nKeyCode::K662 => OsCode::KEY_662,\nKeyCode::K663 => OsCode::KEY_663,\nKeyCode::K664 => OsCode::KEY_664,\nKeyCode::K665 => OsCode::KEY_665,\nKeyCode::K666 => OsCode::KEY_666,\nKeyCode::K667 => OsCode::KEY_667,\nKeyCode::K668 => OsCode::KEY_668,\nKeyCode::K669 => OsCode::KEY_669,\nKeyCode::K670 => OsCode::KEY_670,\nKeyCode::K671 => OsCode::KEY_671,\nKeyCode::K672 => OsCode::KEY_672,\nKeyCode::K673 => OsCode::KEY_673,\nKeyCode::K674 => OsCode::KEY_674,\nKeyCode::K675 => OsCode::KEY_675,\nKeyCode::K676 => OsCode::KEY_676,\nKeyCode::K677 => OsCode::KEY_677,\nKeyCode::K678 => OsCode::KEY_678,\nKeyCode::K679 => OsCode::KEY_679,\nKeyCode::K680 => OsCode::KEY_680,\nKeyCode::K681 => OsCode::KEY_681,\nKeyCode::K682 => OsCode::KEY_682,\nKeyCode::K683 => OsCode::KEY_683,\nKeyCode::K684 => OsCode::KEY_684,\nKeyCode::K685 => OsCode::KEY_685,\nKeyCode::K686 => OsCode::KEY_686,\nKeyCode::K687 => OsCode::KEY_687,\nKeyCode::K688 => OsCode::KEY_688,\nKeyCode::K689 => OsCode::KEY_689,\nKeyCode::K690 => OsCode::KEY_690,\nKeyCode::K691 => OsCode::KEY_691,\nKeyCode::K692 => OsCode::KEY_692,\nKeyCode::K693 => OsCode::KEY_693,\nKeyCode::K694 => OsCode::KEY_694,\nKeyCode::K695 => OsCode::KEY_695,\nKeyCode::K696 => OsCode::KEY_696,\nKeyCode::K697 => OsCode::KEY_697,\nKeyCode::K698 => OsCode::KEY_698,\nKeyCode::K699 => OsCode::KEY_699,\nKeyCode::K700 => OsCode::KEY_700,\nKeyCode::K701 => OsCode::KEY_701,\nKeyCode::K702 => OsCode::KEY_702,\nKeyCode::K703 => OsCode::KEY_703,\nKeyCode::K704 => OsCode::BTN_TRIGGER_HAPPY1,\nKeyCode::K705 => OsCode::BTN_TRIGGER_HAPPY2,\nKeyCode::K706 => OsCode::BTN_TRIGGER_HAPPY3,\nKeyCode::K707 => OsCode::BTN_TRIGGER_HAPPY4,\nKeyCode::K708 => OsCode::BTN_TRIGGER_HAPPY5,\nKeyCode::K709 => OsCode::BTN_TRIGGER_HAPPY6,\nKeyCode::K710 => OsCode::BTN_TRIGGER_HAPPY7,\nKeyCode::K711 => OsCode::BTN_TRIGGER_HAPPY8,\nKeyCode::K712 => OsCode::BTN_TRIGGER_HAPPY9,\nKeyCode::K713 => OsCode::BTN_TRIGGER_HAPPY10,\nKeyCode::K714 => OsCode::BTN_TRIGGER_HAPPY11,\nKeyCode::K715 => OsCode::BTN_TRIGGER_HAPPY12,\nKeyCode::K716 => OsCode::BTN_TRIGGER_HAPPY13,\nKeyCode::K717 => OsCode::BTN_TRIGGER_HAPPY14,\nKeyCode::K718 => OsCode::BTN_TRIGGER_HAPPY15,\nKeyCode::K719 => OsCode::BTN_TRIGGER_HAPPY16,\nKeyCode::K720 => OsCode::BTN_TRIGGER_HAPPY17,\nKeyCode::K721 => OsCode::BTN_TRIGGER_HAPPY18,\nKeyCode::K722 => OsCode::BTN_TRIGGER_HAPPY19,\nKeyCode::K723 => OsCode::BTN_TRIGGER_HAPPY20,\nKeyCode::K724 => OsCode::BTN_TRIGGER_HAPPY21,\nKeyCode::K725 => OsCode::BTN_TRIGGER_HAPPY22,\nKeyCode::K726 => OsCode::BTN_TRIGGER_HAPPY23,\nKeyCode::K727 => OsCode::BTN_TRIGGER_HAPPY24,\nKeyCode::K728 => OsCode::BTN_TRIGGER_HAPPY25,\nKeyCode::K729 => OsCode::BTN_TRIGGER_HAPPY26,\nKeyCode::K730 => OsCode::BTN_TRIGGER_HAPPY27,\nKeyCode::K731 => OsCode::BTN_TRIGGER_HAPPY28,\nKeyCode::K732 => OsCode::BTN_TRIGGER_HAPPY29,\nKeyCode::K733 => OsCode::BTN_TRIGGER_HAPPY30,\nKeyCode::K734 => OsCode::BTN_TRIGGER_HAPPY31,\nKeyCode::K735 => OsCode::BTN_TRIGGER_HAPPY32,\nKeyCode::K736 => OsCode::BTN_TRIGGER_HAPPY33,\nKeyCode::K737 => OsCode::BTN_TRIGGER_HAPPY34,\nKeyCode::K738 => OsCode::BTN_TRIGGER_HAPPY35,\nKeyCode::K739 => OsCode::BTN_TRIGGER_HAPPY36,\nKeyCode::K740 => OsCode::BTN_TRIGGER_HAPPY37,\nKeyCode::K741 => OsCode::BTN_TRIGGER_HAPPY38,\nKeyCode::K742 => OsCode::BTN_TRIGGER_HAPPY39,\nKeyCode::K743 => OsCode::BTN_TRIGGER_HAPPY40,\nKeyCode::K744 => OsCode::BTN_MAX,\nKeyCode::MWU => OsCode::MouseWheelUp,\nKeyCode::MWD => OsCode::MouseWheelDown,\nKeyCode::MWL => OsCode::MouseWheelLeft,\nKeyCode::MWR => OsCode::MouseWheelRight,\n\n=== osc to u16\nKEY_RESERVED = 0,\nKEY_ESC = 1,\nKEY_1 = 2,\nKEY_2 = 3,\nKEY_3 = 4,\nKEY_4 = 5,\nKEY_5 = 6,\nKEY_6 = 7,\nKEY_7 = 8,\nKEY_8 = 9,\nKEY_9 = 10,\nKEY_0 = 11,\nKEY_MINUS = 12,\nKEY_EQUAL = 13,\nKEY_BACKSPACE = 14,\nKEY_TAB = 15,\nKEY_Q = 16,\nKEY_W = 17,\nKEY_E = 18,\nKEY_R = 19,\nKEY_T = 20,\nKEY_Y = 21,\nKEY_U = 22,\nKEY_I = 23,\nKEY_O = 24,\nKEY_P = 25,\nKEY_LEFTBRACE = 26,\nKEY_RIGHTBRACE = 27,\nKEY_ENTER = 28,\nKEY_LEFTCTRL = 29,\nKEY_A = 30,\nKEY_S = 31,\nKEY_D = 32,\nKEY_F = 33,\nKEY_G = 34,\nKEY_H = 35,\nKEY_J = 36,\nKEY_K = 37,\nKEY_L = 38,\nKEY_SEMICOLON = 39,\nKEY_APOSTROPHE = 40,\nKEY_GRAVE = 41,\nKEY_LEFTSHIFT = 42,\nKEY_BACKSLASH = 43,\nKEY_Z = 44,\nKEY_X = 45,\nKEY_C = 46,\nKEY_V = 47,\nKEY_B = 48,\nKEY_N = 49,\nKEY_M = 50,\nKEY_COMMA = 51,\nKEY_DOT = 52,\nKEY_SLASH = 53,\nKEY_RIGHTSHIFT = 54,\nKEY_KPASTERISK = 55,\nKEY_LEFTALT = 56,\nKEY_SPACE = 57,\nKEY_CAPSLOCK = 58,\nKEY_F1 = 59,\nKEY_F2 = 60,\nKEY_F3 = 61,\nKEY_F4 = 62,\nKEY_F5 = 63,\nKEY_F6 = 64,\nKEY_F7 = 65,\nKEY_F8 = 66,\nKEY_F9 = 67,\nKEY_F10 = 68,\nKEY_NUMLOCK = 69,\nKEY_SCROLLLOCK = 70,\nKEY_KP7 = 71,\nKEY_KP8 = 72,\nKEY_KP9 = 73,\nKEY_KPMINUS = 74,\nKEY_KP4 = 75,\nKEY_KP5 = 76,\nKEY_KP6 = 77,\nKEY_KPPLUS = 78,\nKEY_KP1 = 79,\nKEY_KP2 = 80,\nKEY_KP3 = 81,\nKEY_KP0 = 82,\nKEY_KPDOT = 83,\nKEY_84 = 84,\nKEY_ZENKAKUHANKAKU = 85,\nKEY_102ND = 86,\nKEY_F11 = 87,\nKEY_F12 = 88,\nKEY_RO = 89,\nKEY_KATAKANA = 90,\nKEY_HIRAGANA = 91,\nKEY_HENKAN = 92,\nKEY_KATAKANAHIRAGANA = 93,\nKEY_MUHENKAN = 94,\nKEY_KPJPCOMMA = 95,\nKEY_KPENTER = 96,\nKEY_RIGHTCTRL = 97,\nKEY_KPSLASH = 98,\nKEY_SYSRQ = 99,\nKEY_RIGHTALT = 100,\nKEY_LINEFEED = 101,\nKEY_HOME = 102,\nKEY_UP = 103,\nKEY_PAGEUP = 104,\nKEY_LEFT = 105,\nKEY_RIGHT = 106,\nKEY_END = 107,\nKEY_DOWN = 108,\nKEY_PAGEDOWN = 109,\nKEY_INSERT = 110,\nKEY_DELETE = 111,\nKEY_MACRO = 112,\nKEY_MUTE = 113,\nKEY_VOLUMEDOWN = 114,\nKEY_VOLUMEUP = 115,\nKEY_POWER = 116,\nKEY_KPEQUAL = 117,\nKEY_KPPLUSMINUS = 118,\nKEY_PAUSE = 119,\nKEY_SCALE = 120,\nKEY_KPCOMMA = 121,\nKEY_HANGEUL = 122,\nKEY_HANJA = 123,\nKEY_YEN = 124,\nKEY_LEFTMETA = 125,\nKEY_RIGHTMETA = 126,\nKEY_COMPOSE = 127,\nKEY_STOP = 128,\nKEY_AGAIN = 129,\nKEY_PROPS = 130,\nKEY_UNDO = 131,\nKEY_FRONT = 132,\nKEY_COPY = 133,\nKEY_OPEN = 134,\nKEY_PASTE = 135,\nKEY_FIND = 136,\nKEY_CUT = 137,\nKEY_HELP = 138,\nKEY_MENU = 139,\nKEY_CALC = 140,\nKEY_SETUP = 141,\nKEY_SLEEP = 142,\nKEY_WAKEUP = 143,\nKEY_FILE = 144,\nKEY_SENDFILE = 145,\nKEY_DELETEFILE = 146,\nKEY_XFER = 147,\nKEY_PROG1 = 148,\nKEY_PROG2 = 149,\nKEY_WWW = 150,\nKEY_MSDOS = 151,\nKEY_COFFEE = 152,\nKEY_ROTATE_DISPLAY = 153,\nKEY_CYCLEWINDOWS = 154,\nKEY_MAIL = 155,\nKEY_BOOKMARKS = 156,\nKEY_COMPUTER = 157,\nKEY_BACK = 158,\nKEY_FORWARD = 159,\nKEY_CLOSECD = 160,\nKEY_EJECTCD = 161,\nKEY_EJECTCLOSECD = 162,\nKEY_NEXTSONG = 163,\nKEY_PLAYPAUSE = 164,\nKEY_PREVIOUSSONG = 165,\nKEY_STOPCD = 166,\nKEY_RECORD = 167,\nKEY_REWIND = 168,\nKEY_PHONE = 169,\nKEY_ISO = 170,\nKEY_CONFIG = 171,\nKEY_HOMEPAGE = 172,\nKEY_REFRESH = 173,\nKEY_EXIT = 174,\nKEY_MOVE = 175,\nKEY_EDIT = 176,\nKEY_SCROLLUP = 177,\nKEY_SCROLLDOWN = 178,\nKEY_KPLEFTPAREN = 179,\nKEY_KPRIGHTPAREN = 180,\nKEY_NEW = 181,\nKEY_REDO = 182,\nKEY_F13 = 183,\nKEY_F14 = 184,\nKEY_F15 = 185,\nKEY_F16 = 186,\nKEY_F17 = 187,\nKEY_F18 = 188,\nKEY_F19 = 189,\nKEY_F20 = 190,\nKEY_F21 = 191,\nKEY_F22 = 192,\nKEY_F23 = 193,\nKEY_F24 = 194,\nKEY_195 = 195,\nKEY_196 = 196,\nKEY_197 = 197,\nKEY_198 = 198,\nKEY_199 = 199,\nKEY_PLAYCD = 200,\nKEY_PAUSECD = 201,\nKEY_PROG3 = 202,\nKEY_PROG4 = 203,\nKEY_DASHBOARD = 204,\nKEY_SUSPEND = 205,\nKEY_CLOSE = 206,\nKEY_PLAY = 207,\nKEY_FASTFORWARD = 208,\nKEY_BASSBOOST = 209,\nKEY_PRINT = 210,\nKEY_HP = 211,\nKEY_CAMERA = 212,\nKEY_SOUND = 213,\nKEY_QUESTION = 214,\nKEY_EMAIL = 215,\nKEY_CHAT = 216,\nKEY_SEARCH = 217,\nKEY_CONNECT = 218,\nKEY_FINANCE = 219,\nKEY_SPORT = 220,\nKEY_SHOP = 221,\nKEY_ALTERASE = 222,\nKEY_CANCEL = 223,\nKEY_BRIGHTNESSDOWN = 224,\nKEY_BRIGHTNESSUP = 225,\nKEY_MEDIA = 226,\nKEY_SWITCHVIDEOMODE = 227,\nKEY_KBDILLUMTOGGLE = 228,\nKEY_KBDILLUMDOWN = 229,\nKEY_KBDILLUMUP = 230,\nKEY_SEND = 231,\nKEY_REPLY = 232,\nKEY_FORWARDMAIL = 233,\nKEY_SAVE = 234,\nKEY_DOCUMENTS = 235,\nKEY_BATTERY = 236,\nKEY_BLUETOOTH = 237,\nKEY_WLAN = 238,\nKEY_UWB = 239,\nKEY_UNKNOWN = 240,\nKEY_VIDEO_NEXT = 241,\nKEY_VIDEO_PREV = 242,\nKEY_BRIGHTNESS_CYCLE = 243,\nKEY_BRIGHTNESS_AUTO = 244,\nKEY_DISPLAY_OFF = 245,\nKEY_WWAN = 246,\nKEY_RFKILL = 247,\nKEY_MICMUTE = 248,\nKEY_249 = 249,\nKEY_250 = 250,\nKEY_251 = 251,\nKEY_252 = 252,\nKEY_253 = 253,\nKEY_254 = 254,\nKEY_255 = 255,\nBTN_0 = 256,\nBTN_1 = 257,\nBTN_2 = 258,\nBTN_3 = 259,\nBTN_4 = 260,\nBTN_5 = 261,\nBTN_6 = 262,\nBTN_7 = 263,\nBTN_8 = 264,\nBTN_9 = 265,\nKEY_266 = 266,\nKEY_267 = 267,\nKEY_268 = 268,\nKEY_269 = 269,\nKEY_270 = 270,\nKEY_271 = 271,\nBTN_LEFT = 272,\nBTN_RIGHT = 273,\nBTN_MIDDLE = 274,\nBTN_SIDE = 275,\nBTN_EXTRA = 276,\nBTN_FORWARD = 277,\nBTN_BACK = 278,\nBTN_TASK = 279,\nKEY_280 = 280,\nKEY_281 = 281,\nKEY_282 = 282,\nKEY_283 = 283,\nKEY_284 = 284,\nKEY_285 = 285,\nKEY_286 = 286,\nKEY_287 = 287,\nBTN_TRIGGER = 288,\nBTN_THUMB = 289,\nBTN_THUMB2 = 290,\nBTN_TOP = 291,\nBTN_TOP2 = 292,\nBTN_PINKIE = 293,\nBTN_BASE = 294,\nBTN_BASE2 = 295,\nBTN_BASE3 = 296,\nBTN_BASE4 = 297,\nBTN_BASE5 = 298,\nBTN_BASE6 = 299,\nKEY_300 = 300,\nKEY_301 = 301,\nKEY_302 = 302,\nBTN_DEAD = 303,\nBTN_SOUTH = 304,\nBTN_EAST = 305,\nBTN_C = 306,\nBTN_NORTH = 307,\nBTN_WEST = 308,\nBTN_Z = 309,\nBTN_TL = 310,\nBTN_TR = 311,\nBTN_TL2 = 312,\nBTN_TR2 = 313,\nBTN_SELECT = 314,\nBTN_START = 315,\nBTN_MODE = 316,\nBTN_THUMBL = 317,\nBTN_THUMBR = 318,\nKEY_319 = 319,\nBTN_TOOL_PEN = 320,\nBTN_TOOL_RUBBER = 321,\nBTN_TOOL_BRUSH = 322,\nBTN_TOOL_PENCIL = 323,\nBTN_TOOL_AIRBRUSH = 324,\nBTN_TOOL_FINGER = 325,\nBTN_TOOL_MOUSE = 326,\nBTN_TOOL_LENS = 327,\nBTN_TOOL_QUINTTAP = 328,\nBTN_STYLUS3 = 329,\nBTN_TOUCH = 330,\nBTN_STYLUS = 331,\nBTN_STYLUS2 = 332,\nBTN_TOOL_DOUBLETAP = 333,\nBTN_TOOL_TRIPLETAP = 334,\nBTN_TOOL_QUADTAP = 335,\nBTN_GEAR_DOWN = 336,\nBTN_GEAR_UP = 337,\nKEY_338 = 338,\nKEY_339 = 339,\nKEY_340 = 340,\nKEY_341 = 341,\nKEY_342 = 342,\nKEY_343 = 343,\nKEY_344 = 344,\nKEY_345 = 345,\nKEY_346 = 346,\nKEY_347 = 347,\nKEY_348 = 348,\nKEY_349 = 349,\nKEY_350 = 350,\nKEY_351 = 351,\nKEY_OK = 352,\nKEY_SELECT = 353,\nKEY_GOTO = 354,\nKEY_CLEAR = 355,\nKEY_POWER2 = 356,\nKEY_OPTION = 357,\nKEY_INFO = 358,\nKEY_TIME = 359,\nKEY_VENDOR = 360,\nKEY_ARCHIVE = 361,\nKEY_PROGRAM = 362,\nKEY_CHANNEL = 363,\nKEY_FAVORITES = 364,\nKEY_EPG = 365,\nKEY_PVR = 366,\nKEY_MHP = 367,\nKEY_LANGUAGE = 368,\nKEY_TITLE = 369,\nKEY_SUBTITLE = 370,\nKEY_ANGLE = 371,\nKEY_FULL_SCREEN = 372,\nKEY_MODE = 373,\nKEY_KEYBOARD = 374,\nKEY_ASPECT_RATIO = 375,\nKEY_PC = 376,\nKEY_TV = 377,\nKEY_TV2 = 378,\nKEY_VCR = 379,\nKEY_VCR2 = 380,\nKEY_SAT = 381,\nKEY_SAT2 = 382,\nKEY_CD = 383,\nKEY_TAPE = 384,\nKEY_RADIO = 385,\nKEY_TUNER = 386,\nKEY_PLAYER = 387,\nKEY_TEXT = 388,\nKEY_DVD = 389,\nKEY_AUX = 390,\nKEY_MP3 = 391,\nKEY_AUDIO = 392,\nKEY_VIDEO = 393,\nKEY_DIRECTORY = 394,\nKEY_LIST = 395,\nKEY_MEMO = 396,\nKEY_CALENDAR = 397,\nKEY_RED = 398,\nKEY_GREEN = 399,\nKEY_YELLOW = 400,\nKEY_BLUE = 401,\nKEY_CHANNELUP = 402,\nKEY_CHANNELDOWN = 403,\nKEY_FIRST = 404,\nKEY_LAST = 405,\nKEY_AB = 406,\nKEY_NEXT = 407,\nKEY_RESTART = 408,\nKEY_SLOW = 409,\nKEY_SHUFFLE = 410,\nKEY_BREAK = 411,\nKEY_PREVIOUS = 412,\nKEY_DIGITS = 413,\nKEY_TEEN = 414,\nKEY_TWEN = 415,\nKEY_VIDEOPHONE = 416,\nKEY_GAMES = 417,\nKEY_ZOOMIN = 418,\nKEY_ZOOMOUT = 419,\nKEY_ZOOMRESET = 420,\nKEY_WORDPROCESSOR = 421,\nKEY_EDITOR = 422,\nKEY_SPREADSHEET = 423,\nKEY_GRAPHICSEDITOR = 424,\nKEY_PRESENTATION = 425,\nKEY_DATABASE = 426,\nKEY_NEWS = 427,\nKEY_VOICEMAIL = 428,\nKEY_ADDRESSBOOK = 429,\nKEY_MESSENGER = 430,\nKEY_DISPLAYTOGGLE = 431,\nKEY_SPELLCHECK = 432,\nKEY_LOGOFF = 433,\nKEY_DOLLAR = 434,\nKEY_EURO = 435,\nKEY_FRAMEBACK = 436,\nKEY_FRAMEFORWARD = 437,\nKEY_CONTEXT_MENU = 438,\nKEY_MEDIA_REPEAT = 439,\nKEY_10CHANNELSUP = 440,\nKEY_10CHANNELSDOWN = 441,\nKEY_IMAGES = 442,\nKEY_443 = 443,\nKEY_444 = 444,\nKEY_445 = 445,\nKEY_446 = 446,\nKEY_447 = 447,\nKEY_DEL_EOL = 448,\nKEY_DEL_EOS = 449,\nKEY_INS_LINE = 450,\nKEY_DEL_LINE = 451,\nKEY_452 = 452,\nKEY_453 = 453,\nKEY_454 = 454,\nKEY_455 = 455,\nKEY_456 = 456,\nKEY_457 = 457,\nKEY_458 = 458,\nKEY_459 = 459,\nKEY_460 = 460,\nKEY_461 = 461,\nKEY_462 = 462,\nKEY_463 = 463,\nKEY_FN = 464,\nKEY_FN_ESC = 465,\nKEY_FN_F1 = 466,\nKEY_FN_F2 = 467,\nKEY_FN_F3 = 468,\nKEY_FN_F4 = 469,\nKEY_FN_F5 = 470,\nKEY_FN_F6 = 471,\nKEY_FN_F7 = 472,\nKEY_FN_F8 = 473,\nKEY_FN_F9 = 474,\nKEY_FN_F10 = 475,\nKEY_FN_F11 = 476,\nKEY_FN_F12 = 477,\nKEY_FN_1 = 478,\nKEY_FN_2 = 479,\nKEY_FN_D = 480,\nKEY_FN_E = 481,\nKEY_FN_F = 482,\nKEY_FN_S = 483,\nKEY_FN_B = 484,\nKEY_485 = 485,\nKEY_486 = 486,\nKEY_487 = 487,\nKEY_488 = 488,\nKEY_489 = 489,\nKEY_490 = 490,\nKEY_491 = 491,\nKEY_492 = 492,\nKEY_493 = 493,\nKEY_494 = 494,\nKEY_495 = 495,\nKEY_496 = 496,\nKEY_BRL_DOT1 = 497,\nKEY_BRL_DOT2 = 498,\nKEY_BRL_DOT3 = 499,\nKEY_BRL_DOT4 = 500,\nKEY_BRL_DOT5 = 501,\nKEY_BRL_DOT6 = 502,\nKEY_BRL_DOT7 = 503,\nKEY_BRL_DOT8 = 504,\nKEY_BRL_DOT9 = 505,\nKEY_BRL_DOT10 = 506,\nKEY_507 = 507,\nKEY_508 = 508,\nKEY_509 = 509,\nKEY_510 = 510,\nKEY_511 = 511,\nKEY_NUMERIC_0 = 512,\nKEY_NUMERIC_1 = 513,\nKEY_NUMERIC_2 = 514,\nKEY_NUMERIC_3 = 515,\nKEY_NUMERIC_4 = 516,\nKEY_NUMERIC_5 = 517,\nKEY_NUMERIC_6 = 518,\nKEY_NUMERIC_7 = 519,\nKEY_NUMERIC_8 = 520,\nKEY_NUMERIC_9 = 521,\nKEY_NUMERIC_STAR = 522,\nKEY_NUMERIC_POUND = 523,\nKEY_NUMERIC_A = 524,\nKEY_NUMERIC_B = 525,\nKEY_NUMERIC_C = 526,\nKEY_NUMERIC_D = 527,\nKEY_CAMERA_FOCUS = 528,\nKEY_WPS_BUTTON = 529,\nKEY_TOUCHPAD_TOGGLE = 530,\nKEY_TOUCHPAD_ON = 531,\nKEY_TOUCHPAD_OFF = 532,\nKEY_CAMERA_ZOOMIN = 533,\nKEY_CAMERA_ZOOMOUT = 534,\nKEY_CAMERA_UP = 535,\nKEY_CAMERA_DOWN = 536,\nKEY_CAMERA_LEFT = 537,\nKEY_CAMERA_RIGHT = 538,\nKEY_ATTENDANT_ON = 539,\nKEY_ATTENDANT_OFF = 540,\nKEY_ATTENDANT_TOGGLE = 541,\nKEY_LIGHTS_TOGGLE = 542,\nKEY_543 = 543,\nBTN_DPAD_UP = 544,\nBTN_DPAD_DOWN = 545,\nBTN_DPAD_LEFT = 546,\nBTN_DPAD_RIGHT = 547,\nKEY_548 = 548,\nKEY_549 = 549,\nKEY_550 = 550,\nKEY_551 = 551,\nKEY_552 = 552,\nKEY_553 = 553,\nKEY_554 = 554,\nKEY_555 = 555,\nKEY_556 = 556,\nKEY_557 = 557,\nKEY_558 = 558,\nKEY_559 = 559,\nKEY_ALS_TOGGLE = 560,\nKEY_ROTATE_LOCK_TOGGLE = 561,\nKEY_562 = 562,\nKEY_563 = 563,\nKEY_564 = 564,\nKEY_565 = 565,\nKEY_566 = 566,\nKEY_567 = 567,\nKEY_568 = 568,\nKEY_569 = 569,\nKEY_570 = 570,\nKEY_571 = 571,\nKEY_572 = 572,\nKEY_573 = 573,\nKEY_574 = 574,\nKEY_575 = 575,\nKEY_BUTTONCONFIG = 576,\nKEY_TASKMANAGER = 577,\nKEY_JOURNAL = 578,\nKEY_CONTROLPANEL = 579,\nKEY_APPSELECT = 580,\nKEY_SCREENSAVER = 581,\nKEY_VOICECOMMAND = 582,\nKEY_ASSISTANT = 583,\nKEY_KBD_LAYOUT_NEXT = 584,\nKEY_585 = 585,\nKEY_586 = 586,\nKEY_587 = 587,\nKEY_588 = 588,\nKEY_589 = 589,\nKEY_590 = 590,\nKEY_591 = 591,\nKEY_BRIGHTNESS_MIN = 592,\nKEY_BRIGHTNESS_MAX = 593,\nKEY_594 = 594,\nKEY_595 = 595,\nKEY_596 = 596,\nKEY_597 = 597,\nKEY_598 = 598,\nKEY_599 = 599,\nKEY_600 = 600,\nKEY_601 = 601,\nKEY_602 = 602,\nKEY_603 = 603,\nKEY_604 = 604,\nKEY_605 = 605,\nKEY_606 = 606,\nKEY_607 = 607,\nKEY_KBDINPUTASSIST_PREV = 608,\nKEY_KBDINPUTASSIST_NEXT = 609,\nKEY_KBDINPUTASSIST_PREVGROUP = 610,\nKEY_KBDINPUTASSIST_NEXTGROUP = 611,\nKEY_KBDINPUTASSIST_ACCEPT = 612,\nKEY_KBDINPUTASSIST_CANCEL = 613,\nKEY_RIGHT_UP = 614,\nKEY_RIGHT_DOWN = 615,\nKEY_LEFT_UP = 616,\nKEY_LEFT_DOWN = 617,\nKEY_ROOT_MENU = 618,\nKEY_MEDIA_TOP_MENU = 619,\nKEY_NUMERIC_11 = 620,\nKEY_NUMERIC_12 = 621,\nKEY_AUDIO_DESC = 622,\nKEY_3D_MODE = 623,\nKEY_NEXT_FAVORITE = 624,\nKEY_STOP_RECORD = 625,\nKEY_PAUSE_RECORD = 626,\nKEY_VOD = 627,\nKEY_UNMUTE = 628,\nKEY_FASTREVERSE = 629,\nKEY_SLOWREVERSE = 630,\nKEY_DATA = 631,\nKEY_ONSCREEN_KEYBOARD = 632,\nKEY_633 = 633,\nKEY_634 = 634,\nKEY_635 = 635,\nKEY_636 = 636,\nKEY_637 = 637,\nKEY_638 = 638,\nKEY_639 = 639,\nKEY_640 = 640,\nKEY_641 = 641,\nKEY_642 = 642,\nKEY_643 = 643,\nKEY_644 = 644,\nKEY_645 = 645,\nKEY_646 = 646,\nKEY_647 = 647,\nKEY_648 = 648,\nKEY_649 = 649,\nKEY_650 = 650,\nKEY_651 = 651,\nKEY_652 = 652,\nKEY_653 = 653,\nKEY_654 = 654,\nKEY_655 = 655,\nKEY_656 = 656,\nKEY_657 = 657,\nKEY_658 = 658,\nKEY_659 = 659,\nKEY_660 = 660,\nKEY_661 = 661,\nKEY_662 = 662,\nKEY_663 = 663,\nKEY_664 = 664,\nKEY_665 = 665,\nKEY_666 = 666,\nKEY_667 = 667,\nKEY_668 = 668,\nKEY_669 = 669,\nKEY_670 = 670,\nKEY_671 = 671,\nKEY_672 = 672,\nKEY_673 = 673,\nKEY_674 = 674,\nKEY_675 = 675,\nKEY_676 = 676,\nKEY_677 = 677,\nKEY_678 = 678,\nKEY_679 = 679,\nKEY_680 = 680,\nKEY_681 = 681,\nKEY_682 = 682,\nKEY_683 = 683,\nKEY_684 = 684,\nKEY_685 = 685,\nKEY_686 = 686,\nKEY_687 = 687,\nKEY_688 = 688,\nKEY_689 = 689,\nKEY_690 = 690,\nKEY_691 = 691,\nKEY_692 = 692,\nKEY_693 = 693,\nKEY_694 = 694,\nKEY_695 = 695,\nKEY_696 = 696,\nKEY_697 = 697,\nKEY_698 = 698,\nKEY_699 = 699,\nKEY_700 = 700,\nKEY_701 = 701,\nKEY_702 = 702,\nKEY_703 = 703,\nBTN_TRIGGER_HAPPY1 = 704,\nBTN_TRIGGER_HAPPY2 = 705,\nBTN_TRIGGER_HAPPY3 = 706,\nBTN_TRIGGER_HAPPY4 = 707,\nBTN_TRIGGER_HAPPY5 = 708,\nBTN_TRIGGER_HAPPY6 = 709,\nBTN_TRIGGER_HAPPY7 = 710,\nBTN_TRIGGER_HAPPY8 = 711,\nBTN_TRIGGER_HAPPY9 = 712,\nBTN_TRIGGER_HAPPY10 = 713,\nBTN_TRIGGER_HAPPY11 = 714,\nBTN_TRIGGER_HAPPY12 = 715,\nBTN_TRIGGER_HAPPY13 = 716,\nBTN_TRIGGER_HAPPY14 = 717,\nBTN_TRIGGER_HAPPY15 = 718,\nBTN_TRIGGER_HAPPY16 = 719,\nBTN_TRIGGER_HAPPY17 = 720,\nBTN_TRIGGER_HAPPY18 = 721,\nBTN_TRIGGER_HAPPY19 = 722,\nBTN_TRIGGER_HAPPY20 = 723,\nBTN_TRIGGER_HAPPY21 = 724,\nBTN_TRIGGER_HAPPY22 = 725,\nBTN_TRIGGER_HAPPY23 = 726,\nBTN_TRIGGER_HAPPY24 = 727,\nBTN_TRIGGER_HAPPY25 = 728,\nBTN_TRIGGER_HAPPY26 = 729,\nBTN_TRIGGER_HAPPY27 = 730,\nBTN_TRIGGER_HAPPY28 = 731,\nBTN_TRIGGER_HAPPY29 = 732,\nBTN_TRIGGER_HAPPY30 = 733,\nBTN_TRIGGER_HAPPY31 = 734,\nBTN_TRIGGER_HAPPY32 = 735,\nBTN_TRIGGER_HAPPY33 = 736,\nBTN_TRIGGER_HAPPY34 = 737,\nBTN_TRIGGER_HAPPY35 = 738,\nBTN_TRIGGER_HAPPY36 = 739,\nBTN_TRIGGER_HAPPY37 = 740,\nBTN_TRIGGER_HAPPY38 = 741,\nBTN_TRIGGER_HAPPY39 = 742,\nBTN_TRIGGER_HAPPY40 = 743,\nBTN_MAX = 744,\nMouseWheelUp = 745,\nMouseWheelDown = 746,\nMouseWheelLeft = 747,\nMouseWheelRight = 748,\nKEY_MAX = 767,\n\n=== all kcs\nNo,\nErrorRollOver,\nPostFail,\nErrorUndefined,\nA,\nB,\nC,\nD,\nE,\nF,\nG,\nH,\nI,\nJ,\nK,\nL,\nM,\nN,\nO,\nP,\nQ,\nR,\nS,\nT,\nU,\nV,\nW,\nX,\nY,\nZ,\nKb1,\nKb2,\nKb3,\nKb4,\nKb5,\nKb6,\nKb7,\nKb8,\nKb9,\nKb0,\nEnter,\nEscape,\nBSpace,\nTab,\nSpace,\nMinus,\nEqual,\nLBracket,\nRBracket,\nBslash,\nNonUsHash,\nSColon,\nQuote,\nGrave,\nComma,\nDot,\nSlash,\nCapsLock,\nF1,\nF2,\nF3,\nF4,\nF5,\nF6,\nF7,\nF8,\nF9,\nF10,\nF11,\nF12,\nPScreen,\nScrollLock,\nPause,\nInsert,\nHome,\nPgUp,\nDelete,\nEnd,\nPgDown,\nRight,\nLeft,\nDown,\nUp,\nNumLock,\nKpSlash,\nKpAsterisk,\nKpMinus,\nKpPlus,\nKpEnter,\nKp1,\nKp2,\nKp3,\nKp4,\nKp5,\nKp6,\nKp7,\nKp8,\nKp9,\nKp0,\nKpDot,\nNonUsBslash,\nApplication,\nPower,\nKpEqual,\nF13,\nF14,\nF15,\nF16,\nF17,\nF18,\nF19,\nF20,\nF21,\nF22,\nF23,\nF24,\nExecute,\nHelp,\nMenu,\nSelect,\nStop,\nAgain,\nUndo,\nCut,\nCopy,\nPaste,\nFind,\nMute,\nVolUp,\nVolDown,\nLockingCapsLock,\nLockingNumLock,\nLockingScrollLock,\nKpComma,\nKpEqualSign,\nIntl1,\nIntl2,\nIntl3,\nIntl4,\nIntl5,\nIntl6,\nIntl7,\nIntl8,\nIntl9,\nLang1,\nLang2,\nLang3,\nLang4,\nLang5,\nLang6,\nLang7,\nLang8,\nLang9,\nAltErase,\nSysReq,\nCancel,\nClear,\nPrior,\nReturn,\nSeparator,\nOut,\nOper,\nClearAgain,\nCrSel,\nExSel,\nWakeup,\nBrightnessUp,\nBrightnessDown,\nKbdIllumUp,\nKbdIllumDown,\nK0xAA,\nK0xAB,\nK0xAC,\nK0xAD,\nK0xAE,\nK0xAF,\nK0xB0,\nK0xB1,\nK0xB2,\nK0xB3,\nK0xB4,\nK0xB5,\nK0xB6,\nK0xB7,\nK0xB8,\nK0xB9,\nK0xBA,\nK0xBB,\nK0xBC,\nK0xBD,\nK0xBE,\nK0xBF,\nK0xC0,\nK0xC1,\nK0xC2,\nK0xC3,\nK0xC4,\nK0xC5,\nK0xC6,\nK0xC7,\nK0xC8,\nK0xC9,\nK0xCA,\nK0xCB,\nK0xCC,\nK0xCD,\nK0xCE,\nK0xCF,\nK0xD0,\nK0xD1,\nK0xD2,\nK0xD3,\nK0xD4,\nK0xD5,\nK0xD6,\nK0xD7,\nK0xD8,\nK0xD9,\nK0xDA,\nK0xDB,\nK0xDC,\nK0xDD,\nK0xDE,\nK0xDF,\nLCtrl,\nLShift,\nLAlt,\nLGui,\nRCtrl,\nRShift,\nRAlt,\nRGui,\nMediaPlayPause,\nMediaStopCD,\nMediaPreviousSong,\nMediaNextSong,\nMediaEjectCD,\nMediaVolUp,\nMediaVolDown,\nMediaMute,\nMediaWWW,\nMediaBack,\nMediaForward,\nMediaStop,\nMediaFind,\nMediaScrollUp,\nMediaScrollDown,\nMediaEdit,\nMediaSleep,\nMediaCoffee,\nMediaRefresh,\nMediaCalc,\nK252,\nK253,\nK254,\nK255,\nK256,\nK257,\nK258,\nK259,\nK260,\nK261,\nK262,\nK263,\nK264,\nK265,\nK266,\nK267,\nK268,\nK269,\nK270,\nK271,\nK272,\nK273,\nK274,\nK275,\nK276,\nK277,\nK278,\nK279,\nK280,\nK281,\nK282,\nK283,\nK284,\nK285,\nK286,\nK287,\nK288,\nK289,\nK290,\nK291,\nK292,\nK293,\nK294,\nK295,\nK296,\nK297,\nK298,\nK299,\nK300,\nK301,\nK302,\nK303,\nK304,\nK305,\nK306,\nK307,\nK308,\nK309,\nK310,\nK311,\nK312,\nK313,\nK314,\nK315,\nK316,\nK317,\nK318,\nK319,\nK320,\nK321,\nK322,\nK323,\nK324,\nK325,\nK326,\nK327,\nK328,\nK329,\nK330,\nK331,\nK332,\nK333,\nK334,\nK335,\nK336,\nK337,\nK338,\nK339,\nK340,\nK341,\nK342,\nK343,\nK344,\nK345,\nK346,\nK347,\nK348,\nK349,\nK350,\nK351,\nK352,\nK353,\nK354,\nK355,\nK356,\nK357,\nK358,\nK359,\nK360,\nK361,\nK362,\nK363,\nK364,\nK365,\nK366,\nK367,\nK368,\nK369,\nK370,\nK371,\nK372,\nK373,\nK374,\nK375,\nK376,\nK377,\nK378,\nK379,\nK380,\nK381,\nK382,\nK383,\nK384,\nK385,\nK386,\nK387,\nK388,\nK389,\nK390,\nK391,\nK392,\nK393,\nK394,\nK395,\nK396,\nK397,\nK398,\nK399,\nK400,\nK401,\nK402,\nK403,\nK404,\nK405,\nK406,\nK407,\nK408,\nK409,\nK410,\nK411,\nK412,\nK413,\nK414,\nK415,\nK416,\nK417,\nK418,\nK419,\nK420,\nK421,\nK422,\nK423,\nK424,\nK425,\nK426,\nK427,\nK428,\nK429,\nK430,\nK431,\nK432,\nK433,\nK434,\nK435,\nK436,\nK437,\nK438,\nK439,\nK440,\nK441,\nK442,\nK443,\nK444,\nK445,\nK446,\nK447,\nK448,\nK449,\nK450,\nK451,\nK452,\nK453,\nK454,\nK455,\nK456,\nK457,\nK458,\nK459,\nK460,\nK461,\nK462,\nK463,\nK464,\nK465,\nK466,\nK467,\nK468,\nK469,\nK470,\nK471,\nK472,\nK473,\nK474,\nK475,\nK476,\nK477,\nK478,\nK479,\nK480,\nK481,\nK482,\nK483,\nK484,\nK485,\nK486,\nK487,\nK488,\nK489,\nK490,\nK491,\nK492,\nK493,\nK494,\nK495,\nK496,\nK497,\nK498,\nK499,\nK500,\nK501,\nK502,\nK503,\nK504,\nK505,\nK506,\nK507,\nK508,\nK509,\nK510,\nK511,\nK512,\nK513,\nK514,\nK515,\nK516,\nK517,\nK518,\nK519,\nK520,\nK521,\nK522,\nK523,\nK524,\nK525,\nK526,\nK527,\nK528,\nK529,\nK530,\nK531,\nK532,\nK533,\nK534,\nK535,\nK536,\nK537,\nK538,\nK539,\nK540,\nK541,\nK542,\nK543,\nK544,\nK545,\nK546,\nK547,\nK548,\nK549,\nK550,\nK551,\nK552,\nK553,\nK554,\nK555,\nK556,\nK557,\nK558,\nK559,\nK560,\nK561,\nK562,\nK563,\nK564,\nK565,\nK566,\nK567,\nK568,\nK569,\nK570,\nK571,\nK572,\nK573,\nK574,\nK575,\nK576,\nK577,\nK578,\nK579,\nK580,\nK581,\nK582,\nK583,\nK584,\nK585,\nK586,\nK587,\nK588,\nK589,\nK590,\nK591,\nK592,\nK593,\nK594,\nK595,\nK596,\nK597,\nK598,\nK599,\nK600,\nK601,\nK602,\nK603,\nK604,\nK605,\nK606,\nK607,\nK608,\nK609,\nK610,\nK611,\nK612,\nK613,\nK614,\nK615,\nK616,\nK617,\nK618,\nK619,\nK620,\nK621,\nK622,\nK623,\nK624,\nK625,\nK626,\nK627,\nK628,\nK629,\nK630,\nK631,\nK632,\nK633,\nK634,\nK635,\nK636,\nK637,\nK638,\nK639,\nK640,\nK641,\nK642,\nK643,\nK644,\nK645,\nK646,\nK647,\nK648,\nK649,\nK650,\nK651,\nK652,\nK653,\nK654,\nK655,\nK656,\nK657,\nK658,\nK659,\nK660,\nK661,\nK662,\nK663,\nK664,\nK665,\nK666,\nK667,\nK668,\nK669,\nK670,\nK671,\nK672,\nK673,\nK674,\nK675,\nK676,\nK677,\nK678,\nK679,\nK680,\nK681,\nK682,\nK683,\nK684,\nK685,\nK686,\nK687,\nK688,\nK689,\nK690,\nK691,\nK692,\nK693,\nK694,\nK695,\nK696,\nK697,\nK698,\nK699,\nK700,\nK701,\nK702,\nK703,\nK704,\nK705,\nK706,\nK707,\nK708,\nK709,\nK710,\nK711,\nK712,\nK713,\nK714,\nK715,\nK716,\nK717,\nK718,\nK719,\nK720,\nK721,\nK722,\nK723,\nK724,\nK725,\nK726,\nK727,\nK728,\nK729,\nK730,\nK731,\nK732,\nK733,\nK734,\nK735,\nK736,\nK737,\nK738,\nK739,\nK740,\nK741,\nK742,\nK743,\nK744,\nMWU,\nMWD,\nMWL,\nMWR,\nKeyMax,\n"
  },
  {
    "path": "key-sort-add/src/main.rs",
    "content": "//! one:\n//!\n//! Takes a file formatted as:\n//!\n//!     KEY_RESERVED = 0,\n//!     KEY_ESC = 1,\n//!     KEY_1 = 2,\n//!     KEY_2 = 3,\n//!     KEY_3 = 4,\n//!     KEY_4 = 5,\n//!     ...\n//!\n//! Outputs to stdout a sorted version of the file with numeric gaps filled in with:\n//!\n//!     KEY_X = X,\n//!\n//! two: mapping.txt to ensure KeyCode and OsCode can simply be transmuted into each other.\n\nuse std::io::Read;\n\nfn main() {\n    match std::env::args().nth(1).expect(\"function parameter\").as_str() {\n        \"one\" => one(),\n        \"two\" => two(),\n        _ => panic!(\"unknown capabality\"),\n    }\n}\n\nfn one() {\n    let mut f = std::fs::File::open(std::env::args().nth(2).expect(\"filename parameter\"))\n        .expect(\"file open\");\n    let mut s = String::new();\n    f.read_to_string(&mut s).expect(\"read file\");\n    let mut keys = s\n        .lines()\n        .map(|l| {\n            let mut segments = l.trim_end_matches(',').trim().split(\" = \");\n            let key = segments.next().expect(\"a string\");\n            let num: u16 = u16::from_str_radix(\n                segments\n                    .next()\n                    .map(|s| s.trim_start_matches(\"0x\"))\n                    .expect(\"string after =\"),\n                10,\n            )\n            .expect(\"u16\");\n            (key.to_owned(), num)\n        })\n        .collect::<Vec<_>>();\n    keys.sort_by_key(|k| k.1);\n    let mut keys_to_add = vec![];\n    let mut cur_key = keys.iter();\n    let mut prev_key = keys.iter();\n    cur_key.next();\n    for cur in cur_key {\n        let prev = prev_key.next().expect(\"lagging iterator is valid\");\n        for missing in prev.1 + 1..cur.1 {\n            keys_to_add.push((format!(\"K{missing}\"), missing));\n        }\n    }\n    keys.append(&mut keys_to_add);\n    keys.sort_by_key(|k| k.1);\n    for key in keys {\n        println!(\"{} = {},\", key.0, key.1);\n    }\n}\n\nfn two() {\n    use std::collections::HashMap;\n\n    let mut f = std::fs::File::open(std::env::args().nth(2).expect(\"filename parameter\"))\n        .expect(\"file open\");\n    let mut s = String::new();\n    f.read_to_string(&mut s).expect(\"read file\");\n    let mut lines = s.lines();\n\n    // filter out useless lines\n    while let Some(line) = lines.next() {\n        if line == \"=== kc to osc\" {\n            break;\n        }\n    }\n\n    // parse kc to osc\n    let mut kc_to_osc: HashMap<&str, &str> = HashMap::new();\n    while let Some(line) = lines.next() {\n        if line.trim().is_empty() {\n            continue;\n        }\n        if line == \"=== osc to u16\" {\n            break;\n        }\n        let (kc, osc) = line.split_once(\" => \").expect(\"arrow separator\");\n        let kc = kc.trim_start_matches(\"KeyCode::\");\n        let osc = osc.trim_end_matches(',')\n                .trim_start_matches(\"OsCode::\");\n        kc_to_osc.insert(kc, osc);\n    }\n\n    // parse osc to u16\n    let mut osc_vals: HashMap<&str, u16> = HashMap::new();\n    while let Some(line) = lines.next() {\n        if line.trim().is_empty() {\n            continue;\n        }\n        if line == \"=== all kcs\" {\n            break;\n        }\n        let (kc, num) = line.split_once(\" = \").expect(\"equal separator\");\n        let num = num.trim_end_matches(',').parse::<u16>().expect(\"u16\");\n        osc_vals.insert(kc, num);\n    }\n\n    // parse kcs\n    let mut kc_vals: Vec<(&str, Option<u16>)> = vec![];\n    while let Some(line) = lines.next() {\n        if line.trim().is_empty() {\n            continue;\n        }\n        let kc = line.trim_end_matches(',');\n        let val: Option<u16> = kc_to_osc.get(&kc)\n            .and_then(|osc| osc_vals.get(osc))\n            .copied();\n        kc_vals.push((kc, val));\n    }\n\n    for (kc, val) in kc_vals.iter() {\n        println!(\"{kc} = {},\", val.unwrap_or(65535));\n    }\n}\n"
  },
  {
    "path": "keyberon/.gitignore",
    "content": "# Ignore Cargo.lock since this is a library crate\nCargo.lock\n"
  },
  {
    "path": "keyberon/CHANGELOG.md",
    "content": "# v0.2.0\n\n* New Keyboard::leds_mut function for getting underlying leds object.\n* Made Layout::current_layer public for getting current active layer.\n* Added a procedural macro for defining layouts (`keyberon::layout::layout`)\n* Corrected HID report descriptor\n* Add max_packet_size() to HidDevice to allow differing report sizes\n* Allows default layer to be set on a Layout externally\n* Add Chording for multiple keys pressed at the same time to equal another key\n\nBreaking changes:\n* Row and Column pins are now a simple array. For the STM32 MCU, you\n  should now use `.downgrade()` to have an homogenous array. \n* `Action::HoldTap` now takes a configuration for different behaviors.\n* `Action::HoldTap` now takes the `tap_hold_interval` field. Not\n  implemented yet.\n* `Action` is now generic, for the `Action::Custom(T)` variant,\n  allowing custom actions to be handled outside of keyberon. This\n  functionality can be used to drive non keyboard actions, such as resetting\n  the microcontroller, driving leds (for backlight or underglow for\n  example), managing a mouse emulation, or any other ideas you can\n  have. As there is a default value for the type parameter, the update\n  should be transparent.\n* Layers don't sum anymore, the last pressed layer action set the layer.\n* Rename MeidaCoffee in MediaCoffee to fix typo.\n\n# v0.1.1\n\n*  HidClass::control_xxx: check interface number [#26](https://github.com/TeXitoi/keyberon/pull/26)\n\n# v0.1.0\n\nFirst published version.\n"
  },
  {
    "path": "keyberon/Cargo.toml",
    "content": "[package]\nname = \"kanata-keyberon\"\nversion = \"0.1110.0\"\nauthors = [\"Guillaume Pinot <texitoi@texitoi.eu>\", \"Robin Krahl <robin.krahl@ireas.org>\", \"jtroo <j.andreitabs@gmail.com>\"]\nedition = \"2021\"\ndescription = \"Pure Rust keyboard firmware. Fork intended for use with kanata.\"\ndocumentation = \"https://docs.rs/keyberon\"\nrepository = \"https://github.com/TeXitoi/keyberon\"\nkeywords = [\"keyboard\", \"kanata\"]\ncategories = [\"no-std\"]\nlicense = \"MIT\"\nreadme = \"README.md\"\n\n[features]\ntap_hold_tracker = []\n\n[dependencies]\nkanata-keyberon-macros = { version = \"0.2.0\" }\nheapless = \"0.7.16\"\nrustc-hash = \"1.1.0\"\narraydeque = { version = \"0.5.1\", default-features = false }\n"
  },
  {
    "path": "keyberon/KEYBOARDS.md",
    "content": "| Keyboard                                                                   | PCB or Handwired | MCU       | Feature Status                                                                                 |\n| -                                                                          | -                | -         | -                                                                                              |\n| [KeySeeBee](https://github.com/TeXitoi/keyseebee)                          | PCB              | STM32F072 | <ul><li>[x] Matrix </li><li>[x] Split</li></ul>                                                |\n| [Keyberon-f4](https://github.com/TeXitoi/keyberon-f4)                      | Handwired        | STM32F401 | <ul><li>[x] Matrix </li></ul>                                                                  |\n| [Arisu](https://github.com/help-14/arisu-handwired)                        | Handwired        | STM32F401 | <ul><li>[x] Matrix </li></ul>                                                                  |\n| [ortho60-keyberon](https://github.com/TeXitoi/ortho60-keyberon)            | PCB              | STM32F103 | <ul><li>[x] Matrix </li></ul>                                                                  |\n| [keyberon-grid](https://github.com/TeXitoi/keyberon-grid)                  | Handwired        | STM32F103 | <ul><li>[x] Matrix </li></ul>                                                                  |\n| [Clueboard 66% LP](https://github.com/wezm/clueboard-rust-firmware)        | PCB              | STM32F303 | <ul><li>[x] Matrix </li><li>[ ] LEDs</li><li>[ ] Speakers</li></ul>                            |\n| [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>                                                                  |\n| [corne-xiao](https://github.com/lehmanju/corne-xiao)                       | PCB              | ATSAMD21  | <ul><li>[x] Matrix </li></ul>                                                                  |\n| [pinci](https://github.com/camrbuss/pinci)                                 | PCB              | RP2040    | <ul><li>[x] Matrix </li><li>[x] Split</li></ul>                                                |\n| [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> |\n| [keyboard-labs](https://github.com/rgoulter/keyboard-labs)                 | PCB              | STM32F401 | <ul><li>[x] Matrix </li><li>[x] Split</li></ul>                                                |\n| [makerdiary M60](https://github.com/jamesmunns/m60-keyboard/)              | PCB              | nRF52840  | <ul><li>[x] Matrix </li><li>[x] RGB LEDs</li> |\n| [PouetPouet](https://github.com/dkm/pouetpouet-board)                          | PCB              | STM32F072 | <ul><li>[x] Matrix </li></ul>                                                |\n| [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>          |\n| [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>         |\n"
  },
  {
    "path": "keyberon/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 Guillaume P.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "keyberon/README.md",
    "content": "# kanata-keyberon\n\n## Note\n\nThis is a fork intended for use by the [kanata keyboard remapper software](https://github.com/jtroo/kanata).\nPlease make contributions to the [original project](https://github.com/TeXitoi/keyberon) where applicable.\n\nThis crate does not follow semver. It tracks the version of kanata.\n"
  },
  {
    "path": "keyberon/keyberon-macros/Cargo.toml",
    "content": "[package]\nname = \"kanata-keyberon-macros\"\nversion = \"0.2.0\"\nauthors = [\"Antoni Simka <antonisimka.8@gmail.com>\"]\nedition = \"2018\"\ndescription = \"Macros for keyberon. Fork for kanata project\"\nlicense = \"MIT\"\n\n[lib]\nproc-macro = true\n\n[dependencies]\nproc-macro2 = \"1.0\"\nquote = \"1.0\"\n"
  },
  {
    "path": "keyberon/keyberon-macros/README.md",
    "content": "# kanata keyberon macros\n\n## Note\n\nThis is a fork intended for use by the [kanata keyboard remapper software](https://github.com/jtroo/keyberon).\nPlease make contributions to the [original project](https://github.com/TeXitoi/keyberon) where applicable.\n"
  },
  {
    "path": "keyberon/keyberon-macros/src/lib.rs",
    "content": "extern crate proc_macro;\nuse proc_macro2::{Delimiter, Group, Literal, Punct, Spacing, TokenStream, TokenTree};\nuse quote::quote;\n\n#[proc_macro]\npub fn layout(input: proc_macro::TokenStream) -> proc_macro::TokenStream {\n    let input: TokenStream = input.into();\n\n    let mut out = TokenStream::new();\n\n    let mut inside = TokenStream::new();\n\n    for t in input {\n        match t {\n            TokenTree::Group(g) if g.delimiter() == Delimiter::Brace => {\n                let layer = parse_layer(g.stream());\n                inside.extend(quote! {\n                    [#layer],\n                });\n            }\n            _ => panic!(\"{}\", \"Invalid token, expected layer: {{ ... }}\"),\n        }\n    }\n\n    let all: TokenStream = quote! { [#inside] };\n    out.extend(all);\n\n    out.into()\n}\n\nfn parse_layer(input: TokenStream) -> TokenStream {\n    let mut out = TokenStream::new();\n    for t in input {\n        match t {\n            TokenTree::Group(g) if g.delimiter() == Delimiter::Bracket => {\n                let row = parse_row(g.stream());\n                out.extend(quote! {\n                    [#row],\n                });\n            }\n            TokenTree::Punct(p) if p.as_char() == ',' => (),\n            _ => panic!(\"Invalid token, expected row: [ ... ]\"),\n        }\n    }\n    out\n}\n\nfn parse_row(input: TokenStream) -> TokenStream {\n    let mut out = TokenStream::new();\n    for t in input {\n        match t {\n            TokenTree::Ident(i) => match i.to_string().as_str() {\n                \"n\" => out.extend(quote! { keyberon::action::Action::NoOp, }),\n                \"t\" => out.extend(quote! { keyberon::action::Action::Trans, }),\n                _ => out.extend(quote! {\n                    keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::#i),\n                }),\n            },\n            TokenTree::Punct(p) => punctuation_to_keycode(&p, &mut out),\n            TokenTree::Literal(l) => literal_to_keycode(&l, &mut out),\n            TokenTree::Group(g) => parse_group(&g, &mut out),\n        }\n    }\n    out\n}\n\nfn parse_group(g: &Group, out: &mut TokenStream) {\n    match g.delimiter() {\n        // Handle empty groups\n        Delimiter::Parenthesis if g.stream().is_empty() => {\n            eprintln!(\"Expected a layer number in layer switch\");\n        }\n        Delimiter::Brace if g.stream().is_empty() => {\n            eprintln!(\"Expected an action - group cannot be empty\");\n        }\n        Delimiter::Bracket if g.stream().is_empty() => {\n            eprintln!(\"Expected keycodes - keycode group cannot be empty\");\n        }\n\n        // Momentary layer switch (Action::Layer)\n        Delimiter::Parenthesis => {\n            let tokens = g.stream();\n            out.extend(quote! { keyberon::action::Action::Layer(#tokens), });\n        }\n        // Pass the expression unchanged (adding a comma after it)\n        Delimiter::Brace => out.extend(g.stream().into_iter().chain(TokenStream::from(\n            TokenTree::Punct(Punct::new(',', Spacing::Alone)),\n        ))),\n        // Multiple keycodes (Action::MultipleKeyCodes)\n        Delimiter::Bracket => parse_keycode_group(g.stream(), out),\n\n        // Is this reachable?\n        Delimiter::None => eprintln!(\"Unexpected group\"),\n    }\n}\n\nfn parse_keycode_group(input: TokenStream, out: &mut TokenStream) {\n    let mut inner = TokenStream::new();\n    for t in input {\n        match t {\n            TokenTree::Ident(i) => inner.extend(quote! {\n                keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::#i),\n            }),\n            TokenTree::Punct(p) => punctuation_to_keycode(&p, &mut inner),\n            TokenTree::Literal(l) => literal_to_keycode(&l, &mut inner),\n            TokenTree::Group(g) => parse_group(&g, &mut inner),\n        }\n    }\n    out.extend(quote! { keyberon::action::Action::MultipleActions(&[#inner].as_slice()), });\n}\n\nfn punctuation_to_keycode(p: &Punct, out: &mut TokenStream) {\n    match p.as_char() {\n        // Normal punctuation\n        '-' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Minus), }),\n        '=' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Equal), }),\n        ';' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::SColon), }),\n        ',' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Comma), }),\n        '.' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Dot), }),\n        '/' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Slash), }),\n\n        // Shifted punctuation\n        '!' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb1].as_slice()), }),\n        '@' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb2].as_slice()), }),\n        '#' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb3].as_slice()), }),\n        '$' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb4].as_slice()), }),\n        '%' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb5].as_slice()), }),\n        '^' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb6].as_slice()), }),\n        '&' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb7].as_slice()), }),\n        '*' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb8].as_slice()), }),\n        '_' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Minus].as_slice()), }),\n        '+' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Equal].as_slice()), }),\n        '|' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Bslash].as_slice()), }),\n        '~' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Grave].as_slice()), }),\n        '<' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Comma].as_slice()), }),\n        '>' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Dot].as_slice()), }),\n        '?' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Slash].as_slice()), }),\n        // Is this reachable?\n        _ => eprintln!(\"Punctuation could not be parsed as a keycode\")\n    }\n}\n\nfn literal_to_keycode(l: &Literal, out: &mut TokenStream) {\n    //let repr = l.to_string();\n    match l.to_string().as_str() {\n        \"1\" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb1), }),\n        \"2\" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb2), }),\n        \"3\" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb3), }),\n        \"4\" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb4), }),\n        \"5\" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb5), }),\n        \"6\" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb6), }),\n        \"7\" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb7), }),\n        \"8\" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb8), }),\n        \"9\" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb9), }),\n        \"0\" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb0), }),\n\n        // Char literals; mostly punctuation which can't be properly tokenized alone\n        r#\"'\\''\"# => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Quote), }),\n        r#\"'\\\\'\"# => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Bslash), }),\n        // Shifted characters\n        \"'['\" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::LBracket), }),\n        \"']'\" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::RBracket), }),\n        \"'`'\" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Grave), }),\n        \"'\\\"'\" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Quote].as_slice()), }),\n        \"'('\" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb9].as_slice()), }),\n        \"')'\" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb0].as_slice()), }),\n        \"'{'\" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::LBracket].as_slice()), }),\n        \"'}'\" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::RBracket].as_slice()), }),\n        \"'_'\" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Minus].as_slice()), }),\n\n        s if s.starts_with('\\'') => eprintln!(\"Literal could not be parsed as a keycode\"),\n\n        s if s.starts_with('\\\"')  => {\n            eprintln!(\"Typing strings on key press is not yet supported\")\n        }\n        _ => eprintln!(\"Literal could not be parsed as a keycode\")\n    }\n}\n"
  },
  {
    "path": "keyberon/src/action/switch.rs",
    "content": "//! Handle processing of the switch action for Keyberon.\n//!\n//! Limitations:\n//! - Maximum opcode length: 4095\n//! - Maximum boolean expression depth: 8\n//! - Maximum key recency: 7, where 0 is the most recent key press\n//!\n//! The intended use is to build up a `Switch` struct and use that in the `Layout`.\n//!\n//! The `Layout` will use `Switch::actions` to iterate over the actions that should be activated\n//! when the corresponding key is pressed.\n\nuse super::*;\nuse crate::layout::{HistoricalEvent, KCoord};\n\nuse crate::key_code::*;\n\nuse BooleanOperator::*;\nuse BreakOrFallthrough::*;\n\npub const MAX_OPCODE_LEN: u16 = 0x0FFF;\npub const OP_MASK: u16 = 0xF000;\npub const MAX_BOOL_EXPR_DEPTH: usize = 8;\npub const MAX_KEY_RECENCY: u8 = 7;\n\npub type Case<'a, T> = (&'a [OpCode], &'a Action<'a, T>, BreakOrFallthrough);\n\n#[derive(Debug, Clone, Copy, PartialEq)]\n/// Behaviour of a switch action. Each case is a 3-tuple of:\n///\n/// - the boolean expression (array of opcodes)\n/// - the action to evaluate if the expression evaluates to true\n/// - whether to break or fallthrough to the next case if the expression evaluates to true\npub struct Switch<'a, T: 'a> {\n    pub cases: &'a [Case<'a, T>],\n}\n\n// NOTE: have exhausted our opcodes for u16!\n//\n// Future rewrite: do traditional u8 opcodes, with variable length for the total opcode depending\n// on the first one encountered? Or could be lazy and use u32 and have 4 bytes for every opcode.\n// This probably isn't that performance-sensitive anyway... it's triggering on every input.\n\nconst OR_VAL: u16 = 0x1000;\nconst AND_VAL: u16 = 0x2000;\nconst NOT_VAL: u16 = 0x3000;\n\nconst INPUT_VAL: u16 = 851;\nconst HISTORICAL_INPUT_VAL: u16 = 852;\nconst LAYER_VAL: u16 = 853;\nconst BASE_LAYER_VAL: u16 = 854;\n\n// Binary values:\n// 0b0100 ...\n// 0b0110 ...\n//\n// How-far-back are in bits 12-10 (3 bits)\n// Time is compressed in bits 9-0 (10 bits)\nconst TICKS_SINCE_VAL_GT: u16 = 0x4000;\nconst TICKS_SINCE_VAL_LT: u16 = 0x6000;\n\n// Highest bit in u16. Lower 3 bits in the highest nibble are \"how far back\". This means that\n// switch can look back up to 8 keys.\nconst HISTORICAL_KEYCODE_VAL: u16 = 0x8000;\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\n/// Boolean operator. Notably missing today is Not.\npub enum BooleanOperator {\n    Or,\n    And,\n    Not,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]\n/// OpCode for a switch case boolean expression.\npub struct OpCode(u16);\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\n/// The more useful interpretion of an OpCode.\nenum OpCodeType {\n    BooleanOp(OperatorAndEndIndex),\n    KeyCode(u16),\n    HistoricalKeyCode(HistoricalKeyCode),\n    Input(KCoord),\n    HistoricalInput(HistoricalInput),\n    TicksSinceLessThan(TicksSinceNthKey),\n    TicksSinceGreaterThan(TicksSinceNthKey),\n    Layer(u16),\n    BaseLayer(u16),\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\n/// The operation type and the opcode index at which evaluating this type ends.\nstruct OperatorAndEndIndex {\n    pub op: BooleanOperator,\n    pub idx: usize,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\n/// An op that checks specifically for a key that is a certain number of key presses back in\n/// history.\nstruct HistoricalKeyCode {\n    key_code: u16,\n    how_far_back: u8,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\n/// An op that checks specifically for a key that is a certain number of key presses back in\n/// history.\nstruct HistoricalInput {\n    input: KCoord,\n    how_far_back: u8,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\nstruct TicksSinceNthKey {\n    nth_key: u8,\n    ticks_since: u16,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq)]\n/// Whether or not a case should break out of the switch if it evaluates to true or fallthrough to\n/// the next case.\npub enum BreakOrFallthrough {\n    Break,\n    Fallthrough,\n}\n\nimpl<'a, T> Switch<'a, T> {\n    /// Iterates over the actions (if any) that are activated in the `Switch` based on its cases,\n    /// the currently active keys, and historically pressed keys.\n    ///\n    /// The `historical_keys` parameter should iterate in the order of most-recent-first.\n    pub fn actions<A1, A2, H1, H2, L>(\n        &self,\n        active_keys: A1,\n        active_positions: A2,\n        historical_keys: H1,\n        historical_positions: H2,\n        layers: L,\n        default_layer: u16,\n    ) -> SwitchActions<'a, T, A1, A2, H1, H2, L>\n    where\n        A1: Iterator<Item = KeyCode> + Clone,\n        A2: Iterator<Item = KCoord> + Clone,\n        H1: Iterator<Item = HistoricalEvent<KeyCode>> + Clone,\n        H2: Iterator<Item = HistoricalEvent<KCoord>> + Clone,\n        L: Iterator<Item = u16> + Clone,\n    {\n        SwitchActions {\n            cases: self.cases,\n            active_keys,\n            active_positions,\n            historical_keys,\n            historical_positions,\n            layers,\n            default_layer,\n            case_index: 0,\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\n/// Iterator returned by `Switch::actions`.\npub struct SwitchActions<'a, T, A1, A2, H1, H2, L>\nwhere\n    A1: Iterator<Item = KeyCode> + Clone,\n    A2: Iterator<Item = KCoord> + Clone,\n    H1: Iterator<Item = HistoricalEvent<KeyCode>> + Clone,\n    H2: Iterator<Item = HistoricalEvent<KCoord>> + Clone,\n    L: Iterator<Item = u16> + Clone,\n{\n    cases: &'a [(&'a [OpCode], &'a Action<'a, T>, BreakOrFallthrough)],\n    active_keys: A1,\n    active_positions: A2,\n    historical_keys: H1,\n    historical_positions: H2,\n    layers: L,\n    default_layer: u16,\n    case_index: usize,\n}\n\nimpl<'a, T, A1, A2, H1, H2, L> Iterator for SwitchActions<'a, T, A1, A2, H1, H2, L>\nwhere\n    A1: Iterator<Item = KeyCode> + Clone,\n    A2: Iterator<Item = KCoord> + Clone,\n    H1: Iterator<Item = HistoricalEvent<KeyCode>> + Clone,\n    H2: Iterator<Item = HistoricalEvent<KCoord>> + Clone,\n    L: Iterator<Item = u16> + Clone,\n{\n    type Item = &'a Action<'a, T>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        while self.case_index < self.cases.len() {\n            let case = &self.cases[self.case_index];\n            if evaluate_boolean(\n                case.0,\n                self.active_keys.clone(),\n                self.active_positions.clone(),\n                self.historical_keys.clone(),\n                self.historical_positions.clone(),\n                self.layers.clone(),\n                self.default_layer,\n            ) {\n                let ret_ac = case.1;\n                match case.2 {\n                    Break => self.case_index = self.cases.len(),\n                    Fallthrough => self.case_index += 1,\n                }\n                return Some(ret_ac);\n            }\n            self.case_index += 1;\n        }\n        None\n    }\n}\n\nimpl BooleanOperator {\n    fn to_u16(self) -> u16 {\n        match self {\n            Or => OR_VAL,\n            And => AND_VAL,\n            Not => NOT_VAL,\n        }\n    }\n}\nfn lossy_compress_ticks(t: u16) -> u16 {\n    match t {\n        0..=255 => t,\n        256..=2303 => (t - 255) / 8 + 255,\n        _ => (t - 2303) / 128 + 511,\n    }\n}\n\nfn lossy_decompress_ticks(t: u16) -> u16 {\n    match t {\n        0..=255 => t,\n        256..=511 => (t - 255) * 8 + 255,\n        _ => (t - 511) * 128 + 2303,\n    }\n}\n\nimpl OpCode {\n    /// Return a new OpCode that checks if the key active or not.\n    pub fn new_key(kc: KeyCode) -> Self {\n        assert!((kc as u16) <= KEY_MAX);\n        Self(kc as u16 & MAX_OPCODE_LEN)\n    }\n\n    /// Return a new OpCode that checks if the n'th most recent key, defined by `key_recency`,\n    /// matches the input keycode.\n    pub fn new_key_history(kc: KeyCode, key_recency: u8) -> Self {\n        assert!((kc as u16) <= MAX_OPCODE_LEN);\n        assert!(key_recency <= MAX_KEY_RECENCY);\n        Self((kc as u16 & MAX_OPCODE_LEN) | HISTORICAL_KEYCODE_VAL | ((key_recency as u16) << 12))\n    }\n\n    /// Returns a new opcode that returns true if the n'th most recent key was pressed greater\n    /// than `ticks_since` ticks ago.\n    ///\n    /// At 256 ticks or above, this has a resolution of 8ms (rounded down). At 2304 ticks or\n    /// above, this has a resolution of 128 ms (rounded down).\n    pub fn new_ticks_since_gt(nth_key: u8, ticks_since: u16) -> Self {\n        assert!(nth_key <= MAX_KEY_RECENCY);\n        Self(TICKS_SINCE_VAL_GT | lossy_compress_ticks(ticks_since) | (u16::from(nth_key) << 10))\n    }\n\n    /// Returns a new opcode that returns true if the n'th most recent key was pressed greater\n    /// than `ticks_since` ticks ago.\n    ///\n    /// At 256 ticks or above, this has a resolution of 8ms (rounded down). At 2304 ticks or\n    /// above, this has a resolution of 128 ms (rounded down).\n    pub fn new_ticks_since_lt(nth_key: u8, ticks_since: u16) -> Self {\n        assert!(nth_key <= MAX_KEY_RECENCY);\n        Self(TICKS_SINCE_VAL_LT | lossy_compress_ticks(ticks_since) | (u16::from(nth_key) << 10))\n    }\n\n    /// Return a new OpCode for a boolean operation that ends (non-inclusive) at the specified\n    /// index.\n    pub fn new_bool(op: BooleanOperator, end_idx: u16) -> Self {\n        assert!(end_idx <= MAX_OPCODE_LEN);\n        Self((end_idx & MAX_OPCODE_LEN) + op.to_u16())\n    }\n\n    /// Return OpCodes specifying an active input check.\n    pub fn new_active_input(input: KCoord) -> (Self, Self) {\n        assert!(input.0 < 4);\n        assert!(input.1 < 0x0400);\n        (\n            Self(INPUT_VAL),\n            Self((u16::from(input.0 & 3) << 14) + input.1),\n        )\n    }\n\n    /// Return OpCodes specifying an active input check.\n    pub fn new_historical_input(input: KCoord, key_recency: u8) -> (Self, Self) {\n        assert!(input.0 < 4);\n        assert!(input.1 < 0x0400);\n        assert!(key_recency < 0x8);\n        (\n            Self(HISTORICAL_INPUT_VAL),\n            Self((u16::from(input.0 & 3) << 14) + (u16::from(key_recency) << 11) + input.1),\n        )\n    }\n\n    /// Return OpCodes specifying an active layer check.\n    pub fn new_layer(layer: u16) -> (Self, Self) {\n        assert!(usize::from(layer) < crate::layout::MAX_LAYERS);\n        (Self(LAYER_VAL), Self(layer))\n    }\n\n    /// Return OpCodes specifying an base layer check.\n    pub fn new_base_layer(base_layer: u16) -> (Self, Self) {\n        assert!(usize::from(base_layer) < crate::layout::MAX_LAYERS);\n        (Self(BASE_LAYER_VAL), Self(base_layer))\n    }\n\n    /// Return the interpretation of this `OpCode`.\n    fn opcode_type(self, next: Option<OpCode>) -> OpCodeType {\n        if self.0 < KEY_MAX {\n            OpCodeType::KeyCode(self.0)\n        } else if self.0 <= MAX_OPCODE_LEN {\n            let op2 = next.expect(\"next should be some for opcode {self:?}\");\n            match self.0 {\n                INPUT_VAL => OpCodeType::Input((((op2.0 >> 14) & 0x3) as u8, op2.0 & 0x3FF)),\n                HISTORICAL_INPUT_VAL => OpCodeType::HistoricalInput(HistoricalInput {\n                    input: (((op2.0 >> 14) & 0x3) as u8, op2.0 & 0x3FF),\n                    how_far_back: (op2.0 >> 11) as u8 & 0x7,\n                }),\n                LAYER_VAL => OpCodeType::Layer(op2.0),\n                BASE_LAYER_VAL => OpCodeType::BaseLayer(op2.0),\n                _ => unreachable!(\"unexpected opcode {self:?}\"),\n            }\n        } else {\n            match self.0 & 0xE000 {\n                TICKS_SINCE_VAL_LT => OpCodeType::TicksSinceLessThan(TicksSinceNthKey {\n                    nth_key: ((self.0 & 0x1C00) >> 10) as u8,\n                    ticks_since: lossy_decompress_ticks(self.0 & 0x03FF),\n                }),\n                TICKS_SINCE_VAL_GT => OpCodeType::TicksSinceGreaterThan(TicksSinceNthKey {\n                    nth_key: ((self.0 & 0x1C00) >> 10) as u8,\n                    ticks_since: lossy_decompress_ticks(self.0 & 0x03FF),\n                }),\n                0x8000..=0xF000 => OpCodeType::HistoricalKeyCode(HistoricalKeyCode {\n                    key_code: self.0 & 0x0FFF,\n                    how_far_back: ((self.0 & 0x7000) >> 12) as u8,\n                }),\n                _ => OpCodeType::BooleanOp(OperatorAndEndIndex::from(self.0)),\n            }\n        }\n    }\n}\n\nimpl From<u16> for OperatorAndEndIndex {\n    fn from(value: u16) -> Self {\n        Self {\n            op: match value & OP_MASK {\n                OR_VAL => Or,\n                AND_VAL => And,\n                NOT_VAL => Not,\n                _ => unreachable!(\"public interface should protect from this\"),\n            },\n            idx: usize::from(value & MAX_OPCODE_LEN),\n        }\n    }\n}\n\n/// Evaluate the return value of an expression evaluated on the given key codes.\nfn evaluate_boolean(\n    bool_expr: &[OpCode],\n    key_codes: impl Iterator<Item = KeyCode> + Clone,\n    inputs: impl Iterator<Item = KCoord> + Clone,\n    historical_keys: impl Iterator<Item = HistoricalEvent<KeyCode>> + Clone,\n    historical_inputs: impl Iterator<Item = HistoricalEvent<KCoord>> + Clone,\n    layers: impl Iterator<Item = u16> + Clone,\n    default_layer: u16,\n) -> bool {\n    let mut ret = true;\n    let mut current_index = 0;\n    let mut current_end_index = bool_expr.len();\n    let mut current_op = Or;\n    let mut stack: arraydeque::ArrayDeque<\n        OperatorAndEndIndex,\n        MAX_BOOL_EXPR_DEPTH,\n        arraydeque::behavior::Saturating,\n    > = Default::default();\n    while current_index < bool_expr.len() {\n        if current_index >= current_end_index {\n            match stack.pop_back() {\n                Some(operator) => {\n                    (current_op, current_end_index) = (operator.op, operator.idx);\n                }\n                None => break,\n            }\n            // Short-circuiting logic\n            if matches!((ret, current_op), (true, Or | Not) | (false, And))\n                || current_index >= current_end_index\n            {\n                if current_op == Not {\n                    ret = false;\n                }\n                current_index = current_end_index;\n                continue;\n            }\n        }\n        match bool_expr[current_index].opcode_type(bool_expr.get(current_index + 1).copied()) {\n            OpCodeType::BooleanOp(operator) => {\n                let res = stack.push_back(OperatorAndEndIndex {\n                    op: current_op,\n                    idx: current_end_index,\n                });\n                assert!(\n                    res.is_ok(),\n                    \"exceeded boolean op depth {MAX_BOOL_EXPR_DEPTH}\"\n                );\n                (current_op, current_end_index) = (operator.op, operator.idx);\n                current_index += 1;\n                continue;\n            }\n            OpCodeType::KeyCode(kc) => {\n                ret = key_codes.clone().any(|kc_input| kc_input as u16 == kc);\n            }\n            OpCodeType::HistoricalKeyCode(hkc) => {\n                ret = historical_keys\n                    .clone()\n                    .nth(hkc.how_far_back as usize)\n                    .map(|he| he.event as u16 == hkc.key_code)\n                    .unwrap_or(false);\n            }\n            OpCodeType::TicksSinceLessThan(tsnk) => {\n                ret = historical_keys\n                    .clone()\n                    .nth(tsnk.nth_key.into())\n                    .map(|he| he.ticks_since_occurrence <= tsnk.ticks_since)\n                    .unwrap_or(false);\n            }\n            OpCodeType::TicksSinceGreaterThan(tsnk) => {\n                ret = historical_keys\n                    .clone()\n                    .nth(tsnk.nth_key.into())\n                    .map(|he| he.ticks_since_occurrence > tsnk.ticks_since)\n                    .unwrap_or(false);\n            }\n            OpCodeType::Input(coord) => {\n                // opcode has size 2\n                current_index += 1;\n                ret = inputs.clone().any(|c| c == coord)\n            }\n            OpCodeType::HistoricalInput(hki) => {\n                // opcode has size 2\n                current_index += 1;\n                ret = historical_inputs\n                    .clone()\n                    .nth(hki.how_far_back as usize)\n                    .map(|he| he.event == hki.input)\n                    .unwrap_or(false)\n            }\n            OpCodeType::Layer(layer) => {\n                // opcode has size 2\n                current_index += 1;\n                ret = layers.clone().next().map(|l| l == layer).unwrap_or(false)\n            }\n            OpCodeType::BaseLayer(base_layer) => {\n                // opcode has size 2\n                current_index += 1;\n                ret = default_layer == base_layer;\n            }\n        };\n        if current_op == Not {\n            ret = !ret;\n        }\n        if matches!((ret, current_op), (true, Or) | (false, And | Not)) {\n            current_index = current_end_index;\n            continue;\n        }\n        current_index += 1;\n    }\n    while let Some(OperatorAndEndIndex { op, .. }) = stack.pop_back() {\n        if op == Not {\n            ret = !ret;\n        }\n    }\n    ret\n}\n\n#[cfg(test)]\nfn evaluate_bool_test(opcodes: &[OpCode], keycodes: impl Iterator<Item = KeyCode> + Clone) -> bool {\n    evaluate_boolean(\n        opcodes,\n        keycodes,\n        [].iter().copied(),\n        [].iter().copied(),\n        [].iter().copied(),\n        [].iter().copied(),\n        0,\n    )\n}\n\n#[test]\nfn bool_evaluation_test_0() {\n    let opcodes = [\n        OpCode::new_bool(And, 9),\n        OpCode::new_key(KeyCode::A),\n        OpCode::new_key(KeyCode::B),\n        OpCode::new_bool(Or, 6),\n        OpCode::new_key(KeyCode::C),\n        OpCode::new_key(KeyCode::D),\n        OpCode::new_bool(Or, 9),\n        OpCode::new_key(KeyCode::E),\n        OpCode::new_key(KeyCode::F),\n    ];\n    let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F];\n    assert!(evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_1() {\n    let opcodes = [\n        OpCode::new_bool(And, 9),\n        OpCode::new_key(KeyCode::A),\n        OpCode::new_key(KeyCode::B),\n        OpCode::new_bool(Or, 6),\n        OpCode::new_key(KeyCode::C),\n        OpCode::new_key(KeyCode::D),\n        OpCode::new_bool(Or, 9),\n        OpCode::new_key(KeyCode::E),\n        OpCode::new_key(KeyCode::F),\n    ];\n    let keycodes = [\n        KeyCode::A,\n        KeyCode::B,\n        KeyCode::C,\n        KeyCode::D,\n        KeyCode::E,\n        KeyCode::F,\n    ];\n    assert!(evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_2() {\n    let opcodes = [\n        OpCode(0x2009),\n        OpCode(KeyCode::A as u16),\n        OpCode(KeyCode::B as u16),\n        OpCode(0x1006),\n        OpCode(KeyCode::C as u16),\n        OpCode(KeyCode::D as u16),\n        OpCode(0x1009),\n        OpCode(KeyCode::E as u16),\n        OpCode(KeyCode::F as u16),\n    ];\n    let keycodes = [KeyCode::A, KeyCode::B, KeyCode::E, KeyCode::F];\n    assert!(!evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_3() {\n    let opcodes = [\n        OpCode(0x2009),\n        OpCode(KeyCode::A as u16),\n        OpCode(KeyCode::B as u16),\n        OpCode(0x1006),\n        OpCode(KeyCode::C as u16),\n        OpCode(KeyCode::D as u16),\n        OpCode(0x1009),\n        OpCode(KeyCode::E as u16),\n        OpCode(KeyCode::F as u16),\n    ];\n    let keycodes = [KeyCode::B, KeyCode::C, KeyCode::D, KeyCode::E, KeyCode::F];\n    assert!(!evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_4() {\n    let opcodes = [];\n    let keycodes = [];\n    assert!(evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_5() {\n    let opcodes = [];\n    let keycodes = [\n        KeyCode::A,\n        KeyCode::B,\n        KeyCode::C,\n        KeyCode::D,\n        KeyCode::E,\n        KeyCode::F,\n    ];\n    assert!(evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_6() {\n    let opcodes = [OpCode(KeyCode::A as u16), OpCode(KeyCode::B as u16)];\n    let keycodes = [\n        KeyCode::A,\n        KeyCode::B,\n        KeyCode::C,\n        KeyCode::D,\n        KeyCode::E,\n        KeyCode::F,\n    ];\n    assert!(evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_7() {\n    let opcodes = [OpCode(KeyCode::A as u16), OpCode(KeyCode::B as u16)];\n    let keycodes = [KeyCode::C, KeyCode::D, KeyCode::E, KeyCode::F];\n    assert!(!evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_9() {\n    let opcodes = [\n        OpCode(0x2003),\n        OpCode(KeyCode::A as u16),\n        OpCode(KeyCode::B as u16),\n        OpCode(KeyCode::C as u16),\n    ];\n    let keycodes = [KeyCode::C, KeyCode::D, KeyCode::E, KeyCode::F];\n    assert!(evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_10() {\n    let opcodes = [\n        OpCode(0x2004),\n        OpCode(KeyCode::A as u16),\n        OpCode(KeyCode::B as u16),\n        OpCode(KeyCode::C as u16),\n    ];\n    let keycodes = [KeyCode::C, KeyCode::D, KeyCode::E, KeyCode::F];\n    assert!(!evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_11() {\n    let opcodes = [\n        OpCode(0x1003),\n        OpCode(KeyCode::A as u16),\n        OpCode(KeyCode::B as u16),\n    ];\n    let keycodes = [KeyCode::C, KeyCode::D, KeyCode::E, KeyCode::F];\n    assert!(!evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_12() {\n    let opcodes = [\n        OpCode(0x1005),\n        OpCode(0x2004),\n        OpCode(KeyCode::A as u16),\n        OpCode(KeyCode::B as u16),\n        OpCode(KeyCode::C as u16),\n    ];\n    let keycodes = [KeyCode::C, KeyCode::D, KeyCode::E, KeyCode::F];\n    assert!(evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_max_depth_does_not_panic() {\n    let opcodes = [\n        OpCode(0x1008),\n        OpCode(0x1008),\n        OpCode(0x1008),\n        OpCode(0x1008),\n        OpCode(0x1008),\n        OpCode(0x1008),\n        OpCode(0x1008),\n        OpCode(0x1008),\n    ];\n    let keycodes = [];\n    assert!(evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\n#[should_panic]\nfn bool_evaluation_test_more_than_max_depth_panics() {\n    let opcodes = [\n        OpCode(0x1009),\n        OpCode(0x1009),\n        OpCode(0x1009),\n        OpCode(0x1009),\n        OpCode(0x1009),\n        OpCode(0x1009),\n        OpCode(0x1009),\n        OpCode(0x1009),\n        OpCode(0x1009),\n    ];\n    let keycodes = [];\n    assert!(evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn switch_fallthrough() {\n    let sw = Switch {\n        cases: &[\n            (&[], &Action::<()>::KeyCode(KeyCode::A), Fallthrough),\n            (&[], &Action::<()>::KeyCode(KeyCode::B), Fallthrough),\n        ],\n    };\n    let mut actions = sw.actions(\n        [].iter().copied(),\n        [].iter().copied(),\n        [].iter().copied(),\n        [].iter().copied(),\n        [].iter().copied(),\n        0,\n    );\n    assert_eq!(actions.next(), Some(&Action::<()>::KeyCode(KeyCode::A)));\n    assert_eq!(actions.next(), Some(&Action::<()>::KeyCode(KeyCode::B)));\n    assert_eq!(actions.next(), None);\n}\n\n#[test]\nfn switch_break() {\n    let sw = Switch {\n        cases: &[\n            (&[], &Action::<()>::KeyCode(KeyCode::A), Break),\n            (&[], &Action::<()>::KeyCode(KeyCode::B), Break),\n        ],\n    };\n    let mut actions = sw.actions(\n        [].iter().copied(),\n        [].iter().copied(),\n        [].iter().copied(),\n        [].iter().copied(),\n        [].iter().copied(),\n        0,\n    );\n    assert_eq!(actions.next(), Some(&Action::<()>::KeyCode(KeyCode::A)));\n    assert_eq!(actions.next(), None);\n}\n\n#[test]\nfn switch_no_actions() {\n    let sw = Switch {\n        cases: &[\n            (\n                &[OpCode::new_key(KeyCode::A)],\n                &Action::<()>::KeyCode(KeyCode::A),\n                Break,\n            ),\n            (\n                &[OpCode::new_key(KeyCode::A)],\n                &Action::<()>::KeyCode(KeyCode::B),\n                Break,\n            ),\n        ],\n    };\n    let mut actions = sw.actions(\n        [].iter().copied(),\n        [].iter().copied(),\n        [].iter().copied(),\n        [].iter().copied(),\n        [].iter().copied(),\n        0,\n    );\n    assert_eq!(actions.next(), None);\n}\n\n#[test]\nfn switch_historical_1() {\n    let opcode_true = [OpCode(0x8000 | KeyCode::A as u16)];\n    let opcode_true2 = [OpCode(0xF000 | KeyCode::H as u16)];\n    let opcode_false = [OpCode(0x9000 | KeyCode::A as u16)];\n    let opcode_false2 = [OpCode(0xE000 | KeyCode::H as u16)];\n    assert_eq!(\n        OpCode::new_key_history(KeyCode::A, 0),\n        OpCode(0x8000 | KeyCode::A as u16)\n    );\n    assert_eq!(\n        OpCode::new_key_history(KeyCode::H, 7),\n        OpCode(0xF000 | KeyCode::H as u16)\n    );\n    let hist_keycodes = [\n        HistoricalEvent {\n            event: KeyCode::A,\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: KeyCode::B,\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: KeyCode::C,\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: KeyCode::D,\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: KeyCode::E,\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: KeyCode::F,\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: KeyCode::G,\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: KeyCode::H,\n            ticks_since_occurrence: 0,\n        },\n    ];\n    assert!(evaluate_boolean(\n        opcode_true.as_slice(),\n        [].iter().copied(),\n        [].iter().copied(),\n        hist_keycodes.iter().copied(),\n        [].iter().copied(),\n        [].iter().copied(),\n        0,\n    ));\n    assert!(evaluate_boolean(\n        opcode_true2.as_slice(),\n        [].iter().copied(),\n        [].iter().copied(),\n        hist_keycodes.iter().copied(),\n        [].iter().copied(),\n        [].iter().copied(),\n        0,\n    ));\n    assert!(!evaluate_boolean(\n        opcode_false.as_slice(),\n        [].iter().copied(),\n        [].iter().copied(),\n        hist_keycodes.iter().copied(),\n        [].iter().copied(),\n        [].iter().copied(),\n        0,\n    ));\n    assert!(!evaluate_boolean(\n        opcode_false2.as_slice(),\n        [].iter().copied(),\n        [].iter().copied(),\n        hist_keycodes.iter().copied(),\n        [].iter().copied(),\n        [].iter().copied(),\n        0,\n    ));\n}\n\n#[test]\nfn switch_historical_bools() {\n    let opcodes_true_and = [\n        OpCode::new_bool(And, 3),\n        OpCode::new_key_history(KeyCode::A, 0),\n        OpCode::new_key_history(KeyCode::B, 1),\n    ];\n    let opcodes_false_and1 = [\n        OpCode::new_bool(And, 3),\n        OpCode::new_key_history(KeyCode::A, 0),\n        OpCode::new_key_history(KeyCode::B, 2),\n    ];\n    let opcodes_false_and2 = [\n        OpCode::new_bool(And, 3),\n        OpCode::new_key_history(KeyCode::B, 2),\n        OpCode::new_key_history(KeyCode::A, 0),\n    ];\n    let opcodes_true_or1 = [\n        OpCode::new_bool(Or, 3),\n        OpCode::new_key_history(KeyCode::A, 0),\n        OpCode::new_key_history(KeyCode::B, 1),\n    ];\n    let opcodes_true_or2 = [\n        OpCode::new_bool(Or, 3),\n        OpCode::new_key_history(KeyCode::A, 0),\n        OpCode::new_key_history(KeyCode::B, 2),\n    ];\n    let opcodes_true_or3 = [\n        OpCode::new_bool(Or, 3),\n        OpCode::new_key_history(KeyCode::B, 2),\n        OpCode::new_key_history(KeyCode::A, 0),\n    ];\n    let opcodes_false_or = [\n        OpCode::new_bool(Or, 3),\n        OpCode::new_key_history(KeyCode::A, 1),\n        OpCode::new_key_history(KeyCode::B, 2),\n    ];\n    let hist_keycodes = [\n        HistoricalEvent {\n            event: KeyCode::A,\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: KeyCode::B,\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: KeyCode::C,\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: KeyCode::D,\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: KeyCode::E,\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: KeyCode::F,\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: KeyCode::G,\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: KeyCode::H,\n            ticks_since_occurrence: 0,\n        },\n    ];\n\n    let test = |opcodes: &[OpCode], expectation: bool| {\n        assert_eq!(\n            evaluate_boolean(\n                opcodes,\n                [].iter().copied(),\n                [].iter().copied(),\n                hist_keycodes.iter().copied(),\n                [].iter().copied(),\n                [].iter().copied(),\n                0,\n            ),\n            expectation\n        );\n    };\n    test(&opcodes_true_and, true);\n    test(&opcodes_true_or1, true);\n    test(&opcodes_true_or2, true);\n    test(&opcodes_true_or3, true);\n    test(&opcodes_false_and1, false);\n    test(&opcodes_false_and2, false);\n    test(&opcodes_false_or, false);\n}\n\n#[test]\nfn switch_historical_ticks_since() {\n    let opcodes_true_and = [\n        OpCode::new_bool(And, 3),\n        OpCode::new_ticks_since_gt(0, 99),\n        OpCode::new_ticks_since_lt(0, 101),\n    ];\n    let opcodes_false_and1 = [\n        OpCode::new_bool(And, 3),\n        OpCode::new_ticks_since_gt(1, 200),\n        OpCode::new_ticks_since_lt(1, 240),\n    ];\n    let opcodes_false_and2 = [\n        OpCode::new_bool(And, 3),\n        OpCode::new_ticks_since_gt(2, 300),\n        OpCode::new_ticks_since_lt(2, 300),\n    ];\n    let opcodes_true_or1 = [\n        OpCode::new_bool(Or, 3),\n        OpCode::new_ticks_since_gt(3, 500),\n        OpCode::new_ticks_since_lt(3, 510),\n    ];\n    let opcodes_true_or2 = [\n        OpCode::new_bool(Or, 3),\n        OpCode::new_ticks_since_gt(4, 500),\n        OpCode::new_ticks_since_lt(4, 511),\n    ];\n    let opcodes_true_or3 = [\n        OpCode::new_bool(Or, 3),\n        OpCode::new_ticks_since_gt(5, 980),\n        OpCode::new_ticks_since_lt(5, 999),\n    ];\n    let opcodes_false_or1 = [\n        OpCode::new_bool(Or, 3),\n        OpCode::new_ticks_since_gt(6, 40200),\n        OpCode::new_ticks_since_lt(6, 39999),\n    ];\n    let opcodes_false_or2 = [\n        OpCode::new_bool(Or, 3),\n        OpCode::new_ticks_since_gt(5, 1030),\n        OpCode::new_ticks_since_lt(5, 999),\n    ];\n    let opcodes_false_or3 = [\n        OpCode::new_bool(Or, 3),\n        OpCode::new_ticks_since_gt(4, 520),\n        OpCode::new_ticks_since_lt(4, 511),\n    ];\n    let opcodes_false_or4 = [\n        OpCode::new_bool(Or, 3),\n        OpCode::new_ticks_since_gt(3, 520),\n        OpCode::new_ticks_since_lt(3, 510),\n    ];\n    let opcodes_false_or5 = [\n        OpCode::new_bool(Or, 3),\n        OpCode::new_ticks_since_gt(2, 265),\n        OpCode::new_ticks_since_lt(2, 255),\n    ];\n    let opcodes_false_or6 = [\n        OpCode::new_bool(Or, 3),\n        OpCode::new_ticks_since_gt(1, 256),\n        OpCode::new_ticks_since_lt(1, 254),\n    ];\n    let hist_keycodes = [\n        HistoricalEvent {\n            event: KeyCode::A,\n            ticks_since_occurrence: 100,\n        },\n        HistoricalEvent {\n            event: KeyCode::B,\n            ticks_since_occurrence: 255,\n        },\n        HistoricalEvent {\n            event: KeyCode::C,\n            ticks_since_occurrence: 256,\n        },\n        HistoricalEvent {\n            event: KeyCode::D,\n            ticks_since_occurrence: 511,\n        },\n        HistoricalEvent {\n            event: KeyCode::E,\n            ticks_since_occurrence: 512,\n        },\n        HistoricalEvent {\n            event: KeyCode::F,\n            ticks_since_occurrence: 1000,\n        },\n        HistoricalEvent {\n            event: KeyCode::G,\n            ticks_since_occurrence: 40000,\n        },\n    ];\n\n    let test = |opcodes: &[OpCode], expectation: bool| {\n        assert_eq!(\n            evaluate_boolean(\n                opcodes,\n                [].iter().copied(),\n                [].iter().copied(),\n                hist_keycodes.iter().copied(),\n                [].iter().copied(),\n                [].iter().copied(),\n                0,\n            ),\n            expectation\n        );\n    };\n    test(&opcodes_true_and, true);\n    test(&opcodes_true_or1, true);\n    test(&opcodes_true_or2, true);\n    test(&opcodes_true_or3, true);\n    test(&opcodes_false_and1, false);\n    test(&opcodes_false_and2, false);\n    test(&opcodes_false_or1, false);\n    test(&opcodes_false_or2, false);\n    test(&opcodes_false_or3, false);\n    test(&opcodes_false_or4, false);\n    test(&opcodes_false_or5, false);\n    test(&opcodes_false_or6, false);\n}\n\n#[test]\nfn bool_evaluation_test_not_0() {\n    // Full inverse of a previous test\n    let opcodes = [\n        OpCode::new_bool(Not, 10),\n        OpCode::new_bool(And, 10),\n        OpCode::new_key(KeyCode::A),\n        OpCode::new_key(KeyCode::B),\n        OpCode::new_bool(Or, 7),\n        OpCode::new_key(KeyCode::C),\n        OpCode::new_key(KeyCode::D),\n        OpCode::new_bool(Or, 10),\n        OpCode::new_key(KeyCode::E),\n        OpCode::new_key(KeyCode::F),\n    ];\n    let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F];\n    assert!(!evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_not_1() {\n    // Both A and B exist, should be false\n    let opcodes = [\n        OpCode::new_bool(Not, 3),\n        OpCode::new_key(KeyCode::A),\n        OpCode::new_key(KeyCode::B),\n    ];\n    let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F];\n    assert!(!evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_not_2() {\n    // Neither X nor Y exist, should be false\n    let opcodes = [\n        OpCode::new_bool(Not, 3),\n        OpCode::new_key(KeyCode::X),\n        OpCode::new_key(KeyCode::Y),\n    ];\n    let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F];\n    assert!(evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_not_3() {\n    let opcodes = [\n        OpCode::new_key(KeyCode::C),\n        OpCode::new_bool(Not, 3),\n        OpCode::new_key(KeyCode::D),\n    ];\n    let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F];\n    assert!(!evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_not_4() {\n    let opcodes = [\n        OpCode::new_bool(And, 10),\n        OpCode::new_key(KeyCode::A),\n        OpCode::new_key(KeyCode::B),\n        OpCode::new_bool(Or, 7),\n        OpCode::new_key(KeyCode::C),\n        OpCode::new_bool(Not, 7),\n        OpCode::new_key(KeyCode::D),\n        OpCode::new_bool(Or, 10),\n        OpCode::new_key(KeyCode::E),\n        OpCode::new_key(KeyCode::F),\n    ];\n    let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F];\n    assert!(!evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_not_5() {\n    let opcodes = [\n        OpCode::new_bool(Not, 4),\n        OpCode::new_key(KeyCode::C),\n        OpCode::new_bool(Not, 4),\n        OpCode::new_key(KeyCode::D),\n    ];\n    let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F];\n    assert!(evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_not_6() {\n    // C does not exist, D does. Ensure C nonexistence does not short-circuit\n    // and existence of D is checked.\n    let opcodes = [\n        OpCode::new_bool(Not, 3),\n        OpCode::new_key(KeyCode::C),\n        OpCode::new_key(KeyCode::D),\n    ];\n    let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F];\n    assert!(!evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_or_equivalency_not_6() {\n    let opcodes = [\n        OpCode::new_bool(Not, 4),\n        OpCode::new_bool(Or, 4),\n        OpCode::new_key(KeyCode::C),\n        OpCode::new_key(KeyCode::D),\n    ];\n    let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F];\n    assert!(!evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_not_7() {\n    // A exists, make sure this short-circuits, and E nonexistence does not override the return.\n    let opcodes = [\n        OpCode::new_bool(Not, 3),\n        OpCode::new_key(KeyCode::A),\n        OpCode::new_key(KeyCode::E),\n    ];\n    let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F];\n    assert!(!evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_or_equivalency_not_7() {\n    let opcodes = [\n        OpCode::new_bool(Not, 4),\n        OpCode::new_bool(Or, 4),\n        OpCode::new_key(KeyCode::A),\n        OpCode::new_key(KeyCode::E),\n    ];\n    let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F];\n    assert!(!evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_not_8() {\n    let opcodes = [\n        OpCode::new_bool(Not, 4),\n        OpCode::new_bool(Not, 4),\n        OpCode::new_bool(Not, 4),\n        OpCode::new_key(KeyCode::A),\n    ];\n    let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F];\n    assert!(!evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn bool_evaluation_test_not_9() {\n    let opcodes = [\n        OpCode::new_bool(Not, 4),\n        OpCode::new_bool(Not, 4),\n        OpCode::new_bool(Not, 4),\n        OpCode::new_key(KeyCode::C),\n    ];\n    let keycodes = [KeyCode::A, KeyCode::B, KeyCode::D, KeyCode::F];\n    assert!(evaluate_bool_test(\n        opcodes.as_slice(),\n        keycodes.iter().copied(),\n    ));\n}\n\n#[test]\nfn switch_inputs() {\n    let (op1, op2) = OpCode::new_active_input((0, 1));\n    let (op3, op4) = OpCode::new_active_input((1, 2));\n    let (op5, op6) = OpCode::new_active_input((1, 3));\n    let (op7, op8) = OpCode::new_active_input((3, 3));\n    let opcodes_true_and = [OpCode::new_bool(And, 5), op1, op2, op3, op4];\n    let opcodes_false_and1 = [OpCode::new_bool(And, 5), op1, op2, op5, op6];\n    let opcodes_false_and2 = [OpCode::new_bool(And, 5), op5, op6, op1, op2];\n    let opcodes_false_or = [OpCode::new_bool(Or, 5), op7, op8, op5, op6];\n    let opcodes_true_or1 = [OpCode::new_bool(Or, 5), op1, op2, op5, op6];\n    let opcodes_true_or2 = [OpCode::new_bool(Or, 5), op7, op8, op3, op4];\n    let active_inputs = [(0, 1), (1, 2), (2, 3), (3, 4)];\n    let test = |opcodes: &[OpCode], expectation: bool| {\n        assert_eq!(\n            evaluate_boolean(\n                opcodes,\n                [].iter().copied(),\n                active_inputs.iter().copied(),\n                [].iter().copied(),\n                [].iter().copied(),\n                [].iter().copied(),\n                0,\n            ),\n            expectation\n        );\n    };\n    test(&opcodes_true_and, true);\n    test(&opcodes_false_and1, false);\n    test(&opcodes_false_and2, false);\n    test(&opcodes_false_or, false);\n    test(&opcodes_true_or1, true);\n    test(&opcodes_true_or2, true);\n}\n\n#[test]\nfn switch_historical_inputs() {\n    let (op1, op2) = OpCode::new_historical_input((0, 0), 0);\n    let (op3, op4) = OpCode::new_historical_input((3, 750), 7);\n    let (op5, op6) = OpCode::new_historical_input((1, 3), 0);\n    let (op7, op8) = OpCode::new_historical_input((3, 3), 7);\n    let opcodes_true_and = [OpCode::new_bool(And, 5), op1, op2, op3, op4];\n    let opcodes_false_and1 = [OpCode::new_bool(And, 5), op1, op2, op5, op6];\n    let opcodes_false_and2 = [OpCode::new_bool(And, 5), op5, op6, op1, op2];\n    let opcodes_false_or = [OpCode::new_bool(Or, 5), op7, op8, op5, op6];\n    let opcodes_true_or1 = [OpCode::new_bool(Or, 5), op1, op2, op5, op6];\n    let opcodes_true_or2 = [OpCode::new_bool(Or, 5), op7, op8, op3, op4];\n    let historical_inputs = [\n        HistoricalEvent {\n            event: (0, 0),\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: (1, 750),\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: (2, 1),\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: (3, 749),\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: (0, 1),\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: (1, 2),\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: (2, 3),\n            ticks_since_occurrence: 0,\n        },\n        HistoricalEvent {\n            event: (3, 750),\n            ticks_since_occurrence: 0,\n        },\n    ];\n    let test = |opcodes: &[OpCode], expectation: bool| {\n        assert_eq!(\n            evaluate_boolean(\n                opcodes,\n                [].iter().copied(),\n                [].iter().copied(),\n                [].iter().copied(),\n                historical_inputs.iter().copied(),\n                [].iter().copied(),\n                0,\n            ),\n            expectation\n        );\n    };\n    test(&opcodes_true_and, true);\n    test(&opcodes_false_and1, false);\n    test(&opcodes_false_and2, false);\n    test(&opcodes_false_or, false);\n    test(&opcodes_true_or1, true);\n    test(&opcodes_true_or2, true);\n}\n"
  },
  {
    "path": "keyberon/src/action.rs",
    "content": "//! The different actions that can be executed via any given key.\n\nuse crate::key_code::KeyCode;\nuse crate::layout::{KCoord, QueuedIter, WaitingAction};\nuse core::fmt::Debug;\n\npub mod switch;\npub use switch::*;\n\n/// The different types of actions we support for key sequences/macros\n#[non_exhaustive]\n#[derive(Clone, Copy, Eq, PartialEq)]\npub enum SequenceEvent<'a, T: 'a> {\n    /// No operation action: just do nothing (a placeholder).\n    NoOp,\n    /// A keypress/keydown\n    Press(KeyCode),\n    /// Key release/keyup\n    Release(KeyCode),\n    /// A shortcut for `Press(KeyCode), Release(KeyCode)`\n    Tap(KeyCode),\n    /// For sequences that need to wait a bit before continuing\n    Delay {\n        /// How long (in ticks) this Delay will last\n        duration: u32, // NOTE: This isn't a u16 because that's only max ~65 seconds (assuming 1000 ticks/sec)\n    },\n    /// Custom event in sequence.\n    Custom(&'a T),\n    /// Cancels the running sequence and can be used to mark the end of a sequence\n    /// instead of using a number of Release() events\n    Complete,\n}\n\nimpl<T> Debug for SequenceEvent<'_, T> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::NoOp => write!(f, \"NoOp\"),\n            Self::Press(arg0) => f.debug_tuple(\"Press\").field(arg0).finish(),\n            Self::Release(arg0) => f.debug_tuple(\"Release\").field(arg0).finish(),\n            Self::Tap(arg0) => f.debug_tuple(\"Tap\").field(arg0).finish(),\n            Self::Delay { duration } => {\n                f.debug_struct(\"Delay\").field(\"duration\", duration).finish()\n            }\n            Self::Custom(_) => write!(f, \"Custom\"),\n            Self::Complete => write!(f, \"Complete\"),\n        }\n    }\n}\n\n/// Behavior configuration of HoldTap.\n#[non_exhaustive]\n#[derive(Clone, Copy)]\npub enum HoldTapConfig<'a> {\n    /// Only the timeout will determine between hold and tap action.\n    ///\n    /// This is a sane default.\n    Default,\n    /// If there is a key press, the hold action is activated.\n    ///\n    /// This behavior is interesting for a key which the tap action is\n    /// not used in the flow of typing, like escape for example. If\n    /// you are annoyed by accidental tap, you can try this behavior.\n    HoldOnOtherKeyPress,\n    /// Resolves based on release order after both keys are down.\n    /// If the other key releases first (modifier still held) → Hold.\n    /// If the modifier releases first (other key still held) → Tap.\n    /// The buffer field specifies a grace period in ticks (ms) after the\n    /// initial press during which release-order logic is ignored and fast\n    /// typing will resolve as Tap.\n    Order { buffer: u16 },\n    /// If there is a press and release of another key, the hold\n    /// action is activated.\n    ///\n    /// This behavior is interesting for fast typist: the different\n    /// between hold and tap would more be based on the sequence of\n    /// events than on timing. Be aware that doing the good succession\n    /// of key might require some training.\n    PermissiveHold,\n    /// A custom configuration. Allows the behavior to be controlled by a caller\n    /// supplied handler function.\n    ///\n    /// The first argument to the custom handler will be an iterator that returns\n    /// [Stacked] [Events](Event). The order of the events matches the order the\n    /// corresponding key was pressed/released, i.e. the first event is the\n    /// event first received after the HoldTap action key is pressed.\n    ///\n    /// The second argument is the coordinate `(row, col)` of the key that\n    /// initiated the HoldTap action, allowing the handler to identify which\n    /// physical key is waiting for resolution.\n    ///\n    /// The return value should be the intended action that should be used. A\n    /// [Some] value will cause one of: [WaitingAction::Tap] for the configured\n    /// tap action, [WaitingAction::Hold] for the hold action, and\n    /// [WaitingAction::NoOp] to drop handling of the key press. A [None]\n    /// value will cause a fallback to the timeout-based approach. If the\n    /// timeout is not triggered, the next tick will call the custom handler\n    /// again.\n    /// The bool value defines if the timeout check should be skipped at the\n    /// next tick. This should generally be false. This is used by `tap-hold-\n    /// except-keys` to handle presses even when the timeout has been reached.\n    #[allow(clippy::type_complexity)]\n    Custom(&'a (dyn Fn(QueuedIter, KCoord) -> (Option<WaitingAction>, bool) + Send + Sync)),\n}\n\nimpl Debug for HoldTapConfig<'_> {\n    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n        match self {\n            HoldTapConfig::Default => f.write_str(\"Default\"),\n            HoldTapConfig::HoldOnOtherKeyPress => f.write_str(\"HoldOnOtherKeyPress\"),\n            HoldTapConfig::Order { .. } => f.write_str(\"Order\"),\n            HoldTapConfig::PermissiveHold => f.write_str(\"PermissiveHold\"),\n            HoldTapConfig::Custom(_) => f.write_str(\"Custom\"),\n        }\n    }\n}\n\nimpl PartialEq for HoldTapConfig<'_> {\n    fn eq(&self, other: &Self) -> bool {\n        #[allow(clippy::match_like_matches_macro)]\n        match (self, other) {\n            (HoldTapConfig::Default, HoldTapConfig::Default)\n            | (HoldTapConfig::HoldOnOtherKeyPress, HoldTapConfig::HoldOnOtherKeyPress)\n            | (HoldTapConfig::PermissiveHold, HoldTapConfig::PermissiveHold) => true,\n            (HoldTapConfig::Order { .. }, HoldTapConfig::Order { .. }) => true,\n            _ => false,\n        }\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n/// A state that that can be released from the active states via the ReleaseState action.\npub enum ReleasableState {\n    /// Release an active keycode\n    KeyCode(KeyCode),\n    /// Release an active layer\n    Layer(usize),\n}\n\n/// Perform different actions on key hold/tap.\n///\n/// If the key is held more than `timeout` ticks (usually\n/// milliseconds), performs the `hold` action, else performs the\n/// `tap` action.  Mostly used with a modifier for the hold action\n/// and a normal key on the tap action. Any action can be\n/// performed, but using a `HoldTap` in a `HoldTap` is not\n/// specified (but guaranteed to not crash).\n///\n/// Different behaviors can be configured using the config field,\n/// but whatever the configuration is, if the key is pressed more\n/// than `timeout`, the hold action is activated (if no other\n/// action was determined before).\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct HoldTapAction<'a, T>\nwhere\n    T: 'a,\n{\n    /// The duration, in ticks (usually milliseconds) giving the\n    /// difference between a hold and a tap.\n    pub timeout: u16,\n    /// The hold action.\n    pub hold: Action<'a, T>,\n    /// The tap action.\n    pub tap: Action<'a, T>,\n    /// The timeout action\n    pub timeout_action: Action<'a, T>,\n    /// Behavior configuration.\n    pub config: HoldTapConfig<'a>,\n    /// Configuration of the tap and hold holds the tap action.\n    ///\n    /// If you press and release the key in such a way that the tap\n    /// action is performed, and then press it again in less than\n    /// `tap_hold_interval` ticks, the tap action will\n    /// be held. This allows the tap action to be held by\n    /// pressing, releasing and holding the key, allowing the computer\n    /// to auto repeat the tap behavior. The timeout starts on the\n    /// first press of the key, NOT on the release.\n    ///\n    /// Pressing a different key in between will not result in the\n    /// behaviour described above; the HoldTap key must be pressed twice\n    /// in a row.\n    ///\n    /// To deactivate the functionality, set this to 0.\n    pub tap_hold_interval: u16,\n    /// Specifically the `tap-hold-release-timeout` action variant\n    /// can benefit from resetting the timeout after a new press,\n    /// because a human might have a slow release but they did\n    /// indeed want a hold to activate.\n    pub on_press_reset_timeout_to: Option<std::num::NonZeroU16>,\n    /// Per-action override for the global `tap_hold_require_prior_idle` setting.\n    /// If `Some(n)`, uses `n` instead of the global value (0 = disabled for this action).\n    /// If `None`, falls back to the global `defcfg` value.\n    pub require_prior_idle: Option<u16>,\n}\n\n/// Define one shot key behaviour.\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct OneShot<'a, T = core::convert::Infallible>\nwhere\n    T: 'a,\n{\n    /// Action to activate until timeout expires or exactly one non-one-shot key is activated.\n    pub action: &'a Action<'a, T>,\n    /// Timeout after which one shot will expire. Note: timeout will be overwritten if another\n    /// one shot key is pressed.\n    pub timeout: u16,\n    /// Configuration of one shot end behaviour. Note: this will be overwritten if another one shot\n    /// key is pressed. Consider keeping this consistent between all your one shot keys to prevent\n    /// surprising behaviour.\n    pub end_config: OneShotEndConfig,\n}\n\n/// Determine the ending behaviour of the one shot key.\n#[non_exhaustive]\n#[derive(Debug, Clone, Copy, Eq, PartialEq)]\npub enum OneShotEndConfig {\n    /// End one shot activation on first non-one-shot key press.\n    EndOnFirstPress,\n    /// End one shot activation on first non-one-shot key press or a repress of an already-pressed\n    /// one-shot key.\n    EndOnFirstPressOrRepress,\n    /// End one shot activation on first non-one-shot key release.\n    EndOnFirstRelease,\n    /// End one shot activation on first non-one-shot key release or a repress of an already-pressed\n    /// one-shot key.\n    EndOnFirstReleaseOrRepress,\n}\n\n/// Defines the maximum number of one shot keys that can be combined.\npub const ONE_SHOT_MAX_ACTIVE: usize = 16;\n\n/// Define tap dance behaviour.\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct TapDance<'a, T = core::convert::Infallible>\nwhere\n    T: 'a,\n{\n    /// List of actions that activate based on number of taps. Only one of the actions will\n    /// activate. Tapping the tap-dance key once will activate the action in index 0, three\n    /// times will activate the action in index 2.\n    pub actions: &'a [&'a Action<'a, T>],\n    /// Timeout after which a tap will expire and become an action. A new tap for the same\n    /// tap-dance key will reset this timeout.\n    pub timeout: u16,\n    /// Determine behaviour of tap dance. Eager evaluation will activate every action in the\n    /// sequence as keys are pressed. Lazy will activate only a single action, decided by the\n    /// number of taps in the sequence.\n    pub config: TapDanceConfig,\n}\n\n/// Determines the behaviour for a `TapDance`.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum TapDanceConfig {\n    Lazy,\n    Eager,\n}\n\n/// A group of chords (actions mapped to a combination of multiple physical keys pressed together).\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct ChordsGroup<'a, T = core::convert::Infallible>\nwhere\n    T: 'a,\n{\n    /// List of key coordinates participating in this chord group, each with the corresponding [ChordKeys] they map to.\n    pub coords: &'a [((u8, u16), ChordKeys)],\n    /// Map of chords to actions they execute.\n    pub chords: &'a [(ChordKeys, &'a Action<'a, T>)],\n    /// Timeout after which a chord will expire and either trigger its action or be discarded if there is no corresponding action.\n    /// A chord may trigger its action even before this timeout expires, if a chord key is released, a non-chord key is pressed or the pressed chord is already uniquely identifyable.\n    pub timeout: u16,\n}\n\nimpl<'a, T> ChordsGroup<'a, T> {\n    /// Gets the chord keys corresponding to the given key coordinates.\n    pub fn get_keys(&self, coord: (u8, u16)) -> Option<ChordKeys> {\n        self.coords.iter().find(|c| c.0 == coord).map(|c| c.1)\n    }\n\n    /// Gets the chord action assigned to the given chord keys.\n    pub fn get_chord(&self, keys: ChordKeys) -> Option<&'a Action<'a, T>> {\n        self.chords\n            .iter()\n            .find(|(chord_keys, _)| *chord_keys == keys)\n            .map(|(_, action)| *action)\n    }\n\n    /// Gets the chord action assigned to the given chord keys if they are already unambigous (i.e. there is no key that could still be pressed that would result in a different chord).\n    pub fn get_chord_if_unambiguous(&self, keys: ChordKeys) -> Option<&'a Action<'a, T>> {\n        self.chords\n            .iter()\n            .try_fold(None, |res, &(chord_keys, action)| {\n                if chord_keys == keys {\n                    Ok(Some(action))\n                } else if chord_keys | keys == chord_keys {\n                    // The given keys are a subset of this chord but not an exact match\n                    // -> ambiguity\n                    Err(())\n                } else {\n                    Ok(res)\n                }\n            })\n            .unwrap_or_default()\n    }\n}\n\n/// A set of virtual keys (represented as a bit mask) pressed together.\n/// The keys do not directly correspond to physical keys. They are unique to a given [ChordGroup] and their mapping from physical keys is definied in [ChordGroup.coords].\n/// As such, each chord group can effectively have at most 32 different keys (though multiple physical keys may be mapped to the same virtual key).\npub type ChordKeys = u128;\n\n/// Defines the maximum number of (virtual) keys that can be used in a single chords group.\npub const MAX_CHORD_KEYS: usize = ChordKeys::BITS as usize;\n\n/// An action that can do one of two actions. The `left` action is the default. The `right` action\n/// will trigger if any of the key codes in `right_triggers` are active in the current layout\n/// state.\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct ForkConfig<'a, T> {\n    pub left: Action<'a, T>,\n    pub right: Action<'a, T>,\n    pub right_triggers: &'a [KeyCode],\n}\n\n/// The different actions that can be done.\n#[derive(Clone, Copy, PartialEq, Debug)]\npub enum Action<'a, T = core::convert::Infallible>\nwhere\n    T: 'a,\n{\n    /// No operation action: just do nothing.\n    NoOp,\n    /// Transparent, i.e. get the action from the default layer. On\n    /// the default layer, it is equivalent to `NoOp`.\n    Trans,\n    /// A key code, i.e. a classic key.\n    KeyCode(KeyCode),\n    /// Multiple key codes sent at the same time, as if these keys\n    /// were pressed at the same time. Useful to send a shifted key,\n    /// or complex shortcuts like Ctrl+Alt+Del in a single key press.\n    MultipleKeyCodes(&'a &'a [KeyCode]),\n    /// Multiple actions sent at the same time.\n    MultipleActions(&'a &'a [Action<'a, T>]),\n    /// While pressed, change the current layer. That's the classic\n    /// Fn key. If several layer actions are hold at the same time,\n    /// the last pressed defines the current layer.\n    Layer(usize),\n    /// Change the default layer.\n    DefaultLayer(usize),\n\n    /// A sequence of SequenceEvents\n    Sequence {\n        /// An array of SequenceEvents that will be triggered (in order)\n        events: &'a &'a [SequenceEvent<'a, T>],\n    },\n    /// A sequence of SequenceEvents, which will be repeated so long as the key is held.\n    RepeatableSequence {\n        /// An array of SequenceEvents that will be triggered (in order)\n        events: &'a &'a [SequenceEvent<'a, T>],\n    },\n    /// Cancels any running sequences\n    CancelSequences,\n    /// Action to release either a keycode state or a layer state.\n    ReleaseState(ReleasableState),\n\n    /// Perform different actions on key hold/tap (see [`HoldTapAction`]).\n    HoldTap(&'a HoldTapAction<'a, T>),\n    /// Custom action.\n    ///\n    /// Define a user defined action. This enum can be anything you\n    /// want, as long as it has the `'a` lifetime. It can be used\n    /// to drive any non keyboard related actions that you might\n    /// manage with key events.\n    Custom(T),\n    /// One shot key. Also known as \"sticky key\". See `struct OneShot` for configuration info.\n    /// Activates `action` until a single other key that is not also a one shot key is used. For\n    /// example, a one shot key can be used to activate shift for exactly one keypress or switch to\n    /// another layer for exactly one keypress. Holding a one shot key will be treated as a normal\n    /// held keypress.\n    ///\n    /// If you use one shot outside of its intended use cases (modifier key action or layer\n    /// action) then you will likely have undesired behaviour. E.g. one shot with the space\n    /// key will hold space until either another key is pressed or the timeout occurs, which will\n    /// probably send many undesired space characters to your active application.\n    OneShot(&'a OneShot<'a, T>),\n    /// An action to ignore processing of events for OneShot.\n    OneShotIgnoreEventsTicks(u16),\n    /// Tap-dance key. When tapping the key N times in quck succession, activates the N'th action\n    /// in `actions`. The action will activate in the following conditions:\n    ///\n    /// - a different key is pressed\n    /// - `timeout` ticks elapse since the last tap of the same tap-dance key\n    /// - the number of taps is equal to the length of `actions`.\n    TapDance(&'a TapDance<'a, T>),\n    /// Chord key. Enters chording mode where multiple keys may be pressed together to active\n    /// different actions depending on the specific combination (\"chord\") pressed.\n    /// See `struct ChordGroup` for configuration info.\n    ///\n    /// Keys participating in chording mode are listed in `coords`.\n    /// Chording mode ends when a non-participating key is pressed, a participating key is released,\n    /// the timeout expires, or when the pressed chord uniquely identifies an action (i.e. there are\n    /// no more keys you could press to change the result).\n    Chords(&'a ChordsGroup<'a, T>),\n    /// Repeat the previous action.\n    Repeat,\n    /// Fork action that can activate one of two potential actions depending on what keys are\n    /// currently active.\n    Fork(&'a ForkConfig<'a, T>),\n    /// Action that can activate 0 to N actions based on what keys are currently\n    /// active and the boolean logic of each case.\n    ///\n    /// The maximum number of actions that can activate the same time is governed by\n    /// `ACTION_QUEUE_LEN`.\n    Switch(&'a Switch<'a, T>),\n    /// Disregard the entire layer stack, i.e. the current base layer and any while-held layers,\n    /// and select the action from `Layout.src_keys`.\n    Src,\n}\n\nimpl<T> Action<'_, T> {\n    /// Gets the layer number if the action is the `Layer` action.\n    pub fn layer(self) -> Option<usize> {\n        match self {\n            Action::Layer(l) => Some(l),\n            _ => None,\n        }\n    }\n    /// Returns an iterator on the `KeyCode` corresponding to the action.\n    pub fn key_codes(&self) -> impl Iterator<Item = KeyCode> + '_ {\n        match self {\n            Action::KeyCode(kc) => core::slice::from_ref(kc).iter().cloned(),\n            Action::MultipleKeyCodes(kcs) => kcs.iter().cloned(),\n            _ => [].iter().cloned(),\n        }\n    }\n}\n\n/// A shortcut to create a `Action::KeyCode`, useful to create compact\n/// layout.\npub const fn k<T>(kc: KeyCode) -> Action<'static, T> {\n    Action::KeyCode(kc)\n}\n\n/// A shortcut to create a `Action::Layer`, useful to create compact\n/// layout.\npub const fn l<T>(layer: usize) -> Action<'static, T> {\n    Action::Layer(layer)\n}\n\n/// A shortcut to create a `Action::DefaultLayer`, useful to create compact\n/// layout.\npub const fn d<T>(layer: usize) -> Action<'static, T> {\n    Action::DefaultLayer(layer)\n}\n"
  },
  {
    "path": "keyberon/src/chord.rs",
    "content": "//! Module for chords v2 implementation.\n\nuse std::cell::Cell;\n\nuse arraydeque::ArrayDeque;\nuse heapless::Vec as HVec;\nuse rustc_hash::FxHashMap;\n\nuse crate::{\n    action::Action,\n    key_code::KEY_MAX,\n    layout::{Event, Queue, Queued, QueuedAction},\n};\n\n// Macro to help with this boilerplate.\n// $v should probably be `self` at points of use.\n// Ownership rules make this difficult to do as a regular fn,\n// because impl function calls don't understand split borrowing.\nmacro_rules! no_chord_activations {\n    ($v:expr) => {{\n        $v.ticks_to_ignore_chord = $v.configured_ticks_to_ignore_chord;\n    }};\n}\n\npub(crate) const TRIGGER_TAPHOLD_COORD: (u8, u16) = (0, 0);\n\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\npub enum ReleaseBehaviour {\n    OnFirstRelease,\n    OnLastRelease,\n}\n\n#[derive(Clone)]\npub struct ChordV2<'a, T> {\n    /// The action associated with this chord.\n    pub action: &'a Action<'a, T>,\n    /// The full set of keys that need to be pressed to activate this chord.\n    pub participating_keys: &'a [u16],\n    /// The number of ticks during which, after the first press of a participant,\n    /// this chord can be activated if all participants get pressed.\n    /// In other words, after the number of ticks defined by `pending_duration`\n    /// elapses, this chord can no longer be completed.\n    pub pending_duration: u16,\n    /// The layers on which this chord is disabled.\n    pub disabled_layers: &'a [u16],\n    /// When should the action for this chord be released.\n    pub release_behaviour: ReleaseBehaviour,\n}\n\nimpl<'a, T> std::fmt::Debug for ChordV2<'a, T> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {\n        f.debug_struct(\"Point\")\n            .field(\"participating_keys\", &self.participating_keys)\n            .field(\"pending_duration\", &self.pending_duration)\n            .field(\"disabled_layers\", &self.disabled_layers)\n            .field(\"release_behaviour\", &self.release_behaviour)\n            .finish()\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct ChordsForKey<'a, T> {\n    /// Chords that this key participates in.\n    pub chords: Vec<&'a ChordV2<'a, T>>,\n}\n\n#[derive(Debug, Clone)]\npub struct ChordsForKeys<'a, T> {\n    pub mapping: FxHashMap<u16, ChordsForKey<'a, T>>,\n}\n\nconst SMOL_Q_LEN: usize = 16;\n\nstruct ActiveChord<'a, T> {\n    /// Chords uses a virtual coordinate in the keyberon state for an activated chord.\n    /// This field tracks which coordinate to release when the chord itself is released.\n    coordinate: u16,\n    /// Keys left to release.\n    /// For OnFirstRelease, this should have length 0.\n    remaining_keys_to_release: HVec<u16, SMOL_Q_LEN>,\n    /// Necessary to include here make sure that, for OnFirstRelease,\n    /// random other releases that are not part of this chord,\n    /// do not release this chord.\n    participating_keys: &'a [u16],\n    /// Action associated with the active chord.\n    /// This needs to be stored here\n    action: &'a Action<'a, T>,\n    /// In the case of Unread, this chord has not yet been consumed by the layout code.\n    /// This might happen for a while because of tap-hold-related delays.\n    /// In the Releasable status, the active chord has been consumed and can be released.\n    status: ActiveChordStatus,\n    /// Tracks how old an action is.\n    delay: u16,\n}\n\nfn tick_ach<T>(acc: &mut ActiveChord<T>) {\n    acc.delay = acc.delay.saturating_add(1);\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\nenum ActiveChordStatus {\n    /// -> UnreadPendingRelease if chord released before being consumed\n    /// -> Releasable if consumed\n    Unread,\n    /// -> Released once consumed\n    UnreadReleased,\n    /// Can remove at any time.\n    /// -> Released once released\n    Releasable,\n    /// Remove on next tick_chv2\n    Released,\n}\nuse ActiveChordStatus::*;\n\n/// Like the layout Queue but smaller.\npub(crate) type SmolQueue = ArrayDeque<Queued, SMOL_Q_LEN, arraydeque::behavior::Wrapping>;\n\n/// Global input chords configuration.\npub struct ChordsV2<'a, T> {\n    // Note: Interior fields do not need to be pub or mutable via impl pub fn.\n    // Like a layout, this should be destroyed and recreated on a live reload.\n    //\n    /// Queued inputs that can potentially activate a chord but have not yet.\n    /// Inputs will leave if they are determined that they will not activate a chord,\n    /// or if a chord activates.\n    queue: Queue,\n    /// Information about what chords are possible and what keys they are associated with.\n    chords: ChordsForKeys<'a, T>,\n    /// Chords that are active, i.e. ones that have not yet been released.\n    active_chords: HVec<ActiveChord<'a, T>, 10>,\n    /// When a key leaves the combo queue without activating a chord,\n    /// this activates a timer during which keys cannot activate chords\n    /// and are always forwarded directly to the standard input queue.\n    ///\n    /// This keeps track of the timer.\n    ticks_to_ignore_chord: u16,\n    /// Initial value for the above when the appropriate event happens.\n    /// This must have a minimum value even if not configured by the user,\n    /// or if configured by the user to be zero. (maybe forbid that config)\n    configured_ticks_to_ignore_chord: u16,\n    /// Optimization: if there are no new inputs, the code can skip some processing work.\n    /// This tracks the next time that a change will happen, so that the processing work\n    /// is **not** skipped when something needs to be checked.\n    ticks_until_next_state_change: u16,\n    /// Optimization: the below is part of skipping processing work - if this is has changed,\n    /// then processing work cannot be skipped.\n    prev_active_layer: u16,\n    /// Optimization: the below is part of skipping processing work - if this is has changed,\n    /// then processing work cannot be skipped.\n    prev_queue_len: u8,\n    /// Virtual coordinate for use in the layout state.\n    next_coord: Cell<u16>,\n}\n\nimpl<T> std::fmt::Debug for ChordsV2<'_, T> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"ChordsV2\")\n    }\n}\n\nimpl<'a, T> ChordsV2<'a, T> {\n    pub fn new(chords: ChordsForKeys<'a, T>, ticks_ignore_chord: u16) -> Self {\n        assert!(ticks_ignore_chord >= 5);\n        Self {\n            queue: Queue::new(),\n            chords,\n            active_chords: HVec::new(),\n            ticks_to_ignore_chord: 0,\n            configured_ticks_to_ignore_chord: ticks_ignore_chord,\n            ticks_until_next_state_change: 0,\n            prev_active_layer: u16::MAX,\n            prev_queue_len: u8::MAX,\n            next_coord: Cell::new(KEY_MAX + 1),\n        }\n    }\n\n    pub fn is_idle_chv2(&self) -> bool {\n        self.queue.is_empty() && self.active_chords.is_empty()\n    }\n\n    pub fn accepts_chords_chv2(&self) -> bool {\n        self.ticks_to_ignore_chord == 0\n    }\n\n    pub fn push_back_chv2(&mut self, item: Queued) -> Option<Queued> {\n        self.queue.push_back(item)\n    }\n\n    pub fn chords(&self) -> &ChordsForKeys<'a, T> {\n        &self.chords\n    }\n\n    pub(crate) fn get_action_chv2(&mut self) -> QueuedAction<'a, T> {\n        self.active_chords\n            .iter_mut()\n            .find_map(|ach| match ach.status {\n                Unread => {\n                    ach.status = Releasable;\n                    // Note on LayerStack being default (empty):\n                    // A chordv2 is not allowed to use transparency,\n                    // so it does not need to handle this case.\n                    Some(Some((\n                        (0, ach.coordinate),\n                        ach.delay,\n                        ach.action,\n                        Default::default(),\n                    )))\n                }\n                UnreadReleased => {\n                    ach.status = Released;\n                    Some(Some((\n                        (0, ach.coordinate),\n                        ach.delay,\n                        ach.action,\n                        Default::default(),\n                    )))\n                }\n                Releasable | Released => None,\n            })\n            .unwrap_or_default()\n    }\n\n    /// Update the times in the queue without activating any chords yet.\n    /// Returns queued events that are no longer usable in chords.\n    pub(crate) fn tick_chv2(&mut self, active_layer: u16) -> SmolQueue {\n        let mut q = SmolQueue::new();\n        self.queue.iter_mut().for_each(Queued::tick_qd);\n        let prev_active_chord_len = self.active_chords.len();\n        self.active_chords.iter_mut().for_each(tick_ach);\n        self.drain_inputs(&mut q, active_layer);\n        if self.active_chords.len() != prev_active_chord_len {\n            // A chord was activated. Forward a no-op press event to potentially trigger\n            // HoldOnOtherKeyPress or PermissiveHold.\n            // FLAW: this does not associate with the actual input keys and thus cannot correctly\n            // trigger the early tap for *-keys variants of kanata tap-hold.\n            q.push_back(Queued::new_press(\n                TRIGGER_TAPHOLD_COORD.0,\n                TRIGGER_TAPHOLD_COORD.1,\n            ));\n        }\n        if self\n            .active_chords\n            .iter()\n            .any(|ach| matches!(ach.status, UnreadReleased | Released))\n        {\n            // A chord was released. Forward a no-op release event to potentially trigger\n            // PermissiveHold.\n            // FLAW: see above\n            q.push_back(Queued::new_release(\n                TRIGGER_TAPHOLD_COORD.0,\n                TRIGGER_TAPHOLD_COORD.1,\n            ));\n        }\n        self.clear_released_chords(&mut q);\n        self.ticks_to_ignore_chord = self.ticks_to_ignore_chord.saturating_sub(1);\n        q\n    }\n\n    fn next_coord(&self) -> u16 {\n        let ret = self.next_coord.get();\n        let mut new = ret + 1;\n        if new > KEY_MAX + 50 {\n            new = KEY_MAX + 1;\n        }\n        self.next_coord.set(new);\n        ret\n    }\n\n    fn drain_inputs(&mut self, drainq: &mut SmolQueue, active_layer: u16) {\n        if self.ticks_to_ignore_chord > 0 {\n            self.drain_without_new_activations(drainq);\n            return;\n        }\n        if self.ticks_until_next_state_change > 0\n            && self.prev_active_layer == active_layer\n            && usize::from(self.prev_queue_len) == self.queue.len()\n        {\n            self.ticks_until_next_state_change =\n                self.ticks_until_next_state_change.saturating_sub(1);\n            return;\n        }\n        self.ticks_until_next_state_change = 0;\n        self.prev_active_layer = active_layer;\n        debug_assert!(self.queue.capacity() < 255);\n        self.prev_queue_len = self.queue.len() as u8;\n\n        self.drain_virtual_keys(drainq);\n        self.drain_releases(drainq);\n        self.process_presses(active_layer);\n    }\n\n    /// Used to process keys while chordsv2 is in the disabled state from rapid typing.\n    /// Releases must still be processed to release already-activated chords.\n    fn drain_without_new_activations(&mut self, drainq: &mut SmolQueue) {\n        let achs = &mut self.active_chords;\n        for qd in self.queue.iter() {\n            if let Event::Release(_, j) = qd.event {\n                // Release the key from active chords.\n                achs.iter_mut().for_each(|ach| {\n                    if !ach.participating_keys.contains(&j) {\n                        return;\n                    }\n                    ach.remaining_keys_to_release.retain(|pk| *pk != j);\n                    if ach.remaining_keys_to_release.is_empty() {\n                        ach.status = match ach.status {\n                            Unread | UnreadReleased => UnreadReleased,\n                            Releasable | Released => Released,\n                        }\n                    }\n                });\n            }\n            drainq.push_back(*qd);\n        }\n        self.queue.clear();\n    }\n\n    fn drain_virtual_keys(&mut self, drainq: &mut SmolQueue) {\n        self.queue.retain(|qd| {\n            match qd.event {\n                // Only row 0 is real inputs.\n                // Drain other rows (at the time of writing should only be index 1).\n                Event::Press(0, _) | Event::Release(0, _) => true,\n                _ => {\n                    let overflow = drainq.push_back(*qd);\n                    assert!(overflow.is_none(), \"oops overflowed drain queue\");\n                    false\n                }\n            }\n        });\n    }\n\n    fn drain_releases(&mut self, drainq: &mut SmolQueue) {\n        let achs = &mut self.active_chords;\n        let mut presses = HVec::<_, SMOL_Q_LEN>::new();\n        self.queue.retain(|qd| match qd.event {\n            Event::Press(_, j) => {\n                let overflow = presses.push(j);\n                debug_assert!(overflow.is_ok());\n                true\n            }\n            Event::Release(_, j) => {\n                // Release the key from active chords.\n                achs.iter_mut().for_each(|ach| {\n                    if !ach.participating_keys.contains(&j) {\n                        return;\n                    }\n                    ach.remaining_keys_to_release.retain(|pk| *pk != j);\n                    if ach.remaining_keys_to_release.is_empty() {\n                        ach.status = match ach.status {\n                            Unread | UnreadReleased => UnreadReleased,\n                            Releasable | Released => Released,\n                        }\n                    }\n                });\n                if presses.is_empty() {\n                    drainq.push_back(*qd);\n                    false\n                } else {\n                    true\n                }\n            }\n        })\n    }\n\n    fn process_presses(&mut self, active_layer: u16) {\n        let mut presses = HVec::<u16, SMOL_Q_LEN>::new();\n        let mut relevant_release_found = false;\n        for qd in self.queue.iter() {\n            match qd.event {\n                Event::Press(_, j) => {\n                    let overflowed = presses.push(j);\n                    debug_assert!(overflowed.is_ok(), \"too many presses in queue\");\n                }\n                Event::Release(_, j) => {\n                    if presses.contains(&j) {\n                        relevant_release_found = true;\n                        break;\n                    }\n                }\n            }\n        }\n        let prev_active_chords_len = self.active_chords.len();\n        let Some(starting_press) = presses.first() else {\n            return;\n        };\n        let Some(possible_chords) = self.chords.mapping.get(starting_press) else {\n            no_chord_activations!(self);\n            return;\n        };\n\n        // For subsequent keypresses,\n        // all must fit into a single chord for chord state to remain pending\n        // instead of activating a chord,\n        // and there must also be a longer chord that can still potentially be activated.\n        //\n        // Prioritization of chord activation:\n        // 1. Timed out chord\n        // 2. Longer chord\n        let mut accumulated_presses = HVec::<u16, SMOL_Q_LEN>::new();\n        let mut chord_candidates = HVec::<&ChordV2<'a, T>, SMOL_Q_LEN>::new();\n        let mut prev_count = usize::MAX;\n        let mut min_timeout;\n\n        assert!(!presses.is_empty());\n        let since = self.queue.iter().next().unwrap().since;\n\n        for press in presses.iter().copied() {\n            min_timeout = u16::MAX;\n            accumulated_presses\n                .push(press)\n                .expect(\"accpresses same len as presses\");\n\n            let count_possible = if prev_count == chord_candidates.len() {\n                // optimization: no longer need to check the whole list.\n                // chord_candidates will keep getting shrunk.\n                chord_candidates.retain(|chc| chc.participating_keys.contains(&press));\n                for chc in chord_candidates.iter() {\n                    if chc.pending_duration > since {\n                        min_timeout = std::cmp::min(min_timeout, chc.pending_duration);\n                    }\n                }\n                chord_candidates.len()\n            } else {\n                chord_candidates.clear();\n                possible_chords\n                    .chords\n                    .iter()\n                    .filter(|pch| !pch.disabled_layers.contains(&active_layer))\n                    .filter(|pch| {\n                        if accumulated_presses\n                            .iter()\n                            .all(|acp| pch.participating_keys.contains(acp))\n                        {\n                            // If full, can't run the optimization above, but not fatal.\n                            // Can ignore the overflow.\n                            let _overflow = chord_candidates.push(pch);\n                            if pch.pending_duration > since {\n                                min_timeout = std::cmp::min(min_timeout, pch.pending_duration);\n                            }\n                            true\n                        } else {\n                            false\n                        }\n                    })\n                    .count()\n            };\n\n            match count_possible {\n                1 => {\n                    // Found a chord that is not fully overlapped by another.\n                    // Activate the chord if it is completed\n                    let coord = self.next_coord();\n                    let cch = chord_candidates[0];\n                    if cch\n                        .participating_keys\n                        .iter()\n                        .all(|pk| accumulated_presses.contains(pk))\n                    {\n                        let ach = get_active_chord(cch, since, coord, relevant_release_found);\n                        let overflow = self.active_chords.push(ach);\n                        assert!(overflow.is_ok(), \"active chords has room\");\n                        break;\n                    }\n                }\n                0 => {\n                    // If reached this, it means we went from 2+ -> 0,\n                    // or we got to zero at the first iteration.\n                    // Backtrack one accumulated press then:\n                    // - activate a chord if one completed\n                    // - clear the input queue otherwise\n                    let _ = accumulated_presses.pop();\n                    chord_candidates.clear();\n                    let completed_chord = possible_chords\n                        .chords\n                        .iter()\n                        .filter(|pch| !pch.disabled_layers.contains(&active_layer))\n                        .find(\n                            // Ensure the two lists have the same set of keys\n                            |pch| {\n                                accumulated_presses\n                                    .iter()\n                                    .all(|acp| pch.participating_keys.contains(acp))\n                                    && pch\n                                        .participating_keys\n                                        .iter()\n                                        .all(|pk| accumulated_presses.contains(pk))\n                            },\n                        );\n                    match completed_chord {\n                        Some(cch) => {\n                            let coord = self.next_coord();\n                            let ach = get_active_chord(cch, since, coord, relevant_release_found);\n                            let overflow = self.active_chords.push(ach);\n                            assert!(overflow.is_ok(), \"active chords has room\");\n                        }\n                        None => no_chord_activations!(self),\n                    }\n                    break;\n                }\n                _ => {}\n            }\n            self.ticks_until_next_state_change = match min_timeout {\n                u16::MAX => 0,\n                t => t.saturating_sub(since),\n            };\n            prev_count = count_possible;\n        }\n        if self.ticks_until_next_state_change == 0 || relevant_release_found {\n            // Find a chord that matches exactly and activate that,\n            // otherwise clear the input queue.\n            let completed_chord = if chord_candidates.is_full() {\n                possible_chords\n                    .chords\n                    .iter()\n                    .filter(|pch| !pch.disabled_layers.contains(&active_layer))\n                    .find(\n                        // Ensure the two lists have the same set of keys\n                        |pch| {\n                            accumulated_presses\n                                .iter()\n                                .all(|acp| pch.participating_keys.contains(acp))\n                                && pch\n                                    .participating_keys\n                                    .iter()\n                                    .all(|pk| accumulated_presses.contains(pk))\n                        },\n                    )\n            } else {\n                chord_candidates\n                    .iter()\n                    .filter(|pch| !pch.disabled_layers.contains(&active_layer))\n                    .find(\n                        // Ensure the two lists have the same set of keys\n                        |pch| {\n                            accumulated_presses\n                                .iter()\n                                .all(|acp| pch.participating_keys.contains(acp))\n                                && pch\n                                    .participating_keys\n                                    .iter()\n                                    .all(|pk| accumulated_presses.contains(pk))\n                        },\n                    )\n            };\n            match completed_chord {\n                Some(cch) => {\n                    let ach =\n                        get_active_chord(cch, since, self.next_coord(), relevant_release_found);\n                    let overflow = self.active_chords.push(ach);\n                    assert!(overflow.is_ok(), \"active chords has room\");\n                }\n                None => {\n                    no_chord_activations!(self)\n                }\n            }\n        }\n\n        // Clear presses from the queue if they were consumed by a chord.\n        if self.active_chords.len() > prev_active_chords_len {\n            self.queue.retain(|qd| match qd.event {\n                Event::Press(_, j) => !accumulated_presses.contains(&j),\n                _ => true,\n            });\n        }\n    }\n\n    fn clear_released_chords(&mut self, drainq: &mut SmolQueue) {\n        self.active_chords.retain(|ach| {\n            if ach.status == Released {\n                let overflow = drainq.push_back(Queued {\n                    event: Event::Release(0, ach.coordinate),\n                    since: 0,\n                });\n                assert!(overflow.is_none(), \"oops overflowed drain queue\");\n                false\n            } else {\n                true\n            }\n        });\n    }\n}\n\nfn get_active_chord<'a, T>(\n    cch: &ChordV2<'a, T>,\n    since: u16,\n    coord: u16,\n    release_found: bool,\n) -> ActiveChord<'a, T> {\n    let mut remaining_keys_to_release = HVec::new();\n    if cch.release_behaviour == ReleaseBehaviour::OnLastRelease {\n        remaining_keys_to_release.extend(cch.participating_keys.iter().copied());\n    };\n    ActiveChord {\n        coordinate: coord,\n        remaining_keys_to_release,\n        participating_keys: cch.participating_keys,\n        action: cch.action,\n        status: if release_found && cch.release_behaviour == ReleaseBehaviour::OnFirstRelease {\n            ActiveChordStatus::UnreadReleased\n        } else {\n            ActiveChordStatus::Unread\n        },\n        delay: since,\n    }\n}\n"
  },
  {
    "path": "keyberon/src/key_code.rs",
    "content": "//! Key code definitions.\n\n/// Used for switch opcode purposes. Keys should not exceed this amount.\npub const KEY_MAX: u16 = 850;\n\n#[test]\nfn keycode_max_test() {\n    assert!((KeyCode::KeyMax as u16) < KEY_MAX);\n}\n\n#[allow(missing_docs)]\n/// Define a key code according to the HID specification.  Their names\n/// correspond to the american QWERTY layout.\n#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]\n#[repr(u16)]\npub enum KeyCode {\n    ErrorUndefined = 0,\n    Escape = 1,\n    Kb1 = 2,\n    Kb2 = 3,\n    Kb3 = 4,\n    Kb4 = 5,\n    Kb5 = 6,\n    Kb6 = 7,\n    Kb7 = 8,\n    Kb8 = 9,\n    Kb9 = 10,\n    Kb0 = 11,\n    Minus = 12,\n    Equal = 13,\n    BSpace = 14,\n    Tab = 15,\n    Q = 16,\n    W = 17,\n    E = 18,\n    R = 19,\n    T = 20,\n    Y = 21,\n    U = 22,\n    I = 23,\n    O = 24,\n    P = 25,\n    LBracket = 26,\n    RBracket = 27,\n    Enter = 28,\n    LCtrl = 29,\n    A = 30,\n    S = 31,\n    D = 32,\n    F = 33,\n    G = 34,\n    H = 35,\n    J = 36,\n    K = 37,\n    L = 38,\n    SColon = 39,\n    Quote = 40,\n    Grave = 41,\n    LShift = 42,\n    Bslash = 43,\n    Z = 44,\n    X = 45,\n    C = 46,\n    V = 47,\n    B = 48,\n    N = 49,\n    M = 50,\n    Comma = 51,\n    Dot = 52,\n    Slash = 53,\n    RShift = 54,\n    KpAsterisk = 55,\n    LAlt = 56,\n    Space = 57,\n    CapsLock = 58,\n    F1 = 59,\n    F2 = 60,\n    F3 = 61,\n    F4 = 62,\n    F5 = 63,\n    F6 = 64,\n    F7 = 65,\n    F8 = 66,\n    F9 = 67,\n    F10 = 68,\n    NumLock = 69,\n    ScrollLock = 70,\n    Kp7 = 71,\n    Kp8 = 72,\n    Kp9 = 73,\n    KpMinus = 74,\n    Kp4 = 75,\n    Kp5 = 76,\n    Kp6 = 77,\n    KpPlus = 78,\n    Kp1 = 79,\n    Kp2 = 80,\n    Kp3 = 81,\n    Kp0 = 82,\n    KpDot = 83,\n    K0xBF = 84,\n    K0xC0 = 85,\n    NonUsBslash = 86,\n    F11 = 87,\n    F12 = 88,\n    Intl1 = 89,\n    K0xB1 = 90,\n    K0xB3 = 91,\n    K0xB0 = 92,\n    K0xB2 = 93,\n    K0xAF = 94,\n    NonUsHash = 95,\n    KpEnter = 96,\n    RCtrl = 97,\n    KpSlash = 98,\n    SysReq = 99,\n    RAlt = 100,\n    K0xC1 = 101,\n    Home = 102,\n    Up = 103,\n    PgUp = 104,\n    Left = 105,\n    Right = 106,\n    End = 107,\n    Down = 108,\n    PgDown = 109,\n    Insert = 110,\n    Delete = 111,\n    K0xC2 = 112,\n    Mute = 113,\n    VolDown = 114,\n    VolUp = 115,\n    Power = 116,\n    KpEqual = 117,\n    K0xC3 = 118,\n    Pause = 119,\n    K0xC4 = 120,\n    KpComma = 121,\n    Lang1 = 122,\n    Lang2 = 123,\n    Intl3 = 124,\n    LGui = 125,\n    RGui = 126,\n    Application = 127,\n    Stop = 128,\n    Again = 129,\n    K0xC5 = 130,\n    Undo = 131,\n    K0xC6 = 132,\n    Copy = 133,\n    K0xC7 = 134,\n    Paste = 135,\n    Find = 136,\n    Cut = 137,\n    Help = 138,\n    Menu = 139,\n    MediaCalc = 140,\n    K0xC8 = 141,\n    MediaSleep = 142,\n    Wakeup = 143,\n    K0xC9 = 144,\n    K0xCA = 145,\n    K0xCB = 146,\n    K0xCC = 147,\n    K0xCD = 148,\n    K0xCE = 149,\n    MediaWWW = 150,\n    K0xCF = 151,\n    MediaCoffee = 152,\n    K0xD0 = 153,\n    K0xD1 = 154,\n    K0xAE = 155,\n    K0xD2 = 156,\n    K0xD3 = 157,\n    MediaBack = 158,\n    MediaForward = 159,\n    MediaStop = 160,\n    MediaEjectCD = 161,\n    MediaFind = 162,\n    MediaNextSong = 163,\n    MediaPlayPause = 164,\n    MediaPreviousSong = 165,\n    MediaStopCD = 166,\n    K0xD4 = 167,\n    K0xD5 = 168,\n    K0xD6 = 169,\n    K0xD7 = 170,\n    K0xD8 = 171,\n    K0xAD = 172,\n    MediaRefresh = 173,\n    K0xD9 = 174,\n    K0xDA = 175,\n    MediaEdit = 176,\n    MediaScrollUp = 177,\n    MediaScrollDown = 178,\n    K0xDB = 179,\n    K0xDC = 180,\n    K0xDD = 181,\n    K0xDE = 182,\n    F13 = 183,\n    F14 = 184,\n    F15 = 185,\n    F16 = 186,\n    F17 = 187,\n    F18 = 188,\n    F19 = 189,\n    F20 = 190,\n    F21 = 191,\n    F22 = 192,\n    F23 = 193,\n    F24 = 194,\n    Execute = 195,\n    LockingCapsLock = 196,\n    LockingNumLock = 197,\n    LockingScrollLock = 198,\n    KpEqualSign = 199,\n    Intl2 = 200,\n    Intl4 = 201,\n    Intl5 = 202,\n    Intl6 = 203,\n    Intl7 = 204,\n    Intl8 = 205,\n    Intl9 = 206,\n    Select = 207,\n    Lang3 = 208,\n    Lang4 = 209,\n    PScreen = 210,\n    Lang5 = 211,\n    Lang6 = 212,\n    Lang7 = 213,\n    Lang8 = 214,\n    K0xAB = 215,\n    Lang9 = 216,\n    K0xDF = 217,\n    K0xBE = 218,\n    Clear = 219,\n    K220 = 220,\n    K0xAC = 221,\n    AltErase = 222,\n    Cancel = 223,\n    BrightnessDown = 224,\n    BrightnessUp = 225,\n    K0xAA = 226,\n    Prior = 227,\n    Return = 228,\n    KbdIllumDown = 229,\n    KbdIllumUp = 230,\n    Separator = 231,\n    Out = 232,\n    Oper = 233,\n    ClearAgain = 234,\n    CrSel = 235,\n    ExSel = 236,\n    K0xB4 = 237,\n    K0xB5 = 238,\n    K0xB6 = 239,\n    No = 240,\n    K0xB7 = 241,\n    K0xB8 = 242,\n    K0xB9 = 243,\n    K0xBA = 244,\n    K0xBB = 245,\n    K0xBC = 246,\n    K0xBD = 247,\n    MediaMute = 248,\n    K249 = 249,\n    PostFail = 250,\n    ErrorRollOver = 251,\n    K252 = 252,\n    K253 = 253,\n    K254 = 254,\n    K255 = 255,\n    K256 = 256,\n    K257 = 257,\n    K258 = 258,\n    K259 = 259,\n    K260 = 260,\n    K261 = 261,\n    K262 = 262,\n    K263 = 263,\n    K264 = 264,\n    K265 = 265,\n    K266 = 266,\n    K267 = 267,\n    K268 = 268,\n    K269 = 269,\n    K270 = 270,\n    K271 = 271,\n    K272 = 272,\n    K273 = 273,\n    K274 = 274,\n    K275 = 275,\n    K276 = 276,\n    K277 = 277,\n    K278 = 278,\n    K279 = 279,\n    K280 = 280,\n    K281 = 281,\n    K282 = 282,\n    K283 = 283,\n    K284 = 284,\n    K285 = 285,\n    K286 = 286,\n    K287 = 287,\n    K288 = 288,\n    K289 = 289,\n    K290 = 290,\n    K291 = 291,\n    K292 = 292,\n    K293 = 293,\n    K294 = 294,\n    K295 = 295,\n    K296 = 296,\n    K297 = 297,\n    K298 = 298,\n    K299 = 299,\n    K300 = 300,\n    K301 = 301,\n    K302 = 302,\n    K303 = 303,\n    K304 = 304,\n    K305 = 305,\n    K306 = 306,\n    K307 = 307,\n    K308 = 308,\n    K309 = 309,\n    K310 = 310,\n    K311 = 311,\n    K312 = 312,\n    K313 = 313,\n    K314 = 314,\n    K315 = 315,\n    K316 = 316,\n    K317 = 317,\n    K318 = 318,\n    K319 = 319,\n    K320 = 320,\n    K321 = 321,\n    K322 = 322,\n    K323 = 323,\n    K324 = 324,\n    K325 = 325,\n    K326 = 326,\n    K327 = 327,\n    K328 = 328,\n    K329 = 329,\n    K330 = 330,\n    K331 = 331,\n    K332 = 332,\n    K333 = 333,\n    K334 = 334,\n    K335 = 335,\n    K336 = 336,\n    K337 = 337,\n    K338 = 338,\n    K339 = 339,\n    K340 = 340,\n    K341 = 341,\n    K342 = 342,\n    K343 = 343,\n    K344 = 344,\n    K345 = 345,\n    K346 = 346,\n    K347 = 347,\n    K348 = 348,\n    K349 = 349,\n    K350 = 350,\n    K351 = 351,\n    K352 = 352,\n    K353 = 353,\n    K354 = 354,\n    K355 = 355,\n    K356 = 356,\n    K357 = 357,\n    K358 = 358,\n    K359 = 359,\n    K360 = 360,\n    K361 = 361,\n    K362 = 362,\n    K363 = 363,\n    K364 = 364,\n    K365 = 365,\n    K366 = 366,\n    K367 = 367,\n    K368 = 368,\n    K369 = 369,\n    K370 = 370,\n    K371 = 371,\n    K372 = 372,\n    K373 = 373,\n    K374 = 374,\n    K375 = 375,\n    K376 = 376,\n    K377 = 377,\n    K378 = 378,\n    K379 = 379,\n    K380 = 380,\n    K381 = 381,\n    K382 = 382,\n    K383 = 383,\n    K384 = 384,\n    K385 = 385,\n    K386 = 386,\n    K387 = 387,\n    K388 = 388,\n    K389 = 389,\n    K390 = 390,\n    K391 = 391,\n    K392 = 392,\n    K393 = 393,\n    K394 = 394,\n    K395 = 395,\n    K396 = 396,\n    K397 = 397,\n    K398 = 398,\n    K399 = 399,\n    K400 = 400,\n    K401 = 401,\n    K402 = 402,\n    K403 = 403,\n    K404 = 404,\n    K405 = 405,\n    K406 = 406,\n    K407 = 407,\n    K408 = 408,\n    K409 = 409,\n    K410 = 410,\n    K411 = 411,\n    K412 = 412,\n    K413 = 413,\n    K414 = 414,\n    K415 = 415,\n    K416 = 416,\n    K417 = 417,\n    K418 = 418,\n    K419 = 419,\n    K420 = 420,\n    K421 = 421,\n    K422 = 422,\n    K423 = 423,\n    K424 = 424,\n    K425 = 425,\n    K426 = 426,\n    K427 = 427,\n    K428 = 428,\n    K429 = 429,\n    K430 = 430,\n    K431 = 431,\n    K432 = 432,\n    K433 = 433,\n    K434 = 434,\n    K435 = 435,\n    K436 = 436,\n    K437 = 437,\n    K438 = 438,\n    K439 = 439,\n    K440 = 440,\n    K441 = 441,\n    K442 = 442,\n    K443 = 443,\n    K444 = 444,\n    K445 = 445,\n    K446 = 446,\n    K447 = 447,\n    K448 = 448,\n    K449 = 449,\n    K450 = 450,\n    K451 = 451,\n    K452 = 452,\n    K453 = 453,\n    K454 = 454,\n    K455 = 455,\n    K456 = 456,\n    K457 = 457,\n    K458 = 458,\n    K459 = 459,\n    K460 = 460,\n    K461 = 461,\n    K462 = 462,\n    K463 = 463,\n    K464 = 464,\n    K465 = 465,\n    K466 = 466,\n    K467 = 467,\n    K468 = 468,\n    K469 = 469,\n    K470 = 470,\n    K471 = 471,\n    K472 = 472,\n    K473 = 473,\n    K474 = 474,\n    K475 = 475,\n    K476 = 476,\n    K477 = 477,\n    K478 = 478,\n    K479 = 479,\n    K480 = 480,\n    K481 = 481,\n    K482 = 482,\n    K483 = 483,\n    K484 = 484,\n    K485 = 485,\n    K486 = 486,\n    K487 = 487,\n    K488 = 488,\n    K489 = 489,\n    K490 = 490,\n    K491 = 491,\n    K492 = 492,\n    K493 = 493,\n    K494 = 494,\n    K495 = 495,\n    K496 = 496,\n    K497 = 497,\n    K498 = 498,\n    K499 = 499,\n    K500 = 500,\n    K501 = 501,\n    K502 = 502,\n    K503 = 503,\n    K504 = 504,\n    K505 = 505,\n    K506 = 506,\n    K507 = 507,\n    K508 = 508,\n    K509 = 509,\n    K510 = 510,\n    K511 = 511,\n    K512 = 512,\n    K513 = 513,\n    K514 = 514,\n    K515 = 515,\n    K516 = 516,\n    K517 = 517,\n    K518 = 518,\n    K519 = 519,\n    K520 = 520,\n    K521 = 521,\n    K522 = 522,\n    K523 = 523,\n    K524 = 524,\n    K525 = 525,\n    K526 = 526,\n    K527 = 527,\n    K528 = 528,\n    K529 = 529,\n    K530 = 530,\n    K531 = 531,\n    K532 = 532,\n    K533 = 533,\n    K534 = 534,\n    K535 = 535,\n    K536 = 536,\n    K537 = 537,\n    K538 = 538,\n    K539 = 539,\n    K540 = 540,\n    K541 = 541,\n    K542 = 542,\n    K543 = 543,\n    K544 = 544,\n    K545 = 545,\n    K546 = 546,\n    K547 = 547,\n    K548 = 548,\n    K549 = 549,\n    K550 = 550,\n    K551 = 551,\n    K552 = 552,\n    K553 = 553,\n    K554 = 554,\n    K555 = 555,\n    K556 = 556,\n    K557 = 557,\n    K558 = 558,\n    K559 = 559,\n    K560 = 560,\n    K561 = 561,\n    K562 = 562,\n    K563 = 563,\n    K564 = 564,\n    K565 = 565,\n    K566 = 566,\n    K567 = 567,\n    K568 = 568,\n    K569 = 569,\n    K570 = 570,\n    K571 = 571,\n    K572 = 572,\n    K573 = 573,\n    K574 = 574,\n    K575 = 575,\n    K576 = 576,\n    K577 = 577,\n    K578 = 578,\n    K579 = 579,\n    K580 = 580,\n    K581 = 581,\n    K582 = 582,\n    K583 = 583,\n    K584 = 584,\n    K585 = 585,\n    K586 = 586,\n    K587 = 587,\n    K588 = 588,\n    K589 = 589,\n    K590 = 590,\n    K591 = 591,\n    K592 = 592,\n    K593 = 593,\n    K594 = 594,\n    K595 = 595,\n    K596 = 596,\n    K597 = 597,\n    K598 = 598,\n    K599 = 599,\n    K600 = 600,\n    K601 = 601,\n    K602 = 602,\n    K603 = 603,\n    K604 = 604,\n    K605 = 605,\n    K606 = 606,\n    K607 = 607,\n    K608 = 608,\n    K609 = 609,\n    K610 = 610,\n    K611 = 611,\n    K612 = 612,\n    K613 = 613,\n    K614 = 614,\n    K615 = 615,\n    K616 = 616,\n    K617 = 617,\n    K618 = 618,\n    K619 = 619,\n    K620 = 620,\n    K621 = 621,\n    K622 = 622,\n    K623 = 623,\n    K624 = 624,\n    K625 = 625,\n    K626 = 626,\n    K627 = 627,\n    K628 = 628,\n    K629 = 629,\n    K630 = 630,\n    K631 = 631,\n    K632 = 632,\n    K633 = 633,\n    K634 = 634,\n    K635 = 635,\n    K636 = 636,\n    K637 = 637,\n    K638 = 638,\n    K639 = 639,\n    K640 = 640,\n    K641 = 641,\n    K642 = 642,\n    K643 = 643,\n    K644 = 644,\n    K645 = 645,\n    K646 = 646,\n    K647 = 647,\n    K648 = 648,\n    K649 = 649,\n    K650 = 650,\n    K651 = 651,\n    K652 = 652,\n    K653 = 653,\n    K654 = 654,\n    K655 = 655,\n    K656 = 656,\n    K657 = 657,\n    K658 = 658,\n    K659 = 659,\n    K660 = 660,\n    K661 = 661,\n    K662 = 662,\n    K663 = 663,\n    K664 = 664,\n    K665 = 665,\n    K666 = 666,\n    K667 = 667,\n    K668 = 668,\n    K669 = 669,\n    K670 = 670,\n    K671 = 671,\n    K672 = 672,\n    K673 = 673,\n    K674 = 674,\n    K675 = 675,\n    K676 = 676,\n    K677 = 677,\n    K678 = 678,\n    K679 = 679,\n    K680 = 680,\n    K681 = 681,\n    K682 = 682,\n    K683 = 683,\n    K684 = 684,\n    K685 = 685,\n    K686 = 686,\n    K687 = 687,\n    K688 = 688,\n    K689 = 689,\n    K690 = 690,\n    K691 = 691,\n    K692 = 692,\n    K693 = 693,\n    K694 = 694,\n    K695 = 695,\n    K696 = 696,\n    K697 = 697,\n    K698 = 698,\n    K699 = 699,\n    K700 = 700,\n    K701 = 701,\n    K702 = 702,\n    K703 = 703,\n    K704 = 704,\n    K705 = 705,\n    K706 = 706,\n    K707 = 707,\n    K708 = 708,\n    K709 = 709,\n    K710 = 710,\n    K711 = 711,\n    K712 = 712,\n    K713 = 713,\n    K714 = 714,\n    K715 = 715,\n    K716 = 716,\n    K717 = 717,\n    K718 = 718,\n    K719 = 719,\n    K720 = 720,\n    K721 = 721,\n    K722 = 722,\n    K723 = 723,\n    K724 = 724,\n    K725 = 725,\n    K726 = 726,\n    K727 = 727,\n    K728 = 728,\n    K729 = 729,\n    K730 = 730,\n    K731 = 731,\n    K732 = 732,\n    K733 = 733,\n    K734 = 734,\n    K735 = 735,\n    K736 = 736,\n    K737 = 737,\n    K738 = 738,\n    K739 = 739,\n    K740 = 740,\n    K741 = 741,\n    K742 = 742,\n    K743 = 743,\n    K744 = 744,\n    MWU = 745,\n    MWD = 746,\n    MWL = 747,\n    MWR = 748,\n    K749 = 749,\n    K750 = 750,\n    K751 = 751,\n    K752 = 752,\n    K753 = 753,\n    K754 = 754,\n    K755 = 755,\n    K756 = 756,\n    K757 = 757,\n    K758 = 758,\n    K759 = 759,\n    K760 = 760,\n    K761 = 761,\n    K762 = 762,\n    K763 = 763,\n    K764 = 764,\n    K765 = 765,\n    K766 = 766,\n    KeyMax = 767,\n}\n\nimpl KeyCode {\n    pub fn is_mod(self) -> bool {\n        use KeyCode::*;\n        matches!(\n            self,\n            LShift | RShift | LCtrl | RCtrl | LAlt | RAlt | LGui | RGui\n        )\n    }\n}\n\nuse core::fmt;\nimpl fmt::Display for KeyCode {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        match self {\n            KeyCode::Kb1 => write!(f, \"1\"),\n            KeyCode::Kb2 => write!(f, \"2\"),\n            KeyCode::Kb3 => write!(f, \"3\"),\n            KeyCode::Kb4 => write!(f, \"4\"),\n            KeyCode::Kb5 => write!(f, \"5\"),\n            KeyCode::Kb6 => write!(f, \"6\"),\n            KeyCode::Kb7 => write!(f, \"7\"),\n            KeyCode::Kb8 => write!(f, \"8\"),\n            KeyCode::Kb9 => write!(f, \"9\"),\n            KeyCode::Kb0 => write!(f, \"0\"),\n            KeyCode::LCtrl => write!(f, \"‹⎈\"),\n            KeyCode::RCtrl => write!(f, \"⎈›\"),\n            KeyCode::LShift => write!(f, \"‹⇧\"),\n            KeyCode::RShift => write!(f, \"⇧›\"),\n            KeyCode::LAlt => write!(f, \"‹⎇\"),\n            KeyCode::RAlt => write!(f, \"⎇›\"),\n            KeyCode::LGui => write!(f, \"‹◆\"),\n            KeyCode::RGui => write!(f, \"◆›\"),\n            KeyCode::Enter => write!(f, \"⏎\"),\n            KeyCode::Escape => write!(f, \"⎋\"),\n            KeyCode::BSpace => write!(f, \"␈\"),\n            KeyCode::Tab => write!(f, \"⭾\"),\n            KeyCode::Space => write!(f, \"␠\"),\n            KeyCode::Minus => write!(f, \"−\"),\n            KeyCode::Equal => write!(f, \"=\"),\n            KeyCode::LBracket => write!(f, \"[\"),\n            KeyCode::RBracket => write!(f, \"]\"),\n            KeyCode::Bslash => write!(f, \"\\\\\"),\n            KeyCode::NonUsHash => write!(f, \"#\"),\n            KeyCode::SColon => write!(f, \";\"),\n            KeyCode::Quote => write!(f, \"'\"),\n            KeyCode::Grave => write!(f, \"`\"),\n            KeyCode::Comma => write!(f, \",\"),\n            KeyCode::Dot => write!(f, \".\"),\n            KeyCode::Slash => write!(f, \"/\"),\n            KeyCode::CapsLock => write!(f, \"⇪\"),\n            KeyCode::Insert => write!(f, \"⎀\"),\n            KeyCode::Delete => write!(f, \"␡\"),\n            KeyCode::Home => write!(f, \"⇤\"),\n            KeyCode::End => write!(f, \"⇥\"),\n            KeyCode::PgDown => write!(f, \"⇟\"),\n            KeyCode::PgUp => write!(f, \"⇞\"),\n            KeyCode::Down => write!(f, \"▼\"),\n            KeyCode::Up => write!(f, \"▲\"),\n            KeyCode::Right => write!(f, \"▶\"),\n            KeyCode::Left => write!(f, \"◀\"),\n            KeyCode::NumLock => write!(f, \"⇭\"),\n            KeyCode::KpSlash => write!(f, \"🔢/\"),\n            KeyCode::KpAsterisk => write!(f, \"🔢*\"),\n            KeyCode::KpMinus => write!(f, \"🔢−\"),\n            KeyCode::KpPlus => write!(f, \"🔢+\"),\n            KeyCode::KpEnter => write!(f, \"🔢⏎\"),\n            KeyCode::Kp0 => write!(f, \"🔢0\"),\n            KeyCode::Kp1 => write!(f, \"🔢1\"),\n            KeyCode::Kp2 => write!(f, \"🔢2\"),\n            KeyCode::Kp3 => write!(f, \"🔢3\"),\n            KeyCode::Kp4 => write!(f, \"🔢4\"),\n            KeyCode::Kp5 => write!(f, \"🔢5\"),\n            KeyCode::Kp6 => write!(f, \"🔢6\"),\n            KeyCode::Kp7 => write!(f, \"🔢7\"),\n            KeyCode::Kp8 => write!(f, \"🔢8\"),\n            KeyCode::Kp9 => write!(f, \"🔢9\"),\n            KeyCode::KpDot => write!(f, \"🔢.\"),\n            KeyCode::KpEqual => write!(f, \"🔢=\"),\n            KeyCode::NonUsBslash => write!(f, \"|\"),\n            KeyCode::Application => write!(f, \"☰\"),\n            KeyCode::Mute => write!(f, \"🔇\"),\n            KeyCode::VolUp => write!(f, \"🔊\"),\n            KeyCode::VolDown => write!(f, \"🔉\"),\n            _ => write!(f, \"{self:?}\"),\n        }\n    }\n}\n"
  },
  {
    "path": "keyberon/src/layout/contextual_execution.rs",
    "content": "//! Information about what state the keyberon layout is in\r\n//! and handling conditional execution based on state.\r\n\r\nuse super::*;\r\n\r\n#[derive(Clone, Copy, Debug)]\r\npub(super) struct ContextualExecution {\r\n    /// Known pause case:\r\n    /// - When replicating output keys during chordv1 activation.\r\n    pub(super) pause_historical_keys_updates: bool,\r\n}\r\n\r\nimpl ContextualExecution {\r\n    pub(super) fn new() -> Self {\r\n        Self {\r\n            pause_historical_keys_updates: false,\r\n        }\r\n    }\r\n\r\n    /// Push into historical keys while checking the pause state.\r\n    pub(super) fn push_historical_key<T: Copy>(&self, h: &mut History<T>, e: T) {\r\n        if !self.pause_historical_keys_updates {\r\n            h.push_front(e);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "keyberon/src/layout.rs",
    "content": "//! Layout management.\n\n/// A procedural macro to generate [Layers](type.Layers.html)\n/// ## Syntax\n/// Items inside the macro are converted to Actions as such:\n/// - [`Action::KeyCode`]: Idents are automatically understood as keycodes: `A`, `RCtrl`, `Space`\n///     - Punctuation, numbers and other literals that aren't special to the rust parser are converted\n///       to KeyCodes as well: `,` becomes `KeyCode::Commma`, `2` becomes `KeyCode::Kb2`, `/` becomes `KeyCode::Slash`\n///     - Characters which require shifted keys are converted to `Action::MultipleKeyCodes(&[LShift, <character>])`:\n///       `!` becomes `Action::MultipleKeyCodes(&[LShift, Kb1])` etc\n///     - Characters special to the rust parser (parentheses, brackets, braces, quotes, apostrophes, underscores, backslashes and backticks)\n///       left alone cause parsing errors and as such have to be enclosed by apostrophes: `'['` becomes `KeyCode::LBracket`,\n///       `'\\''` becomes `KeyCode::Quote`, `'\\\\'` becomes `KeyCode::BSlash`\n/// - [`Action::NoOp`]: Lowercase `n`\n/// - [`Action::Trans`]: Lowercase `t`\n/// - [`Action::Layer`]: A number in parentheses: `(1)`, `(4 - 2)`, `(0x4u8 as usize)`\n/// - [`Action::MultipleActions`]: Actions in brackets: `[LCtrl S]`, `[LAlt LCtrl C]`, `[(2) B {Action::NoOp}]`\n/// - Other `Action`s: anything in braces (`{}`) is copied unchanged to the final layout - `{ Action::Custom(42) }`\n///   simply becomes `Action::Custom(42)`\n///\n/// **Important note**: comma (`,`) is a keycode on its own, and can't be used to separate keycodes as one would have\n/// to do when not using a macro.\npub use kanata_keyberon_macros::*;\n\nmod contextual_execution;\nuse contextual_execution::*;\n\nuse std::num::NonZeroU16;\n\nuse crate::chord::*;\nuse crate::key_code::KeyCode;\nuse crate::{action::*, multikey_buffer::MultiKeyBuffer};\nuse arraydeque::ArrayDeque;\nuse heapless::Vec;\n\nuse State::*;\n\n/// The coordinate type.\n/// First item is either 0 or 1 denoting real key or virtual key, respectively.\n/// Second item is the position in layout.\npub type KCoord = (u8, u16);\n\n/// The Layers type.\n///\n/// `Layers` type is an array of layers which contain the description\n/// of actions on the switch matrix. For example `layers[1][2][3]`\n/// corresponds to the key on the first layer, row 2, column 3.\n/// The generic parameters are in order: the number of columns, rows and layers,\n/// and the type contained in custom actions.\npub type Layers<'a, const C: usize, const R: usize, T = core::convert::Infallible> =\n    &'a [[[Action<'a, T>; C]; R]];\n\nconst QUEUE_SIZE: usize = 32;\npub type QueueLen = u8;\n\n#[test]\nfn check_queue_size() {\n    use std::convert::TryFrom;\n    let _v = QueueLen::try_from(QUEUE_SIZE).unwrap();\n}\n\n/// The current event queue.\n///\n/// Events can be retrieved by iterating over this struct and calling [Queued::event].\npub(crate) type Queue = ArrayDeque<Queued, QUEUE_SIZE, arraydeque::behavior::Wrapping>;\n\n/// A list of queued press events. Used for special handling of potentially multiple press events\n/// that occur during a Waiting event.\ntype PressedQueue = ArrayDeque<KCoord, QUEUE_SIZE>;\n\n/// The maximum number of actions that can be activated concurrently via chord decomposition or\n/// activation of multiple switch cases using fallthrough.\npub const ACTION_QUEUE_LEN: usize = 8;\n\n/// The queue is currently only used for chord decomposition when a longer chord does not result in\n/// an action, but splitting it into smaller chords would. The buffer size of 8 should be more than\n/// enough for real world usage, but if one wanted to be extra safe, this should be ChordKeys::BITS\n/// since that should guarantee that all potentially queueable actions can fit.\ntype ActionQueue<'a, T> =\n    ArrayDeque<QueuedAction<'a, T>, ACTION_QUEUE_LEN, arraydeque::behavior::Wrapping>;\ntype Delay = u16;\npub(crate) type QueuedAction<'a, T> = Option<(KCoord, Delay, &'a Action<'a, T>, LayerStack)>;\n\npub const REAL_KEY_ROW: u8 = 0;\n\nconst HISTORICAL_EVENT_LEN: usize = 8;\nconst EXTRA_WAITING_LEN: usize = 8;\n#[test]\nfn extra_waiting_size_constraint() {\n    assert!(EXTRA_WAITING_LEN < i8::MAX as usize);\n}\n\n/// The layout manager. It takes `Event`s and `tick`s as input, and\n/// generate keyboard reports.\npub struct Layout<'a, const C: usize, const R: usize, T = core::convert::Infallible>\nwhere\n    T: 'a + std::fmt::Debug,\n{\n    /// Fallback for transparent keys inside actions that are on `default_layer`.\n    pub src_keys: &'a [Action<'a, T>; C],\n    pub layers: &'a [[[Action<'a, T>; C]; R]],\n    pub default_layer: usize,\n    /// Key states.\n    pub states: Vec<State<'a, T>, 64>,\n    pub waiting: Option<WaitingState<'a, T>>,\n    pub extra_waiting:\n        ArrayDeque<WaitingState<'a, T>, EXTRA_WAITING_LEN, arraydeque::behavior::Wrapping>,\n    pub tap_dance_eager: Option<TapDanceEagerState<'a, T>>,\n    pub queue: Queue,\n    pub oneshot: OneShotState,\n    pub keys_to_suppress_for_one_cycle: Vec<KeyCode, 8>,\n    pub last_press_tracker: LastPressTracker,\n    pub active_sequences: ArrayDeque<SequenceState<'a, T>, 4, arraydeque::behavior::Wrapping>,\n    pub action_queue: ActionQueue<'a, T>,\n    pub rpt_action: Option<&'a Action<'a, T>>,\n    pub historical_keys: History<KeyCode>,\n    pub historical_inputs: History<KCoord>,\n    pub quick_tap_hold_timeout: bool,\n    /// If a different key was pressed within this many ticks before a HoldTap key,\n    /// immediately resolve as tap (typing streak detection). 0 = disabled.\n    pub tap_hold_require_prior_idle: u16,\n    pub chords_v2: Option<ChordsV2<'a, T>>,\n    rpt_multikey_key_buffer: MultiKeyBuffer<'a, T>,\n    trans_resolution_behavior_v2: bool,\n    delegate_to_first_layer: bool,\n    contextual_execution: ContextualExecution,\n    /// Tracks tap-hold activation events (hold/tap resolved).\n    /// Only stores data when the `tap_hold_tracker` feature is enabled;\n    /// otherwise this is a zero-sized no-op.\n    pub tap_hold_tracker: crate::tap_hold_tracker::TapHoldTracker,\n}\n\npub use crate::tap_hold_tracker::{HoldActivatedInfo, TapActivatedInfo};\n\npub struct History<T> {\n    events: ArrayDeque<T, HISTORICAL_EVENT_LEN, arraydeque::behavior::Wrapping>,\n    ticks_since_occurrences: ArrayDeque<u16, HISTORICAL_EVENT_LEN, arraydeque::behavior::Wrapping>,\n}\n\n#[derive(Copy, Clone)]\npub struct HistoricalEvent<T> {\n    pub event: T,\n    pub ticks_since_occurrence: u16,\n}\n\nimpl<T> History<T>\nwhere\n    T: Copy,\n{\n    fn new() -> Self {\n        Self {\n            ticks_since_occurrences: ArrayDeque::new(),\n            events: ArrayDeque::new(),\n        }\n    }\n\n    fn tick_hist(&mut self) {\n        let ticks = self.ticks_since_occurrences.as_uninit_slice_mut();\n        for tick_count in ticks {\n            unsafe {\n                *tick_count.assume_init_mut() = tick_count.assume_init().saturating_add(1);\n            }\n        }\n    }\n\n    fn push_front(&mut self, event: T) {\n        self.ticks_since_occurrences.push_front(0);\n        self.events.push_front(event);\n    }\n\n    pub fn iter_hevents(&self) -> impl Iterator<Item = HistoricalEvent<T>> + '_ + Clone {\n        self.events\n            .iter()\n            .copied()\n            .zip(self.ticks_since_occurrences.iter().copied())\n            .map(|(event, ticks_since_occurrence)| HistoricalEvent {\n                event,\n                ticks_since_occurrence,\n            })\n    }\n}\n\n/// An event on the key matrix.\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\npub enum Event {\n    /// Press event with coordinates (i, j).\n    Press(u8, u16),\n    /// Release event with coordinates (i, j).\n    Release(u8, u16),\n}\nimpl Event {\n    /// Returns the coordinates (i, j) of the event.\n    pub fn coord(self) -> KCoord {\n        match self {\n            Event::Press(i, j) => (i, j),\n            Event::Release(i, j) => (i, j),\n        }\n    }\n\n    /// Transforms the coordinates of the event.\n    ///\n    /// # Example\n    ///\n    /// ```\n    /// # use kanata_keyberon::layout::Event;\n    /// assert_eq!(\n    ///     Event::Press(3, 10),\n    ///     Event::Press(3, 1).transform(|i, j| (i, 11 - j)),\n    /// );\n    /// ```\n    pub fn transform(self, f: impl FnOnce(u8, u16) -> KCoord) -> Self {\n        match self {\n            Event::Press(i, j) => {\n                let (i, j) = f(i, j);\n                Event::Press(i, j)\n            }\n            Event::Release(i, j) => {\n                let (i, j) = f(i, j);\n                Event::Release(i, j)\n            }\n        }\n    }\n\n    /// Returns `true` if the event is a key press.\n    pub fn is_press(self) -> bool {\n        match self {\n            Event::Press(..) => true,\n            Event::Release(..) => false,\n        }\n    }\n\n    /// Returns `true` if the event is a key release.\n    pub fn is_release(self) -> bool {\n        match self {\n            Event::Release(..) => true,\n            Event::Press(..) => false,\n        }\n    }\n}\n\n/// Event from custom action.\n#[derive(Debug, Default, PartialEq, Eq)]\npub enum CustomEvent<'a, T: 'a> {\n    /// No custom action.\n    #[default]\n    NoEvent,\n    /// The given custom action key is pressed.\n    Press(&'a T),\n    /// The given custom action key is released.\n    Release(&'a T),\n}\nimpl<T> CustomEvent<'_, T> {\n    /// Update an event according to a new event.\n    ///\n    ///The event can only be modified in the order `NoEvent < Press <\n    /// Release`\n    fn update(&mut self, e: Self) {\n        use CustomEvent::*;\n        match (&e, &self) {\n            (Release(_), NoEvent) | (Release(_), Press(_)) => *self = e,\n            (Press(_), NoEvent) => *self = e,\n            _ => (),\n        }\n    }\n}\n\n/// Metadata about normal key flags.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]\npub struct NormalKeyFlags(pub u8);\n\npub const NORMAL_KEY_FLAG_CLEAR_ON_NEXT_ACTION: u8 = 0x01;\npub const NORMAL_KEY_FLAG_CLEAR_ON_NEXT_RELEASE: u8 = 0x02;\n\nimpl NormalKeyFlags {\n    pub fn nkf_clear_on_next_action(self) -> bool {\n        (self.0 & NORMAL_KEY_FLAG_CLEAR_ON_NEXT_ACTION) == NORMAL_KEY_FLAG_CLEAR_ON_NEXT_ACTION\n    }\n    pub fn nkf_clear_on_next_release(self) -> bool {\n        (self.0 & NORMAL_KEY_FLAG_CLEAR_ON_NEXT_RELEASE) == NORMAL_KEY_FLAG_CLEAR_ON_NEXT_RELEASE\n    }\n}\n\n#[derive(Debug, Eq, PartialEq)]\npub enum State<'a, T: 'a> {\n    NormalKey {\n        keycode: KeyCode,\n        coord: KCoord,\n        flags: NormalKeyFlags,\n    },\n    LayerModifier {\n        value: usize,\n        coord: KCoord,\n    },\n    Custom {\n        value: &'a T,\n        coord: KCoord,\n    },\n    FakeKey {\n        keycode: KeyCode,\n    }, // Fake key event for sequences\n    RepeatingSequence {\n        sequence: &'a &'a [SequenceEvent<'a, T>],\n        coord: KCoord,\n    },\n    SeqCustomPending(&'a T),\n    SeqCustomActive(&'a T),\n    Tombstone,\n    NoOpInput {\n        coord: KCoord,\n    },\n}\nimpl<T> Copy for State<'_, T> {}\nimpl<T> Clone for State<'_, T> {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\nimpl<'a, T: 'a> State<'a, T> {\n    pub fn keycode(&self) -> Option<KeyCode> {\n        match self {\n            NormalKey { keycode, .. } => Some(*keycode),\n            FakeKey { keycode } => Some(*keycode),\n            _ => None,\n        }\n    }\n    pub fn coord(&self) -> Option<KCoord> {\n        match self {\n            NormalKey { coord, .. }\n            | LayerModifier { coord, .. }\n            | Custom { coord, .. }\n            | NoOpInput { coord }\n            | RepeatingSequence { coord, .. } => Some(*coord),\n            _ => None,\n        }\n    }\n    fn keycode_in_coords(&self, coords: &OneShotCoords) -> Option<KeyCode> {\n        match self {\n            NormalKey { keycode, coord, .. } => {\n                if coords.contains(coord) {\n                    Some(*keycode)\n                } else {\n                    None\n                }\n            }\n            _ => None,\n        }\n    }\n    /// Returns None if the key has been released and Some otherwise.\n    pub fn release(&self, c: KCoord, custom: &mut CustomEvent<'a, T>) -> Option<Self> {\n        match *self {\n            NormalKey { coord, .. }\n            | LayerModifier { coord, .. }\n            | RepeatingSequence { coord, .. }\n            | NoOpInput { coord }\n                if coord == c =>\n            {\n                None\n            }\n            Custom { value, coord } if coord == c => {\n                custom.update(CustomEvent::Release(value));\n                None\n            }\n            _ => Some(*self),\n        }\n    }\n    pub fn release_state(&self, s: ReleasableState) -> Option<Self> {\n        match (*self, s) {\n            (\n                NormalKey { keycode: k1, .. } | FakeKey { keycode: k1 },\n                ReleasableState::KeyCode(k2),\n            ) => {\n                if k1 == k2 {\n                    None\n                } else {\n                    Some(*self)\n                }\n            }\n            (LayerModifier { value: l1, .. }, ReleasableState::Layer(l2)) => {\n                if l1 == l2 {\n                    None\n                } else {\n                    Some(*self)\n                }\n            }\n            _ => Some(*self),\n        }\n    }\n    fn seq_release(&self, kc: KeyCode) -> Option<Self> {\n        match *self {\n            FakeKey { keycode, .. } if keycode == kc => None,\n            _ => Some(*self),\n        }\n    }\n    fn get_layer(&self) -> Option<usize> {\n        match self {\n            LayerModifier { value, .. } => Some(*value),\n            _ => None,\n        }\n    }\n    pub fn clear_on_next_release(&self) -> bool {\n        match self {\n            NormalKey { flags, .. } => {\n                (flags.0 & NORMAL_KEY_FLAG_CLEAR_ON_NEXT_RELEASE)\n                    == NORMAL_KEY_FLAG_CLEAR_ON_NEXT_RELEASE\n            }\n            _ => false,\n        }\n    }\n}\n\n#[derive(Copy, Clone, Debug)]\npub(crate) struct TapDanceState<'a, T: 'a> {\n    actions: &'a [&'a Action<'a, T>],\n    timeout: u16,\n    num_taps: u16,\n}\n\n#[derive(Copy, Clone, Debug)]\npub struct TapDanceEagerState<'a, T: 'a> {\n    coord: KCoord,\n    actions: &'a [&'a Action<'a, T>],\n    timeout: u16,\n    orig_timeout: u16,\n    num_taps: u16,\n}\n\nimpl<T> TapDanceEagerState<'_, T> {\n    fn tick_tde(&mut self) {\n        self.timeout = self.timeout.saturating_sub(1);\n    }\n\n    fn is_expired(&self) -> bool {\n        self.timeout == 0 || usize::from(self.num_taps) >= self.actions.len()\n    }\n\n    fn set_expired(&mut self) {\n        self.timeout = 0;\n    }\n\n    fn incr_taps(&mut self) {\n        self.num_taps += 1;\n        self.timeout = self.orig_timeout;\n    }\n}\n\n#[derive(Debug)]\npub(crate) enum WaitingConfig<'a, T: 'a + std::fmt::Debug> {\n    HoldTap(HoldTapConfig<'a>),\n    TapDance(TapDanceState<'a, T>),\n    Chord(&'a ChordsGroup<'a, T>),\n}\n\n#[derive(Debug)]\npub struct WaitingState<'a, T: 'a + std::fmt::Debug> {\n    coord: KCoord,\n    timeout: u16,\n    on_press_reset_timeout_to: Option<NonZeroU16>,\n    delay: u16,\n    ticks: u16,\n    hold: &'a Action<'a, T>,\n    tap: &'a Action<'a, T>,\n    timeout_action: &'a Action<'a, T>,\n    config: WaitingConfig<'a, T>,\n    layer_stack: LayerStack,\n    prev_queue_len: QueueLen,\n}\n\n/// Actions that can be triggered for a key configured for HoldTap.\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\npub enum WaitingAction {\n    /// Trigger the holding event.\n    Hold,\n    /// Trigger the tapping event.\n    Tap,\n    /// Trigger the timeout event.\n    Timeout,\n    /// Drop this event. It will act as if no key was pressed.\n    NoOp,\n}\n\nimpl<'a, T: std::fmt::Debug> WaitingState<'a, T> {\n    fn tick_wt(\n        &mut self,\n        queued: &mut Queue,\n        action_queue: &mut ActionQueue<'a, T>,\n    ) -> Option<(WaitingAction, Option<PressedQueue>)> {\n        self.timeout = self.timeout.saturating_sub(1);\n        self.ticks = self.ticks.saturating_add(1);\n        let mut pq = None;\n        let (ret, cfg_change) = match self.config {\n            WaitingConfig::HoldTap(htc) => (self.handle_hold_tap(htc, queued), None),\n            WaitingConfig::TapDance(ref tds) => {\n                let (ret, num_taps) =\n                    self.handle_tap_dance(tds.num_taps, tds.actions.len(), queued);\n                self.prev_queue_len = queued.len() as u8;\n                // Due to ownership issues, handle_tap_dance can't contain all of the necessary\n                // logic.\n                if ret.is_some() {\n                    let idx = core::cmp::min(num_taps.into(), tds.actions.len()).saturating_sub(1);\n                    self.tap = tds.actions[idx];\n                }\n                if num_taps > tds.num_taps {\n                    self.timeout = tds.timeout;\n                }\n                (\n                    ret,\n                    Some(WaitingConfig::TapDance(TapDanceState { num_taps, ..*tds })),\n                )\n            }\n            WaitingConfig::Chord(config) => {\n                if let Some((ret, action, cpq)) = self.handle_chord(config, queued, action_queue) {\n                    self.tap = action;\n                    pq = Some(cpq);\n                    (Some(ret), None)\n                } else {\n                    (None, None)\n                }\n            }\n        };\n        if let Some(cfg) = cfg_change {\n            self.config = cfg;\n        }\n        ret.map(|v| (v, pq))\n    }\n\n    fn handle_hold_tap(&mut self, cfg: HoldTapConfig, queued: &Queue) -> Option<WaitingAction> {\n        if queued.len() as u8 == self.prev_queue_len && self.timeout > 0 {\n            // Fast path: nothing has changed since last tick and we haven't timed out yet.\n            return None;\n        }\n        if let Some(timeout_reset_val) = self.on_press_reset_timeout_to {\n            if let Some(last) = queued.iter().next_back() {\n                if last.event.is_press() {\n                    self.timeout = timeout_reset_val.into();\n                }\n            }\n        }\n        self.prev_queue_len = queued.len() as u8;\n        let mut skip_timeout = false;\n        match cfg {\n            HoldTapConfig::Default => (),\n            HoldTapConfig::HoldOnOtherKeyPress => {\n                if queued.iter().any(|s| s.event.is_press()) {\n                    return Some(WaitingAction::Hold);\n                }\n            }\n            HoldTapConfig::Order { buffer, .. } => {\n                // Like PermissiveHold: if another key was pressed AND released\n                // (while modifier is still held), resolve as Hold.\n                // If modifier is released first, the fallthrough below handles Tap.\n                //\n                // Buffer: key presses that occurred within `buffer` ticks of the\n                // hold-tap key press are ignored by release-order logic, allowing\n                // fast typing to resolve as Tap regardless of release order.\n                let mut queued = queued.iter();\n                while let Some(q) = queued.next() {\n                    if q.event.is_press() {\n                        // Elapsed ticks since this key entered the queue, compared against buffer window.\n                        let press_tick = self.ticks.saturating_sub(q.since);\n                        if press_tick < buffer {\n                            continue;\n                        }\n                        let (i, j) = q.event.coord();\n                        let target = Event::Release(i, j);\n                        if queued.clone().any(|q| q.event == target) {\n                            return Some(WaitingAction::Hold);\n                        }\n                    }\n                }\n            }\n            HoldTapConfig::PermissiveHold => {\n                let mut queued = queued.iter();\n                while let Some(q) = queued.next() {\n                    if q.event.is_press() {\n                        let (i, j) = q.event.coord();\n                        let target = Event::Release(i, j);\n                        if queued.clone().any(|q| q.event == target) {\n                            return Some(WaitingAction::Hold);\n                        }\n                    }\n                }\n            }\n            HoldTapConfig::Custom(func) => {\n                let (waiting_action, local_skip) = (func)(QueuedIter(queued.iter()), self.coord);\n                if waiting_action.is_some() {\n                    return waiting_action;\n                }\n                skip_timeout = local_skip;\n            }\n        }\n        if let Some(&Queued {\n            since: since_release,\n            ..\n        }) = queued\n            .iter()\n            .find(|s| self.is_corresponding_release(&s.event))\n        {\n            if self.timeout >= self.delay.saturating_sub(since_release) {\n                Some(WaitingAction::Tap)\n            } else {\n                Some(WaitingAction::Timeout)\n            }\n        } else if self.timeout == 0 && (!skip_timeout) {\n            Some(WaitingAction::Timeout)\n        } else {\n            None\n        }\n    }\n\n    fn handle_tap_dance(\n        &self,\n        num_taps: u16,\n        max_taps: usize,\n        queued: &mut Queue,\n    ) -> (Option<WaitingAction>, u16) {\n        if queued.len() as u8 == self.prev_queue_len && self.timeout > 0 {\n            // Fast path: nothing has changed since last tick and we haven't timed out yet.\n            return (None, num_taps);\n        }\n        // Evict events with the same coordinates except for the final release. E.g. if 3 taps have\n        // occurred, this will remove all `Press` events and 2 `Release` events. This is done so\n        // that the state machine processes the entire tap dance sequence as a single press and\n        // single release regardless of how many taps were actually done.\n        let evict_same_coord_events = |num_taps: u16, queued: &mut Queue| {\n            let mut releases_to_remove = num_taps.saturating_sub(1);\n            queued.retain(|s| {\n                let mut do_retain = true;\n                if self.is_corresponding_release(&s.event) {\n                    if releases_to_remove > 0 {\n                        do_retain = false;\n                        releases_to_remove = releases_to_remove.saturating_sub(1)\n                    }\n                } else if self.is_corresponding_press(&s.event) {\n                    do_retain = false;\n                }\n                do_retain\n            });\n        };\n        if self.timeout == 0 {\n            evict_same_coord_events(num_taps, queued);\n            return (Some(WaitingAction::Tap), num_taps);\n        }\n        // Get the number of sequential taps for this tap-dance key. If a different key was\n        // pressed, activate a tap-dance action.\n        match queued.iter().try_fold(1, |same_tap_count, s| {\n            if self.is_corresponding_press(&s.event) {\n                Ok(same_tap_count + 1)\n            } else if matches!(s.event, Event::Press(..)) {\n                Err((same_tap_count, ()))\n            } else {\n                Ok(same_tap_count)\n            }\n        }) {\n            Ok(num_taps) if usize::from(num_taps) >= max_taps => {\n                evict_same_coord_events(num_taps, queued);\n                (Some(WaitingAction::Tap), num_taps)\n            }\n            Ok(num_taps) => (None, num_taps),\n            Err((num_taps, _)) => {\n                evict_same_coord_events(num_taps, queued);\n                (Some(WaitingAction::Tap), num_taps)\n            }\n        }\n    }\n\n    fn handle_chord(\n        &mut self,\n        config: &'a ChordsGroup<'a, T>,\n        queued: &mut Queue,\n        action_queue: &mut ActionQueue<'a, T>,\n    ) -> Option<(WaitingAction, &'a Action<'a, T>, PressedQueue)> {\n        if queued.len() as u8 == self.prev_queue_len && self.timeout.saturating_sub(self.delay) > 0\n        {\n            // Fast path: nothing has changed since last tick and we haven't timed out yet.\n            return None;\n        }\n        self.prev_queue_len = queued.len() as u8;\n\n        // need to keep track of how many Press events we handled so we can filter them out later\n        let mut handled_press_events = 0;\n        let start_chord_coord = self.coord;\n        let mut released_coord = None;\n\n        // Compute the set of chord keys that are currently pressed\n        // `Ok` when chording mode may continue\n        // `Err` when it should end for various reasons\n        let active = queued\n            .iter()\n            .try_fold(config.get_keys(self.coord).unwrap_or(0), |active, s| {\n                if self.delay.saturating_sub(s.since) > self.timeout {\n                    Ok(active)\n                } else if let Some(chord_keys) = config.get_keys(s.event.coord()) {\n                    match s.event {\n                        Event::Press(_, _) => {\n                            handled_press_events += 1;\n                            Ok(active | chord_keys)\n                        }\n                        Event::Release(i, j) => {\n                            // release chord quickly by changing the coordinate to the released\n                            // key, to be consistent with chord decomposition behaviour.\n                            released_coord = Some((i, j));\n                            Err(active)\n                        }\n                    }\n                } else if matches!(s.event, Event::Press(..)) {\n                    Err(active) // pressed a non-chord key, abort\n                } else {\n                    Ok(active)\n                }\n            })\n            .and_then(|active| {\n                if self.timeout.saturating_sub(self.delay) == 0 {\n                    Err(active) // timeout expired, abort\n                } else {\n                    Ok(active)\n                }\n            });\n\n        let res = match active {\n            Ok(active) => {\n                // Chording mode still active, only trigger action if it's unambiguous\n                if let Some(action) = config.get_chord_if_unambiguous(active) {\n                    if let Some(coord) = released_coord {\n                        self.coord = coord;\n                    }\n                    (WaitingAction::Tap, action)\n                } else {\n                    return None; // nothing to do yet, we'll check back later\n                }\n            }\n            Err(active) => {\n                // Abort chording mode. Trigger a chord action if there is one.\n                if let Some(action) = config.get_chord(active) {\n                    if let Some(coord) = released_coord {\n                        self.coord = coord;\n                    }\n                    (WaitingAction::Tap, action)\n                } else {\n                    self.decompose_chord_into_action_queue(config, queued, action_queue);\n                    (WaitingAction::NoOp, &Action::NoOp)\n                }\n            }\n        };\n\n        let mut pq = PressedQueue::new();\n        let _ = pq.push_back(start_chord_coord);\n\n        // Return all press events that were logically handled by this chording event\n        queued.retain(|s| {\n            if self.delay.saturating_sub(s.since) > self.timeout {\n                true\n            } else if matches!(s.event, Event::Press(i, j) if config.get_keys((i, j)).is_some())\n                && handled_press_events > 0\n            {\n                handled_press_events -= 1;\n                let _ = pq.push_back(s.event().coord());\n                false\n            } else {\n                true\n            }\n        });\n\n        Some((res.0, res.1, pq))\n    }\n\n    fn decompose_chord_into_action_queue(\n        &mut self,\n        config: &'a ChordsGroup<'a, T>,\n        queued: &Queue,\n        action_queue: &mut ActionQueue<'a, T>,\n    ) {\n        let mut chord_key_order = [0u128; ChordKeys::BITS as usize];\n\n        // Default to the initial coordinate. But if a key is released early (before the timeout\n        // occurs), use that key for action releases. That way the chord is released as early as\n        // possible.\n        let mut default_associated_coord = self.coord;\n\n        let starting_mask = config.get_keys(self.coord).unwrap_or(0);\n        let mut mask_bits_set = 1;\n        chord_key_order[0] = starting_mask;\n        let _ = queued.iter().try_fold(starting_mask, |active, s| {\n            if self.delay.saturating_sub(s.since) > self.timeout {\n                Ok(active)\n            } else if let Some(chord_keys) = config.get_keys(s.event.coord()) {\n                match s.event {\n                    Event::Press(..) => {\n                        if active | chord_keys != active {\n                            chord_key_order[mask_bits_set] = chord_keys;\n                            mask_bits_set += 1;\n                        }\n                        Ok(active | chord_keys)\n                    }\n                    Event::Release(i, j) => {\n                        default_associated_coord = (i, j);\n                        Err(active) // released a chord key, abort\n                    }\n                }\n            } else if matches!(s.event, Event::Press(..)) {\n                Err(active) // pressed a non-chord key, abort\n            } else {\n                Ok(active)\n            }\n        });\n        let len = mask_bits_set;\n        let chord_keys = &chord_key_order[0..len];\n\n        let get_coord_for_chord = |mask: ChordKeys| -> (u8, u16) {\n            if config.get_keys(default_associated_coord).unwrap_or(0) & mask > 0 {\n                // This might be a release.\n                // If it belongs to the associated action, prefer to use it.\n                return default_associated_coord;\n            }\n            if self.coord != default_associated_coord\n                && config.get_keys(self.coord).unwrap_or(0) & mask > 0\n            {\n                // The first coordinate not in queued\n                // so must be explicitly checked if it is not the default coord.\n                return self.coord;\n            }\n            queued\n                .iter()\n                .find_map(|q| {\n                    let coord = q.event.coord();\n                    let qmask = config.get_keys(coord).unwrap_or(0);\n                    match qmask & mask {\n                        0 => None,\n                        _ => Some(coord),\n                    }\n                })\n                .unwrap_or(default_associated_coord)\n        };\n\n        // Compute actions using the following description:\n        //\n        // Let's say we have a chord group with keys (h j k l). The full set (h j k l) is not\n        // defined with an action, but the user has pressed all of h, j, k, l in the listed order,\n        // so now kanata needs to break down the combo. How should it work?\n        //\n        // Figuratively \"release\" keys in reverse-temporal order until a valid chord is found. So\n        // first, l is figuratively released, and if (h j k) is a valid chord, that action will\n        // activate. If (l) by itself is valid that then activates after (h j k) is finished.\n        //\n        // In the case that (h j k) is not a chord, instead activate (h j). If that is a valid\n        // chord, then try to activate (k l) together, and if not, evaluate (k), then (l).\n        //\n        // If (h j) is not a valid chord, try to activate (h). Then try to activate (j k l). If\n        // that is invalid, try (j k), then (j). If (j k) is valid, try (l). If (j) is valid, try\n        // (k l). If (k l) is invalid, try (k) then (l).\n        //\n        // The possible executions, listed in descending order of priority (first listed has\n        // highest execution priority) are:\n        // (h   j   k   l)\n        // (h   j   k) (l)\n        // (h   j) (k   l)\n        // (h   j) (k) (l)\n        // (h) (j   k   l)\n        // (h) (j   k) (l)\n        // (h) (j) (k   l)\n        // (h) (j) (k) (l)\n\n        let mut start = 0;\n        let mut end = len;\n        let delay = self.delay + self.ticks;\n        while start < len {\n            let sub_chord = &chord_keys[start..end];\n            let chord_mask = sub_chord\n                .iter()\n                .copied()\n                .reduce(|acc, e| acc | e)\n                .unwrap_or(0);\n            if let Some(action) = config.get_chord(chord_mask) {\n                let coord = get_coord_for_chord(chord_mask);\n                // Note on LayerStack being default (empty):\n                // A chordv1 allows transparency, so this is broken right now,\n                // and could result in an infinite loop, because\n                // the queue activating code falls back to top-level resolution order\n                // if the stored layer stack is empty.\n                let _ = action_queue.push_back(Some((coord, delay, action, Default::default())));\n            } else {\n                end -= 1;\n                // shrink from end until something is found, or have checked up to and including\n                // the individual start key.\n                while end > start {\n                    let sub_chord = &chord_keys[start..end];\n                    let chord_mask = sub_chord\n                        .iter()\n                        .copied()\n                        .reduce(|acc, e| acc | e)\n                        .unwrap_or(0);\n                    if let Some(action) = config.get_chord(chord_mask) {\n                        let coord = get_coord_for_chord(chord_mask);\n                        let _ = action_queue.push_back(Some((\n                            coord,\n                            delay,\n                            action,\n                            Default::default(),\n                        )));\n                        break;\n                    }\n                    end -= 1;\n                }\n            }\n            start = if end <= start { start + 1 } else { end };\n            end = len;\n        }\n    }\n\n    fn is_corresponding_release(&self, event: &Event) -> bool {\n        matches!(event, Event::Release(i, j) if (*i, *j) == self.coord)\n    }\n\n    fn is_corresponding_press(&self, event: &Event) -> bool {\n        matches!(event, Event::Press(i, j) if (*i, *j) == self.coord)\n    }\n}\n\ntype OneShotCoords = ArrayDeque<KCoord, ONE_SHOT_MAX_ACTIVE, arraydeque::behavior::Wrapping>;\n\n#[derive(Debug, Copy, Clone)]\npub struct SequenceState<'a, T: 'a> {\n    cur_event: Option<SequenceEvent<'a, T>>,\n    delay: u32,              // Keeps track of SequenceEvent::Delay time remaining\n    tapped: Option<KeyCode>, // Keycode of a key that should be released at the next tick\n    remaining_events: &'a [SequenceEvent<'a, T>],\n}\n\ntype ReleasedOneShotKeys = Vec<KCoord, ONE_SHOT_MAX_ACTIVE>;\n\n// Using a u16 for indices instead of usize.\n// Need to check against this value in code that creates layers.\npub const MAX_LAYERS: usize = 60000;\n\n// Use heapless Vec for perf - avoid pointer indirections.\n// Use u16 for more efficient cache. 12*u16 = 3*u64 = 24 bytes.\n// Then there is a usize for the length, totaling 32 bytes.\n// Cache line is typically 64 bytes, so this takes half a cache line.\n// Above all assumes x86-64.\npub const MAX_ACTIVE_LAYERS: usize = 12;\n\n/// Because we only need a read-only stack and efficient iteration over contained\n/// items, LayerStack items are in reverse order over usual back-to-front order\n/// of items in array-based stack implementations.\ntype LayerStack = Vec<u16, MAX_ACTIVE_LAYERS>;\n\n/// Contains the state of one shot keys that are currently active.\npub struct OneShotState {\n    /// KCoordinates of one shot keys that are active\n    pub keys: ArrayDeque<KCoord, ONE_SHOT_MAX_ACTIVE, arraydeque::behavior::Wrapping>,\n    /// KCoordinates of one shot keys that have been released\n    pub released_keys: ArrayDeque<KCoord, ONE_SHOT_MAX_ACTIVE, arraydeque::behavior::Wrapping>,\n    /// Fix #1874:\n    /// Represents the one-shot state that must not be released on physical key release.\n    /// Consider the case `(multi a (one-shot 100 b))`,\n    /// when only tracking coordinates (which this used to in the past),\n    /// the `a` would not release a even upon releasing the action key\n    /// because its state falls on the same coordinate as the oneshot b.\n    /// The fix is to explicitly know which key/layer states\n    /// — which are the only actions allowed within one-shot —\n    /// should be kept, and normally release others.\n    pub state_to_retain_on_release:\n        ArrayDeque<OneShotRetainableState, ONE_SHOT_MAX_ACTIVE, arraydeque::behavior::Wrapping>,\n    /// Used to keep track of already-pressed keys for the release variants.\n    pub other_pressed_keys: ArrayDeque<KCoord, ONE_SHOT_MAX_ACTIVE, arraydeque::behavior::Wrapping>,\n    /// Timeout (ms) after which all one shot keys expire\n    pub timeout: u16,\n    /// Contains the end config of the most recently pressed one shot key\n    pub end_config: OneShotEndConfig,\n    /// Marks if release of the one shot keys should be done on the next tick\n    pub release_on_next_tick: bool,\n    /// The number of ticks to delay the release of the one-shot activation\n    /// for EndOnFirstPress(OrRepress).\n    /// This used to not exist and effectively be 1 (1ms),\n    /// but that is too short for some environments.\n    /// When too short, applications or desktop environments process\n    /// the key release before the next press,\n    /// even if temporally the release was sent after.\n    pub pause_input_processing_delay: u16,\n    /// If pause_input_processing_delay is used, this will be >0,\n    /// meaning input processing should be paused to prevent extra presses\n    /// from coming in while OneShot has not yet been released.\n    ///\n    /// May also be reused for other purposes...\n    pub pause_input_processing_ticks: u16,\n\n    /// Number of ticks to ignore press events for.\n    pub ticks_to_ignore_events: u16,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]\npub enum OneShotRetainableState {\n    KeyCode { coord: KCoord, kc: KeyCode },\n    Layer { coord: KCoord, layer: u16 },\n}\n\nimpl OneShotRetainableState {\n    pub fn coord(&self) -> KCoord {\n        match self {\n            Self::KeyCode { coord, .. } | Self::Layer { coord, .. } => *coord,\n        }\n    }\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]\nenum OneShotHandlePressKey {\n    OneShotKey(KCoord),\n    Other(KCoord),\n}\n\nimpl OneShotState {\n    fn tick_osh(&mut self) -> Option<ReleasedOneShotKeys> {\n        if self.keys.is_empty() {\n            return None;\n        }\n        self.ticks_to_ignore_events = self.ticks_to_ignore_events.saturating_sub(1);\n        self.timeout = self.timeout.saturating_sub(1);\n        if self.release_on_next_tick || self.timeout == 0 {\n            self.release_on_next_tick = false;\n            self.timeout = 0;\n            self.pause_input_processing_ticks = 0;\n            self.ticks_to_ignore_events = 0;\n            self.keys.clear();\n            self.other_pressed_keys.clear();\n            self.state_to_retain_on_release.clear();\n            Some(self.released_keys.drain(..).collect())\n        } else {\n            None\n        }\n    }\n\n    /// Returns the coordinates associated with an active oneshot.\n    /// The intended use of this is for `rpt-any` to be able to repeat\n    /// the output chord of a one-shot+normal key,\n    /// which are two separate actions that users\n    /// would likely want to combine under a repeat condition.\n    fn handle_press(&mut self, key: OneShotHandlePressKey) -> OneShotCoords {\n        let mut oneshot_coords = ArrayDeque::new();\n        if self.keys.is_empty() || self.ticks_to_ignore_events > 0 {\n            return oneshot_coords;\n        }\n        match key {\n            OneShotHandlePressKey::OneShotKey(pressed_coord) => {\n                if matches!(\n                    self.end_config,\n                    OneShotEndConfig::EndOnFirstReleaseOrRepress\n                        | OneShotEndConfig::EndOnFirstPressOrRepress\n                ) && self.keys.contains(&pressed_coord)\n                {\n                    self.release_on_next_tick = true;\n                    oneshot_coords.extend(self.keys.iter().copied());\n                }\n\n                self.released_keys.retain(|coord| *coord != pressed_coord);\n            }\n            OneShotHandlePressKey::Other(pressed_coord) => {\n                if matches!(\n                    self.end_config,\n                    OneShotEndConfig::EndOnFirstPress | OneShotEndConfig::EndOnFirstPressOrRepress\n                ) {\n                    self.timeout = core::cmp::min(self.pause_input_processing_delay, self.timeout);\n                    self.pause_input_processing_ticks = self.pause_input_processing_delay;\n                } else {\n                    let _ = self.other_pressed_keys.push_back(pressed_coord);\n                }\n                oneshot_coords.extend(self.keys.iter().copied());\n            }\n        };\n        oneshot_coords\n    }\n\n    /// Returns true if the caller should handle the release normally and false otherwise.\n    /// The second value in the tuple represents an overflow of released one shot keys and should\n    /// be released is it is `Some`.\n    fn handle_release(&mut self, (i, j): KCoord) -> (bool, Option<KCoord>) {\n        if self.keys.is_empty() {\n            return (true, None);\n        }\n        if !self.keys.contains(&(i, j)) {\n            if matches!(\n                self.end_config,\n                OneShotEndConfig::EndOnFirstRelease | OneShotEndConfig::EndOnFirstReleaseOrRepress\n            ) && self.other_pressed_keys.contains(&(i, j))\n            {\n                self.release_on_next_tick = true;\n            }\n            (true, None)\n        } else {\n            // delay release for one shot keys\n            (false, self.released_keys.push_back((i, j)))\n        }\n    }\n\n    fn add_state_to_retain(&mut self, state: OneShotRetainableState) {\n        if !self.state_to_retain_on_release.contains(&state) {\n            self.state_to_retain_on_release.push_back(state);\n        }\n    }\n}\n\n/// An iterator over the currently queued events.\n///\n/// Events can be retrieved by iterating over this struct and calling [Queued::event].\n#[derive(Clone)]\npub struct QueuedIter<'a>(arraydeque::Iter<'a, Queued>);\n\nimpl<'a> Iterator for QueuedIter<'a> {\n    type Item = &'a Queued;\n    fn next(&mut self) -> Option<Self::Item> {\n        self.0.next()\n    }\n    fn size_hint(&self) -> (usize, Option<usize>) {\n        self.0.size_hint()\n    }\n}\n\n/// An event, waiting in a queue to be processed.\n#[derive(Debug, Copy, Clone)]\npub struct Queued {\n    pub(crate) event: Event,\n    pub(crate) since: u16,\n}\nimpl From<Event> for Queued {\n    fn from(event: Event) -> Self {\n        Queued { event, since: 0 }\n    }\n}\nimpl Queued {\n    pub(crate) fn new_press(i: u8, j: u16) -> Self {\n        Self {\n            since: 0,\n            event: Event::Press(i, j),\n        }\n    }\n\n    pub(crate) fn new_release(i: u8, j: u16) -> Self {\n        Self {\n            since: 0,\n            event: Event::Release(i, j),\n        }\n    }\n\n    pub(crate) fn tick_qd(&mut self) {\n        self.since = self.since.saturating_add(1);\n    }\n\n    /// Get the [Event] from this object.\n    pub fn event(&self) -> Event {\n        self.event\n    }\n}\n\n#[derive(Default)]\npub struct LastPressTracker {\n    pub coord: KCoord,\n    pub tap_hold_timeout: u16,\n}\n\nimpl LastPressTracker {\n    fn tick_lpt(&mut self) {\n        self.tap_hold_timeout = self.tap_hold_timeout.saturating_sub(1);\n    }\n    fn update_coord(&mut self, coord: KCoord) {\n        if coord.0 == REAL_KEY_ROW {\n            // Only update if it's a real key press.\n            self.coord = coord;\n        }\n    }\n}\n\nimpl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout<'a, C, R, T> {\n    /// Creates a new `Layout` object.\n    fn new(layers: &'a [[[Action<T>; C]; R]]) -> Self {\n        assert!(layers.len() < MAX_LAYERS);\n        Self {\n            src_keys: &[Action::NoOp; C],\n            layers,\n            default_layer: 0,\n            states: Vec::new(),\n            waiting: None,\n            extra_waiting: ArrayDeque::new(),\n            tap_dance_eager: None,\n            queue: ArrayDeque::new(),\n            oneshot: OneShotState {\n                timeout: 0,\n                end_config: OneShotEndConfig::EndOnFirstPress,\n                keys: ArrayDeque::new(),\n                released_keys: ArrayDeque::new(),\n                state_to_retain_on_release: ArrayDeque::new(),\n                other_pressed_keys: ArrayDeque::new(),\n                release_on_next_tick: false,\n                pause_input_processing_delay: 0,\n                pause_input_processing_ticks: 0,\n                ticks_to_ignore_events: 0,\n            },\n            keys_to_suppress_for_one_cycle: Vec::new(),\n            last_press_tracker: Default::default(),\n            active_sequences: ArrayDeque::new(),\n            action_queue: ArrayDeque::new(),\n            rpt_action: None,\n            historical_keys: History::new(),\n            historical_inputs: History::new(),\n            rpt_multikey_key_buffer: unsafe { MultiKeyBuffer::new() },\n            quick_tap_hold_timeout: false,\n            tap_hold_require_prior_idle: 0,\n            trans_resolution_behavior_v2: true,\n            delegate_to_first_layer: false,\n            chords_v2: None,\n            contextual_execution: ContextualExecution::new(),\n            tap_hold_tracker: Default::default(),\n        }\n    }\n    pub fn new_with_trans_action_settings(\n        src_keys: &'a [Action<T>; C],\n        layers: &'a [[[Action<T>; C]; R]],\n        trans_resolution_behavior_v2: bool,\n        delegate_to_first_layer: bool,\n    ) -> Self {\n        let mut new = Self::new(layers);\n        new.src_keys = src_keys;\n        new.trans_resolution_behavior_v2 = trans_resolution_behavior_v2;\n        new.delegate_to_first_layer = delegate_to_first_layer;\n        new\n    }\n\n    /// Iterates on the key codes of the current state.\n    pub fn keycodes(&self) -> impl Iterator<Item = KeyCode> + Clone + '_ {\n        let keys_to_suppress_for_one_cycle = self.keys_to_suppress_for_one_cycle.clone();\n        self.states\n            .iter()\n            .filter_map(State::keycode)\n            .filter(move |kc| !keys_to_suppress_for_one_cycle.contains(kc))\n    }\n    fn waiting_into_hold(&mut self, idx: i8) -> CustomEvent<'a, T> {\n        let waiting = if idx < 0 {\n            self.waiting.as_ref()\n        } else {\n            self.extra_waiting.get(idx as usize)\n        };\n        if let Some(w) = waiting {\n            let hold = w.hold;\n            let coord = w.coord;\n            let delay = match w.config {\n                WaitingConfig::HoldTap(..) | WaitingConfig::Chord(_) => w.delay + w.ticks,\n                WaitingConfig::TapDance(_) => 0,\n            };\n            let layer_stack = w.layer_stack.clone();\n            self.tap_hold_tracker.set_hold_activated(coord, &w.config);\n            if idx < 0 {\n                self.waiting = None;\n            } else {\n                self.extra_waiting.remove(idx as usize);\n            }\n            if coord == self.last_press_tracker.coord {\n                self.last_press_tracker.tap_hold_timeout = 0;\n            }\n            // Similar issue happens for the quick tap-hold tap as with on-press release;\n            // the rapidity of the release can cause issues. See pause_input_processing_delay\n            // comments for more detail.\n            self.oneshot.pause_input_processing_ticks = self.oneshot.pause_input_processing_delay;\n            self.do_action(hold, coord, delay, false, &mut layer_stack.into_iter())\n        } else {\n            CustomEvent::NoEvent\n        }\n    }\n    fn waiting_into_tap(&mut self, pq: Option<PressedQueue>, idx: i8) -> CustomEvent<'a, T> {\n        let waiting = if idx < 0 {\n            self.waiting.as_ref()\n        } else {\n            self.extra_waiting.get(idx as usize)\n        };\n        if let Some(w) = waiting {\n            let tap = w.tap;\n            let coord = w.coord;\n            let delay = match w.config {\n                WaitingConfig::HoldTap(..) | WaitingConfig::Chord(_) => w.delay + w.ticks,\n                WaitingConfig::TapDance(_) => 0,\n            };\n            let layer_stack = w.layer_stack.clone();\n            self.tap_hold_tracker.set_tap_activated(coord, &w.config);\n            if idx < 0 {\n                self.waiting = None;\n            } else {\n                self.extra_waiting.remove(idx as usize);\n            }\n            let ret = self.do_action(\n                tap,\n                coord,\n                delay,\n                false,\n                &mut layer_stack.clone().into_iter(),\n            );\n\n            if let Some(pq) = pq {\n                self.contextual_execution.pause_historical_keys_updates = true;\n                match tap {\n                    Action::KeyCode(_)\n                    | Action::MultipleKeyCodes(_)\n                    | Action::OneShot(_)\n                    | Action::Layer(_) => {\n                        // The current intent of this block is to ensure that simple actions like\n                        // key presses or layer-while-held remain pressed as long as a single key from\n                        // the input chord remains held. The behaviour of these actions is correct in\n                        // the case of repeating do_action, so there is currently no harm in doing\n                        // this. Other action types are more problematic though.\n                        for other_coord in pq.iter().copied() {\n                            self.do_action(\n                                tap,\n                                other_coord,\n                                delay,\n                                false,\n                                &mut layer_stack.clone().into_iter(),\n                            );\n                        }\n                    }\n                    Action::MultipleActions(acs) => {\n                        // Like above block, but for the same simple actions within MultipleActions\n                        for ac in acs.iter() {\n                            if matches!(\n                                ac,\n                                Action::KeyCode(_)\n                                    | Action::MultipleKeyCodes(_)\n                                    | Action::OneShot(_)\n                                    | Action::Layer(_)\n                            ) {\n                                for other_coord in pq.iter().copied() {\n                                    self.do_action(\n                                        ac,\n                                        other_coord,\n                                        delay,\n                                        false,\n                                        &mut layer_stack.clone().into_iter(),\n                                    );\n                                }\n                            }\n                        }\n                    }\n                    _ => {}\n                }\n                self.contextual_execution.pause_historical_keys_updates = false;\n            }\n\n            // Similar issue happens for the quick tap-hold tap as with on-press release;\n            // the rapidity of the release can cause issues. See pause_input_processing_delay\n            // comments for more detail.\n            self.oneshot.pause_input_processing_ticks = self.oneshot.pause_input_processing_delay;\n            ret\n        } else {\n            CustomEvent::NoEvent\n        }\n    }\n    fn waiting_into_timeout(&mut self, idx: i8) -> CustomEvent<'a, T> {\n        let waiting = if idx < 0 {\n            self.waiting.as_ref()\n        } else {\n            self.extra_waiting.get(idx as usize)\n        };\n        if let Some(w) = waiting {\n            let timeout_action = w.timeout_action;\n            let coord = w.coord;\n            let delay = match w.config {\n                WaitingConfig::HoldTap(..) | WaitingConfig::Chord(_) => w.delay + w.ticks,\n                WaitingConfig::TapDance(_) => 0,\n            };\n            let layer_stack = w.layer_stack.clone();\n            self.tap_hold_tracker.set_hold_activated(coord, &w.config);\n            if idx < 0 {\n                self.waiting = None;\n            } else {\n                self.extra_waiting.remove(idx as usize);\n            }\n            if coord == self.last_press_tracker.coord {\n                self.last_press_tracker.tap_hold_timeout = 0;\n            }\n            self.do_action(\n                timeout_action,\n                coord,\n                delay,\n                false,\n                &mut layer_stack.into_iter(),\n            )\n        } else {\n            CustomEvent::NoEvent\n        }\n    }\n    fn drop_waiting(&mut self) -> CustomEvent<'a, T> {\n        self.waiting = None;\n        CustomEvent::NoEvent\n    }\n    /// A time event.\n    ///\n    /// This method must be called regularly, typically every millisecond.\n    ///\n    /// Returns the corresponding `CustomEvent`, allowing to manage\n    /// custom actions thanks to the `Action::Custom` variant.\n    pub fn tick(&mut self) -> CustomEvent<'a, T> {\n        let active_layer = self.current_layer() as u16;\n        if let Some(chv2) = self.chords_v2.as_mut() {\n            self.queue.extend(chv2.tick_chv2(active_layer).drain(0..));\n            if let chord_action @ Some(_) = chv2.get_action_chv2() {\n                self.action_queue.push_back(chord_action);\n                self.oneshot.pause_input_processing_ticks =\n                    self.oneshot.pause_input_processing_delay;\n            }\n        }\n        self.keys_to_suppress_for_one_cycle.clear();\n        if let Some(Some((coord, delay, action, layer_stack))) = self.action_queue.pop_front() {\n            // If there's anything in the action queue, don't process anything else yet - execute\n            // everything. Otherwise an action may never be released.\n            return self.do_action(action, coord, delay, false, &mut layer_stack.into_iter());\n        }\n        self.queue.iter_mut().for_each(Queued::tick_qd);\n        self.last_press_tracker.tick_lpt();\n        if let Some(ref mut tde) = self.tap_dance_eager {\n            tde.tick_tde();\n            if tde.is_expired() {\n                self.tap_dance_eager = None;\n            }\n        }\n        self.process_sequences();\n\n        self.historical_keys.tick_hist();\n        self.historical_inputs.tick_hist();\n\n        let mut custom = CustomEvent::NoEvent;\n        if let Some(released_keys) = self.oneshot.tick_osh() {\n            for key in released_keys.iter() {\n                custom.update(self.dequeue(Queued {\n                    event: Event::Release(key.0, key.1),\n                    since: 0,\n                }));\n            }\n        }\n\n        custom.update(match &mut self.waiting {\n            Some(w) => match w.tick_wt(&mut self.queue, &mut self.action_queue) {\n                Some((WaitingAction::Hold, _)) => self.waiting_into_hold(-1),\n                Some((WaitingAction::Tap, pq)) => self.waiting_into_tap(pq, -1),\n                Some((WaitingAction::Timeout, _)) => self.waiting_into_timeout(-1),\n                Some((WaitingAction::NoOp, _)) => self.drop_waiting(),\n                None => CustomEvent::NoEvent,\n            },\n            None => {\n                if self.extra_waiting.is_empty() {\n                    // Due to the possible delay in the key release for EndOnFirstPress\n                    // because some apps/DEs do not handle it properly if done too quickly,\n                    // undesirable behaviour of extra presses making it in before\n                    // the release happens might occur.\n                    //\n                    // A mitigation against that is to pause input processing.\n                    if self.oneshot.pause_input_processing_ticks > 0 {\n                        self.oneshot.pause_input_processing_ticks =\n                            self.oneshot.pause_input_processing_ticks.saturating_sub(1);\n                        CustomEvent::NoEvent\n                    } else {\n                        match self.queue.pop_front() {\n                            Some(s) => self.dequeue(s),\n                            None => CustomEvent::NoEvent,\n                        }\n                    }\n                } else {\n                    CustomEvent::NoEvent\n                }\n            }\n        });\n        let custom = self.process_extra_waitings(custom);\n        self.process_sequence_custom(custom)\n    }\n    /// Takes care of draining and populating the `active_sequences` ArrayDeque,\n    /// giving us sequences (aka macros) of nearly limitless length!\n    fn process_sequences(&mut self) {\n        // Iterate over all active sequence events\n        for _ in 0..self.active_sequences.len() {\n            if let Some(mut seq) = self.active_sequences.pop_front() {\n                // If we've encountered a SequenceEvent::Delay we must count\n                // that down completely before doing anything else...\n                if seq.delay > 0 {\n                    seq.delay = seq.delay.saturating_sub(1);\n                } else if let Some(keycode) = seq.tapped {\n                    // Clear out the Press() matching this Tap()'s keycode\n                    self.states.retain(|s| s.seq_release(keycode).is_some());\n                    seq.tapped = None;\n                } else {\n                    // Pull the next SequenceEvent\n                    if let [e, tail @ ..] = seq.remaining_events {\n                        seq.cur_event = Some(*e);\n                        seq.remaining_events = tail;\n                    }\n                    // Process it (SequenceEvent)\n                    match seq.cur_event {\n                        Some(SequenceEvent::Complete) => {\n                            seq.remaining_events = &[];\n                        }\n                        Some(SequenceEvent::Press(keycode)) => {\n                            // Start tracking this fake key Press() event\n                            let _ = self.states.push(FakeKey { keycode });\n                            self.contextual_execution\n                                .push_historical_key(&mut self.historical_keys, keycode);\n                            // Fine to fake (0, 0). This is sequences anyway. In Kanata, nothing\n                            // valid should be at (0, 0) that this would interfere with.\n                            self.oneshot\n                                .handle_press(OneShotHandlePressKey::Other((0, 0)));\n                        }\n                        Some(SequenceEvent::Tap(keycode)) => {\n                            // Same as Press() except we track it for one tick via seq.tapped:\n                            let _ = self.states.push(FakeKey { keycode });\n                            self.contextual_execution\n                                .push_historical_key(&mut self.historical_keys, keycode);\n                            self.oneshot\n                                .handle_press(OneShotHandlePressKey::Other((0, 0)));\n                            seq.tapped = Some(keycode);\n                        }\n                        Some(SequenceEvent::Release(keycode)) => {\n                            // Nothing valid should be at (0, 0). It's fine to fake this.\n                            self.oneshot.handle_release((0, 0));\n                            self.states.retain(|s| s.seq_release(keycode).is_some());\n                        }\n                        Some(SequenceEvent::Delay { duration }) => {\n                            // Setup a delay that will be decremented once per tick until 0\n                            if duration > 0 {\n                                // -1 to start since this tick counts\n                                seq.delay = duration - 1;\n                            }\n                        }\n                        Some(SequenceEvent::Custom(custom)) => {\n                            let _ = self.states.push(State::SeqCustomPending(custom));\n                        }\n                        _ => {} // We'll never get here\n                    }\n                }\n                if !seq.remaining_events.is_empty() {\n                    // Put it back\n                    self.active_sequences.push_back(seq);\n                }\n            }\n        }\n        if self.active_sequences.is_empty() {\n            // Push only the latest pressed repeating macro.\n            if let Some(State::RepeatingSequence { sequence, .. }) = self\n                .states\n                .iter()\n                .rev()\n                .find(|s| matches!(s, State::RepeatingSequence { .. }))\n            {\n                self.active_sequences.push_back(SequenceState {\n                    cur_event: None,\n                    delay: 0,\n                    tapped: None,\n                    remaining_events: sequence,\n                });\n            }\n        }\n    }\n\n    fn process_extra_waitings(&mut self, current_custom: CustomEvent<'a, T>) -> CustomEvent<'a, T> {\n        if !matches!(current_custom, CustomEvent::NoEvent) {\n            return current_custom;\n        }\n        let mut waiting_action = (0, None);\n        for (i, w) in self.extra_waiting.iter_mut().enumerate() {\n            match w.tick_wt(&mut self.queue, &mut self.action_queue) {\n                None => {}\n                wa => {\n                    waiting_action = (i as isize, wa);\n                    // break - only complete one at a time even if potentially multiple have\n                    // completed, so that only one custom event is returned.\n                    //\n                    // Theoretically if we could call the waiting_into_* functions, we could do that\n                    // here and break only if custom is None, but that runs into mutability\n                    // problems. I don't expect any perceptible degradation between from not doing\n                    // the above.\n                    break;\n                }\n            }\n        }\n        let i = waiting_action.0;\n        match waiting_action.1 {\n            Some((WaitingAction::Hold, _)) => self.waiting_into_hold(i as i8),\n            Some((WaitingAction::Tap, pq)) => self.waiting_into_tap(pq, i as i8),\n            Some((WaitingAction::Timeout, _)) => self.waiting_into_timeout(i as i8),\n            Some((WaitingAction::NoOp, _)) => self.drop_waiting(),\n            None => current_custom,\n        }\n    }\n\n    fn process_sequence_custom(\n        &mut self,\n        mut current_custom: CustomEvent<'a, T>,\n    ) -> CustomEvent<'a, T> {\n        if self.states.is_empty() || !matches!(current_custom, CustomEvent::NoEvent) {\n            return current_custom;\n        }\n        // It is important to note that this code cannot simply be replaced by `retain_mut`.\n        // The `retain_mut` function is not chosen\n        // because it is important to break on the first `SeqCustom` that is discovered.\n        // Such functionality could be replaced by a marker to ignore processing,\n        // but for now that is not necessary.\n        self.states.retain(|s| !matches!(s, State::Tombstone));\n        for state in self.states.iter_mut() {\n            match state {\n                State::SeqCustomPending(custom) => {\n                    current_custom.update(CustomEvent::Press(custom));\n                    *state = State::SeqCustomActive(custom);\n                    break;\n                }\n                State::SeqCustomActive(custom) => {\n                    current_custom.update(CustomEvent::Release(custom));\n                    *state = State::Tombstone;\n                    break;\n                }\n                _ => continue,\n            };\n        }\n        current_custom\n    }\n    fn dequeue(&mut self, queue: Queued) -> CustomEvent<'a, T> {\n        use Event::*;\n        match queue.event {\n            Release(i, j) => {\n                let mut custom = CustomEvent::NoEvent;\n                let (do_release, overflow_key) = self.oneshot.handle_release((i, j));\n                if do_release {\n                    self.states.retain(|s| {\n                        !s.clear_on_next_release() && s.release((i, j), &mut custom).is_some()\n                    });\n                } else {\n                    // Fix #1874:\n                    // Might still need to apply release,\n                    // but need to check against states on same coordinate\n                    // that aren't part of a OneShot.\n                    self.states.retain(|s| {\n                        match s {\n                            NormalKey { coord, keycode, .. } => {\n                                // NormalKey is a valid oneshot state,\n                                // may need to keep.\n                                *coord != (i, j)\n                                    || self.oneshot.state_to_retain_on_release.contains(\n                                        &OneShotRetainableState::KeyCode {\n                                            coord: *coord,\n                                            kc: *keycode,\n                                        },\n                                    )\n                            }\n                            LayerModifier { coord, value } => {\n                                // LayerModifier is a valid oneshot state,\n                                // may need to keep.\n                                *coord != (i, j)\n                                    || self.oneshot.state_to_retain_on_release.contains(\n                                        &OneShotRetainableState::Layer {\n                                            coord: *coord,\n                                            layer: *value as u16,\n                                        },\n                                    )\n                            }\n                            // Everything else is not a valid oneshot state,\n                            // if it falls on the same coordinate\n                            // as a oneshot key, it should still be released here.\n                            _ => {\n                                !s.clear_on_next_release()\n                                    && s.release((i, j), &mut custom).is_some()\n                            }\n                        }\n                    });\n                }\n                if let Some((i2, j2)) = overflow_key {\n                    self.states\n                        .retain(|s| s.release((i2, j2), &mut custom).is_some());\n                }\n                custom\n            }\n\n            Press(i, j) => {\n                let mut layer_stack = self.trans_resolution_layer_order().into_iter();\n                if let Some(tde) = &mut self.tap_dance_eager {\n                    if (i, j) == self.last_press_tracker.coord && !tde.is_expired() {\n                        let tde_action = tde.actions[usize::from(tde.num_taps)];\n                        tde.incr_taps();\n                        let custom = self.do_action(\n                            tde_action,\n                            (i, j),\n                            queue.since,\n                            false,\n                            &mut layer_stack.skip(1),\n                        );\n                        custom\n                    } else {\n                        // i == 0 means real key, i == 1 means fake key. Let fake keys do whatever, but\n                        // interrupt tap-dance-eager if real key.\n                        if i == REAL_KEY_ROW {\n                            tde.set_expired();\n                        }\n                        self.do_action(&Action::Trans, (i, j), queue.since, false, &mut layer_stack)\n                    }\n                } else {\n                    self.do_action(&Action::Trans, (i, j), queue.since, false, &mut layer_stack)\n                }\n            }\n        }\n    }\n    /// Register a key event.\n    pub fn event(&mut self, event: Event) {\n        if let Event::Press(x, y) = event {\n            self.historical_inputs.push_front((x, y));\n        }\n        if let Some(overflow) = if let Some(ch) = self.chords_v2.as_mut() {\n            ch.push_back_chv2(event.into())\n        } else {\n            self.queue.push_back(event.into())\n        } {\n            for i in -1..(EXTRA_WAITING_LEN as i8) {\n                self.waiting_into_hold(i);\n            }\n            self.dequeue(overflow);\n        }\n    }\n\n    /// Put a key event at the front instead of back.\n    /// These events will not participate in chordsv2.\n    pub fn event_to_front(&mut self, event: Event) {\n        if let Event::Press(x, y) = event {\n            self.historical_inputs.push_front((x, y));\n        }\n        if let Some(overflow) = self.queue.push_front(event.into()) {\n            for i in -1..(EXTRA_WAITING_LEN as i8) {\n                self.waiting_into_hold(i);\n            }\n            self.dequeue(overflow);\n        }\n    }\n\n    /// Resolve coordinate to first non-Trans actions.\n    /// Trans on base layer, resolves to key from defsrc.\n    fn resolve_coord(\n        &self,\n        coord: KCoord,\n        layer_stack: &mut (impl Iterator<Item = u16> + Clone),\n    ) -> &'a Action<'a, T> {\n        use crate::action::Action::*;\n        let x = coord.0 as usize;\n        let y = coord.1 as usize;\n        assert!(x <= self.layers[0].len());\n        assert!(y <= self.layers[0][0].len());\n        for layer in layer_stack {\n            assert!(usize::from(layer) <= self.layers.len());\n            let action = &self.layers[usize::from(layer)][x][y];\n            match action {\n                Trans => continue,\n                action => return action,\n            }\n        }\n        if x == 0 { &self.src_keys[y] } else { &NoOp }\n    }\n    fn do_action(\n        &mut self,\n        action: &'a Action<'a, T>,\n        coord: KCoord,\n        delay: u16,\n        is_oneshot: bool,\n        layer_stack: &mut (impl Iterator<Item = u16> + Clone), // used to resolve Trans action\n    ) -> CustomEvent<'a, T> {\n        let mut action = action;\n        if let Trans = action {\n            action = self.resolve_coord(coord, layer_stack);\n        }\n        let action = action;\n\n        if self.last_press_tracker.coord != coord && coord.0 == REAL_KEY_ROW {\n            self.last_press_tracker.tap_hold_timeout = 0;\n        }\n        use Action::*;\n        self.states.retain(|s| match s {\n            // Need to solve a problem here - if the output chord `S-=` is active, and then a\n            // different keypress `=` happens, the `lsft =` states are cleared; but the `=`\n            // is immediately re-added again. This means the release is never observed..\n            //\n            // Bug introduced:\n            //\n            // If doing something like typing parentheses using output chords,\n            // e.g. S-9 followed by S-0,\n            // the trivial fix will suppress the shift key for a bit\n            // but the `0` still gets output,\n            // resulting in an unshifted `0` which is incorrect.\n            //\n            // Fix added:\n            //\n            // Do not apply the suppression to modifiers.\n            // Modifiers typically don't have a usage pattern similar to\n            // the real use case of `S-=` followed by `=` example,\n            // such as `S-=` followed by only `lsft`,\n            // with a desire for the lone `lsft` to actually activate something.\n            NormalKey { flags, keycode, .. } => match flags.nkf_clear_on_next_action() {\n                true => {\n                    self.oneshot.pause_input_processing_ticks += 2;\n                    if !keycode.is_mod() {\n                        let _ = self.keys_to_suppress_for_one_cycle.push(*keycode);\n                    }\n                    false\n                }\n                false => true,\n            },\n            _ => true,\n        });\n        match action {\n            NoOp => {\n                // There is an interaction between oneshot and chordsv2 here.\n                // chordsv2 sends fake queued press/release events at the coordinate level in order\n                // to trigger other \"waiting\" style actions, namely tap-hold. However, these can\n                // potentially interfere with oneshot by triggering early oneshot activation. This\n                // is resolved by ignoring actions at the coordinate at which the fake events are\n                // sent.\n                if !is_oneshot && coord != TRIGGER_TAPHOLD_COORD {\n                    self.oneshot\n                        .handle_press(OneShotHandlePressKey::Other(coord));\n                }\n                self.rpt_action = Some(action);\n                let _ = self.states.push(NoOpInput { coord });\n            }\n            Src => {\n                let action = &self.src_keys[usize::from(coord.1)];\n                // Risk: infinite recursive resulting in stack overflow.\n                // In practice this is not expected to happen.\n                // The `src_keys` actions are all expected to be `KeyCode` or `NoOp` actions.\n                self.do_action(action, coord, delay, is_oneshot, &mut std::iter::empty());\n            }\n            Trans => {\n                // Transparent action should be resolved to non-transparent one near the top\n                // of `do_action`.\n                unreachable!(\"Trans action should have been resolved earlier\")\n            }\n            Repeat => {\n                // Notes around repeat:\n                //\n                // Though this action seems conceptually simple, in reality there are a lot of\n                // decisions to be made around how exactly actions repeat. For example: in a\n                // tap-dance action, would one expect the tap-dance to be repeated or the inner\n                // action that was most activated within the tap-dance?\n                //\n                // Currently the answer to these questions is: what is easy/possible to do? E.g.\n                // fork and switch are inconsistent with each other even though the actions are\n                // conceptually very similar. This is because switch can potentially activate\n                // multiple actions (but not always), so uses the action queue, while fork does\n                // not. As another example, tap-dance and tap-hold will repeat the inner action and\n                // not the outer (tap-dance|hold) but multi will repeat the entire outer multi\n                // action.\n                if let Some(ac) = self.rpt_action {\n                    self.do_action(ac, coord, delay, is_oneshot, &mut std::iter::empty());\n                }\n            }\n            HoldTap(HoldTapAction {\n                timeout,\n                hold,\n                tap,\n                timeout_action,\n                config,\n                tap_hold_interval,\n                on_press_reset_timeout_to,\n                require_prior_idle,\n            }) => {\n                // Typing streak detection: if a different physical key was pressed\n                // recently, resolve as tap immediately without entering WaitingState.\n                // Per-action override takes precedence over the global defcfg value.\n                let idle_threshold = require_prior_idle.unwrap_or(self.tap_hold_require_prior_idle);\n                if idle_threshold > 0 {\n                    let prior_idle_tap = self\n                        .historical_inputs\n                        .iter_hevents()\n                        .find(|prior| prior.event.0 == REAL_KEY_ROW && prior.event != coord)\n                        .is_some_and(|prior| prior.ticks_since_occurrence <= idle_threshold);\n                    if prior_idle_tap {\n                        let custom = self.do_action(tap, coord, delay, is_oneshot, layer_stack);\n                        self.last_press_tracker.update_coord(coord);\n                        return custom;\n                    }\n                }\n                let mut custom = CustomEvent::NoEvent;\n                if *tap_hold_interval == 0\n                    || coord != self.last_press_tracker.coord\n                    || self.last_press_tracker.tap_hold_timeout == 0\n                {\n                    let ticks = match self.quick_tap_hold_timeout {\n                        // Leave 1 tick to timeout as it will be consumed in the next processing cycle\n                        true => delay.min(timeout.saturating_sub(1)),\n                        false => 0,\n                    };\n                    let waiting: WaitingState<T> = WaitingState {\n                        coord,\n                        timeout: timeout.saturating_sub(ticks),\n                        delay: delay.saturating_sub(ticks),\n                        ticks,\n                        on_press_reset_timeout_to: *on_press_reset_timeout_to,\n                        hold,\n                        tap,\n                        timeout_action,\n                        config: WaitingConfig::HoldTap(*config),\n                        layer_stack: layer_stack.collect(),\n                        prev_queue_len: QueueLen::MAX,\n                    };\n                    if self.waiting.is_some() {\n                        self.extra_waiting.push_back(waiting);\n                    } else {\n                        self.waiting = Some(waiting);\n                    }\n                    self.last_press_tracker.tap_hold_timeout = *tap_hold_interval;\n                } else {\n                    self.last_press_tracker.tap_hold_timeout = 0;\n                    custom.update(self.do_action(tap, coord, delay, is_oneshot, layer_stack));\n                }\n                // Need to set tap_hold_tracker coord AFTER the checks.\n                self.last_press_tracker.update_coord(coord);\n                return custom;\n            }\n            &OneShot(oneshot) => {\n                self.last_press_tracker.update_coord(coord);\n                let custom =\n                    self.do_action(oneshot.action, coord, delay, true, &mut std::iter::empty());\n                // Note - set rpt_action after doing the inner oneshot action. This means that the\n                // whole oneshot will be repeated by rpt-any rather than only the inner action.\n                self.rpt_action = Some(action);\n                self.oneshot\n                    .handle_press(OneShotHandlePressKey::OneShotKey(coord));\n                self.oneshot.timeout = oneshot.timeout;\n                self.oneshot.end_config = oneshot.end_config;\n                if let Some(overflow) = self.oneshot.keys.push_back((coord.0, coord.1)) {\n                    self.event(Event::Release(overflow.0, overflow.1));\n                }\n                return custom;\n            }\n            &OneShotIgnoreEventsTicks(ticks) => {\n                self.last_press_tracker.update_coord(coord);\n                self.rpt_action = Some(action);\n                self.oneshot.ticks_to_ignore_events = ticks;\n            }\n            &TapDance(td) => {\n                self.last_press_tracker.update_coord(coord);\n                match td.config {\n                    TapDanceConfig::Lazy => {\n                        self.waiting = Some(WaitingState {\n                            coord,\n                            timeout: td.timeout,\n                            delay,\n                            ticks: 0,\n                            hold: &Action::NoOp,\n                            tap: &Action::NoOp,\n                            timeout_action: &Action::NoOp,\n                            on_press_reset_timeout_to: None,\n                            config: WaitingConfig::TapDance(TapDanceState {\n                                actions: td.actions,\n                                timeout: td.timeout,\n                                num_taps: 1,\n                            }),\n                            layer_stack: layer_stack.collect(),\n                            prev_queue_len: QueueLen::MAX,\n                        });\n                    }\n                    TapDanceConfig::Eager => {\n                        match self.tap_dance_eager {\n                            None => {\n                                self.tap_dance_eager = Some(TapDanceEagerState {\n                                    coord,\n                                    actions: td.actions,\n                                    timeout: td.timeout,\n                                    orig_timeout: td.timeout,\n                                    num_taps: 1,\n                                })\n                            }\n                            Some(tde) => {\n                                if tde.coord != coord {\n                                    self.tap_dance_eager = Some(TapDanceEagerState {\n                                        coord,\n                                        actions: td.actions,\n                                        timeout: td.timeout,\n                                        orig_timeout: td.timeout,\n                                        num_taps: 1,\n                                    });\n                                }\n                            }\n                        };\n                        return self.do_action(td.actions[0], coord, delay, false, layer_stack);\n                    }\n                }\n            }\n            &Chords(chords) => {\n                self.last_press_tracker.update_coord(coord);\n                self.waiting = Some(WaitingState {\n                    coord,\n                    timeout: chords.timeout,\n                    delay,\n                    ticks: 0,\n                    hold: &Action::NoOp,\n                    tap: &Action::NoOp,\n                    timeout_action: &Action::NoOp,\n                    on_press_reset_timeout_to: None,\n                    config: WaitingConfig::Chord(chords),\n                    layer_stack: layer_stack.collect(),\n                    prev_queue_len: QueueLen::MAX,\n                });\n            }\n            &KeyCode(keycode) => {\n                self.last_press_tracker.update_coord(coord);\n                // Most-recent-first!\n                self.contextual_execution\n                    .push_historical_key(&mut self.historical_keys, keycode);\n                let _ = self.states.push(NormalKey {\n                    coord,\n                    keycode,\n                    flags: NormalKeyFlags(0),\n                });\n                let mut oneshot_coords = ArrayDeque::new();\n                if !is_oneshot {\n                    oneshot_coords = self\n                        .oneshot\n                        .handle_press(OneShotHandlePressKey::Other(coord));\n                } else {\n                    self.oneshot\n                        .add_state_to_retain(OneShotRetainableState::KeyCode {\n                            coord,\n                            kc: keycode,\n                        });\n                }\n                if oneshot_coords.is_empty() {\n                    self.rpt_action = Some(action);\n                } else {\n                    self.rpt_action = None;\n                    unsafe {\n                        self.rpt_multikey_key_buffer.clear();\n                        for kc in self\n                            .states\n                            .iter()\n                            .filter_map(|kc| State::keycode_in_coords(kc, &oneshot_coords))\n                        {\n                            self.rpt_multikey_key_buffer.push(kc);\n                        }\n                        self.rpt_multikey_key_buffer.push(keycode);\n                        self.rpt_action = Some(self.rpt_multikey_key_buffer.get_ref());\n                    }\n                }\n            }\n            &MultipleKeyCodes(v) => {\n                self.last_press_tracker.update_coord(coord);\n                for &keycode in *v {\n                    // BUG:\n                    // In the original implementation, activating an action sequence such as b ->\n                    // S-b will not type a separate instance of \"B\" because b is already held and\n                    // wont be re-sent by the outer processing loop.\n                    //\n                    // FIX:\n                    // If `keycode` is not a mod, suppress it for a cycle if it exists in the\n                    // status. This is not expected to have any negative perceptible effects.\n                    if !keycode.is_mod() && self.keycodes().any(|kc| kc == keycode) {\n                        let _ = self.keys_to_suppress_for_one_cycle.push(keycode);\n                    }\n                    self.contextual_execution\n                        .push_historical_key(&mut self.historical_keys, keycode);\n                    let _ = self.states.push(NormalKey {\n                        coord,\n                        keycode,\n                        // In Kanata, this action is only ever used with output chords. Output\n                        // chords within a one-shot are ignored because someone might do something\n                        // like (one-shot C-S-lalt to get 3 modifiers. These are probably intended\n                        // to remain held. However, other output chords are usually used to type\n                        // symbols or accented characters, e.g. S-1 or RA-a. Clearing chord keys on\n                        // the next action allows a subsequent typed key to not have modifiers\n                        // alongside it. But if the symbol or accented character is held down, key\n                        // repeat works just fine.\n                        flags: NormalKeyFlags(if is_oneshot {\n                            0\n                        } else {\n                            NORMAL_KEY_FLAG_CLEAR_ON_NEXT_ACTION\n                        }),\n                    });\n                }\n\n                let mut oneshot_coords = ArrayDeque::new();\n                if !is_oneshot {\n                    oneshot_coords = self\n                        .oneshot\n                        .handle_press(OneShotHandlePressKey::Other(coord));\n                } else {\n                    for &keycode in *v {\n                        self.oneshot\n                            .add_state_to_retain(OneShotRetainableState::KeyCode {\n                                coord,\n                                kc: keycode,\n                            });\n                    }\n                }\n                if oneshot_coords.is_empty() {\n                    self.rpt_action = Some(action);\n                } else {\n                    self.rpt_action = None;\n                    unsafe {\n                        self.rpt_multikey_key_buffer.clear();\n                        for kc in self\n                            .states\n                            .iter()\n                            .filter_map(|s| s.keycode_in_coords(&oneshot_coords))\n                        {\n                            self.rpt_multikey_key_buffer.push(kc);\n                        }\n                        for &keycode in *v {\n                            self.rpt_multikey_key_buffer.push(keycode);\n                        }\n                        self.rpt_action = Some(self.rpt_multikey_key_buffer.get_ref());\n                    }\n                }\n            }\n            &MultipleActions(v) => {\n                self.last_press_tracker.update_coord(coord);\n                let mut custom = CustomEvent::NoEvent;\n                for action in *v {\n                    custom.update(self.do_action(\n                        action,\n                        coord,\n                        delay,\n                        is_oneshot,\n                        &mut layer_stack.clone(),\n                    ));\n                }\n                // Save the whole multi action instead of the final action in multi so that Repeat\n                // repeats all of the actions in this multi.\n                self.rpt_action = Some(action);\n                return custom;\n            }\n            Sequence { events } => {\n                self.active_sequences.push_back(SequenceState {\n                    cur_event: None,\n                    delay: 0,\n                    tapped: None,\n                    remaining_events: events,\n                });\n                if !is_oneshot {\n                    self.oneshot\n                        .handle_press(OneShotHandlePressKey::Other(coord));\n                }\n                self.rpt_action = Some(action);\n            }\n            RepeatableSequence { events } => {\n                self.active_sequences.push_back(SequenceState {\n                    cur_event: None,\n                    delay: 0,\n                    tapped: None,\n                    remaining_events: events,\n                });\n                let _ = self.states.push(RepeatingSequence {\n                    sequence: events,\n                    coord,\n                });\n                if !is_oneshot {\n                    self.oneshot\n                        .handle_press(OneShotHandlePressKey::Other(coord));\n                }\n                self.rpt_action = Some(action);\n            }\n            CancelSequences => {\n                // Clear any and all running sequences then clean up any leftover FakeKey events\n                self.active_sequences.clear();\n                for fake_key in self.states.clone().iter() {\n                    if let FakeKey { keycode } = *fake_key {\n                        self.states.retain(|s| s.seq_release(keycode).is_some());\n                    }\n                }\n                if !is_oneshot {\n                    self.oneshot\n                        .handle_press(OneShotHandlePressKey::Other(coord));\n                }\n                self.rpt_action = Some(action);\n            }\n            &Layer(value) => {\n                self.last_press_tracker.update_coord(coord);\n                let _ = self.states.push(LayerModifier { value, coord });\n                if !is_oneshot {\n                    self.oneshot\n                        .handle_press(OneShotHandlePressKey::Other(coord));\n                } else {\n                    self.oneshot\n                        .add_state_to_retain(OneShotRetainableState::Layer {\n                            coord,\n                            layer: value as u16,\n                        });\n                }\n                // Notably missing in Layer and below in DefaultLayer is setting rpt_action. This\n                // is so that if the Repeat key is on a different layer than the base, it can still\n                // be used to repeat the previous non-layer-changing action.\n            }\n            DefaultLayer(value) => {\n                self.last_press_tracker.update_coord(coord);\n                self.set_default_layer(*value);\n                if !is_oneshot {\n                    self.oneshot\n                        .handle_press(OneShotHandlePressKey::Other(coord));\n                }\n            }\n            Custom(value) => {\n                self.last_press_tracker.update_coord(coord);\n                if !is_oneshot {\n                    self.oneshot\n                        .handle_press(OneShotHandlePressKey::Other(coord));\n                }\n                self.rpt_action = Some(action);\n                if self.states.push(State::Custom { value, coord }).is_ok() {\n                    return CustomEvent::Press(value);\n                }\n            }\n            ReleaseState(rs) => {\n                self.states.retain(|s| s.release_state(*rs).is_some());\n                if !is_oneshot {\n                    self.oneshot\n                        .handle_press(OneShotHandlePressKey::Other(coord));\n                }\n                self.rpt_action = Some(action);\n            }\n            Fork(fcfg) => {\n                let ret = match self.states.iter().any(|s| match s {\n                    NormalKey { keycode, .. } | FakeKey { keycode } => {\n                        fcfg.right_triggers.contains(keycode)\n                    }\n                    _ => false,\n                }) {\n                    false => {\n                        self.do_action(&fcfg.left, coord, delay, false, &mut layer_stack.clone())\n                    }\n                    true => {\n                        self.do_action(&fcfg.right, coord, delay, false, &mut layer_stack.clone())\n                    }\n                };\n                // Repeat the fork rather than the terminal action.\n                self.rpt_action = Some(action);\n                return ret;\n            }\n            Switch(sw) => {\n                let active_keys = self.states.iter().filter_map(State::keycode);\n                let active_coords = self.states.iter().filter_map(State::coord);\n                let historical_keys = self.historical_keys.iter_hevents();\n                let historical_coords = self.historical_inputs.iter_hevents();\n                let layers = self.trans_resolution_layer_order().into_iter();\n                let action_queue = &mut self.action_queue;\n                for ac in sw.actions(\n                    active_keys,\n                    active_coords,\n                    historical_keys,\n                    historical_coords,\n                    layers,\n                    // Note on truncating cast: I expect default layer to be in range by other\n                    // assertions.\n                    self.default_layer as u16,\n                ) {\n                    action_queue.push_back(Some((coord, 0, ac, layer_stack.collect())));\n                }\n                // Switch is not properly repeatable. This has to use the action queue for the\n                // purpose of proper Custom action handling, because a single switch action can\n                // activate multiple inner actions. But because of the use of the action queue,\n                // switch has no way to set `rpt_action` after the queue is depleted. I suppose\n                // that can be fixable, but for now will keep it as-is.\n            }\n        }\n        CustomEvent::NoEvent\n    }\n\n    /// Obtain the index of the current active layer\n    pub fn current_layer(&self) -> usize {\n        self.states\n            .iter()\n            .rev()\n            .find_map(State::get_layer)\n            .unwrap_or(self.default_layer)\n    }\n\n    pub fn active_held_layers(&self) -> impl Iterator<Item = u16> + Clone + '_ {\n        self.states\n            .iter()\n            .filter_map(|s| State::get_layer(s).map(|l| l as u16))\n            .rev()\n    }\n\n    /// Returns a list indices of layers that should be used for [`Action::Trans`] resolution.\n    pub fn trans_resolution_layer_order(&self) -> LayerStack {\n        let current_layer = self.current_layer();\n        if self.trans_resolution_behavior_v2 {\n            let mut v = self.active_held_layers().collect::<LayerStack>();\n            let _ = v.push(self.default_layer as u16);\n            if self.delegate_to_first_layer && current_layer != 0 && self.default_layer != 0 {\n                let _ = v.push(0);\n            }\n            v\n        } else {\n            let mut v = Vec::new();\n            let _ = v.push(current_layer as u16);\n            if self.delegate_to_first_layer && current_layer != 0 {\n                let _ = v.push(0);\n            }\n            v\n        }\n    }\n\n    /// Sets the default layer for the layout\n    pub fn set_default_layer(&mut self, value: usize) {\n        if value < self.layers.len() {\n            self.default_layer = value\n        }\n    }\n}\n\n#[cfg(test)]\nmod test {\n    extern crate std;\n    use super::{Event::*, Layout, *};\n    use crate::action::Action::*;\n    use crate::action::HoldTapConfig;\n    use crate::action::{k, l};\n    use crate::key_code::KeyCode;\n    use crate::key_code::KeyCode::*;\n    use std::collections::BTreeSet;\n\n    #[track_caller]\n    fn assert_keys(expected: &[KeyCode], iter: impl Iterator<Item = KeyCode>) {\n        let expected: BTreeSet<_> = expected.iter().copied().collect();\n        let tested = iter.collect();\n        assert_eq!(expected, tested);\n    }\n\n    #[test]\n    fn basic_hold_tap() {\n        static LAYERS: Layers<2, 1> = &[\n            [[\n                HoldTap(&HoldTapAction {\n                    on_press_reset_timeout_to: None,\n                    require_prior_idle: None,\n                    timeout: 200,\n                    hold: l(1),\n                    tap: k(Space),\n                    timeout_action: k(RShift),\n                    config: HoldTapConfig::Default,\n                    tap_hold_interval: 0,\n                }),\n                HoldTap(&HoldTapAction {\n                    on_press_reset_timeout_to: None,\n                    require_prior_idle: None,\n                    timeout: 200,\n                    hold: k(LCtrl),\n                    timeout_action: k(LShift),\n                    tap: k(Enter),\n                    config: HoldTapConfig::Default,\n                    tap_hold_interval: 0,\n                }),\n            ]],\n            [[Trans, MultipleKeyCodes(&[LCtrl, Enter].as_slice())]],\n        ];\n        let mut layout = Layout::new(LAYERS);\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 0));\n        for _ in 0..197 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift, Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn basic_hold_tap_repress_timeout() {\n        static LAYERS: Layers<2, 1> = &[\n            [[\n                HoldTap(&HoldTapAction {\n                    on_press_reset_timeout_to: None,\n                    require_prior_idle: None,\n                    timeout: 200,\n                    hold: l(1),\n                    tap: k(Space),\n                    timeout_action: l(1),\n                    config: HoldTapConfig::Default,\n                    tap_hold_interval: 0,\n                }),\n                HoldTap(&HoldTapAction {\n                    on_press_reset_timeout_to: None,\n                    require_prior_idle: None,\n                    timeout: 200,\n                    hold: k(LCtrl),\n                    timeout_action: k(LCtrl),\n                    tap: k(Enter),\n                    config: HoldTapConfig::Default,\n                    tap_hold_interval: 0,\n                }),\n            ]],\n            [[Trans, MultipleKeyCodes(&[LCtrl, Enter].as_slice())]],\n        ];\n        let mut layout = Layout::new(LAYERS);\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 0));\n        for _ in 0..197 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LCtrl], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LCtrl], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LCtrl, Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LCtrl], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn hold_tap_interleaved_timeout() {\n        static LAYERS: Layers<2, 1> = &[[[\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n                timeout: 200,\n                hold: k(LAlt),\n                timeout_action: k(LAlt),\n                tap: k(Space),\n                config: HoldTapConfig::Default,\n                tap_hold_interval: 0,\n            }),\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n                timeout: 20,\n                hold: k(LCtrl),\n                timeout_action: k(LCtrl),\n                tap: k(Enter),\n                config: HoldTapConfig::Default,\n                tap_hold_interval: 0,\n            }),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        for _ in 0..15 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space], layout.keycodes());\n        for _ in 0..10 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[Space], layout.keycodes());\n        }\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space, LCtrl], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LCtrl], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn hold_on_press() {\n        static LAYERS: Layers<2, 1> = &[[[\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n                timeout: 200,\n                hold: k(LAlt),\n                timeout_action: k(LAlt),\n                tap: k(Space),\n                config: HoldTapConfig::HoldOnOtherKeyPress,\n                tap_hold_interval: 0,\n            }),\n            k(Enter),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n\n        // Press another key before timeout\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt, Enter], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Enter], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Press another key after timeout\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 0));\n        for _ in 0..200 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt, Enter], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Enter], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn order_clean_tap() {\n        // Press and release modifier with no other keys → Tap.\n        static LAYERS: Layers<2, 1> = &[[[\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                timeout: u16::MAX,\n                hold: k(LAlt),\n                timeout_action: k(Space),\n                tap: k(Space),\n                config: HoldTapConfig::Order { buffer: 0 },\n                tap_hold_interval: 0,\n                require_prior_idle: None,\n            }),\n            k(Enter),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        for _ in 0..50 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn order_hold() {\n        // Modifier down → other down → other up first → Hold.\n        static LAYERS: Layers<2, 1> = &[[[\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                timeout: u16::MAX,\n                hold: k(LAlt),\n                timeout_action: k(Space),\n                tap: k(Space),\n                config: HoldTapConfig::Order { buffer: 0 },\n                tap_hold_interval: 0,\n                require_prior_idle: None,\n            }),\n            k(Enter),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        // Other key releases first → Hold\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt, Enter], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        // Release modifier\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn order_tap() {\n        // Modifier down → other down → modifier up first → Tap.\n        static LAYERS: Layers<2, 1> = &[[[\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                timeout: u16::MAX,\n                hold: k(LAlt),\n                timeout_action: k(Space),\n                tap: k(Space),\n                config: HoldTapConfig::Order { buffer: 0 },\n                tap_hold_interval: 0,\n                require_prior_idle: None,\n            }),\n            k(Enter),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        // Modifier releases first → Tap\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space, Enter], layout.keycodes());\n    }\n\n    #[test]\n    fn order_multi_key_hold() {\n        // TH down → A down → B down → A up (while B still held) → TH up.\n        // A's press+release cycle completes while TH is held → Hold.\n        static LAYERS: Layers<3, 1> = &[[[\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                timeout: u16::MAX,\n                hold: k(LAlt),\n                timeout_action: k(Space),\n                tap: k(Space),\n                config: HoldTapConfig::Order { buffer: 0 },\n                tap_hold_interval: 0,\n                require_prior_idle: None,\n            }),\n            k(Enter),\n            k(Tab),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n\n        // TH down\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        // A down\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        // B down\n        layout.event(Press(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        // A up — A's press+release cycle is complete → Hold resolves\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        // Queued keys replay: Enter press, Tab press, Enter release\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt, Enter], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt, Enter, Tab], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt, Tab], layout.keycodes());\n        // Release B\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        // Release TH\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn order_buffer_ignores_press_within_window() {\n        // TH down (buffer=50) → other key pressed+released within 50 ticks.\n        // Without buffer this would be Hold (other key's press+release cycle\n        // completes while TH held). With buffer=50, the press is ignored by\n        // release-order logic, so TH remains unresolved. Releasing TH → Tap.\n        static LAYERS: Layers<2, 1> = &[[[\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n                timeout: u16::MAX,\n                hold: k(LAlt),\n                timeout_action: k(Space),\n                tap: k(Space),\n                config: HoldTapConfig::Order { buffer: 50 },\n                tap_hold_interval: 0,\n            }),\n            k(Enter),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n\n        // TH down\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        // Other key pressed at tick ~1 (well within 50-tick buffer)\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        // Other key released — would normally trigger Hold, but press is buffered\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        // TH released → Tap (buffered press was ignored).\n        // Space activates, then queued Enter press+release replays.\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space, Enter], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn permissive_hold() {\n        static LAYERS: Layers<2, 1> = &[[[\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n                timeout: 200,\n                hold: k(LAlt),\n                timeout_action: k(LAlt),\n                tap: k(Space),\n                config: HoldTapConfig::PermissiveHold,\n                tap_hold_interval: 0,\n            }),\n            k(Enter),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n\n        // Press and release another key before timeout\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt, Enter], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn simultaneous_hold() {\n        static LAYERS: Layers<3, 1> = &[[[\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n                timeout: 200,\n                hold: k(LAlt),\n                timeout_action: k(LAlt),\n                tap: k(Space),\n                config: HoldTapConfig::Default,\n                tap_hold_interval: 0,\n            }),\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n                timeout: 200,\n                hold: k(RAlt),\n                timeout_action: k(RAlt),\n                tap: k(A),\n                config: HoldTapConfig::Default,\n                tap_hold_interval: 0,\n            }),\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n                timeout: 200,\n                hold: k(LCtrl),\n                timeout_action: k(LCtrl),\n                tap: k(A),\n                config: HoldTapConfig::Default,\n                tap_hold_interval: 0,\n            }),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n        layout.quick_tap_hold_timeout = true;\n\n        // Press and release another key before timeout\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        for _ in 0..196 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt, RAlt], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt, RAlt], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt, RAlt, LCtrl], layout.keycodes());\n    }\n\n    #[test]\n    fn multiple_actions() {\n        static LAYERS: Layers<2, 1> = &[\n            [[MultipleActions(&[l(1), k(LShift)].as_slice()), k(F)]],\n            [[Trans, k(E)]],\n        ];\n        let mut layout = Layout::new(LAYERS);\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift, E], layout.keycodes());\n        layout.event(Release(0, 1));\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn custom() {\n        static LAYERS: Layers<1, 1, i32> = &[[[Action::Custom(42)]]];\n        let mut layout = Layout::new(LAYERS);\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Custom event\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::Press(&42), layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // nothing more\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // release custom\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::Release(&42), layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn multiple_layers() {\n        static LAYERS: Layers<2, 1> = &[\n            [[l(1), l(2)]],\n            [[k(A), l(3)]],\n            [[l(0), k(B)]],\n            [[k(C), k(D)]],\n        ];\n        let mut layout = Layout::new(LAYERS);\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_eq!(0, layout.current_layer());\n        assert_keys(&[], layout.keycodes());\n\n        // press L1\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_eq!(1, layout.current_layer());\n        assert_keys(&[], layout.keycodes());\n        // press L3 on L1\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_eq!(3, layout.current_layer());\n        assert_keys(&[], layout.keycodes());\n        // release L1, still on l3\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_eq!(3, layout.current_layer());\n        assert_keys(&[], layout.keycodes());\n        // press and release C on L3\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[C], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        // release L3, back to L0\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_eq!(0, layout.current_layer());\n        assert_keys(&[], layout.keycodes());\n\n        // back to empty, going to L2\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_eq!(2, layout.current_layer());\n        assert_keys(&[], layout.keycodes());\n        // and press the L0 key on L2\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_eq!(0, layout.current_layer());\n        assert_keys(&[], layout.keycodes());\n        // release the L0, back to L2\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_eq!(2, layout.current_layer());\n        assert_keys(&[], layout.keycodes());\n        // release the L2, back to L0\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_eq!(0, layout.current_layer());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn custom_handler() {\n        fn always_tap(_: QueuedIter, _: KCoord) -> (Option<WaitingAction>, bool) {\n            (Some(WaitingAction::Tap), false)\n        }\n        fn always_hold(_: QueuedIter, _: KCoord) -> (Option<WaitingAction>, bool) {\n            (Some(WaitingAction::Hold), false)\n        }\n        fn always_nop(_: QueuedIter, _: KCoord) -> (Option<WaitingAction>, bool) {\n            (Some(WaitingAction::NoOp), false)\n        }\n        fn always_none(_: QueuedIter, _: KCoord) -> (Option<WaitingAction>, bool) {\n            (None, false)\n        }\n        static LAYERS: Layers<4, 1> = &[[[\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n                timeout: 200,\n                hold: k(Kb1),\n                timeout_action: k(Kb1),\n                tap: k(Kb0),\n                config: HoldTapConfig::Custom(&always_tap),\n                tap_hold_interval: 0,\n            }),\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n                timeout: 200,\n                hold: k(Kb3),\n                timeout_action: k(Kb3),\n                tap: k(Kb2),\n                config: HoldTapConfig::Custom(&always_hold),\n                tap_hold_interval: 0,\n            }),\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n                timeout: 200,\n                hold: k(Kb5),\n                timeout_action: k(Kb5),\n                tap: k(Kb4),\n                config: HoldTapConfig::Custom(&always_nop),\n                tap_hold_interval: 0,\n            }),\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n                timeout: 200,\n                hold: k(Kb7),\n                timeout_action: k(Kb7),\n                tap: k(Kb6),\n                config: HoldTapConfig::Custom(&always_none),\n                tap_hold_interval: 0,\n            }),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Custom handler always taps\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb0], layout.keycodes());\n\n        // nothing more\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Custom handler always holds\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb3], layout.keycodes());\n\n        // nothing more\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Custom handler always prevents any event\n        layout.event(Press(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // even timeout does not trigger\n        for _ in 0..200 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // nothing more\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Custom handler timeout fallback\n        layout.event(Press(0, 3));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        for _ in 0..199 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb7], layout.keycodes());\n    }\n\n    #[test]\n    fn tap_hold_interval() {\n        static LAYERS: Layers<2, 1> = &[[[\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n                timeout: 200,\n                hold: k(LAlt),\n                timeout_action: k(LAlt),\n                tap: k(Space),\n                config: HoldTapConfig::Default,\n                tap_hold_interval: 200,\n            }),\n            k(Enter),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n\n        // press and release the HT key, expect tap action\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // press again within tap_hold_interval, tap action should be in keycode immediately\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space], layout.keycodes());\n\n        // tap action should continue to be in keycodes even after timeout\n        for _ in 0..300 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[Space], layout.keycodes());\n        }\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Press again. This is outside the tap_hold_interval window, so should result in hold\n        // action.\n        layout.event(Press(0, 0));\n        for _ in 0..200 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn tap_hold_interval_interleave() {\n        static LAYERS: Layers<3, 1> = &[[[\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n                timeout: 200,\n                hold: k(LAlt),\n                timeout_action: k(LAlt),\n                tap: k(Space),\n                config: HoldTapConfig::Default,\n                tap_hold_interval: 200,\n            }),\n            k(Enter),\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n                timeout: 200,\n                hold: k(LAlt),\n                timeout_action: k(LAlt),\n                tap: k(Enter),\n                config: HoldTapConfig::Default,\n                tap_hold_interval: 200,\n            }),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n\n        // press and release the HT key, expect tap action\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // press a different key in between\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Enter], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // press HT key again, should result in hold action\n        layout.event(Press(0, 0));\n        for _ in 0..200 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // press HT key, press+release diff key, release HT key\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Enter, Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // press HT key again, should result in hold action\n        layout.event(Press(0, 0));\n        for _ in 0..200 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // press HT key, press+release diff (HT) key, release HT key\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Enter, Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // press HT key again, should result in hold action\n        layout.event(Press(0, 0));\n        for _ in 0..200 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n    }\n\n    #[test]\n    fn tap_hold_interval_short_hold() {\n        static LAYERS: Layers<1, 1> = &[[[HoldTap(&HoldTapAction {\n            on_press_reset_timeout_to: None,\n            require_prior_idle: None,\n            timeout: 50,\n            hold: k(LAlt),\n            timeout_action: k(LAlt),\n            tap: k(Space),\n            config: HoldTapConfig::Default,\n            tap_hold_interval: 200,\n        })]]];\n        let mut layout = Layout::new(LAYERS);\n\n        // press and hold the HT key, expect hold action\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 0));\n        for _ in 0..50 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // press and hold the HT key, expect hold action, even though it's within the\n        // tap_hold_interval\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 0));\n        for _ in 0..50 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn tap_hold_interval_different_hold() {\n        static LAYERS: Layers<2, 1> = &[[[\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n                timeout: 50,\n                hold: k(LAlt),\n                timeout_action: k(LAlt),\n                tap: k(Space),\n                config: HoldTapConfig::Default,\n                tap_hold_interval: 200,\n            }),\n            HoldTap(&HoldTapAction {\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n                timeout: 200,\n                hold: k(RAlt),\n                timeout_action: k(RAlt),\n                tap: k(Enter),\n                config: HoldTapConfig::Default,\n                tap_hold_interval: 200,\n            }),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n\n        // press HT1, press HT2, release HT1 after hold timeout, release HT2, press HT2\n        layout.event(Press(0, 0));\n        layout.event(Press(0, 1));\n        for _ in 0..50 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt, Enter], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Enter], layout.keycodes());\n        // press HT2 again, should result in tap action\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        for _ in 0..300 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[Enter], layout.keycodes());\n        }\n    }\n\n    #[test]\n    fn one_shot() {\n        static LAYERS: Layers<3, 1> = &[[[\n            OneShot(&crate::action::OneShot {\n                timeout: 100,\n                action: &k(LShift),\n                end_config: OneShotEndConfig::EndOnFirstPress,\n            }),\n            k(A),\n            k(B),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n        layout.oneshot.pause_input_processing_delay = 1;\n\n        // Test:\n        // 1. press one-shot\n        // 2. release one-shot\n        // 3. press A within timeout\n        // 4. press B within timeout\n        // 5. release A, B\n        layout.event(Press(0, 0));\n        for _ in 0..25 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        layout.event(Release(0, 0));\n        for _ in 0..25 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        layout.event(Press(0, 1));\n        layout.event(Press(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[A, LShift], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[A, B], layout.keycodes());\n        layout.event(Release(0, 1));\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[B], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test:\n        // 1. press one-shot\n        // 2. release one-shot\n        // 3. press A after timeout\n        // 4. release A\n        layout.event(Press(0, 0));\n        for _ in 0..25 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        layout.event(Release(0, 0));\n        for _ in 0..75 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[A], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test:\n        // 1. press one-shot\n        // 2. press A\n        // 3. release A\n        // 4. release one-shot\n        layout.event(Press(0, 0));\n        for _ in 0..25 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift, A], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test:\n        // 1. press one-shot\n        // 2. press A after timeout\n        // 3. release A\n        // 4. release one-shot\n        layout.event(Press(0, 0));\n        for _ in 0..200 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift, A], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn one_shot_end_press_or_repress() {\n        static LAYERS: Layers<3, 1> = &[[[\n            OneShot(&crate::action::OneShot {\n                timeout: 100,\n                action: &k(LShift),\n                end_config: OneShotEndConfig::EndOnFirstPressOrRepress,\n            }),\n            k(A),\n            k(B),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n        layout.oneshot.pause_input_processing_delay = 1;\n\n        // Test:\n        // 1. press one-shot\n        // 2. release one-shot\n        // 3. press A within timeout\n        // 4. press B within timeout\n        // 5. release A, B\n        layout.event(Press(0, 0));\n        for _ in 0..25 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        layout.event(Release(0, 0));\n        for _ in 0..25 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        layout.event(Press(0, 1));\n        layout.event(Press(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[A, LShift], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[A, B], layout.keycodes());\n        layout.event(Release(0, 1));\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[B], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test:\n        // 1. press one-shot\n        // 2. release one-shot\n        // 3. press A after timeout\n        // 4. release A\n        layout.event(Press(0, 0));\n        for _ in 0..25 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        layout.event(Release(0, 0));\n        for _ in 0..75 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[A], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test:\n        // 1. press one-shot\n        // 2. press A\n        // 3. release A\n        // 4. release one-shot\n        layout.event(Press(0, 0));\n        for _ in 0..25 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift, A], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test:\n        // 1. press one-shot\n        // 2. press A after timeout\n        // 3. release A\n        // 4. release one-shot\n        layout.event(Press(0, 0));\n        for _ in 0..200 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift, A], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test:\n        // 1. press one-shot\n        // 2. release one-shot\n        // 3. press one-shot within timeout\n        // 4. release one-shot quickly - should end\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test:\n        // 1. press one-shot\n        // 2. release one-shot\n        // 3. press one-shot within timeout\n        // 4. release one-shot after timeout\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        for _ in 0..200 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn one_shot_end_on_release() {\n        static LAYERS: Layers<3, 1> = &[[[\n            OneShot(&crate::action::OneShot {\n                timeout: 100,\n                action: &k(LShift),\n                end_config: OneShotEndConfig::EndOnFirstRelease,\n            }),\n            k(A),\n            k(B),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n\n        // Test:\n        // 1. press one-shot\n        // 2. release one-shot\n        // 3. press A within timeout\n        // 4. press B within timeout\n        // 5. release A, B\n        layout.event(Press(0, 0));\n        for _ in 0..25 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        layout.event(Release(0, 0));\n        for _ in 0..25 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        layout.event(Press(0, 1));\n        layout.event(Press(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[A, LShift], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[A, B, LShift], layout.keycodes());\n        layout.event(Release(0, 1));\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[B, LShift], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test:\n        // 1. press one-shot\n        // 2. release one-shot\n        // 3. press A after timeout\n        // 4. release A\n        layout.event(Press(0, 0));\n        for _ in 0..25 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        layout.event(Release(0, 0));\n        for _ in 0..75 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[A], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test:\n        // 1. press one-shot\n        // 2. press A\n        // 3. release A\n        // 4. release one-shot\n        layout.event(Press(0, 0));\n        for _ in 0..25 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift, A], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test:\n        // 1. press one-shot\n        // 2. press A after timeout\n        // 3. release A\n        // 4. release one-shot\n        layout.event(Press(0, 0));\n        for _ in 0..200 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift, A], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test:\n        // 3. press A\n        // 1. press one-shot\n        // 2. release one-shot\n        // 3. release A\n        // 4. press B within timeout\n        // 5. release B\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[A], layout.keycodes());\n        layout.event(Press(0, 0));\n        for _ in 0..25 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[A, LShift], layout.keycodes());\n        }\n        layout.event(Release(0, 0));\n        for _ in 0..25 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[A, LShift], layout.keycodes());\n        }\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Press(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[B, LShift], layout.keycodes());\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn one_shot_multi() {\n        static LAYERS: Layers<4, 1> = &[\n            [[\n                OneShot(&crate::action::OneShot {\n                    timeout: 100,\n                    action: &k(LShift),\n                    end_config: OneShotEndConfig::EndOnFirstPress,\n                }),\n                OneShot(&crate::action::OneShot {\n                    timeout: 100,\n                    action: &k(LCtrl),\n                    end_config: OneShotEndConfig::EndOnFirstPress,\n                }),\n                OneShot(&crate::action::OneShot {\n                    timeout: 100,\n                    action: &Layer(1),\n                    end_config: OneShotEndConfig::EndOnFirstPress,\n                }),\n                NoOp,\n            ]],\n            [[k(A), k(B), k(C), k(D)]],\n        ];\n        let mut layout = Layout::new(LAYERS);\n        layout.oneshot.pause_input_processing_delay = 1;\n\n        layout.event(Press(0, 0));\n        layout.event(Release(0, 0));\n        for _ in 0..90 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        layout.event(Press(0, 1));\n        for _ in 0..90 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift, LCtrl], layout.keycodes());\n        }\n        assert_eq!(layout.current_layer(), 0);\n        layout.event(Press(0, 2));\n        layout.event(Release(0, 2));\n        for _ in 0..90 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift, LCtrl], layout.keycodes());\n            assert_eq!(layout.current_layer(), 1);\n        }\n        layout.event(Press(0, 3));\n        layout.event(Release(0, 3));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift, LCtrl, D], layout.keycodes());\n        assert_eq!(layout.current_layer(), 1);\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LCtrl], layout.keycodes());\n        assert_eq!(layout.current_layer(), 0);\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn one_shot_tap_hold() {\n        static LAYERS: Layers<3, 1> = &[\n            [[\n                OneShot(&crate::action::OneShot {\n                    timeout: 200,\n                    action: &k(LShift),\n                    end_config: OneShotEndConfig::EndOnFirstPress,\n                }),\n                HoldTap(&HoldTapAction {\n                    on_press_reset_timeout_to: None,\n                    require_prior_idle: None,\n                    timeout: 100,\n                    hold: k(LAlt),\n                    timeout_action: k(LAlt),\n                    tap: k(Space),\n                    config: HoldTapConfig::Default,\n                    tap_hold_interval: 0,\n                }),\n                NoOp,\n            ]],\n            [[k(A), k(B), k(C)]],\n        ];\n        let mut layout = Layout::new(LAYERS);\n        layout.oneshot.pause_input_processing_delay = 1;\n\n        layout.event(Press(0, 0));\n        layout.event(Release(0, 0));\n        for _ in 0..90 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        layout.event(Press(0, 1));\n        for _ in 0..90 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift, Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        layout.event(Press(0, 0));\n        layout.event(Release(0, 0));\n        for _ in 0..90 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        layout.event(Press(0, 1));\n        for _ in 0..100 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LShift], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift, LAlt], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn tap_dance_uneager() {\n        static LAYERS: Layers<2, 2> = &[[\n            [\n                TapDance(&crate::action::TapDance {\n                    timeout: 100,\n                    actions: &[\n                        &k(LShift),\n                        &OneShot(&crate::action::OneShot {\n                            timeout: 100,\n                            action: &k(LCtrl),\n                            end_config: OneShotEndConfig::EndOnFirstPress,\n                        }),\n                        &HoldTap(&HoldTapAction {\n                            on_press_reset_timeout_to: None,\n                            require_prior_idle: None,\n                            timeout: 100,\n                            hold: k(LAlt),\n                            timeout_action: k(LAlt),\n                            tap: k(Space),\n                            config: HoldTapConfig::Default,\n                            tap_hold_interval: 0,\n                        }),\n                    ],\n                    config: TapDanceConfig::Lazy,\n                }),\n                k(A),\n            ],\n            [k(B), k(C)],\n        ]];\n        let mut layout = Layout::new(LAYERS);\n\n        // Test: tap-dance first key, timeout\n        layout.event(Press(0, 0));\n        for _ in 0..100 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test: tap-dance first key, press another key\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_keys(&[LShift], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift, A], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[A], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test: tap-dance second key, timeout\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 0));\n        for _ in 0..50 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 0));\n        for _ in 0..99 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        for _ in 0..100 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LCtrl], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test: tap-dance third key, timeout, tap\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 0));\n        for _ in 0..50 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 0));\n        for _ in 0..50 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test: tap-dance third key, timeout, hold\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 0));\n        for _ in 0..50 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 0));\n        for _ in 0..50 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Press(0, 0));\n        for _ in 0..100 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        for _ in 0..200 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[LAlt], layout.keycodes());\n        }\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn tap_dance_eager() {\n        static LAYERS: Layers<2, 2> = &[[\n            [\n                TapDance(&crate::action::TapDance {\n                    timeout: 100,\n                    actions: &[&k(Kb1), &k(Kb2), &k(Kb3)],\n                    config: TapDanceConfig::Eager,\n                }),\n                k(A),\n            ],\n            [k(B), k(C)],\n        ]];\n        let mut layout = Layout::new(LAYERS);\n\n        // Test: tap-dance-eager first key\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb1], layout.keycodes());\n        for _ in 0..200 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[Kb1], layout.keycodes());\n        }\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test: tap-dance-eager first key, press another key\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb1], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb1, A], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_keys(&[Kb1, A], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[A], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test: tap-dance second key\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb1], layout.keycodes());\n        layout.event(Release(0, 0));\n        for _ in 0..50 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb2], layout.keycodes());\n        layout.event(Release(0, 0));\n        for _ in 0..99 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Test: tap-dance third key\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb1], layout.keycodes());\n        layout.event(Release(0, 0));\n        for _ in 0..50 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb2], layout.keycodes());\n        layout.event(Release(0, 0));\n        for _ in 0..50 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb3], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn release_state() {\n        static LAYERS: Layers<2, 1> = &[\n            [[\n                MultipleActions(&(&[KeyCode(LCtrl), Layer(1)] as _)),\n                MultipleActions(&(&[KeyCode(LAlt), Layer(1)] as _)),\n            ]],\n            [[\n                MultipleActions(\n                    &(&[ReleaseState(ReleasableState::KeyCode(LAlt)), KeyCode(Space)] as _),\n                ),\n                ReleaseState(ReleasableState::Layer(1)),\n            ]],\n        ];\n\n        let mut layout = Layout::new(LAYERS);\n\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LAlt], layout.keycodes());\n        assert_eq!(1, layout.current_layer());\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LCtrl], layout.keycodes());\n        assert_eq!(1, layout.current_layer());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_eq!(0, layout.current_layer());\n        assert_keys(&[LCtrl], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LCtrl], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn test_chord() {\n        const GROUP: ChordsGroup<core::convert::Infallible> = ChordsGroup {\n            coords: &[((0, 2), 1), ((0, 3), 2), ((0, 4), 4), ((0, 5), 8)],\n            chords: &[\n                (1, &KeyCode(Kb1)),\n                (2, &KeyCode(Kb2)),\n                (4, &KeyCode(Kb3)),\n                (8, &KeyCode(Kb4)),\n                (3, &KeyCode(Kb5)),\n                (11, &KeyCode(Kb6)),\n            ],\n            timeout: 100,\n        };\n        static LAYERS: Layers<6, 1> = &[[[\n            NoOp,\n            NoOp,\n            Chords(&GROUP),\n            Chords(&GROUP),\n            Chords(&GROUP),\n            Chords(&GROUP),\n        ]]];\n\n        let mut layout = Layout::new(LAYERS);\n        layout.event(Press(0, 2));\n        // timeout on non-terminal chord\n        for _ in 0..50 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Press(0, 3));\n        for _ in 0..49 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb5], layout.keycodes());\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb5], layout.keycodes());\n        layout.event(Release(0, 3));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // timeout on terminal chord with no action associated\n        // combo like (h j k) -> (h j) (k)\n        layout.event(Press(0, 2));\n        for _ in 0..50 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Press(0, 3));\n        for _ in 0..30 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Press(0, 4));\n        for _ in 0..20 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb5], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb5, Kb3], layout.keycodes());\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb3], layout.keycodes());\n        layout.event(Release(0, 3));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb3], layout.keycodes());\n        layout.event(Release(0, 4));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // release terminal chord with no action associated\n        // combo like (h j k) -> (h j) (k)\n        layout.event(Press(0, 2));\n        for _ in 0..50 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Press(0, 3));\n        for _ in 0..30 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Press(0, 4));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 4));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb5], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb5, Kb3], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb5], layout.keycodes());\n        layout.event(Release(0, 2));\n        layout.event(Release(0, 3));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // release terminal chord with no action associated\n        // Test combo like (h j k l) -> (h) (j k l)\n        layout.event(Press(0, 4));\n        layout.event(Press(0, 2));\n        layout.event(Press(0, 3));\n        layout.event(Press(0, 5));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        for _ in 0..30 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb3], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb3, Kb6], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb3], layout.keycodes());\n    }\n\n    #[test]\n    fn test_chord_normalkey_order() {\n        const GROUP: ChordsGroup<core::convert::Infallible> = ChordsGroup {\n            coords: &[((0, 2), 1), ((0, 3), 2), ((0, 4), 4), ((0, 5), 8)],\n            chords: &[\n                (1, &KeyCode(Kb1)),\n                (2, &KeyCode(Kb2)),\n                (4, &KeyCode(Kb3)),\n                (8, &KeyCode(Kb4)),\n                (3, &KeyCode(Kb5)),\n                (11, &KeyCode(Kb6)),\n            ],\n            timeout: 100,\n        };\n        static LAYERS: Layers<6, 1> = &[[[\n            NoOp,\n            k(A),\n            Chords(&GROUP),\n            Chords(&GROUP),\n            Chords(&GROUP),\n            Chords(&GROUP),\n        ]]];\n\n        let mut layout = Layout::new(LAYERS);\n        layout.event(Press(0, 2));\n        // timeout on non-terminal chord\n        for _ in 0..50 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb1], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb1, A], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb1], layout.keycodes());\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn test_chord_multi_waiting_decomposition() {\n        const GROUP: ChordsGroup<core::convert::Infallible> = ChordsGroup {\n            coords: &[((0, 0), 1), ((0, 1), 2)],\n            chords: &[\n                (\n                    1,\n                    &HoldTap(&HoldTapAction {\n                        on_press_reset_timeout_to: None,\n                        require_prior_idle: None,\n                        timeout: 100,\n                        hold: k(A),\n                        timeout_action: k(A),\n                        tap: k(Kb1),\n                        config: HoldTapConfig::Default,\n                        tap_hold_interval: 0,\n                    }),\n                ),\n                (\n                    2,\n                    &HoldTap(&HoldTapAction {\n                        on_press_reset_timeout_to: None,\n                        require_prior_idle: None,\n                        timeout: 100,\n                        hold: k(B),\n                        timeout_action: k(B),\n                        tap: k(Kb2),\n                        config: HoldTapConfig::Default,\n                        tap_hold_interval: 0,\n                    }),\n                ),\n            ],\n            timeout: 100,\n        };\n        static LAYERS: Layers<2, 1> = &[[[Chords(&GROUP), Chords(&GROUP)]]];\n\n        let mut layout = Layout::new(LAYERS);\n        layout.quick_tap_hold_timeout = true;\n        layout.event(Press(0, 0));\n        layout.event(Press(0, 1));\n        // Why does this take 103 ticks?\n        // 0: chord begin\n        // 1: chord decompose\n        // 2: action queue dequeue\n        // 3: action queue dequeue\n        // 4-103: timeout ticks\n        for _ in 0..102 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[A, B], layout.keycodes());\n        layout.event(Release(0, 0));\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[B], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn test_fork() {\n        static LAYERS: Layers<2, 1> = &[[[\n            Fork(&ForkConfig {\n                left: k(Kb1),\n                right: k(Kb2),\n                right_triggers: &[Space],\n            }),\n            k(Space),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb1], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        layout.event(Press(0, 1));\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Space, Kb2], layout.keycodes());\n        layout.event(Release(0, 1));\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[Kb2], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn test_repeat() {\n        static LAYERS: Layers<5, 1> = &[\n            [[\n                k(A),\n                MultipleKeyCodes(&[LShift, B].as_slice()),\n                Repeat,\n                MultipleActions(&[k(C), k(D)].as_slice()),\n                Layer(1),\n            ]],\n            [[\n                k(E),\n                MultipleKeyCodes(&[LShift, F].as_slice()),\n                Repeat,\n                MultipleActions(&[k(G), k(H)].as_slice()),\n                Layer(1),\n            ]],\n        ];\n        let mut layout = Layout::new(LAYERS);\n\n        // Press a key\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[A], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Repeat it, should be the same\n        layout.event(Press(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[A], layout.keycodes());\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Press a chord\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift, B], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Repeat it, should be the same\n        layout.event(Press(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LShift, B], layout.keycodes());\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Press a multiple action\n        layout.event(Press(0, 3));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[C, D], layout.keycodes());\n        layout.event(Release(0, 3));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Repeat it, should be the same\n        layout.event(Press(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[C, D], layout.keycodes());\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Go to a different layer and press a key\n        layout.event(Press(0, 4));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[E], layout.keycodes());\n        layout.event(Release(0, 4));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[E], layout.keycodes());\n        layout.event(Release(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Repeat, should be the same as the other layer\n        layout.event(Press(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[E], layout.keycodes());\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        // Activate the layer action and press repeat there, should still be the same action\n        layout.event(Press(0, 4));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[E], layout.keycodes());\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 4));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn test_clear_multiple_keycodes() {\n        static LAYERS: Layers<2, 1> = &[[[k(A), MultipleKeyCodes(&[LCtrl, Enter].as_slice())]]];\n        let mut layout = Layout::new(LAYERS);\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[LCtrl, Enter], layout.keycodes());\n        // Cancel chord keys on next keypress.\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[A], layout.keycodes());\n    }\n\n    // Tests the new Trans behavior.\n    // https://github.com/jtroo/kanata/issues/738\n    #[test]\n    fn test_trans_in_stacked_held_layers() {\n        static LAYERS: Layers<4, 1> = &[\n            [[Layer(1), NoOp, NoOp, k(A)]],\n            [[NoOp, Layer(2), NoOp, k(B)]],\n            [[NoOp, NoOp, Layer(3), Trans]],\n            [[NoOp, NoOp, NoOp, Trans]],\n        ];\n        let mut layout = Layout::new(LAYERS);\n\n        // change to layer 2\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        // change to layer 3\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        // change to layer 4\n        layout.event(Press(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        // pressing Trans should press a key in layer 2, compared to previous behavior,\n        // where a key in layer 1 would be pressed\n        layout.event(Press(0, 3));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[B], layout.keycodes());\n        layout.event(Release(0, 3));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn test_trans_in_action_on_first_layer() {\n        static DEFSRC_LAYER: [Action; 2] = [NoOp, k(X)];\n        static LAYERS: Layers<2, 1> = &[\n            [[Layer(1), Trans]],\n            [[NoOp, MultipleActions(&[Trans].as_slice())]],\n        ];\n        let mut layout = Layout::new_with_trans_action_settings(&DEFSRC_LAYER, LAYERS, true, true);\n\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[X], layout.keycodes());\n        layout.event(Release(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn test_trans_in_taphold_tap() {\n        static LAYERS: Layers<3, 1> = &[\n            [[Layer(1), NoOp, k(A)]],\n            [[NoOp, Layer(2), k(B)]],\n            [[\n                NoOp,\n                NoOp,\n                HoldTap(&HoldTapAction {\n                    on_press_reset_timeout_to: None,\n                    require_prior_idle: None,\n                    timeout: 50,\n                    hold: k(Space),\n                    timeout_action: k(Space),\n                    tap: Trans,\n                    config: HoldTapConfig::Default,\n                    tap_hold_interval: 200,\n                }),\n            ]],\n        ];\n        let mut layout = Layout::new(LAYERS);\n\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 2)); // press th\n        for _ in 0..10 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Release(0, 2)); // release th\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[B], layout.keycodes()); // B is resolved from Trans\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        // test tap action repeat\n        layout.event(Press(0, 2));\n        for _ in 0..30 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[B], layout.keycodes());\n        }\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn test_trans_in_taphold_hold() {\n        static LAYERS: Layers<3, 1> = &[\n            [[Layer(1), NoOp, k(A)]],\n            [[NoOp, Layer(2), k(B)]],\n            [[\n                NoOp,\n                NoOp,\n                HoldTap(&HoldTapAction {\n                    on_press_reset_timeout_to: None,\n                    require_prior_idle: None,\n                    timeout: 50,\n                    hold: Trans,\n                    timeout_action: Trans,\n                    tap: k(Space),\n                    config: HoldTapConfig::Default,\n                    tap_hold_interval: 200,\n                }),\n            ]],\n        ];\n        let mut layout = Layout::new(LAYERS);\n\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 2)); // press th\n        for _ in 0..50 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        for _ in 0..70 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[B], layout.keycodes()); // B is resolved from Trans\n        }\n        layout.event(Release(0, 2)); // release th\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn test_trans_in_tapdance_lazy() {\n        static LAYERS: Layers<3, 1> = &[\n            [[Layer(1), NoOp, k(A)]],\n            [[NoOp, Layer(2), k(B)]],\n            [[\n                NoOp,\n                NoOp,\n                TapDance(&crate::action::TapDance {\n                    timeout: 100,\n                    actions: &[&Trans, &k(X)],\n                    config: TapDanceConfig::Lazy,\n                }),\n            ]],\n        ];\n        let mut layout = Layout::new(LAYERS);\n\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 2));\n        for _ in 0..10 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Release(0, 2));\n        for _ in 0..90 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[B], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn test_trans_in_tapdance_eager() {\n        static LAYERS: Layers<3, 1> = &[\n            [[Layer(1), NoOp, k(A)]],\n            [[NoOp, Layer(2), k(B)]],\n            [[\n                NoOp,\n                NoOp,\n                TapDance(&crate::action::TapDance {\n                    timeout: 100,\n                    actions: &[&Trans, &k(X)],\n                    config: TapDanceConfig::Eager,\n                }),\n            ]],\n        ];\n        let mut layout = Layout::new(LAYERS);\n\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 2));\n        for _ in 0..10 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[B], layout.keycodes());\n        }\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn test_trans_in_multi() {\n        static LAYERS: Layers<3, 1> = &[\n            [[Layer(1), NoOp, k(A)]],\n            [[NoOp, Layer(2), k(B)]],\n            [[NoOp, NoOp, MultipleActions(&[Trans, k(X)].as_slice())]],\n        ];\n        let mut layout = Layout::new(LAYERS);\n\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 2));\n        for _ in 0..10 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[B, X], layout.keycodes());\n        }\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn test_trans_in_chords() {\n        const GROUP: ChordsGroup<core::convert::Infallible> = ChordsGroup {\n            coords: &[((0, 2), 1), ((0, 3), 2)],\n            chords: &[(1, &Trans), (2, &Trans), (3, &KeyCode(X))],\n            timeout: 100,\n        };\n        static LAYERS: Layers<4, 1> = &[\n            [[Layer(1), NoOp, k(A), k(B)]],\n            [[NoOp, Layer(2), k(C), k(D)]],\n            [[NoOp, NoOp, Chords(&GROUP), Chords(&GROUP)]],\n        ];\n        let mut layout = Layout::new(LAYERS);\n\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 2));\n        for _ in 0..10 {\n            assert_eq!(CustomEvent::NoEvent, layout.tick());\n            assert_keys(&[], layout.keycodes());\n        }\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[C], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn test_trans_in_fork() {\n        static LAYERS: Layers<3, 1> = &[\n            [[Layer(1), NoOp, k(A)]],\n            [[NoOp, Layer(2), k(B)]],\n            [[\n                NoOp,\n                NoOp,\n                Fork(&ForkConfig {\n                    left: Trans,\n                    right: Trans,\n                    right_triggers: &[Space],\n                }),\n            ]],\n        ];\n        let mut layout = Layout::new(LAYERS);\n\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[B], layout.keycodes());\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn test_trans_in_switch() {\n        static LAYERS: Layers<3, 1> = &[\n            [[Layer(1), NoOp, k(A)]],\n            [[NoOp, Layer(2), k(B)]],\n            [[\n                NoOp,\n                NoOp,\n                Switch(&switch::Switch {\n                    cases: &[(&[], &Trans, BreakOrFallthrough::Break)],\n                }),\n            ]],\n        ];\n        let mut layout = Layout::new(LAYERS);\n        layout.trans_resolution_behavior_v2 = true;\n\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n\n        layout.event(Press(0, 2));\n        // No idea why we have to wait 2 ticks here. Is this a bug in switch?\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[B], layout.keycodes());\n\n        layout.event(Release(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n    }\n\n    #[test]\n    fn test_multiple_taphold_trans() {\n        static LAYERS: Layers<4, 1> = &[\n            [[Layer(1), NoOp, NoOp, k(A)]],\n            [[\n                NoOp,\n                Layer(2),\n                NoOp,\n                HoldTap(&HoldTapAction {\n                    on_press_reset_timeout_to: None,\n                    require_prior_idle: None,\n                    timeout: 50,\n                    hold: k(B),\n                    timeout_action: k(B),\n                    tap: Trans,\n                    config: HoldTapConfig::Default,\n                    tap_hold_interval: 200,\n                }),\n            ]],\n            [[\n                NoOp,\n                NoOp,\n                Layer(3),\n                HoldTap(&HoldTapAction {\n                    on_press_reset_timeout_to: None,\n                    require_prior_idle: None,\n                    timeout: 50,\n                    hold: k(C),\n                    timeout_action: k(C),\n                    tap: Trans,\n                    config: HoldTapConfig::Default,\n                    tap_hold_interval: 200,\n                }),\n            ]],\n            [[\n                NoOp,\n                NoOp,\n                NoOp,\n                HoldTap(&HoldTapAction {\n                    on_press_reset_timeout_to: None,\n                    require_prior_idle: None,\n                    timeout: 50,\n                    hold: k(D),\n                    timeout_action: k(D),\n                    tap: Trans,\n                    config: HoldTapConfig::Default,\n                    tap_hold_interval: 200,\n                }),\n            ]],\n        ];\n        let mut layout = Layout::new(LAYERS);\n\n        layout.event(Press(0, 0));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 1));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 2));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Press(0, 3));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 3));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[], layout.keycodes());\n        layout.event(Release(0, 3));\n        assert_eq!(CustomEvent::NoEvent, layout.tick());\n        assert_keys(&[A], layout.keycodes());\n    }\n\n    #[test]\n    fn trans_in_multi_works_with_all_trans_settings() {\n        let permutations: &[(bool, bool)] =\n            &[(false, false), (false, true), (true, false), (true, true)];\n\n        for &(trans_v2, delegate_to_1st) in permutations {\n            static DEFSRC_LAYER: [Action; 3] = [NoOp, NoOp, k(X)];\n            static LAYERS: Layers<3, 1> = &[\n                [[\n                    Layer(1),\n                    DefaultLayer(1),\n                    MultipleActions(&[Trans, k(Y)].as_slice()),\n                ]],\n                [[NoOp, Layer(2), k(B)]],\n                [[NoOp, NoOp, Trans]],\n            ];\n            for &do_layer_switch in &[false, true] {\n                let mut layout = Layout::new_with_trans_action_settings(\n                    &DEFSRC_LAYER,\n                    LAYERS,\n                    trans_v2,\n                    delegate_to_1st,\n                );\n\n                layout.event(Press(0, 2));\n                assert_eq!(CustomEvent::NoEvent, layout.tick());\n                assert_keys(&[X, Y], layout.keycodes());\n                layout.event(Release(0, 2));\n                assert_eq!(CustomEvent::NoEvent, layout.tick());\n                assert_keys(&[], layout.keycodes());\n\n                if do_layer_switch {\n                    layout.event(Press(0, 1));\n                    assert_eq!(CustomEvent::NoEvent, layout.tick());\n                    assert_keys(&[], layout.keycodes());\n                    layout.event(Release(0, 1));\n                    assert_eq!(CustomEvent::NoEvent, layout.tick());\n                    assert_keys(&[], layout.keycodes());\n                    assert_eq!(layout.default_layer, 1);\n                } else {\n                    layout.event(Press(0, 0));\n                    assert_eq!(CustomEvent::NoEvent, layout.tick());\n                    assert_keys(&[], layout.keycodes());\n                }\n\n                layout.event(Press(0, 2));\n                assert_eq!(CustomEvent::NoEvent, layout.tick());\n                assert_keys(&[B], layout.keycodes());\n                layout.event(Release(0, 2));\n                assert_eq!(CustomEvent::NoEvent, layout.tick());\n                assert_keys(&[], layout.keycodes());\n\n                layout.event(Press(0, 1));\n                assert_eq!(CustomEvent::NoEvent, layout.tick());\n                assert_keys(&[], layout.keycodes());\n                layout.event(Press(0, 2));\n                assert_eq!(CustomEvent::NoEvent, layout.tick());\n                assert_keys(\n                    match (trans_v2, delegate_to_1st) {\n                        (false, false) => &[X],\n                        (false, true) => &[X, Y],\n                        (true, _) => &[B],\n                    },\n                    layout.keycodes(),\n                );\n                layout.event(Release(0, 2));\n                assert_eq!(CustomEvent::NoEvent, layout.tick());\n                assert_keys(&[], layout.keycodes());\n            }\n        }\n    }\n\n    #[cfg(feature = \"tap_hold_tracker\")]\n    #[test]\n    fn hold_activated_is_set_on_hold_timeout() {\n        static LAYERS: Layers<1, 1> = &[[[HoldTap(&HoldTapAction {\n            timeout: 5,\n            hold: k(LAlt),\n            timeout_action: k(LAlt),\n            tap: k(Space),\n            config: HoldTapConfig::Default,\n            tap_hold_interval: 0,\n            on_press_reset_timeout_to: None,\n            require_prior_idle: None,\n        })]]];\n        let mut layout = Layout::new(LAYERS);\n        // Nothing set initially.\n        assert!(layout.tap_hold_tracker.take_hold_activated().is_none());\n\n        layout.event(Press(0, 0));\n        for _ in 0..6 {\n            let _ = layout.tick();\n        }\n        // Hold action should be active.\n        assert_keys(&[LAlt], layout.keycodes());\n\n        // Flag should be set exactly once.\n        let info = layout\n            .tap_hold_tracker\n            .take_hold_activated()\n            .expect(\"hold_activated should be set\");\n        assert_eq!(info.coord, (0, 0));\n        assert!(layout.tap_hold_tracker.take_hold_activated().is_none());\n    }\n\n    #[cfg(feature = \"tap_hold_tracker\")]\n    #[test]\n    fn tap_activated_is_set_on_tap_release() {\n        static LAYERS: Layers<2, 1> = &[[[\n            HoldTap(&HoldTapAction {\n                timeout: 200,\n                hold: k(LAlt),\n                timeout_action: k(LAlt),\n                tap: k(Space),\n                config: HoldTapConfig::Default,\n                tap_hold_interval: 0,\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n            }),\n            k(A),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n        assert!(layout.tap_hold_tracker.take_tap_activated().is_none());\n\n        // Quick press and release triggers tap.\n        layout.event(Press(0, 0));\n        let _ = layout.tick();\n        layout.event(Release(0, 0));\n        let _ = layout.tick();\n\n        assert_keys(&[Space], layout.keycodes());\n        let info = layout\n            .tap_hold_tracker\n            .take_tap_activated()\n            .expect(\"tap_activated should be set\");\n        assert_eq!(info.coord, (0, 0));\n        assert!(layout.tap_hold_tracker.take_tap_activated().is_none());\n    }\n\n    #[cfg(feature = \"tap_hold_tracker\")]\n    #[test]\n    fn hold_activated_is_set_on_permissive_hold() {\n        static LAYERS: Layers<2, 1> = &[[[\n            HoldTap(&HoldTapAction {\n                timeout: 200,\n                hold: k(LAlt),\n                timeout_action: k(LAlt),\n                tap: k(Space),\n                config: HoldTapConfig::PermissiveHold,\n                tap_hold_interval: 0,\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n            }),\n            k(A),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n        assert!(layout.tap_hold_tracker.take_hold_activated().is_none());\n\n        // PermissiveHold: press hold-tap key, press another key, release it => hold.\n        layout.event(Press(0, 0));\n        let _ = layout.tick();\n        layout.event(Press(0, 1));\n        let _ = layout.tick();\n        layout.event(Release(0, 1));\n        let _ = layout.tick();\n\n        assert_keys(&[LAlt], layout.keycodes());\n        let info = layout\n            .tap_hold_tracker\n            .take_hold_activated()\n            .expect(\"hold_activated should be set via waiting_into_hold\");\n        assert_eq!(info.coord, (0, 0));\n        assert!(layout.tap_hold_tracker.take_tap_activated().is_none());\n    }\n\n    #[cfg(feature = \"tap_hold_tracker\")]\n    #[test]\n    fn hold_activated_is_set_on_hold_on_other_key_press() {\n        static LAYERS: Layers<2, 1> = &[[[\n            HoldTap(&HoldTapAction {\n                timeout: 200,\n                hold: k(LAlt),\n                timeout_action: k(LAlt),\n                tap: k(Space),\n                config: HoldTapConfig::HoldOnOtherKeyPress,\n                tap_hold_interval: 0,\n                on_press_reset_timeout_to: None,\n                require_prior_idle: None,\n            }),\n            k(A),\n        ]]];\n        let mut layout = Layout::new(LAYERS);\n        assert!(layout.tap_hold_tracker.take_hold_activated().is_none());\n\n        // HoldOnOtherKeyPress: press hold-tap key, then press another key => hold.\n        layout.event(Press(0, 0));\n        let _ = layout.tick();\n        layout.event(Press(0, 1));\n        let _ = layout.tick();\n\n        assert_keys(&[LAlt], layout.keycodes());\n        let info = layout\n            .tap_hold_tracker\n            .take_hold_activated()\n            .expect(\"hold_activated should be set via waiting_into_hold\");\n        assert_eq!(info.coord, (0, 0));\n        assert!(layout.tap_hold_tracker.take_tap_activated().is_none());\n    }\n\n    #[test]\n    fn chord_does_not_set_tap_hold_activated() {\n        const GROUP: ChordsGroup<core::convert::Infallible> = ChordsGroup {\n            coords: &[((0, 0), 1), ((0, 1), 2)],\n            chords: &[(3, &KeyCode(Kb5))],\n            timeout: 100,\n        };\n        static LAYERS: Layers<2, 1> = &[[[Chords(&GROUP), Chords(&GROUP)]]];\n\n        let mut layout = Layout::new(LAYERS);\n        layout.event(Press(0, 0));\n        for _ in 0..50 {\n            let _ = layout.tick();\n        }\n        layout.event(Press(0, 1));\n        for _ in 0..50 {\n            let _ = layout.tick();\n        }\n        assert_keys(&[Kb5], layout.keycodes());\n        // Chord resolution must not set tap or hold activated flags.\n        assert!(layout.tap_hold_tracker.take_hold_activated().is_none());\n        assert!(layout.tap_hold_tracker.take_tap_activated().is_none());\n    }\n\n    #[test]\n    fn tap_dance_does_not_set_tap_hold_activated() {\n        static LAYERS: Layers<2, 1> = &[[[\n            TapDance(&crate::action::TapDance {\n                timeout: 100,\n                actions: &[&k(LShift), &k(LCtrl)],\n                config: TapDanceConfig::Lazy,\n            }),\n            k(A),\n        ]]];\n\n        let mut layout = Layout::new(LAYERS);\n        // Single tap, wait for timeout.\n        layout.event(Press(0, 0));\n        for _ in 0..101 {\n            let _ = layout.tick();\n        }\n        assert_keys(&[LShift], layout.keycodes());\n        // Tap-dance resolution must not set tap or hold activated flags.\n        assert!(layout.tap_hold_tracker.take_hold_activated().is_none());\n        assert!(layout.tap_hold_tracker.take_tap_activated().is_none());\n    }\n}\n"
  },
  {
    "path": "keyberon/src/lib.rs",
    "content": "//! This is a fork intended for use by the [kanata keyboard remapper software](https://github.com/jtroo/kanata).\n//! Please make contributions to the original project.\n\npub mod action;\npub mod chord;\npub mod key_code;\npub mod layout;\nmod multikey_buffer;\npub mod tap_hold_tracker;\n"
  },
  {
    "path": "keyberon/src/multikey_buffer.rs",
    "content": "//! Module for `MultiKeyBuffer`.\n\nuse std::{array, slice};\n\nuse crate::action::{Action, ONE_SHOT_MAX_ACTIVE};\nuse crate::key_code::KeyCode;\n\n// Presumably this should be plenty.\n// ONE_SHOT_MAX_ACTIVE is already likely unreasonably large enough.\n// This buffer capacity adds more onto that,\n// just in case somebody finds a way to use all of the one-shot capacity.\nconst BUFCAP: usize = ONE_SHOT_MAX_ACTIVE + 4;\n\n/// This is an unsafe container that enables a mutable Action::MultipleKeyCodes.\npub(crate) struct MultiKeyBuffer<'a, T> {\n    buf: [KeyCode; BUFCAP],\n    size: usize,\n    ptr: *mut &'static [KeyCode],\n    ac: *mut Action<'a, T>,\n}\n\nunsafe impl<T> Send for MultiKeyBuffer<'_, T> {}\n\nimpl<'a, T> MultiKeyBuffer<'a, T> {\n    /// Create a new instance of `MultiKeyBuffer`.\n    ///\n    /// # Safety\n    ///\n    /// The program should not have any references to the inner buffer when the struct is dropped.\n    pub(crate) unsafe fn new() -> Self {\n        Self {\n            buf: array::from_fn(|_| KeyCode::Escape),\n            size: 0,\n            ptr: Box::leak(Box::new(slice::from_raw_parts(\n                core::ptr::NonNull::dangling().as_ptr(),\n                0,\n            ))),\n            ac: Box::leak(Box::new(Action::NoOp)),\n        }\n    }\n\n    /// Set the current size of the buffer to zero.\n    ///\n    /// # Safety\n    ///\n    /// The program should not have any references to the inner buffer.\n    pub(crate) unsafe fn clear(&mut self) {\n        self.size = 0;\n    }\n\n    /// Push to the end of the buffer. If the buffer is full, this silently fails.\n    ///\n    /// # Safety\n    ///\n    /// The program should not have any references to the inner buffer.\n    pub(crate) unsafe fn push(&mut self, kc: KeyCode) {\n        if self.size < BUFCAP {\n            self.buf[self.size] = kc;\n            self.size += 1;\n        }\n    }\n\n    /// Get a reference to the inner buffer in the form of an `Action`.\n    /// The `Action` will be the variant `MultipleKeyCodes`,\n    /// containing all keys that have been pushed.\n    ///\n    /// # Safety\n    ///\n    /// The program should not have any references to the inner buffer before calling.\n    /// The program should not mutate the buffer after calling this function until after the\n    /// returned reference is dropped.\n    pub(crate) unsafe fn get_ref(&self) -> &'a Action<'a, T> {\n        *self.ac = Action::NoOp;\n        *self.ptr = slice::from_raw_parts(self.buf.as_ptr(), self.size);\n        *self.ac = Action::MultipleKeyCodes(&*self.ptr);\n        &*self.ac\n    }\n}\n\nimpl<T> Drop for MultiKeyBuffer<'_, T> {\n    fn drop(&mut self) {\n        unsafe {\n            drop(Box::from_raw(self.ac));\n            drop(Box::from_raw(self.ptr));\n        }\n    }\n}\n"
  },
  {
    "path": "keyberon/src/tap_hold_tracker.rs",
    "content": "//! Tracks tap-hold activation events for external consumers (e.g. TCP broadcast).\n//!\n//! When the `tap_hold_tracker` feature is enabled, this module stores the\n//! coordinate of the most recent hold/tap activation so that higher-level code\n//! can relay it over the network.  When the feature is disabled the tracker is\n//! a zero-sized no-op — all setters are empty and all getters return `None`.\n//!\n//! The `config` parameter on the setters accepts a `&WaitingConfig` reference;\n//! the `matches!` guard lives inside the method body so that the no-op stub's\n//! empty body causes the compiler to eliminate the call entirely.\n\n#[cfg(feature = \"tap_hold_tracker\")]\nmod inner {\n    use crate::layout::{KCoord, WaitingConfig};\n\n    /// Information about a tap-hold key that just transitioned to hold state.\n    #[derive(Debug, Clone, Copy)]\n    pub struct HoldActivatedInfo {\n        /// The key coordinate (row, column).\n        pub coord: KCoord,\n    }\n\n    /// Information about a tap-hold key that just triggered its tap action.\n    #[derive(Debug, Clone, Copy)]\n    pub struct TapActivatedInfo {\n        /// The key coordinate (row, column).\n        pub coord: KCoord,\n    }\n\n    /// Records the most recent tap-hold activation event.\n    #[derive(Debug, Default)]\n    pub struct TapHoldTracker {\n        hold_activated: Option<HoldActivatedInfo>,\n        tap_activated: Option<TapActivatedInfo>,\n    }\n\n    impl TapHoldTracker {\n        pub(crate) fn set_hold_activated<'a, T: std::fmt::Debug>(\n            &mut self,\n            coord: KCoord,\n            config: &WaitingConfig<'a, T>,\n        ) {\n            if matches!(config, WaitingConfig::HoldTap(..)) {\n                self.hold_activated = Some(HoldActivatedInfo { coord });\n            }\n        }\n\n        pub(crate) fn set_tap_activated<'a, T: std::fmt::Debug>(\n            &mut self,\n            coord: KCoord,\n            config: &WaitingConfig<'a, T>,\n        ) {\n            if matches!(config, WaitingConfig::HoldTap(..)) {\n                self.tap_activated = Some(TapActivatedInfo { coord });\n            }\n        }\n\n        pub fn take_hold_activated(&mut self) -> Option<HoldActivatedInfo> {\n            self.hold_activated.take()\n        }\n\n        pub fn take_tap_activated(&mut self) -> Option<TapActivatedInfo> {\n            self.tap_activated.take()\n        }\n    }\n}\n\n#[cfg(not(feature = \"tap_hold_tracker\"))]\nmod inner {\n    use crate::layout::{KCoord, WaitingConfig};\n\n    /// Stub: no coordinate data stored when the feature is disabled.\n    #[derive(Debug, Clone, Copy)]\n    pub struct HoldActivatedInfo {\n        /// The key coordinate (row, column).\n        pub coord: KCoord,\n    }\n\n    /// Stub: no coordinate data stored when the feature is disabled.\n    #[derive(Debug, Clone, Copy)]\n    pub struct TapActivatedInfo {\n        /// The key coordinate (row, column).\n        pub coord: KCoord,\n    }\n\n    /// Zero-sized no-op tracker when the feature is disabled.\n    #[derive(Debug, Default)]\n    pub struct TapHoldTracker;\n\n    impl TapHoldTracker {\n        #[inline(always)]\n        pub(crate) fn set_hold_activated<'a, T: std::fmt::Debug>(\n            &mut self,\n            _coord: KCoord,\n            _config: &WaitingConfig<'a, T>,\n        ) {\n        }\n\n        #[inline(always)]\n        pub(crate) fn set_tap_activated<'a, T: std::fmt::Debug>(\n            &mut self,\n            _coord: KCoord,\n            _config: &WaitingConfig<'a, T>,\n        ) {\n        }\n\n        #[inline(always)]\n        pub fn take_hold_activated(&mut self) -> Option<HoldActivatedInfo> {\n            None\n        }\n\n        #[inline(always)]\n        pub fn take_tap_activated(&mut self) -> Option<TapActivatedInfo> {\n            None\n        }\n    }\n}\n\npub use inner::*;\n"
  },
  {
    "path": "parser/.gitignore",
    "content": "# Ignore Cargo.lock since this is a library crate\nCargo.lock\ntarget"
  },
  {
    "path": "parser/Cargo.toml",
    "content": "[package]\nname = \"kanata-parser\"\nversion = \"0.1110.0\"\nauthors = [\"jtroo <j.andreitabs@gmail.com>\"]\ndescription = \"A parser for configuration language of kanata, a keyboard remapper.\"\nkeywords = [\"kanata\", \"parser\"]\nhomepage = \"https://github.com/jtroo/kanata\"\nrepository = \"https://github.com/jtroo/kanata\"\nreadme = \"README.md\"\nlicense = \"LGPL-3.0-only\"\nedition = \"2021\"\n\n[dependencies]\nanyhow = \"1\"\nbitflags = \"2.5.0\"\nbytemuck = \"1.15.0\"\nitertools = \"0.12\"\nlog = { version = \"0.4.8\", default-features = false }\nmiette = { version = \"5.7.0\", features = [\"fancy\"] }\nonce_cell = \"1\"\nordered-float = \"5.1.0\"\nparking_lot = \"0.12\"\npatricia_tree = \"0.8\"\nrustc-hash = \"1.1.0\"\nthiserror = \"1.0.38\"\n\nkanata-keyberon = { path = \"../keyberon\", version = \"0.1110.0\" }\n\n[dev-dependencies]\nsimplelog = \"0.12.0\"\n\n[features]\ncmd = []\ninterception_driver = []\ngui = []\nlsp = []\nwin_llhook_read_scancodes = []\nwin_sendinput_send_scancodes = []\nzippychord = []\n"
  },
  {
    "path": "parser/LICENSE",
    "content": "                   GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\n  This version of the GNU Lesser General Public License incorporates\nthe terms and conditions of version 3 of the GNU General Public\nLicense, supplemented by the additional permissions listed below.\n\n  0. Additional Definitions.\n\n  As used herein, \"this License\" refers to version 3 of the GNU Lesser\nGeneral Public License, and the \"GNU GPL\" refers to version 3 of the GNU\nGeneral Public License.\n\n  \"The Library\" refers to a covered work governed by this License,\nother than an Application or a Combined Work as defined below.\n\n  An \"Application\" is any work that makes use of an interface provided\nby the Library, but which is not otherwise based on the Library.\nDefining a subclass of a class defined by the Library is deemed a mode\nof using an interface provided by the Library.\n\n  A \"Combined Work\" is a work produced by combining or linking an\nApplication with the Library.  The particular version of the Library\nwith which the Combined Work was made is also called the \"Linked\nVersion\".\n\n  The \"Minimal Corresponding Source\" for a Combined Work means the\nCorresponding Source for the Combined Work, excluding any source code\nfor portions of the Combined Work that, considered in isolation, are\nbased on the Application, and not on the Linked Version.\n\n  The \"Corresponding Application Code\" for a Combined Work means the\nobject code and/or source code for the Application, including any data\nand utility programs needed for reproducing the Combined Work from the\nApplication, but excluding the System Libraries of the Combined Work.\n\n  1. Exception to Section 3 of the GNU GPL.\n\n  You may convey a covered work under sections 3 and 4 of this License\nwithout being bound by section 3 of the GNU GPL.\n\n  2. Conveying Modified Versions.\n\n  If you modify a copy of the Library, and, in your modifications, a\nfacility refers to a function or data to be supplied by an Application\nthat uses the facility (other than as an argument passed when the\nfacility is invoked), then you may convey a copy of the modified\nversion:\n\n   a) under this License, provided that you make a good faith effort to\n   ensure that, in the event an Application does not supply the\n   function or data, the facility still operates, and performs\n   whatever part of its purpose remains meaningful, or\n\n   b) under the GNU GPL, with none of the additional permissions of\n   this License applicable to that copy.\n\n  3. Object Code Incorporating Material from Library Header Files.\n\n  The object code form of an Application may incorporate material from\na header file that is part of the Library.  You may convey such object\ncode under terms of your choice, provided that, if the incorporated\nmaterial is not limited to numerical parameters, data structure\nlayouts and accessors, or small macros, inline functions and templates\n(ten or fewer lines in length), you do both of the following:\n\n   a) Give prominent notice with each copy of the object code that the\n   Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the object code with a copy of the GNU GPL and this license\n   document.\n\n  4. Combined Works.\n\n  You may convey a Combined Work under terms of your choice that,\ntaken together, effectively do not restrict modification of the\nportions of the Library contained in the Combined Work and reverse\nengineering for debugging such modifications, if you also do each of\nthe following:\n\n   a) Give prominent notice with each copy of the Combined Work that\n   the Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the Combined Work with a copy of the GNU GPL and this license\n   document.\n\n   c) For a Combined Work that displays copyright notices during\n   execution, include the copyright notice for the Library among\n   these notices, as well as a reference directing the user to the\n   copies of the GNU GPL and this license document.\n\n   d) Do one of the following:\n\n       0) Convey the Minimal Corresponding Source under the terms of this\n       License, and the Corresponding Application Code in a form\n       suitable for, and under terms that permit, the user to\n       recombine or relink the Application with a modified version of\n       the Linked Version to produce a modified Combined Work, in the\n       manner specified by section 6 of the GNU GPL for conveying\n       Corresponding Source.\n\n       1) Use a suitable shared library mechanism for linking with the\n       Library.  A suitable mechanism is one that (a) uses at run time\n       a copy of the Library already present on the user's computer\n       system, and (b) will operate properly with a modified version\n       of the Library that is interface-compatible with the Linked\n       Version.\n\n   e) Provide Installation Information, but only if you would otherwise\n   be required to provide such information under section 6 of the\n   GNU GPL, and only to the extent that such information is\n   necessary to install and execute a modified version of the\n   Combined Work produced by recombining or relinking the\n   Application with a modified version of the Linked Version. (If\n   you use option 4d0, the Installation Information must accompany\n   the Minimal Corresponding Source and Corresponding Application\n   Code. If you use option 4d1, you must provide the Installation\n   Information in the manner specified by section 6 of the GNU GPL\n   for conveying Corresponding Source.)\n\n  5. Combined Libraries.\n\n  You may place library facilities that are a work based on the\nLibrary side by side in a single library together with other library\nfacilities that are not Applications and are not covered by this\nLicense, and convey such a combined library under terms of your\nchoice, if you do both of the following:\n\n   a) Accompany the combined library with a copy of the same work based\n   on the Library, uncombined with any other library facilities,\n   conveyed under the terms of this License.\n\n   b) Give prominent notice with the combined library that part of it\n   is a work based on the Library, and explaining where to find the\n   accompanying uncombined form of the same work.\n\n  6. Revised Versions of the GNU Lesser General Public License.\n\n  The Free Software Foundation may publish revised and/or new versions\nof the GNU Lesser General Public License from time to time. Such new\nversions will be similar in spirit to the present version, but may\ndiffer in detail to address new problems or concerns.\n\n  Each version is given a distinguishing version number. If the\nLibrary as you received it specifies that a certain numbered version\nof the GNU Lesser General Public License \"or any later version\"\napplies to it, you have the option of following the terms and\nconditions either of that published version or of any later version\npublished by the Free Software Foundation. If the Library as you\nreceived it does not specify a version number of the GNU Lesser\nGeneral Public License, you may choose any version of the GNU Lesser\nGeneral Public License ever published by the Free Software Foundation.\n\n  If the Library as you received it specifies that a proxy can decide\nwhether future versions of the GNU Lesser General Public License shall\napply, that proxy's public statement of acceptance of any version is\npermanent authorization for you to choose that version for the\nLibrary."
  },
  {
    "path": "parser/README.md",
    "content": "# kanata-parser\n\nA parser for configuration language of [kanata](https://github.com/jtroo/kanata).\n\nThis crate does not follow semver. It tracks the version of kanata.\n"
  },
  {
    "path": "parser/src/cfg/alloc.rs",
    "content": "//! This module contains a helper struct for generating 'static lifetime allocations while still\n//! keeping track of them so that they can be freed later.\n\nuse parking_lot::Mutex;\nuse std::sync::Arc;\n\n/// This struct tracks the allocations that are leaked by its provided methods and frees them when\n/// dropped. The `new` function is unsafe because dropping the struct can create dangling\n/// references. Care must be taken to ensure that all allocations made by this struct's methods are\n/// no longer referenced when the struct gets dropped.\n///\n/// In practice, this is not difficult to do in the `cfg` module which only exposes a single public\n/// method.\n///\n/// To avoid leaks, types transformed to &'static by this struct\n/// should not contain nested allocations,\n/// or if they do, the nested allocations should also\n/// be managed by this struct.\npub(crate) struct Allocations {\n    allocations: Mutex<Vec<Allocation>>,\n}\n\n#[derive(Debug, Copy, Clone)]\npub(crate) struct Allocation {\n    ptr: usize,\n    len: usize,\n}\n\nimpl std::fmt::Debug for Allocations {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Allocations\").finish()\n    }\n}\n\nimpl Drop for Allocations {\n    fn drop(&mut self) {\n        log::debug!(\n            \"freeing allocations of length {}\",\n            self.allocations.lock().len()\n        );\n        for a in self.allocations.lock().iter().rev().copied() {\n            log::debug!(\"freeing ptr 0x{:x} len{}\", a.ptr, a.len);\n            unsafe {\n                drop(Box::<[u8]>::from_raw(std::ptr::slice_from_raw_parts_mut(\n                    a.ptr as *mut u8,\n                    a.len,\n                )))\n            };\n        }\n    }\n}\n\nimpl Allocations {\n    /// Create a new allocations group.\n    ///\n    /// # Safety\n    ///\n    /// Ensure that all associated allocations are no longer referenced before dropping all\n    /// clones of the `Arc`.\n    pub(crate) unsafe fn new() -> Arc<Self> {\n        Arc::new(Self {\n            allocations: Mutex::new(vec![]),\n        })\n    }\n\n    /// Returns a `&'static T` by leaking a newly created Box of `v`.\n    pub(crate) fn sref<T>(&self, v: T) -> &'static T {\n        let p = Box::into_raw(Box::new(v));\n        if (p as usize) < 16 {\n            panic!(\"sref bad ptr\");\n        }\n        log::debug!(\n            \"sref type: {}, ptr:{p:?} sz:{}\",\n            std::any::type_name::<T>(),\n            std::mem::size_of::<T>()\n        );\n        self.allocations.lock().push(Allocation {\n            ptr: p as usize,\n            len: std::mem::size_of::<T>(),\n        });\n        Box::leak(unsafe { Box::from_raw(p) })\n    }\n\n    pub(crate) fn bref_slice<T>(&self, v: Box<[T]>) -> &'static [T] {\n        // An empty slice has no backing allocation. `Box<[T]>` is a fat pointer so the leaked return\n        // will contain a length of 0 and an invalid pointer.\n        if !v.is_empty() {\n            let p = v.as_ptr();\n            log::debug!(\n                \"bref_slice type: {}, ptr:{p:?} sz:{}\",\n                std::any::type_name::<T>(),\n                std::mem::size_of::<T>()\n            );\n            self.allocations.lock().push(Allocation {\n                ptr: p as usize,\n                len: std::mem::size_of::<T>() * v.len(),\n            });\n        }\n        Box::leak(v)\n    }\n\n    /// Returns a &'static [&'static T] from a `Vec<T>` by converting to a boxed slice and leaking it.\n    pub(crate) fn sref_vec<T>(&self, v: Vec<T>) -> &'static [T] {\n        log::debug!(\"sref_vec {}\", std::any::type_name::<T>());\n        self.bref_slice(v.into_boxed_slice())\n    }\n\n    /// Returns a `&'static [&'static T]` by leaking a newly created box and boxed slice of `v`.\n    pub(crate) fn sref_slice<T>(&self, v: T) -> &'static [&'static T] {\n        log::debug!(\"sref_slice {}\", std::any::type_name::<T>());\n        self.bref_slice(vec![self.sref(v)].into_boxed_slice())\n    }\n\n    /// Returns a `&'static str` by leaking a String.\n    pub(crate) fn sref_str(&self, v: String) -> &'static str {\n        if !v.capacity() == 0 {\n            \"\"\n        } else {\n            let len = v.len();\n            let s = v.leak();\n            self.allocations.lock().push(Allocation {\n                ptr: s.as_ptr() as usize,\n                len,\n            });\n            s\n        }\n    }\n}\n"
  },
  {
    "path": "parser/src/cfg/arbitrary_code.rs",
    "content": "use super::*;\n\nuse crate::bail;\nuse anyhow::anyhow;\n\npub(crate) fn parse_arbitrary_code(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"arbitrary code expects one parameter: <code: 0-767>\";\n    if ac_params.len() != 1 {\n        bail!(\"{ERR_MSG}\");\n    }\n    let code = ac_params[0]\n        .atom(s.vars())\n        .map(str::parse::<u16>)\n        .and_then(|c| c.ok())\n        .ok_or_else(|| anyhow!(\"{ERR_MSG}: got {:?}\", ac_params[0]))?;\n    Ok(s.a.sref(Action::Custom(\n        s.a.sref(s.a.sref_slice(CustomAction::SendArbitraryCode(code))),\n    )))\n}\n"
  },
  {
    "path": "parser/src/cfg/caps_word.rs",
    "content": "use super::*;\n\nuse crate::bail;\n\npub(crate) fn parse_caps_word(\n    ac_params: &[SExpr],\n    repress_behaviour: CapsWordRepressBehaviour,\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_STR: &str = \"caps-word expects 1 param: <timeout>\";\n    if ac_params.len() != 1 {\n        bail!(\"{ERR_STR}\\nFound {} params instead of 1\", ac_params.len());\n    }\n    let timeout = parse_non_zero_u16(&ac_params[0], s, \"timeout\")?;\n    Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice(\n        CustomAction::CapsWord(CapsWordCfg {\n            repress_behaviour,\n            keys_to_capitalize: &[\n                KeyCode::A,\n                KeyCode::B,\n                KeyCode::C,\n                KeyCode::D,\n                KeyCode::E,\n                KeyCode::F,\n                KeyCode::G,\n                KeyCode::H,\n                KeyCode::I,\n                KeyCode::J,\n                KeyCode::K,\n                KeyCode::L,\n                KeyCode::M,\n                KeyCode::N,\n                KeyCode::O,\n                KeyCode::P,\n                KeyCode::Q,\n                KeyCode::R,\n                KeyCode::S,\n                KeyCode::T,\n                KeyCode::U,\n                KeyCode::V,\n                KeyCode::W,\n                KeyCode::X,\n                KeyCode::Y,\n                KeyCode::Z,\n                KeyCode::Minus,\n            ],\n            keys_nonterminal: &[\n                KeyCode::Kb0,\n                KeyCode::Kb1,\n                KeyCode::Kb2,\n                KeyCode::Kb3,\n                KeyCode::Kb4,\n                KeyCode::Kb5,\n                KeyCode::Kb6,\n                KeyCode::Kb7,\n                KeyCode::Kb8,\n                KeyCode::Kb9,\n                KeyCode::Kp0,\n                KeyCode::Kp1,\n                KeyCode::Kp2,\n                KeyCode::Kp3,\n                KeyCode::Kp4,\n                KeyCode::Kp5,\n                KeyCode::Kp6,\n                KeyCode::Kp7,\n                KeyCode::Kp8,\n                KeyCode::Kp9,\n                KeyCode::BSpace,\n                KeyCode::Delete,\n                KeyCode::Up,\n                KeyCode::Down,\n                KeyCode::Left,\n                KeyCode::Right,\n            ],\n            timeout,\n        }),\n    )))))\n}\n\npub(crate) fn parse_caps_word_custom(\n    ac_params: &[SExpr],\n    repress_behaviour: CapsWordRepressBehaviour,\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_STR: &str = \"caps-word-custom expects 3 param: <timeout> <keys-to-capitalize> <extra-non-terminal-keys>\";\n    if ac_params.len() != 3 {\n        bail!(\"{ERR_STR}\\nFound {} params instead of 3\", ac_params.len());\n    }\n    let timeout = parse_non_zero_u16(&ac_params[0], s, \"timeout\")?;\n    Ok(s.a.sref(Action::Custom(\n        s.a.sref(\n            s.a.sref_slice(CustomAction::CapsWord(CapsWordCfg {\n                repress_behaviour,\n                keys_to_capitalize: s.a.sref_vec(\n                    parse_key_list(&ac_params[1], s, \"keys-to-capitalize\")?\n                        .into_iter()\n                        .map(KeyCode::from)\n                        .collect(),\n                ),\n                keys_nonterminal: s.a.sref_vec(\n                    parse_key_list(&ac_params[2], s, \"extra-non-terminal-keys\")?\n                        .into_iter()\n                        .map(KeyCode::from)\n                        .collect(),\n                ),\n                timeout,\n            })),\n        ),\n    )))\n}\n"
  },
  {
    "path": "parser/src/cfg/chord.rs",
    "content": "use itertools::Itertools;\nuse kanata_keyberon::chord::{ChordV2, ChordsForKey, ChordsForKeys, ReleaseBehaviour};\nuse rustc_hash::{FxHashMap, FxHashSet};\n\nuse std::fs;\n\nuse crate::{anyhow_expr, bail_expr};\n\nuse super::*;\n\npub(crate) fn parse_defchordv2(\n    exprs: &[SExpr],\n    s: &ParserState,\n) -> Result<ChordsForKeys<'static, KanataCustom>> {\n    if exprs[0].atom(None).expect(\"should be atom\") == \"defchordsv2-experimental\" {\n        log::warn!(\n            \"You should replace defchordsv2-experimental with defchordsv2.\\n\\\n             Using -experimental will be invalid in the future.\"\n        );\n    }\n\n    let mut chunks = exprs[1..].chunks_exact(5);\n    let mut chords_container = ChordsForKeys::<'static, KanataCustom> {\n        mapping: FxHashMap::default(),\n    };\n\n    let mut all_participating_key_sets = FxHashSet::default();\n\n    let all_chords = chunks\n        .by_ref()\n        .flat_map(|chunk| match chunk[0] {\n            // Match a line like\n            // (include filename.txt) () 100 all-released (layer1 layer2)\n            SExpr::List(Spanned {\n                t: ref exprs,\n                span: _,\n            }) if matches!(exprs.first(), Some(SExpr::Atom(a)) if a.t == \"include\") => {\n                let file_name = exprs[1].atom(s.vars()).unwrap();\n                let chord_translation = ChordTranslation::create(\n                    file_name,\n                    &chunk[2],\n                    &chunk[3],\n                    &chunk[4],\n                    &s.layers[0][0],\n                );\n                let chord_definitions = parse_chord_file(file_name).unwrap();\n                let processed = chord_definitions.iter().map(|chord_def| {\n                    let chunk = chord_translation.translate_chord(chord_def);\n                    parse_single_chord(&chunk, s, &mut all_participating_key_sets)\n                });\n                Ok::<_, ParseError>(processed.collect_vec())\n            }\n            _ => Ok(vec![parse_single_chord(\n                chunk,\n                s,\n                &mut all_participating_key_sets,\n            )]),\n        })\n        .flat_map(|vec_result| vec_result.into_iter())\n        .collect::<Vec<Result<_>>>();\n    let unsuccessful = all_chords\n        .iter()\n        .filter_map(|r| r.as_ref().err())\n        .collect::<Vec<_>>();\n    if let Some(e) = unsuccessful.first() {\n        return Err((*e).clone());\n    }\n\n    let successful = all_chords.into_iter().filter_map(Result::ok).collect_vec();\n    for chord in successful {\n        for pkey in chord.participating_keys.iter().copied() {\n            //log::trace!(\"chord for key:{pkey:?} > {chord:?}\");\n            chords_container\n                .mapping\n                .entry(pkey)\n                .or_insert(ChordsForKey { chords: vec![] })\n                .chords\n                .push(s.a.sref(chord.clone()));\n        }\n    }\n    let rem = chunks.remainder();\n    if !rem.is_empty() {\n        bail_expr!(\n            rem.last().unwrap(),\n            \"Incomplete chord entry. Each chord entry must have 5 items:\\n\\\n        participating-keys, action, timeout, release-type, disabled-layers\"\n        );\n    }\n    Ok(chords_container)\n}\n\nfn parse_single_chord(\n    chunk: &[SExpr],\n    s: &ParserState,\n    all_participating_key_sets: &mut FxHashSet<Vec<u16>>,\n) -> Result<ChordV2<'static, KanataCustom>> {\n    let participants = parse_participating_keys(&chunk[0], s)?;\n    if !all_participating_key_sets.insert(participants.clone()) {\n        bail_expr!(\n            &chunk[0],\n            \"Duplicate participating-keys, key sets may be used only once.\"\n        );\n    }\n    let action = parse_action(&chunk[1], s)?;\n    let timeout = parse_timeout(&chunk[2], s)?;\n    let release_behaviour = parse_release_behaviour(&chunk[3], s)?;\n    let disabled_layers = parse_disabled_layers(&chunk[4], s)?;\n    let chord: ChordV2<'static, KanataCustom> = ChordV2 {\n        action,\n        participating_keys: s.a.sref_vec(participants.clone()),\n        pending_duration: timeout,\n        disabled_layers: s.a.sref_vec(disabled_layers),\n        release_behaviour,\n    };\n    Ok(s.a.sref(chord).clone())\n}\n\nfn parse_participating_keys(keys: &SExpr, s: &ParserState) -> Result<Vec<u16>> {\n    let mut participants = keys\n        .list(s.vars())\n        .map(|l| {\n            l.iter()\n                .try_fold(vec![], |mut keys, key| -> Result<Vec<u16>> {\n                    let k = key.atom(s.vars()).and_then(str_to_oscode).ok_or_else(|| {\n                        anyhow_expr!(\n                            key,\n                            \"The first chord item must be a list of keys.\\nInvalid key name.\"\n                        )\n                    })?;\n                    keys.push(k.into());\n                    Ok(keys)\n                })\n        })\n        .ok_or_else(|| anyhow_expr!(keys, \"The first chord item must be a list of keys.\"))??;\n    if participants.len() < 2 {\n        bail_expr!(keys, \"The minimum number of participating chord keys is 2\");\n    }\n    participants.sort();\n    Ok(participants)\n}\n\nfn parse_timeout(chunk: &SExpr, s: &ParserState) -> Result<u16> {\n    let timeout = parse_non_zero_u16(chunk, s, \"chord timeout\")?;\n    Ok(timeout)\n}\n\nfn parse_release_behaviour(\n    release_behaviour_string: &SExpr,\n    s: &ParserState,\n) -> Result<ReleaseBehaviour> {\n    let release_behaviour = release_behaviour_string\n        .atom(s.vars())\n        .and_then(|r| {\n            Some(match r {\n                \"first-release\" => ReleaseBehaviour::OnFirstRelease,\n                \"all-released\" => ReleaseBehaviour::OnLastRelease,\n                _ => return None,\n            })\n        })\n        .ok_or_else(|| {\n            anyhow_expr!(\n                release_behaviour_string,\n                \"Chord release behaviour must be one of:\\n\\\n                first-release | all-released\"\n            )\n        })?;\n    Ok(release_behaviour)\n}\n\nfn parse_disabled_layers(disabled_layers: &SExpr, s: &ParserState) -> Result<Vec<u16>> {\n    let disabled_layers = disabled_layers\n        .list(s.vars())\n        .map(|dl| {\n            dl.iter()\n                .try_fold(vec![], |mut layers, layer| -> Result<Vec<u16>> {\n                    let l_idx = layer\n                        .atom(s.vars())\n                        .and_then(|l| s.layer_idxs.get(l))\n                        .ok_or_else(|| anyhow_expr!(layer, \"Not a known layer name.\"))?;\n                    layers.push((*l_idx) as u16);\n                    Ok(layers)\n                })\n        })\n        .ok_or_else(|| {\n            anyhow_expr!(\n                disabled_layers,\n                \"Disabled layers must be a list of layer names\"\n            )\n        })??;\n    Ok(disabled_layers)\n}\n\nfn parse_chord_file(file_name: &str) -> Result<Vec<ChordDefinition>> {\n    let input_data =\n        fs::read_to_string(file_name).unwrap_or_else(|_| panic!(\"Unable to read file {file_name}\"));\n    let parsed_chords = parse_input(&input_data).unwrap();\n    Ok(parsed_chords)\n}\n\nfn parse_input(input: &str) -> Result<Vec<ChordDefinition>> {\n    input\n        .lines()\n        .filter(|line| !line.trim().is_empty() && !line.trim().starts_with(\"//\"))\n        .map(|line| {\n            let mut caps = line.split('\\t');\n            let error_message = format!(\n                \"Each line needs to have an action separated by a tab character, got '{line}'\"\n            );\n            let keys = caps.next().expect(&error_message);\n            let action = caps.next().expect(&error_message);\n            Ok(ChordDefinition {\n                keys: keys.to_string(),\n                action: action.to_string(),\n            })\n        })\n        .collect()\n}\n\n#[derive(Debug)]\nstruct ChordDefinition {\n    keys: String,\n    action: String,\n}\n\nstruct ChordTranslation<'a> {\n    file_name: &'a str,\n    target_map: FxHashMap<String, String>,\n    postprocess_map: FxHashMap<String, String>,\n    timeout: &'a SExpr,\n    release_behaviour: &'a SExpr,\n    disabled_layers: &'a SExpr,\n}\n\nimpl<'a> ChordTranslation<'a> {\n    fn create(\n        file_name: &'a str,\n        timeout: &'a SExpr,\n        release_behaviour: &'a SExpr,\n        disabled_layers: &'a SExpr,\n        first_layer: &[Action<'static, &&[&CustomAction]>],\n    ) -> Self {\n        let postprocess_map: FxHashMap<String, String> = [\n            (\"semicolon\", \";\"),\n            (\"colon\", \"S-.\"),\n            (\"slash\", \"/\"),\n            (\"apostrophe\", \"'\"),\n            (\"dot\", \".\"),\n            (\" \", \"spc\"),\n        ]\n        .iter()\n        .cloned()\n        .map(|(k, v)| (k.to_string(), v.to_string()))\n        .collect();\n        let target_map = first_layer\n            .iter()\n            .enumerate()\n            .filter_map(|(idx, layout)| {\n                layout\n                    .key_codes()\n                    .next()\n                    .map(|kc| kc.to_string().to_lowercase())\n                    .zip(\n                        idx.try_into()\n                            .ok()\n                            .and_then(OsCode::from_u16)\n                            .map(|osc| osc.to_string().to_lowercase()),\n                    )\n            })\n            .collect::<Vec<_>>()\n            .into_iter()\n            .chain(vec![(\" \".to_string(), \"spc\".to_string())])\n            .collect::<FxHashMap<_, _>>();\n        ChordTranslation {\n            file_name,\n            target_map,\n            postprocess_map,\n            timeout,\n            release_behaviour,\n            disabled_layers,\n        }\n    }\n\n    fn post_process(&self, converted: &str) -> String {\n        self.postprocess_map\n            .get(converted)\n            .map(|c| c.to_string())\n            .unwrap_or_else(|| {\n                if converted.chars().all(|c| c.is_uppercase()) {\n                    format!(\"S-{}\", converted.to_lowercase())\n                } else {\n                    converted.to_string()\n                }\n            })\n    }\n\n    fn participant_keys(&self, keys: &str) -> Vec<String> {\n        keys.chars()\n            .map(|key| {\n                self.target_map\n                    .get(key.to_string().to_lowercase().as_str())\n                    .map(|c| self.postprocess_map.get(c).unwrap_or(c).to_string())\n                    .unwrap_or_else(|| key.to_string())\n            })\n            .collect::<Vec<String>>()\n    }\n\n    fn action(&self, action: &str) -> Vec<String> {\n        let mut action_strings = action\n            .chars()\n            .map(|c| self.post_process(&c.to_string()))\n            .collect_vec();\n        // Wait 50ms for one-shot Shift to release\n        // TODO: This would be better handled by a (multi (release-key lsft)(release-key rsft))\n        // but I haven't gotten that to work yet.\n        action_strings.insert(1, \"50\".to_string());\n        action_strings.extend_from_slice(&[\n            \"sldr\".to_string(),\n            \"spc\".to_string(),\n            \"nop0\".to_string(),\n        ]);\n        action_strings\n    }\n\n    fn translate_chord(&self, chord_def: &ChordDefinition) -> Vec<SExpr> {\n        let sexpr_string = format!(\n            \"(({}) (macro {}))\",\n            self.participant_keys(&chord_def.keys).join(\" \"),\n            self.action(&chord_def.action).join(\" \")\n        );\n        let mut participant_action = sexpr::parse(&sexpr_string, self.file_name).unwrap()[0]\n            .t\n            .clone();\n        participant_action.extend_from_slice(&[\n            self.timeout.clone(),\n            self.release_behaviour.clone(),\n            self.disabled_layers.clone(),\n        ]);\n        participant_action\n    }\n}\n"
  },
  {
    "path": "parser/src/cfg/chord_v1.rs",
    "content": "use super::*;\n\nuse crate::anyhow_expr;\nuse crate::anyhow_span;\nuse crate::bail;\nuse crate::bail_expr;\nuse crate::bail_span;\nuse crate::err_expr;\n\n#[derive(Debug, Clone)]\npub(crate) struct ChordGroup {\n    id: u16,\n    name: String,\n    keys: Vec<String>,\n    coords: Vec<((u8, u16), ChordKeys)>,\n    chords: HashMap<u128, SExpr>,\n    timeout: u16,\n}\n\npub(crate) fn parse_chord(ac_params: &[SExpr], s: &ParserState) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"Action chord expects a chords group name followed by an identifier\";\n    if ac_params.len() != 2 {\n        bail!(ERR_MSG);\n    }\n\n    let name = ac_params[0]\n        .atom(s.vars())\n        .ok_or_else(|| anyhow_expr!(&ac_params[0], \"{ERR_MSG}\"))?;\n    let group = match s.chord_groups.get(name) {\n        Some(t) => t,\n        None => bail_expr!(&ac_params[0], \"Referenced unknown chord group: {}.\", name),\n    };\n    let chord_key_index = ac_params[1]\n        .atom(s.vars())\n        .map(|s| match group.keys.iter().position(|e| e == s) {\n            Some(i) => Ok(i),\n            None => err_expr!(\n                &ac_params[1],\n                r#\"Identifier \"{}\" is not used in chord group \"{}\".\"#,\n                &s,\n                name,\n            ),\n        })\n        .ok_or_else(|| anyhow_expr!(&ac_params[0], \"{ERR_MSG}\"))??;\n    let chord_keys: u128 = 1 << chord_key_index;\n\n    // We don't yet know at this point what the entire chords group will look like nor at which\n    // coords this action will end up. So instead we store a dummy action which will be properly\n    // resolved in `resolve_chord_groups`.\n    Ok(s.a.sref(Action::Chords(s.a.sref(ChordsGroup {\n        timeout: group.timeout,\n        coords: s.a.sref_vec(vec![((0, group.id), chord_keys)]),\n        chords: s.a.sref_vec(vec![]),\n    }))))\n}\n\npub(crate) fn parse_chord_groups(\n    exprs: &[&Spanned<Vec<SExpr>>],\n    s: &mut ParserState,\n) -> Result<()> {\n    const MSG: &str = \"Incorrect number of elements found in defchords.\\nThere should be the group name, followed by timeout, followed by keys-action pairs\";\n    for expr in exprs {\n        let mut subexprs = check_first_expr(expr.t.iter(), \"defchords\")?;\n        let name = subexprs\n            .next()\n            .and_then(|e| e.atom(s.vars()))\n            .ok_or_else(|| anyhow_span!(expr, \"{MSG}\"))?\n            .to_owned();\n        let timeout = match subexprs.next() {\n            Some(e) => parse_non_zero_u16(e, s, \"timeout\")?,\n            None => bail_span!(expr, \"{MSG}\"),\n        };\n        let id = match s.chord_groups.len().try_into() {\n            Ok(id) => id,\n            Err(_) => bail_span!(expr, \"Maximum number of chord groups exceeded.\"),\n        };\n        let mut group = ChordGroup {\n            id,\n            name: name.clone(),\n            keys: Vec::new(),\n            coords: Vec::new(),\n            chords: HashMap::default(),\n            timeout,\n        };\n        // Read k-v pairs from the configuration\n        while let Some(keys_expr) = subexprs.next() {\n            let action = match subexprs.next() {\n                Some(v) => v,\n                None => bail_expr!(\n                    keys_expr,\n                    \"Key list found without action - add an action for this chord\"\n                ),\n            };\n            let mut keys = keys_expr\n                .list(s.vars())\n                .map(|keys| {\n                    keys.iter().map(|key| {\n                        key.atom(s.vars()).ok_or_else(|| {\n                            anyhow_expr!(\n                                key,\n                                \"Chord keys cannot be lists. Invalid key name: {:?}\",\n                                key\n                            )\n                        })\n                    })\n                })\n                .ok_or_else(|| anyhow_expr!(keys_expr, \"Chord must be a list/set of keys\"))?;\n            let mask: u128 = keys.try_fold(0, |mask, key| {\n                let key = key?;\n                let index = match group.keys.iter().position(|k| k == key) {\n                    Some(i) => i,\n                    None => {\n                        let i = group.keys.len();\n                        if i + 1 > MAX_CHORD_KEYS {\n                            bail_expr!(keys_expr, \"Maximum number of keys in a chords group ({MAX_CHORD_KEYS}) exceeded - found {}\", i + 1);\n                        }\n                        group.keys.push(key.to_owned());\n                        i\n                    }\n                };\n                Ok(mask | (1 << index))\n            })?;\n            if group.chords.insert(mask, action.clone()).is_some() {\n                bail_expr!(keys_expr, \"Duplicate chord in group {name}\");\n            }\n        }\n        if s.chord_groups.insert(name.to_owned(), group).is_some() {\n            bail_span!(expr, \"Duplicate chords group: {}\", name);\n        }\n    }\n    Ok(())\n}\n\npub(crate) fn resolve_chord_groups(layers: &mut IntermediateLayers, s: &ParserState) -> Result<()> {\n    let mut chord_groups = s.chord_groups.values().cloned().collect::<Vec<_>>();\n    chord_groups.sort_by_key(|group| group.id);\n\n    for layer in layers.iter() {\n        for (i, row) in layer.iter().enumerate() {\n            for (j, cell) in row.iter().enumerate() {\n                find_chords_coords(&mut chord_groups, (i as u8, j as u16), cell);\n            }\n        }\n    }\n\n    let chord_groups = chord_groups.into_iter().map(|group| {\n        // Check that all keys in the chord group have been assigned to some coordinate\n        for (key_index, key) in group.keys.iter().enumerate() {\n            let key_mask = 1 << key_index;\n            if !group.coords.iter().any(|(_, keys)| keys & key_mask != 0) {\n                bail!(\"coord group `{0}` defines unused key `{1}`, did you forget to bind `(chord {0} {1})`?\", group.name, key)\n            }\n        }\n\n        let chords = group.chords.iter().map(|(mask, action)| {\n            Ok((*mask, parse_action(action, s)?))\n        }).collect::<Result<Vec<_>>>()?;\n\n        Ok(s.a.sref(ChordsGroup {\n            coords: s.a.sref_vec(group.coords),\n            chords: s.a.sref_vec(chords),\n            timeout: group.timeout,\n        }))\n    }).collect::<Result<Vec<_>>>()?;\n\n    for layer in layers.iter_mut() {\n        for row in layer.iter_mut() {\n            for cell in row.iter_mut() {\n                if let Some(action) = fill_chords(&chord_groups, cell, s) {\n                    *cell = action;\n                }\n            }\n        }\n    }\n\n    Ok(())\n}\n\npub(crate) fn find_chords_coords(\n    chord_groups: &mut [ChordGroup],\n    coord: (u8, u16),\n    action: &KanataAction,\n) {\n    match action {\n        Action::Chords(ChordsGroup { coords, .. }) => {\n            for ((_, group_id), chord_keys) in coords.iter() {\n                let group = &mut chord_groups[*group_id as usize];\n                group.coords.push((coord, *chord_keys));\n            }\n        }\n        Action::NoOp\n        | Action::Trans\n        | Action::Src\n        | Action::Repeat\n        | Action::KeyCode(_)\n        | Action::MultipleKeyCodes(_)\n        | Action::Layer(_)\n        | Action::DefaultLayer(_)\n        | Action::Sequence { .. }\n        | Action::RepeatableSequence { .. }\n        | Action::CancelSequences\n        | Action::ReleaseState(_)\n        | Action::OneShotIgnoreEventsTicks(_)\n        | Action::Custom(_) => {}\n        Action::HoldTap(HoldTapAction { tap, hold, .. }) => {\n            find_chords_coords(chord_groups, coord, tap);\n            find_chords_coords(chord_groups, coord, hold);\n        }\n        Action::OneShot(OneShot { action: ac, .. }) => {\n            find_chords_coords(chord_groups, coord, ac);\n        }\n        Action::MultipleActions(actions) => {\n            for ac in actions.iter() {\n                find_chords_coords(chord_groups, coord, ac);\n            }\n        }\n        Action::TapDance(TapDance { actions, .. }) => {\n            for ac in actions.iter() {\n                find_chords_coords(chord_groups, coord, ac);\n            }\n        }\n        Action::Fork(ForkConfig { left, right, .. }) => {\n            find_chords_coords(chord_groups, coord, left);\n            find_chords_coords(chord_groups, coord, right);\n        }\n        Action::Switch(Switch { cases }) => {\n            for case in cases.iter() {\n                find_chords_coords(chord_groups, coord, case.1);\n            }\n        }\n    }\n}\n\npub(crate) fn fill_chords(\n    chord_groups: &[&'static ChordsGroup<&&[&CustomAction]>],\n    action: &KanataAction,\n    s: &ParserState,\n) -> Option<KanataAction> {\n    match action {\n        Action::Chords(ChordsGroup { coords, .. }) => {\n            let ((_, group_id), _) = coords\n                .iter()\n                .next()\n                .expect(\"unresolved chords should have exactly one entry\");\n            Some(Action::Chords(chord_groups[*group_id as usize]))\n        }\n        Action::NoOp\n        | Action::Trans\n        | Action::Repeat\n        | Action::Src\n        | Action::KeyCode(_)\n        | Action::MultipleKeyCodes(_)\n        | Action::Layer(_)\n        | Action::DefaultLayer(_)\n        | Action::Sequence { .. }\n        | Action::RepeatableSequence { .. }\n        | Action::CancelSequences\n        | Action::ReleaseState(_)\n        | Action::OneShotIgnoreEventsTicks(_)\n        | Action::Custom(_) => None,\n        Action::HoldTap(&hta @ HoldTapAction { tap, hold, .. }) => {\n            let new_tap = fill_chords(chord_groups, &tap, s);\n            let new_hold = fill_chords(chord_groups, &hold, s);\n            if new_tap.is_some() || new_hold.is_some() {\n                Some(Action::HoldTap(s.a.sref(HoldTapAction {\n                    hold: new_hold.unwrap_or(hold),\n                    tap: new_tap.unwrap_or(tap),\n                    ..hta\n                })))\n            } else {\n                None\n            }\n        }\n        Action::OneShot(&os @ OneShot { action: ac, .. }) => {\n            fill_chords(chord_groups, ac, s).map(|ac| {\n                Action::OneShot(s.a.sref(OneShot {\n                    action: s.a.sref(ac),\n                    ..os\n                }))\n            })\n        }\n        Action::MultipleActions(actions) => {\n            let new_actions = actions\n                .iter()\n                .map(|ac| fill_chords(chord_groups, ac, s))\n                .collect::<Vec<_>>();\n            if new_actions.iter().any(|it| it.is_some()) {\n                let new_actions = new_actions\n                    .iter()\n                    .zip(**actions)\n                    .map(|(new_ac, ac)| new_ac.unwrap_or(*ac))\n                    .collect::<Vec<_>>();\n                Some(Action::MultipleActions(s.a.sref(s.a.sref_vec(new_actions))))\n            } else {\n                None\n            }\n        }\n        Action::TapDance(&td @ TapDance { actions, .. }) => {\n            let new_actions = actions\n                .iter()\n                .map(|ac| fill_chords(chord_groups, ac, s))\n                .collect::<Vec<_>>();\n            if new_actions.iter().any(|it| it.is_some()) {\n                let new_actions = new_actions\n                    .iter()\n                    .zip(actions)\n                    .map(|(new_ac, ac)| new_ac.map(|v| s.a.sref(v)).unwrap_or(*ac))\n                    .collect::<Vec<_>>();\n                Some(Action::TapDance(s.a.sref(TapDance {\n                    actions: s.a.sref_vec(new_actions),\n                    ..td\n                })))\n            } else {\n                None\n            }\n        }\n        Action::Fork(&fcfg @ ForkConfig { left, right, .. }) => {\n            let new_left = fill_chords(chord_groups, &left, s);\n            let new_right = fill_chords(chord_groups, &right, s);\n            if new_left.is_some() || new_right.is_some() {\n                Some(Action::Fork(s.a.sref(ForkConfig {\n                    left: new_left.unwrap_or(left),\n                    right: new_right.unwrap_or(right),\n                    ..fcfg\n                })))\n            } else {\n                None\n            }\n        }\n        Action::Switch(Switch { cases }) => {\n            let mut new_cases = vec![];\n            for case in cases.iter() {\n                new_cases.push((\n                    case.0,\n                    fill_chords(chord_groups, case.1, s)\n                        .map(|ac| s.a.sref(ac))\n                        .unwrap_or(case.1),\n                    case.2,\n                ));\n            }\n            Some(Action::Switch(s.a.sref(Switch {\n                cases: s.a.sref_vec(new_cases),\n            })))\n        }\n    }\n}\n"
  },
  {
    "path": "parser/src/cfg/clipboard.rs",
    "content": "use super::*;\n\nuse crate::anyhow_expr;\nuse crate::bail;\nuse crate::bail_expr;\n\npub(crate) fn parse_clipboard_set(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"expects 1 parameter: <clipboard string>\";\n    if ac_params.len() != 1 {\n        bail!(\"{CLIPBOARD_SET} {ERR_MSG}, found {}\", ac_params.len());\n    }\n    let expr = &ac_params[0];\n    let clip_string = match expr {\n        SExpr::Atom(filepath) => filepath,\n        SExpr::List(_) => {\n            bail_expr!(&expr, \"Clipboard string cannot be a list\")\n        }\n    };\n    let clip_string = clip_string.t.trim_atom_quotes();\n    Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice(\n        CustomAction::ClipboardSet(s.a.sref_str(clip_string.to_string())),\n    )))))\n}\n\npub(crate) fn parse_clipboard_save(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"expects 1 parameter: <clipboard save id (0-65535)>\";\n    if ac_params.len() != 1 {\n        bail!(\"{CLIPBOARD_SAVE} {ERR_MSG}, found {}\", ac_params.len());\n    }\n    let id = parse_u16(&ac_params[0], s, \"clipboard save ID\")?;\n    Ok(s.a.sref(Action::Custom(\n        s.a.sref(s.a.sref_slice(CustomAction::ClipboardSave(id))),\n    )))\n}\n\npub(crate) fn parse_clipboard_restore(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"expects 1 parameter: <clipboard save id (0-65535)>\";\n    if ac_params.len() != 1 {\n        bail!(\"{CLIPBOARD_RESTORE} {ERR_MSG}, found {}\", ac_params.len());\n    }\n    let id = parse_u16(&ac_params[0], s, \"clipboard save ID\")?;\n    Ok(s.a.sref(Action::Custom(\n        s.a.sref(s.a.sref_slice(CustomAction::ClipboardRestore(id))),\n    )))\n}\n\npub(crate) fn parse_clipboard_save_swap(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str =\n        \"expects 2 parameters: <clipboard save id (0-65535)> <clipboard save id #2>\";\n    if ac_params.len() != 2 {\n        bail!(\"{CLIPBOARD_SAVE_SWAP} {ERR_MSG}, found {}\", ac_params.len());\n    }\n    let id1 = parse_u16(&ac_params[0], s, \"clipboard save ID\")?;\n    let id2 = parse_u16(&ac_params[1], s, \"clipboard save ID\")?;\n    Ok(s.a.sref(Action::Custom(\n        s.a.sref(s.a.sref_slice(CustomAction::ClipboardSaveSwap(id1, id2))),\n    )))\n}\n\npub(crate) fn parse_clipboard_save_set(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"expects 2 parameters: <clipboard save id (0-65535)> <save content>\";\n    if ac_params.len() != 2 {\n        bail!(\"{CLIPBOARD_SAVE_SET} {ERR_MSG}, found {}\", ac_params.len());\n    }\n    let id = parse_u16(&ac_params[0], s, \"clipboard save ID\")?;\n    let save_content = ac_params[1]\n        .atom(s.vars())\n        .ok_or_else(|| anyhow_expr!(&ac_params[1], \"save content must be a string\"))?;\n    Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice(\n        CustomAction::ClipboardSaveSet(id, s.a.sref_str(save_content.into())),\n    )))))\n}\n"
  },
  {
    "path": "parser/src/cfg/cmd.rs",
    "content": "use super::*;\n\nuse crate::bail;\nuse crate::bail_expr;\n\npub(crate) enum CmdType {\n    /// Execute command in own thread.\n    Standard,\n    /// Execute command synchronously and output stdout as macro-like SExpr.\n    OutputKeys,\n    /// Execute command and set clipboard to output. Clipboard content is passed as stdin to the\n    /// command.\n    ClipboardSet,\n    /// Execute command and set clipboard save id to output.\n    /// Clipboard save id content is passed as stdin to the command.\n    ClipboardSaveSet,\n}\n\n// Parse cmd, but there are 2 arguments before specifying normal log and error log\npub(crate) fn parse_cmd_log(ac_params: &[SExpr], s: &ParserState) -> Result<&'static KanataAction> {\n    const ERR_STR: &str =\n        \"cmd-log expects at least 3 strings, <log-level> <error-log-level> <cmd...>\";\n    if !s.is_cmd_enabled {\n        bail!(\n            \"cmd is not enabled for this kanata executable (did you use 'cmd_allowed' variants?), but is set in the configuration\"\n        );\n    }\n    if ac_params.len() < 3 {\n        bail!(ERR_STR);\n    }\n    let mut cmd = vec![];\n    let log_level =\n        if let Some(Ok(input_mode)) = ac_params[0].atom(s.vars()).map(LogLevel::try_from_str) {\n            input_mode\n        } else {\n            bail_expr!(&ac_params[0], \"{ERR_STR}\\n{}\", LogLevel::err_msg());\n        };\n    let error_log_level =\n        if let Some(Ok(input_mode)) = ac_params[1].atom(s.vars()).map(LogLevel::try_from_str) {\n            input_mode\n        } else {\n            bail_expr!(&ac_params[1], \"{ERR_STR}\\n{}\", LogLevel::err_msg());\n        };\n    collect_strings(&ac_params[2..], &mut cmd, s);\n    if cmd.is_empty() {\n        bail!(ERR_STR);\n    }\n    let cmds = cmd.into_iter().map(|v| s.a.sref_str(v)).collect();\n    Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice(\n        CustomAction::CmdLog(log_level, error_log_level, s.a.sref_vec(cmds)),\n    )))))\n}\n\n#[allow(unused_variables)]\npub(crate) fn parse_cmd(\n    ac_params: &[SExpr],\n    s: &ParserState,\n    cmd_type: CmdType,\n) -> Result<&'static KanataAction> {\n    #[cfg(not(feature = \"cmd\"))]\n    {\n        bail!(\n            \"cmd is not enabled for this kanata executable. Use a cmd_allowed prebuilt executable or compile with the feature: cmd.\"\n        );\n    }\n    #[cfg(feature = \"cmd\")]\n    {\n        if matches!(cmd_type, CmdType::ClipboardSaveSet) {\n            const ERR_STR: &str = \"expects a save ID and at least one string\";\n            if !s.is_cmd_enabled {\n                bail!(\"To use cmd you must put in defcfg: danger-enable-cmd yes.\");\n            }\n            if ac_params.len() < 2 {\n                bail!(\"{CLIPBOARD_SAVE_CMD_SET} {ERR_STR}\");\n            }\n            let mut cmd = vec![];\n            let save_id = parse_u16(&ac_params[0], s, \"clipboard save ID\")?;\n            collect_strings(&ac_params[1..], &mut cmd, s);\n            if cmd.is_empty() {\n                bail_expr!(&ac_params[1], \"{CLIPBOARD_SAVE_CMD_SET} {ERR_STR}\");\n            }\n            let cmds = cmd.into_iter().map(|v| s.a.sref_str(v)).collect();\n            return Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice(\n                CustomAction::ClipboardSaveCmdSet(save_id, s.a.sref_vec(cmds)),\n            )))));\n        }\n\n        const ERR_STR: &str = \"cmd expects at least one string\";\n        if !s.is_cmd_enabled {\n            bail!(\"To use cmd you must put in defcfg: danger-enable-cmd yes.\");\n        }\n        let mut cmd = vec![];\n        collect_strings(ac_params, &mut cmd, s);\n        if cmd.is_empty() {\n            bail!(ERR_STR);\n        }\n        let cmds = cmd.into_iter().map(|v| s.a.sref_str(v)).collect();\n        let cmds = s.a.sref_vec(cmds);\n        Ok(s.a\n            .sref(Action::Custom(s.a.sref(s.a.sref_slice(match cmd_type {\n                CmdType::Standard => CustomAction::Cmd(cmds),\n                CmdType::OutputKeys => CustomAction::CmdOutputKeys(cmds),\n                CmdType::ClipboardSet => CustomAction::ClipboardCmdSet(cmds),\n                CmdType::ClipboardSaveSet => unreachable!(),\n            })))))\n    }\n}\n\n/// Recurse through all levels of list nesting and collect into a flat list of strings.\n/// Recursion is DFS, which matches left-to-right reading of the strings as they appear,\n/// if everything was on a single line.\npub(crate) fn collect_strings(params: &[SExpr], strings: &mut Vec<String>, s: &ParserState) {\n    for param in params {\n        if let Some(a) = param.atom(s.vars()) {\n            strings.push(a.trim_atom_quotes().to_owned());\n        } else {\n            // unwrap: this must be a list, since it's not an atom.\n            let l = param.list(s.vars()).unwrap();\n            collect_strings(l, strings, s);\n        }\n    }\n}\n\n#[test]\npub(crate) fn test_collect_strings() {\n    let params = r#\"(gah (squish \"squash\" (splish splosh) \"bah mah\") dah)\"#;\n    let params = sexpr::parse(params, \"noexist\").unwrap();\n    let mut strings = vec![];\n    collect_strings(&params[0].t, &mut strings, &ParserState::default());\n    assert_eq!(\n        &strings,\n        &[\n            \"gah\", \"squish\", \"squash\", \"splish\", \"splosh\", \"bah mah\", \"dah\"\n        ]\n    );\n}\n"
  },
  {
    "path": "parser/src/cfg/custom_tap_hold.rs",
    "content": "use kanata_keyberon::layout::{Event, KCoord, QueuedIter, REAL_KEY_ROW, WaitingAction};\n\nuse crate::keys::OsCode;\n\nuse super::alloc::Allocations;\n\n#[derive(Clone, Copy, PartialEq, Eq, Debug)]\npub(crate) enum Hand {\n    Left,\n    Right,\n    Neutral,\n}\n\n/// Compact mapping from key codes to hand assignments.\n/// Stores only keys that have an explicit left/right assignment;\n/// any key not present is treated as `Hand::Neutral`.\n#[derive(Clone, Copy, Debug)]\npub(crate) struct HandMap {\n    pub(crate) keys: &'static [u16],\n    pub(crate) hands: &'static [Hand],\n}\n\nimpl HandMap {\n    pub(crate) fn get(&self, key_code: u16) -> Hand {\n        self.keys\n            .iter()\n            .position(|&k| k == key_code)\n            .map(|i| self.hands[i])\n            .unwrap_or(Hand::Neutral)\n    }\n}\n\n#[derive(Clone, Copy, Debug)]\npub(crate) enum DecisionBehavior {\n    Tap,\n    Hold,\n    Ignore,\n}\n\n/// The function-trait object stored inside `HoldTapConfig::Custom`.\npub(crate) type CustomTapHoldFn =\n    dyn Fn(QueuedIter, KCoord) -> (Option<WaitingAction>, bool) + Send + Sync;\n\n/// Returns a closure that can be used in `HoldTapConfig::Custom`, which will return early with a\n/// Tap action in the case that any of `keys` are pressed. Otherwise it behaves as\n/// `HoldTapConfig::PermissiveHold` would.\npub(crate) fn custom_tap_hold_release(\n    keys: &[OsCode],\n    a: &Allocations,\n) -> &'static CustomTapHoldFn {\n    let keys = a.sref_vec(Vec::from_iter(keys.iter().copied()));\n    a.sref(\n        move |mut queued: QueuedIter, _coord: KCoord| -> (Option<WaitingAction>, bool) {\n            while let Some(q) = queued.next() {\n                if q.event().is_press() {\n                    let (i, j) = q.event().coord();\n                    // If any key matches the input, do a tap right away.\n                    if i == REAL_KEY_ROW && keys.iter().copied().map(u16::from).any(|j2| j2 == j) {\n                        return (Some(WaitingAction::Tap), false);\n                    }\n                    // Otherwise do the PermissiveHold algorithm.\n                    let target = Event::Release(i, j);\n                    if queued.clone().copied().any(|q| q.event() == target) {\n                        return (Some(WaitingAction::Hold), false);\n                    }\n                }\n            }\n            (None, false)\n        },\n    )\n}\n\n/// Returns a closure that can be used in `HoldTapConfig::Custom`, which will return early with a\n/// Tap action in the case that any of `keys_press_then_release_trigger_tap` are pressed and\n/// released, or if any in `keys_press_trigger_tap` are pressed (no release needed). Otherwise it\n/// behaves as `HoldTapConfig::PermissiveHold` would.\npub(crate) fn custom_tap_hold_release_trigger_tap_release(\n    keys_press_trigger_tap: &[OsCode],\n    keys_press_then_release_trigger_tap: &[OsCode],\n    a: &Allocations,\n) -> &'static CustomTapHoldFn {\n    let keys_press_then_release_trigger_tap = a.sref_vec(Vec::from_iter(\n        keys_press_then_release_trigger_tap\n            .iter()\n            .copied()\n            .map(u16::from),\n    ));\n    let keys_press_trigger_tap = a.sref_vec(Vec::from_iter(\n        keys_press_trigger_tap.iter().copied().map(u16::from),\n    ));\n    a.sref(\n        move |mut queued: QueuedIter, _coord: KCoord| -> (Option<WaitingAction>, bool) {\n            while let Some(q) = queued.next() {\n                if q.event().is_press() {\n                    let (i, j) = q.event().coord();\n                    if i != REAL_KEY_ROW {\n                        continue;\n                    }\n                    // If any pressed key matches the press list and has been released, do\n                    // a tap right away.\n                    if keys_press_trigger_tap.iter().copied().any(|j2| j2 == j) {\n                        return (Some(WaitingAction::Tap), false);\n                    }\n                    // If any pressed key matches the press-release list and has been released, do\n                    // a tap right away.\n                    if keys_press_then_release_trigger_tap\n                        .iter()\n                        .copied()\n                        .any(|j2| j2 == j)\n                    {\n                        let target = Event::Release(i, j);\n                        if queued.clone().copied().any(|q| q.event() == target) {\n                            return (Some(WaitingAction::Tap), false);\n                        }\n                    }\n                    // Otherwise do the PermissiveHold algorithm.\n                    let target = Event::Release(i, j);\n                    if queued.clone().copied().any(|q| q.event() == target) {\n                        return (Some(WaitingAction::Hold), false);\n                    }\n                }\n            }\n            (None, false)\n        },\n    )\n}\n\npub(crate) fn custom_tap_hold_except(keys: &[OsCode], a: &Allocations) -> &'static CustomTapHoldFn {\n    let keys = a.sref_vec(Vec::from_iter(keys.iter().copied()));\n    a.sref(\n        move |mut queued: QueuedIter, _coord: KCoord| -> (Option<WaitingAction>, bool) {\n            for q in queued.by_ref() {\n                if q.event().is_press() {\n                    let (_i, j) = q.event().coord();\n                    // If any key matches the input, do a tap.\n                    if keys.iter().copied().map(u16::from).any(|j2| j2 == j) {\n                        return (Some(WaitingAction::Tap), false);\n                    }\n                    // Otherwise continue with default behavior\n                    return (None, false);\n                }\n            }\n            // Otherwise skip timeout\n            (None, true)\n        },\n    )\n}\n\n/// Returns a closure that can be used in `HoldTapConfig::Custom`, which will return early with a\n/// Tap action in the case that any of `keys` are pressed. Unlike `custom_tap_hold_except`, if no\n/// matching key is pressed, this waits for timeout instead of skipping it.\npub(crate) fn custom_tap_hold_tap_keys(\n    keys: &[OsCode],\n    a: &Allocations,\n) -> &'static CustomTapHoldFn {\n    let keys = a.sref_vec(Vec::from_iter(keys.iter().copied()));\n    a.sref(\n        move |mut queued: QueuedIter, _coord: KCoord| -> (Option<WaitingAction>, bool) {\n            for q in queued.by_ref() {\n                if q.event().is_press() {\n                    let (_i, j) = q.event().coord();\n                    // If any key matches the input, do a tap.\n                    if keys.iter().copied().map(u16::from).any(|j2| j2 == j) {\n                        return (Some(WaitingAction::Tap), false);\n                    }\n                    // Otherwise continue with default behavior (no early hold activation)\n                }\n            }\n            // Wait for timeout (key difference from custom_tap_hold_except which returns true)\n            (None, false)\n        },\n    )\n}\n\npub(crate) fn custom_tap_hold_opposite_hand(\n    hand_map: &'static HandMap,\n    same_hand: DecisionBehavior,\n    neutral_behavior: DecisionBehavior,\n    unknown_hand: DecisionBehavior,\n    neutral_keys: &'static [OsCode],\n    a: &Allocations,\n) -> &'static CustomTapHoldFn {\n    a.sref(\n        move |queued: QueuedIter, coord: KCoord| -> (Option<WaitingAction>, bool) {\n            let (_row, col) = coord;\n            let waiting_hand = hand_map.get(col);\n\n            for q in queued {\n                if !q.event().is_press() {\n                    continue;\n                }\n                let (i, j) = q.event().coord();\n                if i != REAL_KEY_ROW {\n                    continue;\n                }\n\n                // Check neutral-keys first (takes precedence over defhands)\n                if let Some(osc) = OsCode::from_u16(j) {\n                    if neutral_keys.contains(&osc) {\n                        match neutral_behavior {\n                            DecisionBehavior::Tap => return (Some(WaitingAction::Tap), false),\n                            DecisionBehavior::Hold => return (Some(WaitingAction::Hold), false),\n                            DecisionBehavior::Ignore => continue,\n                        }\n                    }\n                }\n\n                let pressed_hand = hand_map.get(j);\n\n                match (waiting_hand, pressed_hand) {\n                    (Hand::Left, Hand::Right) | (Hand::Right, Hand::Left) => {\n                        return (Some(WaitingAction::Hold), false);\n                    }\n                    (Hand::Left, Hand::Left) | (Hand::Right, Hand::Right) => match same_hand {\n                        DecisionBehavior::Tap => return (Some(WaitingAction::Tap), false),\n                        DecisionBehavior::Hold => return (Some(WaitingAction::Hold), false),\n                        DecisionBehavior::Ignore => continue,\n                    },\n                    _ => {\n                        // At least one key is Neutral (not in defhands)\n                        match unknown_hand {\n                            DecisionBehavior::Tap => return (Some(WaitingAction::Tap), false),\n                            DecisionBehavior::Hold => return (Some(WaitingAction::Hold), false),\n                            DecisionBehavior::Ignore => continue,\n                        }\n                    }\n                }\n            }\n            (None, false)\n        },\n    )\n}\n"
  },
  {
    "path": "parser/src/cfg/defcfg.rs",
    "content": "use super::HashSet;\nuse super::sexpr::SExpr;\nuse super::{TrimAtomQuotes, error::*};\nuse crate::cfg::check_first_expr;\nuse crate::custom_action::*;\nuse crate::keys::*;\n#[allow(unused)]\nuse crate::{anyhow_expr, anyhow_span, bail, bail_expr, bail_span};\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\npub enum DeviceDetectMode {\n    KeyboardOnly,\n    KeyboardMice,\n    Any,\n}\n#[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\nimpl std::fmt::Display for DeviceDetectMode {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{self:?}\")\n    }\n}\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\n#[derive(Debug, Clone)]\npub struct CfgLinuxOptions {\n    pub linux_dev: Vec<String>,\n    pub linux_dev_names_include: Option<Vec<String>>,\n    pub linux_dev_names_exclude: Option<Vec<String>>,\n    pub linux_continue_if_no_devs_found: bool,\n    pub linux_unicode_u_code: crate::keys::OsCode,\n    pub linux_unicode_termination: UnicodeTermination,\n    pub linux_x11_repeat_delay_rate: Option<KeyRepeatSettings>,\n    pub linux_use_trackpoint_property: bool,\n    pub linux_output_name: String,\n    pub linux_output_bus_type: LinuxCfgOutputBusType,\n    pub linux_device_detect_mode: Option<DeviceDetectMode>,\n}\n#[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\nimpl Default for CfgLinuxOptions {\n    fn default() -> Self {\n        Self {\n            linux_dev: vec![],\n            linux_dev_names_include: None,\n            linux_dev_names_exclude: None,\n            linux_continue_if_no_devs_found: false,\n            // historically was the only option, so make KEY_U the default\n            linux_unicode_u_code: crate::keys::OsCode::KEY_U,\n            // historically was the only option, so make Enter the default\n            linux_unicode_termination: UnicodeTermination::Enter,\n            linux_x11_repeat_delay_rate: None,\n            linux_use_trackpoint_property: false,\n            linux_output_name: \"kanata\".to_owned(),\n            linux_output_bus_type: LinuxCfgOutputBusType::BusI8042,\n            linux_device_detect_mode: None,\n        }\n    }\n}\n#[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\n#[derive(Debug, Clone, Copy)]\npub enum LinuxCfgOutputBusType {\n    BusUsb,\n    BusI8042,\n    BusVirtual,\n}\n\n#[cfg(any(target_os = \"macos\", target_os = \"unknown\"))]\n#[derive(Debug, Default, Clone)]\npub struct CfgMacosOptions {\n    pub macos_dev_names_include: Option<Vec<String>>,\n    pub macos_dev_names_exclude: Option<Vec<String>>,\n}\n\n#[cfg(any(\n    all(feature = \"interception_driver\", target_os = \"windows\"),\n    target_os = \"unknown\"\n))]\n#[derive(Debug, Clone, Default)]\npub struct CfgWinterceptOptions {\n    pub windows_interception_mouse_hwids: Option<Vec<[u8; HWID_ARR_SZ]>>,\n    pub windows_interception_mouse_hwids_exclude: Option<Vec<[u8; HWID_ARR_SZ]>>,\n    pub windows_interception_keyboard_hwids: Option<Vec<[u8; HWID_ARR_SZ]>>,\n    pub windows_interception_keyboard_hwids_exclude: Option<Vec<[u8; HWID_ARR_SZ]>>,\n}\n\n#[cfg(any(target_os = \"windows\", target_os = \"unknown\"))]\n#[derive(Debug, Clone, Default)]\npub struct CfgWindowsOptions {\n    pub windows_altgr: AltGrBehaviour,\n    pub sync_keystates: bool,\n}\n\n#[cfg(all(any(target_os = \"windows\", target_os = \"unknown\"), feature = \"gui\"))]\n#[derive(Debug, Clone)]\npub struct CfgOptionsGui {\n    /// File name / path to the tray icon file.\n    pub tray_icon: Option<String>,\n    /// Whether to match layer names to icon files without an explicit 'icon' field\n    pub icon_match_layer_name: bool,\n    /// Show tooltip on layer changes showing layer icons\n    pub tooltip_layer_changes: bool,\n    /// Show tooltip on layer changes for the default/base layer\n    pub tooltip_no_base: bool,\n    /// Show tooltip on layer changes even for layers without an icon\n    pub tooltip_show_blank: bool,\n    /// Show tooltip on layer changes for this duration (ms)\n    pub tooltip_duration: u16,\n    /// Show system notification message on config reload\n    pub notify_cfg_reload: bool,\n    /// Disable sound for the system notification message on config reload\n    pub notify_cfg_reload_silent: bool,\n    /// Show system notification message on errors\n    pub notify_error: bool,\n    /// Set tooltip size (width, height)\n    pub tooltip_size: (u16, u16),\n}\n#[cfg(all(any(target_os = \"windows\", target_os = \"unknown\"), feature = \"gui\"))]\nimpl Default for CfgOptionsGui {\n    fn default() -> Self {\n        Self {\n            tray_icon: None,\n            icon_match_layer_name: true,\n            tooltip_layer_changes: false,\n            tooltip_show_blank: false,\n            tooltip_no_base: true,\n            tooltip_duration: 500,\n            notify_cfg_reload: true,\n            notify_cfg_reload_silent: false,\n            notify_error: true,\n            tooltip_size: (24, 24),\n        }\n    }\n}\n\n#[derive(Debug)]\npub struct CfgOptions {\n    pub process_unmapped_keys: bool,\n    pub process_unmapped_keys_exceptions: Option<Vec<(OsCode, SExpr)>>,\n    pub block_unmapped_keys: bool,\n    pub allow_hardware_repeat: bool,\n    pub start_alias: Option<String>,\n    pub enable_cmd: bool,\n    pub sequence_timeout: u16,\n    pub sequence_input_mode: SequenceInputMode,\n    pub sequence_backtrack_modcancel: bool,\n    pub sequence_always_on: bool,\n    pub log_layer_changes: bool,\n    pub delegate_to_first_layer: bool,\n    pub movemouse_inherit_accel_state: bool,\n    pub movemouse_smooth_diagonals: bool,\n    pub override_release_on_activation: bool,\n    pub dynamic_macro_max_presses: u16,\n    pub dynamic_macro_replay_delay_behaviour: ReplayDelayBehaviour,\n    pub concurrent_tap_hold: bool,\n    pub rapid_event_delay: u16,\n    pub trans_resolution_behavior_v2: bool,\n    pub chords_v2_min_idle: u16,\n    pub tap_hold_require_prior_idle: u16,\n    #[cfg(any(\n        all(target_os = \"windows\", feature = \"interception_driver\"),\n        target_os = \"linux\",\n        target_os = \"android\",\n        target_os = \"unknown\"\n    ))]\n    pub mouse_movement_key: Option<OsCode>,\n    #[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\n    pub linux_opts: CfgLinuxOptions,\n    #[cfg(any(target_os = \"macos\", target_os = \"unknown\"))]\n    pub macos_opts: CfgMacosOptions,\n    #[cfg(any(target_os = \"windows\", target_os = \"unknown\"))]\n    pub windows_opts: CfgWindowsOptions,\n    #[cfg(any(\n        all(feature = \"interception_driver\", target_os = \"windows\"),\n        target_os = \"unknown\"\n    ))]\n    pub wintercept_opts: CfgWinterceptOptions,\n    #[cfg(all(any(target_os = \"windows\", target_os = \"unknown\"), feature = \"gui\"))]\n    pub gui_opts: CfgOptionsGui,\n}\n\nimpl Default for CfgOptions {\n    fn default() -> Self {\n        Self {\n            process_unmapped_keys: false,\n            process_unmapped_keys_exceptions: None,\n            block_unmapped_keys: false,\n            allow_hardware_repeat: true,\n            start_alias: None,\n            enable_cmd: false,\n            sequence_timeout: 1000,\n            sequence_input_mode: SequenceInputMode::HiddenSuppressed,\n            sequence_backtrack_modcancel: true,\n            sequence_always_on: false,\n            log_layer_changes: true,\n            delegate_to_first_layer: false,\n            movemouse_inherit_accel_state: false,\n            movemouse_smooth_diagonals: false,\n            override_release_on_activation: false,\n            dynamic_macro_max_presses: 128,\n            dynamic_macro_replay_delay_behaviour: ReplayDelayBehaviour::Recorded,\n            concurrent_tap_hold: false,\n            rapid_event_delay: 5,\n            trans_resolution_behavior_v2: true,\n            chords_v2_min_idle: 5,\n            tap_hold_require_prior_idle: 0,\n            #[cfg(any(\n                all(target_os = \"windows\", feature = \"interception_driver\"),\n                target_os = \"linux\",\n                target_os = \"android\",\n                target_os = \"unknown\"\n            ))]\n            mouse_movement_key: None,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\n            linux_opts: Default::default(),\n            #[cfg(any(target_os = \"windows\", target_os = \"unknown\"))]\n            windows_opts: Default::default(),\n            #[cfg(any(\n                all(feature = \"interception_driver\", target_os = \"windows\"),\n                target_os = \"unknown\"\n            ))]\n            wintercept_opts: Default::default(),\n            #[cfg(any(target_os = \"macos\", target_os = \"unknown\"))]\n            macos_opts: Default::default(),\n            #[cfg(all(any(target_os = \"windows\", target_os = \"unknown\"), feature = \"gui\"))]\n            gui_opts: Default::default(),\n        }\n    }\n}\n\n/// Parse configuration entries from an expression starting with defcfg.\npub fn parse_defcfg(expr: &[SExpr]) -> Result<CfgOptions> {\n    let mut seen_keys = HashSet::default();\n    let mut cfg = CfgOptions::default();\n    let mut exprs = check_first_expr(expr.iter(), \"defcfg\")?;\n    let mut is_process_unmapped_keys_defined = false;\n    // Read k-v pairs from the configuration\n    loop {\n        let key = match exprs.next() {\n            Some(k) => k,\n            None => {\n                if !is_process_unmapped_keys_defined {\n                    log::warn!(\n                        \"The item process-unmapped-keys is not defined in defcfg. Consider whether process-unmapped-keys should be yes vs. no.\"\n                    );\n                }\n                return Ok(cfg);\n            }\n        };\n        let val = match exprs.next() {\n            Some(v) => v,\n            None => bail_expr!(key, \"Found a defcfg option missing a value\"),\n        };\n        match key {\n            SExpr::Atom(k) => {\n                let label = k.t.as_str();\n                if !seen_keys.insert(label) {\n                    bail_expr!(key, \"Duplicate defcfg option {}\", label);\n                }\n                match label {\n                    \"sequence-timeout\" => {\n                        cfg.sequence_timeout = parse_cfg_val_u16(val, label, true)?;\n                    }\n                    \"sequence-input-mode\" => {\n                        let v = sexpr_to_str_or_err(val, label)?;\n                        cfg.sequence_input_mode = SequenceInputMode::try_from_str(v)\n                            .map_err(|e| anyhow_expr!(val, \"{}\", e.to_string()))?;\n                    }\n                    \"sequence-always-on\" => {\n                        cfg.sequence_always_on = parse_defcfg_val_bool(val, label)?\n                    }\n                    \"dynamic-macro-max-presses\" => {\n                        cfg.dynamic_macro_max_presses = parse_cfg_val_u16(val, label, false)?;\n                    }\n                    \"dynamic-macro-replay-delay-behaviour\" => {\n                        cfg.dynamic_macro_replay_delay_behaviour = val\n                            .atom(None)\n                            .map(|v| match v {\n                                \"constant\" => Ok(ReplayDelayBehaviour::Constant),\n                                \"recorded\" => Ok(ReplayDelayBehaviour::Recorded),\n                                _ => bail_expr!(\n                                    val,\n                                    \"this option must be one of: constant | recorded\"\n                                ),\n                            })\n                            .ok_or_else(|| {\n                                anyhow_expr!(val, \"this option must be one of: constant | recorded\")\n                            })??;\n                    }\n                    \"linux-dev\" => {\n                        #[cfg(any(\n                            target_os = \"linux\",\n                            target_os = \"android\",\n                            target_os = \"unknown\"\n                        ))]\n                        {\n                            cfg.linux_opts.linux_dev = parse_dev(val)?;\n                            if cfg.linux_opts.linux_dev.is_empty() {\n                                bail_expr!(\n                                    val,\n                                    \"device list is empty, no devices will be intercepted\"\n                                );\n                            }\n                        }\n                    }\n                    \"linux-dev-names-include\" => {\n                        #[cfg(any(\n                            target_os = \"linux\",\n                            target_os = \"android\",\n                            target_os = \"unknown\"\n                        ))]\n                        {\n                            let dev_names = parse_dev(val)?;\n                            if dev_names.is_empty() {\n                                log::warn!(\"linux-dev-names-include is empty\");\n                            }\n                            cfg.linux_opts.linux_dev_names_include = Some(dev_names);\n                        }\n                    }\n                    \"linux-dev-names-exclude\" => {\n                        #[cfg(any(\n                            target_os = \"linux\",\n                            target_os = \"android\",\n                            target_os = \"unknown\"\n                        ))]\n                        {\n                            cfg.linux_opts.linux_dev_names_exclude = Some(parse_dev(val)?);\n                        }\n                    }\n                    \"linux-unicode-u-code\" => {\n                        #[cfg(any(\n                            target_os = \"linux\",\n                            target_os = \"android\",\n                            target_os = \"unknown\"\n                        ))]\n                        {\n                            let v = sexpr_to_str_or_err(val, label)?;\n                            cfg.linux_opts.linux_unicode_u_code = crate::keys::str_to_oscode(v)\n                                .ok_or_else(|| {\n                                    anyhow_expr!(val, \"unknown code for {label}: {}\", v)\n                                })?;\n                        }\n                    }\n                    \"linux-unicode-termination\" => {\n                        #[cfg(any(\n                            target_os = \"linux\",\n                            target_os = \"android\",\n                            target_os = \"unknown\"\n                        ))]\n                        {\n                            let v = sexpr_to_str_or_err(val, label)?;\n                            cfg.linux_opts.linux_unicode_termination = match v {\n                                \"enter\" => UnicodeTermination::Enter,\n                                \"space\" => UnicodeTermination::Space,\n                                \"enter-space\" => UnicodeTermination::EnterSpace,\n                                \"space-enter\" => UnicodeTermination::SpaceEnter,\n                                _ => bail_expr!(\n                                    val,\n                                    \"{label} got {}. It accepts: enter|space|enter-space|space-enter\",\n                                    v\n                                ),\n                            }\n                        }\n                    }\n                    \"linux-x11-repeat-delay-rate\" => {\n                        #[cfg(any(\n                            target_os = \"linux\",\n                            target_os = \"android\",\n                            target_os = \"unknown\"\n                        ))]\n                        {\n                            let v = sexpr_to_str_or_err(val, label)?;\n                            let delay_rate = v.split(',').collect::<Vec<_>>();\n                            const ERRMSG: &str = \"Invalid value for linux-x11-repeat-delay-rate.\\nExpected two numbers 0-65535 separated by a comma, e.g. 200,25\";\n                            if delay_rate.len() != 2 {\n                                bail_expr!(val, \"{}\", ERRMSG)\n                            }\n                            cfg.linux_opts.linux_x11_repeat_delay_rate = Some(KeyRepeatSettings {\n                                delay: match str::parse::<u16>(delay_rate[0]) {\n                                    Ok(delay) => delay,\n                                    Err(_) => bail_expr!(val, \"{}\", ERRMSG),\n                                },\n                                rate: match str::parse::<u16>(delay_rate[1]) {\n                                    Ok(rate) => rate,\n                                    Err(_) => bail_expr!(val, \"{}\", ERRMSG),\n                                },\n                            });\n                        }\n                    }\n                    \"linux-use-trackpoint-property\" => {\n                        #[cfg(any(\n                            target_os = \"linux\",\n                            target_os = \"android\",\n                            target_os = \"unknown\"\n                        ))]\n                        {\n                            cfg.linux_opts.linux_use_trackpoint_property =\n                                parse_defcfg_val_bool(val, label)?\n                        }\n                    }\n                    \"linux-output-device-name\" => {\n                        #[cfg(any(\n                            target_os = \"linux\",\n                            target_os = \"android\",\n                            target_os = \"unknown\"\n                        ))]\n                        {\n                            let device_name = sexpr_to_str_or_err(val, label)?;\n                            if device_name.is_empty() {\n                                log::warn!(\n                                    \"linux-output-device-name is empty, using kanata as default value\"\n                                );\n                            } else {\n                                cfg.linux_opts.linux_output_name = device_name.to_owned();\n                            }\n                        }\n                    }\n                    \"linux-output-device-bus-type\" => {\n                        let bus_type = sexpr_to_str_or_err(val, label)?;\n                        match bus_type {\n                            \"USB\" | \"I8042\" | \"virtual\" => {}\n                            _ => bail_expr!(\n                                val,\n                                \"Invalid value for linux-output-device-bus-type.\\nExpected one of: USB | I8042 | virtual\"\n                            ),\n                        };\n                        #[cfg(any(\n                            target_os = \"linux\",\n                            target_os = \"android\",\n                            target_os = \"unknown\"\n                        ))]\n                        {\n                            let bus_type = match bus_type {\n                                \"USB\" => LinuxCfgOutputBusType::BusUsb,\n                                \"I8042\" => LinuxCfgOutputBusType::BusI8042,\n                                \"virtual\" => LinuxCfgOutputBusType::BusVirtual,\n                                _ => unreachable!(\"validated earlier\"),\n                            };\n                            cfg.linux_opts.linux_output_bus_type = bus_type;\n                        }\n                    }\n                    \"linux-device-detect-mode\" => {\n                        let detect_mode = sexpr_to_str_or_err(val, label)?;\n                        match detect_mode {\n                            \"any\" | \"keyboard-only\" | \"keyboard-mice\" => {}\n                            _ => bail_expr!(\n                                val,\n                                \"Invalid value for linux-device-detect-mode.\\nExpected one of: any | keyboard-only | keyboard-mice\"\n                            ),\n                        };\n                        #[cfg(any(\n                            target_os = \"linux\",\n                            target_os = \"android\",\n                            target_os = \"unknown\"\n                        ))]\n                        {\n                            let detect_mode = Some(match detect_mode {\n                                \"any\" => DeviceDetectMode::Any,\n                                \"keyboard-only\" => DeviceDetectMode::KeyboardOnly,\n                                \"keyboard-mice\" => DeviceDetectMode::KeyboardMice,\n                                _ => unreachable!(\"validated earlier\"),\n                            });\n                            cfg.linux_opts.linux_device_detect_mode = detect_mode;\n                        }\n                    }\n                    \"windows-altgr\" => {\n                        #[cfg(any(target_os = \"windows\", target_os = \"unknown\"))]\n                        {\n                            const CANCEL: &str = \"cancel-lctl-press\";\n                            const ADD: &str = \"add-lctl-release\";\n                            let v = sexpr_to_str_or_err(val, label)?;\n                            cfg.windows_opts.windows_altgr = match v {\n                                CANCEL => AltGrBehaviour::CancelLctlPress,\n                                ADD => AltGrBehaviour::AddLctlRelease,\n                                _ => bail_expr!(\n                                    val,\n                                    \"Invalid value for {label}: {}. Valid values are {},{}\",\n                                    v,\n                                    CANCEL,\n                                    ADD\n                                ),\n                            }\n                        }\n                    }\n                    \"windows-sync-keystates\" => {\n                        #[cfg(any(target_os = \"windows\", target_os = \"unknown\"))]\n                        {\n                            cfg.windows_opts.sync_keystates = parse_defcfg_val_bool(val, label)?;\n                        }\n                    }\n                    \"windows-interception-mouse-hwid\" => {\n                        #[cfg(any(\n                            all(feature = \"interception_driver\", target_os = \"windows\"),\n                            target_os = \"unknown\"\n                        ))]\n                        {\n                            if cfg\n                                .wintercept_opts\n                                .windows_interception_mouse_hwids_exclude\n                                .is_some()\n                            {\n                                bail_expr!(\n                                    val,\n                                    \"{label} and windows-interception-mouse-hwid-exclude cannot both be included\"\n                                );\n                            }\n                            let v = sexpr_to_str_or_err(val, label)?;\n                            let hwid = v;\n                            log::trace!(\"win hwid: {hwid}\");\n                            let hwid_vec = hwid\n                                .split(',')\n                                .try_fold(vec![], |mut hwid_bytes, hwid_byte| {\n                                    hwid_byte.trim_matches(' ').parse::<u8>().map(|b| {\n                                        hwid_bytes.push(b);\n                                        hwid_bytes\n                                    })\n                                }).map_err(|_| anyhow_expr!(val, \"{label} format is invalid. It should consist of numbers [0,255] separated by commas\"))?;\n                            let hwid_slice = hwid_vec.iter().copied().enumerate()\n                                .try_fold([0u8; HWID_ARR_SZ], |mut hwid, idx_byte| {\n                                    let (i, b) = idx_byte;\n                                    if i > HWID_ARR_SZ {\n                                        bail_expr!(val, \"{label} is too long; it should be up to {HWID_ARR_SZ} numbers [0,255]\")\n                                    }\n                                    hwid[i] = b;\n                                    Ok(hwid)\n                            })?;\n                            match cfg\n                                .wintercept_opts\n                                .windows_interception_mouse_hwids\n                                .as_mut()\n                            {\n                                Some(v) => {\n                                    v.push(hwid_slice);\n                                }\n                                None => {\n                                    cfg.wintercept_opts.windows_interception_mouse_hwids =\n                                        Some(vec![hwid_slice]);\n                                }\n                            }\n                            cfg.wintercept_opts\n                                .windows_interception_mouse_hwids\n                                .as_mut()\n                                .unwrap()\n                                .shrink_to_fit();\n                        }\n                    }\n                    \"windows-interception-mouse-hwids\" => {\n                        #[cfg(any(\n                            all(feature = \"interception_driver\", target_os = \"windows\"),\n                            target_os = \"unknown\"\n                        ))]\n                        {\n                            if cfg\n                                .wintercept_opts\n                                .windows_interception_mouse_hwids_exclude\n                                .is_some()\n                            {\n                                bail_expr!(\n                                    val,\n                                    \"{label} and windows-interception-mouse-hwid-exclude cannot both be included\"\n                                );\n                            }\n                            let parsed_hwids = sexpr_to_hwids_vec(\n                                val,\n                                label,\n                                \"entry in windows-interception-mouse-hwids\",\n                            )?;\n                            match cfg\n                                .wintercept_opts\n                                .windows_interception_mouse_hwids\n                                .as_mut()\n                            {\n                                Some(v) => {\n                                    v.extend(parsed_hwids);\n                                }\n                                None => {\n                                    cfg.wintercept_opts.windows_interception_mouse_hwids =\n                                        Some(parsed_hwids);\n                                }\n                            }\n                            cfg.wintercept_opts\n                                .windows_interception_mouse_hwids\n                                .as_mut()\n                                .unwrap()\n                                .shrink_to_fit();\n                        }\n                    }\n                    \"windows-interception-mouse-hwids-exclude\" => {\n                        #[cfg(any(\n                            all(feature = \"interception_driver\", target_os = \"windows\"),\n                            target_os = \"unknown\"\n                        ))]\n                        {\n                            if cfg\n                                .wintercept_opts\n                                .windows_interception_mouse_hwids\n                                .is_some()\n                            {\n                                bail_expr!(\n                                    val,\n                                    \"{label} and windows-interception-mouse-hwid(s) cannot both be used\"\n                                );\n                            }\n                            let parsed_hwids = sexpr_to_hwids_vec(\n                                val,\n                                label,\n                                \"entry in windows-interception-mouse-hwids-exclude\",\n                            )?;\n                            cfg.wintercept_opts.windows_interception_mouse_hwids_exclude =\n                                Some(parsed_hwids);\n                        }\n                    }\n                    \"windows-interception-keyboard-hwids\" => {\n                        #[cfg(any(\n                            all(feature = \"interception_driver\", target_os = \"windows\"),\n                            target_os = \"unknown\"\n                        ))]\n                        {\n                            if cfg\n                                .wintercept_opts\n                                .windows_interception_keyboard_hwids_exclude\n                                .is_some()\n                            {\n                                bail_expr!(\n                                    val,\n                                    \"{label} and windows-interception-keyboard-hwid-exclude cannot both be used\"\n                                );\n                            }\n                            let parsed_hwids = sexpr_to_hwids_vec(\n                                val,\n                                label,\n                                \"entry in windows-interception-keyboard-hwids\",\n                            )?;\n                            cfg.wintercept_opts.windows_interception_keyboard_hwids =\n                                Some(parsed_hwids);\n                        }\n                    }\n                    \"windows-interception-keyboard-hwids-exclude\" => {\n                        #[cfg(any(\n                            all(feature = \"interception_driver\", target_os = \"windows\"),\n                            target_os = \"unknown\"\n                        ))]\n                        {\n                            if cfg\n                                .wintercept_opts\n                                .windows_interception_keyboard_hwids\n                                .is_some()\n                            {\n                                bail_expr!(\n                                    val,\n                                    \"{label} and windows-interception-keyboard-hwid cannot both be used\"\n                                );\n                            }\n                            let parsed_hwids = sexpr_to_hwids_vec(\n                                val,\n                                label,\n                                \"entry in windows-interception-keyboard-hwids-exclude\",\n                            )?;\n                            cfg.wintercept_opts\n                                .windows_interception_keyboard_hwids_exclude = Some(parsed_hwids);\n                        }\n                    }\n                    \"macos-dev-names-include\" => {\n                        #[cfg(any(target_os = \"macos\", target_os = \"unknown\"))]\n                        {\n                            let dev_names = parse_dev(val)?;\n                            if dev_names.is_empty() {\n                                log::warn!(\"macos-dev-names-include is empty\");\n                            }\n                            cfg.macos_opts.macos_dev_names_include = Some(dev_names);\n                        }\n                    }\n                    \"macos-dev-names-exclude\" => {\n                        #[cfg(any(target_os = \"macos\", target_os = \"unknown\"))]\n                        {\n                            let dev_names = parse_dev(val)?;\n                            if dev_names.is_empty() {\n                                log::warn!(\"macos-dev-names-exclude is empty\");\n                            }\n                            cfg.macos_opts.macos_dev_names_exclude = Some(dev_names);\n                        }\n                    }\n                    \"tray-icon\" => {\n                        #[cfg(all(\n                            any(target_os = \"windows\", target_os = \"unknown\"),\n                            feature = \"gui\"\n                        ))]\n                        {\n                            let icon_path = sexpr_to_str_or_err(val, label)?;\n                            if icon_path.is_empty() {\n                                log::warn!(\"tray-icon is empty\");\n                            }\n                            cfg.gui_opts.tray_icon = Some(icon_path.to_string());\n                        }\n                    }\n                    \"icon-match-layer-name\" => {\n                        #[cfg(all(\n                            any(target_os = \"windows\", target_os = \"unknown\"),\n                            feature = \"gui\"\n                        ))]\n                        {\n                            cfg.gui_opts.icon_match_layer_name = parse_defcfg_val_bool(val, label)?\n                        }\n                    }\n                    \"tooltip-layer-changes\" => {\n                        #[cfg(all(\n                            any(target_os = \"windows\", target_os = \"unknown\"),\n                            feature = \"gui\"\n                        ))]\n                        {\n                            cfg.gui_opts.tooltip_layer_changes = parse_defcfg_val_bool(val, label)?\n                        }\n                    }\n                    \"tooltip-show-blank\" => {\n                        #[cfg(all(\n                            any(target_os = \"windows\", target_os = \"unknown\"),\n                            feature = \"gui\"\n                        ))]\n                        {\n                            cfg.gui_opts.tooltip_show_blank = parse_defcfg_val_bool(val, label)?\n                        }\n                    }\n                    \"tooltip-no-base\" => {\n                        #[cfg(all(\n                            any(target_os = \"windows\", target_os = \"unknown\"),\n                            feature = \"gui\"\n                        ))]\n                        {\n                            cfg.gui_opts.tooltip_no_base = parse_defcfg_val_bool(val, label)?\n                        }\n                    }\n                    \"tooltip-duration\" => {\n                        #[cfg(all(\n                            any(target_os = \"windows\", target_os = \"unknown\"),\n                            feature = \"gui\"\n                        ))]\n                        {\n                            cfg.gui_opts.tooltip_duration = parse_cfg_val_u16(val, label, false)?\n                        }\n                    }\n                    \"notify-cfg-reload\" => {\n                        #[cfg(all(\n                            any(target_os = \"windows\", target_os = \"unknown\"),\n                            feature = \"gui\"\n                        ))]\n                        {\n                            cfg.gui_opts.notify_cfg_reload = parse_defcfg_val_bool(val, label)?\n                        }\n                    }\n                    \"notify-cfg-reload-silent\" => {\n                        #[cfg(all(\n                            any(target_os = \"windows\", target_os = \"unknown\"),\n                            feature = \"gui\"\n                        ))]\n                        {\n                            cfg.gui_opts.notify_cfg_reload_silent =\n                                parse_defcfg_val_bool(val, label)?\n                        }\n                    }\n                    \"notify-error\" => {\n                        #[cfg(all(\n                            any(target_os = \"windows\", target_os = \"unknown\"),\n                            feature = \"gui\"\n                        ))]\n                        {\n                            cfg.gui_opts.notify_error = parse_defcfg_val_bool(val, label)?\n                        }\n                    }\n                    \"tooltip-size\" => {\n                        #[cfg(all(\n                            any(target_os = \"windows\", target_os = \"unknown\"),\n                            feature = \"gui\"\n                        ))]\n                        {\n                            let v = sexpr_to_str_or_err(val, label)?;\n                            let tooltip_size = v.split(',').collect::<Vec<_>>();\n                            const ERRMSG: &str = \"Invalid value for tooltip-size.\\nExpected two numbers 0-65535 separated by a comma, e.g. 24,24\";\n                            if tooltip_size.len() != 2 {\n                                bail_expr!(val, \"{}\", ERRMSG)\n                            }\n                            cfg.gui_opts.tooltip_size = (\n                                match str::parse::<u16>(tooltip_size[0]) {\n                                    Ok(w) => w,\n                                    Err(_) => bail_expr!(val, \"{}\", ERRMSG),\n                                },\n                                match str::parse::<u16>(tooltip_size[1]) {\n                                    Ok(h) => h,\n                                    Err(_) => bail_expr!(val, \"{}\", ERRMSG),\n                                },\n                            );\n                        }\n                    }\n\n                    \"process-unmapped-keys\" => {\n                        is_process_unmapped_keys_defined = true;\n                        if let Some(list) = val.list(None) {\n                            let err = \"Expected (all-except key1 ... keyN).\";\n                            if list.len() < 2 {\n                                bail_expr!(val, \"{err}\");\n                            }\n                            match list[0].atom(None) {\n                                Some(\"all-except\") => {}\n                                _ => {\n                                    bail_expr!(val, \"{err}\");\n                                }\n                            };\n                            // Note: deflocalkeys should already be parsed when parsing defcfg,\n                            // so can use safely use str_to_oscode here; it will include user\n                            // configurations already.\n                            let mut key_exceptions: Vec<(OsCode, SExpr)> = vec![];\n                            for key_expr in list[1..].iter() {\n                                let key = key_expr.atom(None).and_then(str_to_oscode).ok_or_else(\n                                    || anyhow_expr!(key_expr, \"Expected a known key name.\"),\n                                )?;\n                                if key_exceptions.iter().any(|k_exc| k_exc.0 == key) {\n                                    bail_expr!(key_expr, \"Duplicate key name is not allowed.\");\n                                }\n                                key_exceptions.push((key, key_expr.clone()));\n                            }\n                            cfg.process_unmapped_keys = true;\n                            cfg.process_unmapped_keys_exceptions = Some(key_exceptions);\n                        } else {\n                            cfg.process_unmapped_keys = parse_defcfg_val_bool(val, label)?\n                        }\n                    }\n\n                    \"block-unmapped-keys\" => {\n                        cfg.block_unmapped_keys = parse_defcfg_val_bool(val, label)?\n                    }\n                    \"allow-hardware-repeat\" => {\n                        cfg.allow_hardware_repeat = parse_defcfg_val_bool(val, label)?\n                    }\n                    \"alias-to-trigger-on-load\" => {\n                        cfg.start_alias = parse_defcfg_val_string(val, label)?\n                    }\n                    \"danger-enable-cmd\" => cfg.enable_cmd = parse_defcfg_val_bool(val, label)?,\n                    \"sequence-backtrack-modcancel\" => {\n                        cfg.sequence_backtrack_modcancel = parse_defcfg_val_bool(val, label)?\n                    }\n                    \"log-layer-changes\" => {\n                        cfg.log_layer_changes = parse_defcfg_val_bool(val, label)?\n                    }\n                    \"delegate-to-first-layer\" => {\n                        cfg.delegate_to_first_layer = parse_defcfg_val_bool(val, label)?;\n                        if cfg.delegate_to_first_layer {\n                            log::info!(\n                                \"delegating transparent keys on other layers to first defined layer\"\n                            );\n                        }\n                    }\n                    \"linux-continue-if-no-devs-found\" => {\n                        #[cfg(any(\n                            target_os = \"linux\",\n                            target_os = \"android\",\n                            target_os = \"unknown\"\n                        ))]\n                        {\n                            cfg.linux_opts.linux_continue_if_no_devs_found =\n                                parse_defcfg_val_bool(val, label)?\n                        }\n                    }\n                    \"movemouse-smooth-diagonals\" => {\n                        cfg.movemouse_smooth_diagonals = parse_defcfg_val_bool(val, label)?\n                    }\n                    \"movemouse-inherit-accel-state\" => {\n                        cfg.movemouse_inherit_accel_state = parse_defcfg_val_bool(val, label)?\n                    }\n                    \"override-release-on-activation\" => {\n                        cfg.override_release_on_activation = parse_defcfg_val_bool(val, label)?\n                    }\n                    \"concurrent-tap-hold\" => {\n                        cfg.concurrent_tap_hold = parse_defcfg_val_bool(val, label)?\n                    }\n                    \"rapid-event-delay\" => {\n                        cfg.rapid_event_delay = parse_cfg_val_u16(val, label, false)?\n                    }\n                    \"transparent-key-resolution\" => {\n                        let v = sexpr_to_str_or_err(val, label)?;\n                        cfg.trans_resolution_behavior_v2 = match v {\n                            \"to-base-layer\" => false,\n                            \"layer-stack\" => true,\n                            _ => bail_expr!(\n                                val,\n                                \"{label} got {}. It accepts: 'to-base-layer' or 'layer-stack'\",\n                                v\n                            ),\n                        };\n                    }\n                    \"chords-v2-min-idle\" | \"chords-v2-min-idle-experimental\" => {\n                        if label == \"chords-v2-min-idle-experimental\" {\n                            log::warn!(\n                                \"You should replace chords-v2-min-idle-experimental with chords-v2-min-idle\\n\\\n                                        Using -experimental will be invalid in the future.\"\n                            )\n                        }\n                        let min_idle = parse_cfg_val_u16(val, label, true)?;\n                        if min_idle < 5 {\n                            bail_expr!(val, \"{label} must be 5-65535\");\n                        }\n                        cfg.chords_v2_min_idle = min_idle;\n                    }\n                    \"tap-hold-require-prior-idle\" => {\n                        cfg.tap_hold_require_prior_idle = parse_cfg_val_u16(val, label, false)?;\n                    }\n                    \"mouse-movement-key\" => {\n                        #[cfg(any(\n                            all(target_os = \"windows\", feature = \"interception_driver\"),\n                            target_os = \"linux\",\n                            target_os = \"android\",\n                            target_os = \"unknown\"\n                        ))]\n                        {\n                            if let Some(keystr) = parse_defcfg_val_string(val, label)? {\n                                if let Some(key) = str_to_oscode(&keystr) {\n                                    cfg.mouse_movement_key = Some(key);\n                                } else {\n                                    bail_expr!(val, \"{label} not a recognised key code\");\n                                }\n                            } else {\n                                bail_expr!(val, \"{label} not a string for a key code\");\n                            }\n                        }\n                    }\n                    _ => bail_expr!(key, \"Unknown defcfg option {}\", label),\n                };\n            }\n            SExpr::List(_) => {\n                bail_expr!(key, \"Lists are not allowed in as keys in defcfg\");\n            }\n        }\n    }\n}\n\nfn parse_defcfg_val_string(expr: &SExpr, _label: &str) -> Result<Option<String>> {\n    match expr {\n        SExpr::Atom(v) => Ok(Some(v.t.clone())),\n        _ => Ok(None),\n    }\n}\n\npub const FALSE_VALUES: [&str; 3] = [\"no\", \"false\", \"0\"];\npub const TRUE_VALUES: [&str; 3] = [\"yes\", \"true\", \"1\"];\npub const BOOLEAN_VALUES: [&str; 6] = [\"yes\", \"true\", \"1\", \"no\", \"false\", \"0\"];\n\nfn parse_defcfg_val_bool(expr: &SExpr, label: &str) -> Result<bool> {\n    match &expr {\n        SExpr::Atom(v) => {\n            let val = v.t.trim_atom_quotes().to_ascii_lowercase();\n            if TRUE_VALUES.contains(&val.as_str()) {\n                Ok(true)\n            } else if FALSE_VALUES.contains(&val.as_str()) {\n                Ok(false)\n            } else {\n                bail_expr!(\n                    expr,\n                    \"The value for {label} must be one of: {}\",\n                    BOOLEAN_VALUES.join(\", \")\n                );\n            }\n        }\n        SExpr::List(_) => {\n            bail_expr!(\n                expr,\n                \"The value for {label} cannot be a list, it must be one of: {}\",\n                BOOLEAN_VALUES.join(\", \"),\n            )\n        }\n    }\n}\n\nfn parse_cfg_val_u16(expr: &SExpr, label: &str, exclude_zero: bool) -> Result<u16> {\n    let start = if exclude_zero { 1 } else { 0 };\n    match &expr {\n        SExpr::Atom(v) => Ok(str::parse::<u16>(v.t.trim_atom_quotes())\n            .ok()\n            .and_then(|u| {\n                if exclude_zero && u == 0 {\n                    None\n                } else {\n                    Some(u)\n                }\n            })\n            .ok_or_else(|| anyhow_expr!(expr, \"{label} must be {start}-65535\"))?),\n        SExpr::List(_) => {\n            bail_expr!(\n                expr,\n                \"The value for {label} cannot be a list, it must be a number {start}-65535\",\n            )\n        }\n    }\n}\n\npub fn parse_colon_separated_text(paths: &str) -> Vec<String> {\n    let mut all_paths = vec![];\n    let mut full_dev_path = String::new();\n    let mut dev_path_iter = paths.split(':').peekable();\n    while let Some(dev_path) = dev_path_iter.next() {\n        if dev_path.ends_with('\\\\') && dev_path_iter.peek().is_some() {\n            full_dev_path.push_str(dev_path.trim_end_matches('\\\\'));\n            full_dev_path.push(':');\n            continue;\n        } else {\n            full_dev_path.push_str(dev_path);\n        }\n        all_paths.push(full_dev_path.clone());\n        full_dev_path.clear();\n    }\n    all_paths.shrink_to_fit();\n    all_paths\n}\n\n#[cfg(any(\n    target_os = \"linux\",\n    target_os = \"android\",\n    target_os = \"macos\",\n    target_os = \"unknown\"\n))]\npub fn parse_dev(val: &SExpr) -> Result<Vec<String>> {\n    Ok(match val {\n        SExpr::Atom(a) => {\n            let devs = parse_colon_separated_text(a.t.trim_atom_quotes());\n            if devs.len() == 1 && devs[0].is_empty() {\n                bail_expr!(val, \"an empty string is not a valid device name or path\")\n            }\n            devs\n        }\n        SExpr::List(l) => {\n            let r: Result<Vec<String>> =\n                l.t.iter()\n                    .try_fold(Vec::with_capacity(l.t.len()), |mut acc, expr| match expr {\n                        SExpr::Atom(path) => {\n                            let trimmed_path = path.t.trim_atom_quotes().to_string();\n                            if trimmed_path.is_empty() {\n                                bail_span!(\n                                    path,\n                                    \"an empty string is not a valid device name or path\"\n                                )\n                            }\n                            acc.push(trimmed_path);\n                            Ok(acc)\n                        }\n                        SExpr::List(inner_list) => {\n                            bail_span!(inner_list, \"expected strings, found a list\")\n                        }\n                    });\n\n            r?\n        }\n    })\n}\n\nfn sexpr_to_str_or_err<'a>(expr: &'a SExpr, label: &str) -> Result<&'a str> {\n    match expr {\n        SExpr::Atom(a) => Ok(a.t.trim_atom_quotes()),\n        SExpr::List(_) => bail_expr!(expr, \"The value for {label} can't be a list\"),\n    }\n}\n\n#[cfg(any(\n    all(feature = \"interception_driver\", target_os = \"windows\"),\n    target_os = \"unknown\"\n))]\nfn sexpr_to_list_or_err<'a>(expr: &'a SExpr, label: &str) -> Result<&'a [SExpr]> {\n    match expr {\n        SExpr::Atom(_) => bail_expr!(expr, \"The value for {label} must be a list\"),\n        SExpr::List(l) => Ok(&l.t),\n    }\n}\n\n#[cfg(any(\n    all(feature = \"interception_driver\", target_os = \"windows\"),\n    target_os = \"unknown\"\n))]\nfn sexpr_to_hwids_vec(\n    val: &SExpr,\n    label: &str,\n    entry_label: &str,\n) -> Result<Vec<[u8; HWID_ARR_SZ]>> {\n    let hwids = sexpr_to_list_or_err(val, label)?;\n    let mut parsed_hwids = vec![];\n    for hwid_expr in hwids.iter() {\n        let hwid = sexpr_to_str_or_err(hwid_expr, entry_label)?;\n        log::trace!(\"win hwid: {hwid}\");\n        let hwid_vec = hwid\n            .split(',')\n            .try_fold(vec![], |mut hwid_bytes, hwid_byte| {\n                hwid_byte.trim_matches(' ').parse::<u8>().map(|b| {\n                    hwid_bytes.push(b);\n                    hwid_bytes\n                })\n            }).map_err(|_| anyhow_expr!(hwid_expr, \"Entry in {label} is invalid. Entries should be numbers [0,255] separated by commas\"))?;\n        let hwid_slice = hwid_vec.iter().copied().enumerate()\n            .try_fold([0u8; HWID_ARR_SZ], |mut hwid, idx_byte| {\n                let (i, b) = idx_byte;\n                if i > HWID_ARR_SZ {\n                    bail_expr!(hwid_expr, \"entry in {label} is too long; it should be up to {HWID_ARR_SZ} 8-bit unsigned integers\")\n                }\n                hwid[i] = b;\n                Ok(hwid)\n        });\n        parsed_hwids.push(hwid_slice?);\n    }\n    parsed_hwids.shrink_to_fit();\n    Ok(parsed_hwids)\n}\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\npub struct KeyRepeatSettings {\n    pub delay: u16,\n    pub rate: u16,\n}\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\npub enum UnicodeTermination {\n    Enter,\n    Space,\n    SpaceEnter,\n    EnterSpace,\n}\n\n#[cfg(any(target_os = \"windows\", target_os = \"unknown\"))]\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\npub enum AltGrBehaviour {\n    #[default]\n    DoNothing,\n    CancelLctlPress,\n    AddLctlRelease,\n}\n\n#[cfg(any(target_os = \"windows\", target_os = \"unknown\"))]\n#[cfg(any(\n    all(feature = \"interception_driver\", target_os = \"windows\"),\n    target_os = \"unknown\"\n))]\npub const HWID_ARR_SZ: usize = 1024;\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum ReplayDelayBehaviour {\n    /// Always use a fixed number of ticks between presses and releases.\n    /// This is the original kanata behaviour.\n    /// This means that held action activations like in tap-hold do not behave as intended.\n    Constant,\n    /// Use the recorded number of ticks between presses and releases.\n    /// This is newer behaviour.\n    Recorded,\n}\n"
  },
  {
    "path": "parser/src/cfg/defhands.rs",
    "content": "use super::sexpr::*;\nuse super::*;\nuse crate::{anyhow_expr, bail, bail_expr};\n\npub(super) fn parse_defhands(expr: &[SExpr], s: &ParserState) -> Result<custom_tap_hold::HandMap> {\n    use custom_tap_hold::Hand;\n\n    let exprs_iter = check_first_expr(expr.iter(), \"defhands\")?;\n    let mut keys: Vec<u16> = Vec::new();\n    let mut hands: Vec<Hand> = Vec::new();\n    let mut seen_left = false;\n    let mut seen_right = false;\n\n    for group_expr in exprs_iter {\n        let group = group_expr\n            .list(s.vars())\n            .ok_or_else(|| anyhow_expr!(group_expr, \"expected (left ...) or (right ...)\"))?;\n        if group.is_empty() {\n            bail_expr!(group_expr, \"expected (left ...) or (right ...)\");\n        }\n        let hand_name = group[0]\n            .atom(s.vars())\n            .ok_or_else(|| anyhow_expr!(&group[0], \"expected 'left' or 'right'\"))?;\n        let hand = match hand_name {\n            \"left\" => {\n                if seen_left {\n                    bail_expr!(&group[0], \"duplicate 'left' group in defhands\");\n                }\n                seen_left = true;\n                Hand::Left\n            }\n            \"right\" => {\n                if seen_right {\n                    bail_expr!(&group[0], \"duplicate 'right' group in defhands\");\n                }\n                seen_right = true;\n                Hand::Right\n            }\n            _ => bail_expr!(&group[0], \"expected 'left' or 'right', got '{}'\", hand_name),\n        };\n        for key_expr in &group[1..] {\n            let key_name = key_expr\n                .atom(s.vars())\n                .ok_or_else(|| anyhow_expr!(key_expr, \"expected a key name, found list\"))?;\n            let osc = str_to_oscode(key_name)\n                .ok_or_else(|| anyhow_expr!(key_expr, \"unknown key '{}'\", key_name))?;\n            let code = u16::from(osc);\n            if let Some(pos) = keys.iter().position(|&k| k == code) {\n                let existing_name = if hands[pos] == Hand::Left {\n                    \"left\"\n                } else {\n                    \"right\"\n                };\n                bail_expr!(\n                    key_expr,\n                    \"Key already assigned to '{}' hand, cannot also be in '{}'\",\n                    existing_name,\n                    hand_name\n                );\n            }\n            keys.push(code);\n            hands.push(hand);\n        }\n    }\n\n    let keys_static = s.a.sref_vec(keys);\n    let hands_static = s.a.sref_vec(hands);\n    Ok(custom_tap_hold::HandMap {\n        keys: keys_static,\n        hands: hands_static,\n    })\n}\n\npub(super) fn parse_tap_hold_opposite_hand(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    use custom_tap_hold::{DecisionBehavior, custom_tap_hold_opposite_hand};\n\n    const ARITY_MSG: &str = \"tap-hold-opposite-hand expects at least 3 items: \\\n            <timeout> <tap> <hold> [options...]\";\n    if ac_params.is_empty() {\n        bail!(ARITY_MSG);\n    }\n    if ac_params.len() < 3 {\n        bail_expr!(&ac_params[0], \"{}\", ARITY_MSG);\n    }\n    let hand_map = s.hand_map.ok_or_else(|| {\n        anyhow_expr!(\n            &ac_params[0],\n            \"tap-hold-opposite-hand requires defhands to be defined\"\n        )\n    })?;\n\n    let hold_timeout = parse_non_zero_u16(&ac_params[0], s, \"timeout\")?;\n    let tap_action = parse_action(&ac_params[1], s)?;\n    let hold_action = parse_action(&ac_params[2], s)?;\n    if matches!(tap_action, Action::HoldTap { .. }) {\n        bail_expr!(\n            &ac_params[1],\n            \"tap-hold does not work in the tap-action of tap-hold\"\n        );\n    }\n\n    let mut timeout_behavior = DecisionBehavior::Tap;\n    let mut same_hand = DecisionBehavior::Tap;\n    let mut neutral_behavior = DecisionBehavior::Ignore;\n    let mut unknown_hand = DecisionBehavior::Ignore;\n    let mut neutral_keys: Vec<OsCode> = Vec::new();\n    let mut require_prior_idle: Option<u16> = None;\n    let mut seen_options: HashSet<&str> = HashSet::default();\n\n    for option_expr in &ac_params[3..] {\n        let Some(option) = option_expr.list(s.vars()) else {\n            bail_expr!(\n                option_expr,\n                \"expected option list, e.g. `(timeout hold)` or `(neutral-keys spc tab)`\"\n            );\n        };\n        if option.is_empty() {\n            bail_expr!(option_expr, \"option list cannot be empty\");\n        }\n        let kw = option[0]\n            .atom(s.vars())\n            .ok_or_else(|| anyhow_expr!(&option[0], \"option name must be a string\"))?;\n        if !seen_options.insert(kw) {\n            bail_expr!(\n                &option[0],\n                \"duplicate option '{}' in tap-hold-opposite-hand\",\n                kw\n            );\n        }\n        match kw {\n            \"timeout\" => {\n                if option.len() != 2 {\n                    bail_expr!(\n                        option_expr,\n                        \"option must contain exactly 2 items: `(name value)`\"\n                    );\n                }\n                timeout_behavior = parse_decision_behavior_tap_hold(&option[1], s)?;\n            }\n            \"same-hand\" => {\n                if option.len() != 2 {\n                    bail_expr!(\n                        option_expr,\n                        \"option must contain exactly 2 items: `(name value)`\"\n                    );\n                }\n                same_hand = parse_decision_behavior(&option[1], s)?;\n            }\n            \"neutral\" => {\n                if option.len() != 2 {\n                    bail_expr!(\n                        option_expr,\n                        \"option must contain exactly 2 items: `(name value)`\"\n                    );\n                }\n                neutral_behavior = parse_decision_behavior(&option[1], s)?;\n            }\n            \"unknown-hand\" => {\n                if option.len() != 2 {\n                    bail_expr!(\n                        option_expr,\n                        \"option must contain exactly 2 items: `(name value)`\"\n                    );\n                }\n                unknown_hand = parse_decision_behavior(&option[1], s)?;\n            }\n            \"neutral-keys\" => {\n                if option.len() < 2 {\n                    bail_expr!(\n                        option_expr,\n                        \"neutral-keys expects one or more key atoms, e.g. `(neutral-keys spc tab)`\"\n                    );\n                }\n                neutral_keys = parse_key_atoms(&option[1..], s, \"neutral-keys\")?;\n            }\n            \"require-prior-idle\" => {\n                require_prior_idle = Some(tap_hold::parse_require_prior_idle_option(\n                    option,\n                    option_expr,\n                    s,\n                )?);\n            }\n            _ => bail_expr!(\n                &option[0],\n                \"unknown option '{}' for tap-hold-opposite-hand. \\\n                Valid options: timeout, same-hand, neutral, unknown-hand, neutral-keys, require-prior-idle\",\n                kw\n            ),\n        }\n    }\n\n    let timeout_action = match timeout_behavior {\n        DecisionBehavior::Tap => tap_action,\n        DecisionBehavior::Hold => hold_action,\n        DecisionBehavior::Ignore => unreachable!(),\n    };\n\n    let neutral_keys_static = s.a.sref_vec(neutral_keys);\n\n    Ok(s.a.sref(Action::HoldTap(s.a.sref(HoldTapAction {\n        config: HoldTapConfig::Custom(custom_tap_hold_opposite_hand(\n            hand_map,\n            same_hand,\n            neutral_behavior,\n            unknown_hand,\n            neutral_keys_static,\n            &s.a,\n        )),\n        tap_hold_interval: 0,\n        timeout: hold_timeout,\n        tap: *tap_action,\n        hold: *hold_action,\n        timeout_action: *timeout_action,\n        on_press_reset_timeout_to: None,\n        require_prior_idle,\n    }))))\n}\n\nfn parse_key_atoms(exprs: &[SExpr], s: &ParserState, label: &str) -> Result<Vec<OsCode>> {\n    exprs\n        .iter()\n        .map(|key_expr| {\n            let key_name = key_expr\n                .atom(s.vars())\n                .ok_or_else(|| anyhow_expr!(key_expr, \"{label} expects key atoms, found list\"))?;\n            str_to_oscode(key_name)\n                .ok_or_else(|| anyhow_expr!(key_expr, \"unknown key '{key_name}'\"))\n        })\n        .collect()\n}\n\nfn parse_decision_behavior(\n    expr: &SExpr,\n    s: &ParserState,\n) -> Result<custom_tap_hold::DecisionBehavior> {\n    use custom_tap_hold::DecisionBehavior;\n\n    match expr\n        .atom(s.vars())\n        .ok_or_else(|| anyhow_expr!(expr, \"expected tap, hold, or ignore\"))?\n    {\n        \"tap\" => Ok(DecisionBehavior::Tap),\n        \"hold\" => Ok(DecisionBehavior::Hold),\n        \"ignore\" => Ok(DecisionBehavior::Ignore),\n        v => bail_expr!(expr, \"expected tap, hold, or ignore; got '{}'\", v),\n    }\n}\n\nfn parse_decision_behavior_tap_hold(\n    expr: &SExpr,\n    s: &ParserState,\n) -> Result<custom_tap_hold::DecisionBehavior> {\n    use custom_tap_hold::DecisionBehavior;\n\n    match expr\n        .atom(s.vars())\n        .ok_or_else(|| anyhow_expr!(expr, \"expected tap or hold\"))?\n    {\n        \"tap\" => Ok(DecisionBehavior::Tap),\n        \"hold\" => Ok(DecisionBehavior::Hold),\n        v => bail_expr!(expr, \"expected tap or hold for timeout; got '{}'\", v),\n    }\n}\n"
  },
  {
    "path": "parser/src/cfg/deflayer.rs",
    "content": "use super::*;\n\nuse crate::anyhow_expr;\nuse crate::anyhow_span;\nuse crate::bail;\nuse crate::bail_expr;\nuse crate::bail_span;\n\npub(crate) type LayerIndexes = HashMap<String, usize>;\n\npub(crate) const DEFLAYER: &str = \"deflayer\";\npub(crate) const DEFLAYER_MAPPED: &str = \"deflayermap\";\n\n/// Returns layer names and their indexes into the keyberon layout. This also checks that:\n/// - All layers have the same number of items as the defsrc,\n/// - There are no duplicate layer names\n/// - Parentheses weren't used directly or kmonad-style escapes for parentheses weren't used.\npub(crate) fn parse_layer_indexes(\n    exprs: &[SpannedLayerExprs],\n    expected_len: usize,\n    vars: &HashMap<String, SExpr>,\n    _lsp_hints: &mut LspHints,\n) -> Result<(LayerIndexes, LayerIcons)> {\n    let mut layer_indexes = HashMap::default();\n    let mut layer_icons = HashMap::default();\n    for (i, expr_type) in exprs.iter().enumerate() {\n        let (mut subexprs, expr, do_element_count_check, deflayer_keyword) = match expr_type {\n            SpannedLayerExprs::DefsrcMapping(e) => {\n                (check_first_expr(e.t.iter(), DEFLAYER)?, e, true, DEFLAYER)\n            }\n            SpannedLayerExprs::CustomMapping(e) => (\n                check_first_expr(e.t.iter(), DEFLAYER_MAPPED)?,\n                e,\n                false,\n                DEFLAYER_MAPPED,\n            ),\n        };\n        let layer_expr = subexprs.next().ok_or_else(|| {\n            anyhow_span!(\n                expr,\n                \"{deflayer_keyword} requires a layer name after `{deflayer_keyword}` token\"\n            )\n        })?;\n        let (layer_name, _layer_name_span, icon) = {\n            let name = layer_expr.atom(Some(vars));\n            match name {\n                Some(name) => (name.to_owned(), layer_expr.span(), None),\n                None => {\n                    // unwrap: this **must** be a list due to atom() call above.\n                    let list = layer_expr.list(Some(vars)).unwrap();\n                    let first = list.first().ok_or_else(|| anyhow_expr!(\n                            layer_expr,\n                            \"{deflayer_keyword} requires a string name within this pair of parentheses (or a string name without any)\"\n                        ))?;\n                    let name = first.atom(Some(vars)).ok_or_else(|| anyhow_expr!(\n                            layer_expr,\n                            \"layer name after {deflayer_keyword} must be a string when enclosed within one pair of parentheses\"\n                        ))?;\n                    let layer_opts = parse_layer_opts(&list[1..])?;\n                    let icon = layer_opts\n                        .get(DEFLAYER_ICON[0])\n                        .map(|icon_s| icon_s.trim_atom_quotes().to_owned());\n                    (name.to_owned(), first.span(), icon)\n                }\n            }\n        };\n        if layer_indexes.contains_key(&layer_name) {\n            bail_expr!(layer_expr, \"duplicate layer name: {}\", layer_name);\n        }\n        // Check if user tried to use parentheses directly - `(` and `)`\n        // or escaped them like in kmonad - `\\(` and `\\)`.\n        for subexpr in subexprs {\n            if let Some(list) = subexpr.list(None) {\n                if list.is_empty() {\n                    bail_expr!(\n                        subexpr,\n                        \"You can't put parentheses in deflayer directly, because they are special characters for delimiting lists.\\n\\\n                         To get `(` and `)` in US layout, you should use `S-9` and `S-0` respectively.\\n\\\n                         For more context, see: https://github.com/jtroo/kanata/issues/459\"\n                    )\n                }\n                if list.len() == 1\n                    && list\n                        .first()\n                        .is_some_and(|s| s.atom(None).is_some_and(|atom| atom == \"\\\\\"))\n                {\n                    bail_expr!(\n                        subexpr,\n                        \"Escaping shifted characters with `\\\\` is currently not supported in kanata.\\n\\\n                         To get `(` and `)` in US layout, you should use `S-9` and `S-0` respectively.\\n\\\n                         For more context, see: https://github.com/jtroo/kanata/issues/163\"\n                    )\n                }\n            }\n        }\n        if do_element_count_check {\n            let num_actions = expr.t.len() - 2;\n            if num_actions != expected_len {\n                bail_span!(\n                    expr,\n                    \"Layer {} has {} item(s), but requires {} to match defsrc\",\n                    layer_name,\n                    num_actions,\n                    expected_len\n                )\n            }\n        }\n\n        #[cfg(feature = \"lsp\")]\n        _lsp_hints\n            .definition_locations\n            .layer\n            .insert(layer_name.clone(), _layer_name_span.clone());\n\n        layer_indexes.insert(layer_name.clone(), i);\n        layer_icons.insert(layer_name, icon);\n    }\n\n    Ok((layer_indexes, layer_icons))\n}\n\npub(crate) fn parse_layers(\n    s: &ParserState,\n    mapped_keys: &mut MappedKeys,\n    defcfg: &CfgOptions,\n) -> Result<IntermediateLayers> {\n    let mut layers_cfg = new_layers(s.layer_exprs.len());\n    if s.layer_exprs.len() > MAX_LAYERS {\n        bail!(\"Maximum number of layers ({}) exceeded.\", MAX_LAYERS);\n    }\n    let mut defsrc_layer = s.defsrc_layer;\n    for (layer_level, layer) in s.layer_exprs.iter().enumerate() {\n        match layer {\n            // The skip is done to skip the `deflayer` and layer name tokens.\n            LayerExprs::DefsrcMapping(layer) => {\n                // Parse actions in the layer and place them appropriately according\n                // to defsrc mapping order.\n                for (i, ac) in layer.iter().skip(2).enumerate() {\n                    let ac = parse_action(ac, s)?;\n                    layers_cfg[layer_level][0][s.mapping_order[i]] = *ac;\n                }\n            }\n            LayerExprs::CustomMapping(layer) => {\n                // Parse actions as input output pairs\n                let mut pairs = layer[2..].chunks_exact(2);\n                let mut layer_mapped_keys = HashSet::default();\n                let mut defsrc_anykey_used = false;\n                let mut unmapped_anykey_used = false;\n                let mut both_anykey_used = false;\n                for pair in pairs.by_ref() {\n                    let input = &pair[0];\n                    let action = &pair[1];\n\n                    let action = parse_action(action, s)?;\n                    if input.atom(s.vars()).is_some_and(|x| x == \"_\") {\n                        if defsrc_anykey_used {\n                            bail_expr!(input, \"must have only one use of _ within a layer\")\n                        }\n                        if both_anykey_used {\n                            bail_expr!(input, \"must either use _ or ___ within a layer, not both\")\n                        }\n                        for i in 0..s.mapping_order.len() {\n                            if layers_cfg[layer_level][0][s.mapping_order[i]] == DEFAULT_ACTION {\n                                layers_cfg[layer_level][0][s.mapping_order[i]] = *action;\n                            }\n                        }\n                        defsrc_anykey_used = true;\n                    } else if input.atom(s.vars()).is_some_and(|x| x == \"__\") {\n                        if unmapped_anykey_used {\n                            bail_expr!(input, \"must have only one use of __ within a layer\")\n                        }\n                        if !defcfg.process_unmapped_keys {\n                            bail_expr!(\n                                input,\n                                \"must set process-unmapped-keys to yes to use __ to map unmapped keys\"\n                            );\n                        }\n                        if both_anykey_used {\n                            bail_expr!(input, \"must either use __ or ___ within a layer, not both\")\n                        }\n                        for i in 0..layers_cfg[0][0].len() {\n                            if layers_cfg[layer_level][0][i] == DEFAULT_ACTION\n                                && !s.mapping_order.contains(&i)\n                            {\n                                layers_cfg[layer_level][0][i] = *action;\n                            }\n                        }\n                        unmapped_anykey_used = true;\n                    } else if input.atom(s.vars()).is_some_and(|x| x == \"___\") {\n                        if both_anykey_used {\n                            bail_expr!(input, \"must have only one use of ___ within a layer\")\n                        }\n                        if defsrc_anykey_used {\n                            bail_expr!(input, \"must either use _ or ___ within a layer, not both\")\n                        }\n                        if unmapped_anykey_used {\n                            bail_expr!(input, \"must either use __ or ___ within a layer, not both\")\n                        }\n                        if !defcfg.process_unmapped_keys {\n                            bail_expr!(\n                                input,\n                                \"must set process-unmapped-keys to yes to use ___ to also map unmapped keys\"\n                            );\n                        }\n                        for i in 0..layers_cfg[0][0].len() {\n                            if layers_cfg[layer_level][0][i] == DEFAULT_ACTION {\n                                layers_cfg[layer_level][0][i] = *action;\n                            }\n                        }\n                        both_anykey_used = true;\n                    } else {\n                        let input_key = input\n                            .atom(s.vars())\n                            .and_then(str_to_oscode)\n                            .ok_or_else(|| anyhow_expr!(input, \"input must be a key name\"))?;\n                        mapped_keys.insert(input_key);\n                        if !layer_mapped_keys.insert(input_key) {\n                            bail_expr!(input, \"input key must not be repeated within a layer\")\n                        }\n                        layers_cfg[layer_level][0][usize::from(input_key)] = *action;\n                    }\n                }\n                let rem = pairs.remainder();\n                if !rem.is_empty() {\n                    bail_expr!(&rem[0], \"input must by followed by an action\");\n                }\n            }\n        }\n        for (osc, layer_action) in layers_cfg[layer_level][0].iter_mut().enumerate() {\n            if *layer_action == DEFAULT_ACTION {\n                *layer_action = match s.block_unmapped_keys && !is_a_button(osc as u16) {\n                    true => Action::NoOp,\n                    false => Action::Trans,\n                };\n            }\n        }\n\n        // Set fake keys on every layer.\n        for (y, action) in s.virtual_keys.values() {\n            let (x, y) = get_fake_key_coords(*y);\n            layers_cfg[layer_level][x as usize][y as usize] = **action;\n        }\n\n        // If the user has configured delegation to the first (default) layer for transparent keys,\n        // (as opposed to delegation to defsrc), replace the defsrc actions with the actions from\n        // the first layer.\n        if layer_level == 0 && s.delegate_to_first_layer {\n            for (defsrc_ac, default_layer_ac) in defsrc_layer.iter_mut().zip(layers_cfg[0][0]) {\n                if default_layer_ac != Action::Trans {\n                    *defsrc_ac = default_layer_ac;\n                }\n            }\n        }\n\n        // Very last thing - ensure index 0 is always no-op. This shouldn't have any way to be\n        // physically activated. This enable other code to rely on there always being a no-op key.\n        layers_cfg[layer_level][0][0] = Action::NoOp;\n    }\n    Ok(layers_cfg)\n}\n\npub(crate) fn parse_layer_base(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    let idx = layer_idx(ac_params, &s.layer_idxs, s)?;\n    set_layer_change_lsp_hint(&ac_params[0], &mut s.lsp_hints.borrow_mut());\n    Ok(s.a.sref(Action::DefaultLayer(idx)))\n}\n\npub(crate) fn parse_layer_toggle(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    let idx = layer_idx(ac_params, &s.layer_idxs, s)?;\n    set_layer_change_lsp_hint(&ac_params[0], &mut s.lsp_hints.borrow_mut());\n    Ok(s.a.sref(Action::Layer(idx)))\n}\n"
  },
  {
    "path": "parser/src/cfg/deflocalkeys.rs",
    "content": "use super::*;\n\nuse crate::anyhow_expr;\nuse crate::bail_expr;\n\n#[cfg(all(\n    not(feature = \"interception_driver\"),\n    any(\n        not(feature = \"win_llhook_read_scancodes\"),\n        not(feature = \"win_sendinput_send_scancodes\")\n    ),\n    target_os = \"windows\"\n))]\npub(crate) const DEF_LOCAL_KEYS: &str = \"deflocalkeys-win\";\n#[cfg(all(\n    feature = \"win_llhook_read_scancodes\",\n    feature = \"win_sendinput_send_scancodes\",\n    not(feature = \"interception_driver\"),\n    target_os = \"windows\"\n))]\npub(crate) const DEF_LOCAL_KEYS: &str = \"deflocalkeys-winiov2\";\n#[cfg(all(feature = \"interception_driver\", target_os = \"windows\"))]\npub(crate) const DEF_LOCAL_KEYS: &str = \"deflocalkeys-wintercept\";\n#[cfg(target_os = \"macos\")]\npub(crate) const DEF_LOCAL_KEYS: &str = \"deflocalkeys-macos\";\n#[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\npub(crate) const DEF_LOCAL_KEYS: &str = \"deflocalkeys-linux\";\n\npub(crate) fn deflocalkeys_variant_applies_to_current_os(variant: &str) -> bool {\n    variant == DEF_LOCAL_KEYS\n}\n\npub(crate) const DEFLOCALKEYS_VARIANTS: &[&str] = &[\n    \"deflocalkeys-win\",\n    \"deflocalkeys-winiov2\",\n    \"deflocalkeys-wintercept\",\n    \"deflocalkeys-linux\",\n    \"deflocalkeys-macos\",\n];\n\n/// Parse custom keys from an expression starting with deflocalkeys.\npub(crate) fn parse_deflocalkeys(\n    def_local_keys_variant: &str,\n    expr: &[SExpr],\n) -> Result<HashMap<String, OsCode>> {\n    let mut localkeys = HashMap::default();\n    let mut exprs = check_first_expr(expr.iter(), def_local_keys_variant)?;\n    // Read k-v pairs from the configuration\n    while let Some(key_expr) = exprs.next() {\n        let key = key_expr.atom(None).ok_or_else(|| {\n            anyhow_expr!(key_expr, \"No lists are allowed in {def_local_keys_variant}\")\n        })?;\n        if localkeys.contains_key(key) {\n            bail_expr!(\n                key_expr,\n                \"Duplicate {key} found in {def_local_keys_variant}\"\n            );\n        }\n\n        // Bug:\n        // Trying to convert a number to OsCode is OS-dependent and is fallible.\n        // A valid number for Linux could throw an error on Windows.\n        //\n        // Fix:\n        // When the deflocalkeys variant does not apply to the current OS,\n        // use a dummy OsCode to keep the \"same name\" validation\n        // while avoiding the u16->OsCode conversion attempt.\n        if !deflocalkeys_variant_applies_to_current_os(def_local_keys_variant) {\n            localkeys.insert(key.to_owned(), OsCode::KEY_RESERVED);\n            continue;\n        }\n\n        let osc = match exprs.next() {\n            Some(v) => v\n                .atom(None)\n                .ok_or_else(|| anyhow_expr!(v, \"No lists are allowed in {def_local_keys_variant}\"))\n                .and_then(|osc| {\n                    osc.parse::<u16>().map_err(|_| {\n                        anyhow_expr!(v, \"Unknown number in {def_local_keys_variant}: {osc}\")\n                    })\n                })\n                .and_then(|osc| {\n                    OsCode::from_u16(osc).ok_or_else(|| {\n                        anyhow_expr!(v, \"Unknown number in {def_local_keys_variant}: {osc}\")\n                    })\n                })?,\n            None => bail_expr!(key_expr, \"Key without a number in {def_local_keys_variant}\"),\n        };\n        log::debug!(\"custom mapping: {key} {}\", osc.as_u16());\n        localkeys.insert(key.to_owned(), osc);\n    }\n    Ok(localkeys)\n}\n"
  },
  {
    "path": "parser/src/cfg/defsrc.rs",
    "content": "use super::*;\n\nuse crate::anyhow_expr;\nuse crate::bail_expr;\n\n/// Parse mapped keys from an expression starting with defsrc. Returns the key mapping as well as\n/// a vec of the indexes in order. The length of the returned vec should be matched by the length\n/// of all layer declarations.\npub(crate) fn parse_defsrc(\n    expr: &[SExpr],\n    defcfg: &CfgOptions,\n) -> Result<(MappedKeys, Vec<usize>, MouseInDefsrc)> {\n    let exprs = check_first_expr(expr.iter(), \"defsrc\")?;\n    let mut mkeys = MappedKeys::default();\n    let mut ordered_codes = Vec::new();\n    let mut is_mouse_used = MouseInDefsrc::NoMouse;\n    for expr in exprs {\n        let s = match expr {\n            SExpr::Atom(a) => &a.t,\n            _ => bail_expr!(expr, \"No lists allowed in defsrc\"),\n        };\n        let oscode = str_to_oscode(s)\n            .ok_or_else(|| anyhow_expr!(expr, \"Unknown key in defsrc: \\\"{}\\\"\", s))?;\n        is_mouse_used = match (is_mouse_used, oscode) {\n            (\n                MouseInDefsrc::NoMouse,\n                OsCode::BTN_LEFT\n                | OsCode::BTN_RIGHT\n                | OsCode::BTN_MIDDLE\n                | OsCode::BTN_SIDE\n                | OsCode::BTN_EXTRA\n                | OsCode::MouseWheelUp\n                | OsCode::MouseWheelDown\n                | OsCode::MouseWheelLeft\n                | OsCode::MouseWheelRight,\n            ) => MouseInDefsrc::MouseUsed,\n            _ => is_mouse_used,\n        };\n\n        if mkeys.contains(&oscode) {\n            bail_expr!(expr, \"Repeat declaration of key in defsrc: \\\"{}\\\"\", s)\n        }\n        mkeys.insert(oscode);\n        ordered_codes.push(oscode.into());\n    }\n\n    let mapped_exceptions = match &defcfg.process_unmapped_keys_exceptions {\n        Some(excluded_keys) => {\n            for excluded_key in excluded_keys.iter() {\n                log::debug!(\"process unmapped keys exception: {:?}\", excluded_key);\n                if mkeys.contains(&excluded_key.0) {\n                    bail_expr!(\n                        &excluded_key.1,\n                        \"Keys cannot be included in defsrc and also excepted in process-unmapped-keys.\"\n                    );\n                }\n            }\n\n            excluded_keys\n                .iter()\n                .map(|excluded_key| excluded_key.0)\n                .collect()\n        }\n        None => vec![],\n    };\n\n    log::info!(\"process unmapped keys: {}\", defcfg.process_unmapped_keys);\n    if defcfg.process_unmapped_keys {\n        for osc in 0..KEYS_IN_ROW as u16 {\n            if let Some(osc) = OsCode::from_u16(osc) {\n                if osc.is_mouse_code() {\n                    // Bugfix #1879:\n                    // Auto-including mouse activity in mapped keys\n                    // seems strictly incorrect to do, so never do it.\n                    // Users can still choose to opt in if they want.\n                    // Auto-including mouse activity breaks many scenarios.\n                    continue;\n                }\n                match KeyCode::from(osc) {\n                    KeyCode::No => {}\n                    _ => {\n                        if !mapped_exceptions.contains(&osc) {\n                            mkeys.insert(osc);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    mkeys.shrink_to_fit();\n    Ok((mkeys, ordered_codes, is_mouse_used))\n}\n\npub(crate) fn create_defsrc_layer() -> [KanataAction; KEYS_IN_ROW] {\n    let mut layer = [KanataAction::NoOp; KEYS_IN_ROW];\n\n    for (i, ac) in layer.iter_mut().enumerate() {\n        *ac = OsCode::from_u16(i as u16)\n            .map(|osc| Action::KeyCode(osc.into()))\n            .unwrap_or(Action::NoOp);\n    }\n    // Ensure 0-index is no-op.\n    layer[0] = KanataAction::NoOp;\n    layer\n}\n"
  },
  {
    "path": "parser/src/cfg/deftemplate.rs",
    "content": "//! This file is responsible for template expansion.\n//! For simplicity of implementation, there is performance left off the table.\n//! This code runs at parse time and not in runtime\n//! so it is not performance critical.\n//!\n//! The known performance left off the table is:\n//!\n//! - Creating the expanded template recurses through all SExprs every time.\n//!   Instead the code could pre-compute the paths to access every variable\n//!   that needs substition. (perf_1)\n//!\n//! - Replacing the `template-expand|if-equal` items with the appropriate values\n//!   recreates the Vec for every replacement that happens at that recursion depth.\n//!   Instead the code could do recreate the vec only once\n//!   and insert SExprs at the proper places. (perf_2)\n\nuse crate::anyhow_expr;\nuse crate::anyhow_span;\nuse crate::bail_expr;\nuse crate::bail_span;\nuse crate::err_expr;\nuse crate::err_span;\n\nuse super::error::*;\nuse super::sexpr::*;\nuse super::*;\n\n#[derive(Debug)]\nstruct Template {\n    name: String,\n    vars: Vec<String>,\n    // Same as vars above but all names are prefixed with '$'.\n    vars_substitute_names: Vec<String>,\n    content: Vec<SExpr>,\n}\n\n/// Parse `deftemplate`s and expand `template-expand`s.\n///\n/// Syntax of `deftemplate` is:\n///\n/// `(deftemplate <template name> (<list of template vars>) <rest of template>)`\n///\n/// Syntax of `template-expand` is:\n///\n/// `(template-expand <template name> <template var substitutions>)`\npub fn expand_templates(\n    mut toplevel_exprs: Vec<TopLevel>,\n    lsp_hints: &mut LspHints,\n) -> Result<Vec<TopLevel>> {\n    let mut templates: Vec<Template> = vec![];\n\n    // Find defined templates\n    for list in toplevel_exprs.iter_mut() {\n        if !matches!(\n            list.t.first().and_then(|expr| expr.atom(None)),\n            Some(\"deftemplate\")\n        ) {\n            continue;\n        }\n\n        // Parse template name\n        let (name, _name_span) = list\n            .t\n            .get(1)\n            .ok_or_else(|| {\n                anyhow_span!(\n                    list,\n                    \"deftemplate must have the template name as the first parameter\"\n                )\n            })\n            .and_then(|name_expr| {\n                let name = name_expr\n                    .atom(None)\n                    .ok_or_else(|| anyhow_expr!(name_expr, \"template name must be a string\"))?;\n                // check for duplicates\n                if templates.iter().any(|t| t.name == name) {\n                    bail_expr!(name_expr, \"template name was already defined earlier\");\n                }\n                Ok((name, name_expr.span()))\n            })?;\n\n        #[cfg(feature = \"lsp\")]\n        lsp_hints\n            .definition_locations\n            .template\n            .insert(name.to_owned(), _name_span);\n\n        // Parse template variable names\n        let vars = list\n            .t\n            .get(2)\n            .ok_or_else(|| {\n                anyhow_span!(\n                    list,\n                    \"deftemplate must have a list of template variables as the second parameter\"\n                )\n            })\n            .and_then(|v| {\n                v.list(None).ok_or_else(|| {\n                    anyhow_expr!(\n                        v,\n                        \"deftemplate must have a list of template variables the second parameter\"\n                    )\n                })\n            })\n            .and_then(|v| {\n                v.iter().try_fold(vec![], |mut vars, var| {\n                    let s = var.atom(None).map(|a| a.to_owned()).ok_or_else(|| {\n                        anyhow_expr!(var, \"deftemplate variables must be strings\")\n                    })?;\n                    vars.push(s);\n                    Ok(vars)\n                })\n            })?;\n        let vars_substitute_names: Vec<_> = vars.iter().map(|v| format!(\"${v}\")).collect();\n\n        // Validate content of template\n        let content: Vec<SExpr> = list.t.iter().skip(3).cloned().collect();\n        let mut var_usage_counts: HashMap<String, u32> = vars_substitute_names\n            .iter()\n            .map(|v| (v.clone(), 0))\n            .collect();\n        visit_validate_all_atoms_peek_next(&content, &mut |s, s_next| match s.t.as_str() {\n            \"deftemplate\" => err_span!(s, \"deftemplate is not allowed within deftemplate\"),\n            \"template-expand\" | \"t!\" => {\n                match s_next {\n                    Some(next) => {\n                        match next.atom(None) {\n                            Some(name_in_expand) => {\n                                match templates.iter().any(|existing_template| {\n                                    existing_template.name == name_in_expand\n                                }) {\n                                    true => Ok(()),\n                                    false => err_expr!(\n                                        next,\n                                        \"Unknown template name in template-expand. Note that order of declaration matters.\"\n                                    ),\n                                }\n                            }\n                            None => {\n                                // Next expr is list.\n                                // This is invalid syntax, but this will be caught later.\n                                // For simplicity in this function, leave it be.\n                                Ok(())\n                            }\n                        }\n                    }\n                    None => {\n                        // No next expr after expand.\n                        // This is invalid syntax, but this will be caught later.\n                        // For simplicity in this function, leave it be.\n                        Ok(())\n                    }\n                }\n            }\n            s => {\n                if let Some(count) = var_usage_counts.get_mut(s) {\n                    *count += 1;\n                }\n                Ok(())\n            }\n        })?;\n        for (var, count) in var_usage_counts.iter() {\n            if *count == 0 {\n                log::warn!(\"deftemplate variable {var} did not appear in its template {name}\");\n            }\n        }\n\n        templates.push(Template {\n            name: name.to_string(),\n            vars,\n            vars_substitute_names,\n            content,\n        });\n    }\n\n    // Find and do expansions\n    let mut toplevels: Vec<SExpr> = toplevel_exprs\n        .into_iter()\n        .map(|tl| {\n            SExpr::List(Spanned {\n                span: tl.span,\n                t: tl.t,\n            })\n        })\n        .collect();\n    expand(&mut toplevels, &templates, lsp_hints)?;\n\n    toplevels.into_iter().try_fold(vec![], |mut tls, tl| {\n        tls.push(match &tl {\n            SExpr::Atom(_) => bail_expr!(\n                &tl,\n                \"expansion created a string outside any list which is not allowed\"\n            ),\n            SExpr::List(l) => Spanned {\n                t: l.t.clone(),\n                span: l.span.clone(),\n            },\n        });\n        Ok(tls)\n    })\n}\n\nstruct Replacement {\n    exprs: Vec<SExpr>,\n    insert_index: usize,\n}\n\nfn expand(exprs: &mut Vec<SExpr>, templates: &[Template], _lsp_hints: &mut LspHints) -> Result<()> {\n    let mut replacements: Vec<Replacement> = vec![];\n    loop {\n        for (expr_index, expr) in exprs.iter_mut().enumerate() {\n            match expr {\n                SExpr::Atom(_) => continue,\n                SExpr::List(l) => {\n                    if !matches!(\n                        l.t.first().and_then(|expr| expr.atom(None)),\n                        Some(\"template-expand\") | Some(\"t!\")\n                    ) {\n                        expand(&mut l.t, templates, _lsp_hints)?;\n                        continue;\n                    }\n\n                    // found expand, now parse\n                    let template = l\n                        .t\n                        .get(1)\n                        .ok_or_else(|| {\n                            anyhow_span!(\n                                l,\n                                \"template-expand must have a template name as the first parameter\"\n                            )\n                        })\n                        .and_then(|name_expr| {\n                            let name = name_expr.atom(None).ok_or_else(|| {\n                                anyhow_expr!(name_expr, \"template name must be a string\")\n                            })?;\n                            #[cfg(feature = \"lsp\")]\n                            _lsp_hints\n                                .reference_locations\n                                .template\n                                .push(name, name_expr.span());\n                            templates.iter().find(|t| t.name == name).ok_or_else(|| {\n                                anyhow_expr!(\n                                    name_expr,\n                                    \"template name was not defined in any deftemplate\"\n                                )\n                            })\n                        })?;\n                    if l.t.len() - 2 != template.vars.len() {\n                        bail_span!(\n                            l,\n                            \"template-expand of {} needs {} parameters but instead found {}.\\nParameters: {}\",\n                            &template.name,\n                            template.vars.len(),\n                            l.t.len() - 2,\n                            template.vars.join(\" \")\n                        );\n                    }\n\n                    let var_substitutions = l.t.iter().skip(2);\n                    let mut expanded_template = template.content.clone();\n                    // Substitute variables.\n                    // perf_1 : could store substitution knowledge instead of iterating and searching\n                    // every time\n                    visit_mut_all_atoms(&mut expanded_template, &mut |expr: &mut SExpr| {\n                        *expr = match expr {\n                            // Below should not be reached because only atoms should be visited\n                            SExpr::List(_) => unreachable!(),\n                            SExpr::Atom(a) => {\n                                match template\n                                    .vars_substitute_names\n                                    .iter()\n                                    .enumerate()\n                                    .find(|(_, var)| *var == &a.t)\n                                {\n                                    None => expr.clone(),\n                                    Some((var_index, _)) => var_substitutions\n                                        .clone()\n                                        .nth(var_index)\n                                        .expect(\"validated matching var lens\")\n                                        .clone(),\n                                }\n                            }\n                        }\n                    });\n\n                    visit_mut_all_lists(&mut expanded_template, &mut |expr: &mut SExpr| {\n                        *expr = match expr {\n                            // Below should not be reached because only lists should be visited\n                            SExpr::Atom(_) => unreachable!(),\n                            SExpr::List(l) => parse_list_var(l, &HashMap::default()),\n                        };\n                        match expr {\n                            SExpr::Atom(_) => true,\n                            SExpr::List(_) => false,\n                        }\n                    });\n\n                    while evaluate_conditionals(&mut expanded_template)? {}\n\n                    replacements.push(Replacement {\n                        insert_index: expr_index,\n                        exprs: expanded_template,\n                    });\n                }\n            }\n        }\n\n        // Ensure replacements are sorted. They probably are, but may as well make sure.\n        replacements.sort_by_key(|r| r.insert_index);\n        // Must replace last-first to keep unreplaced insertion points stable.\n        // perf_2 : could construct vec in one pass.\n        for replacement in replacements.iter().rev() {\n            let (before, after) = exprs.split_at(replacement.insert_index);\n            let after = after.iter().skip(1); // first element is `(template-expand ...)`\n            let new_vec = before\n                .iter()\n                .cloned()\n                .chain(replacement.exprs.iter().cloned())\n                .chain(after.cloned())\n                .collect();\n            *exprs = new_vec;\n        }\n\n        if replacements.is_empty() {\n            break;\n        }\n        replacements.clear();\n    }\n\n    Ok(())\n}\n\nfn visit_validate_all_atoms(\n    exprs: &[SExpr],\n    visit: &mut dyn FnMut(&Spanned<String>) -> Result<()>,\n) -> Result<()> {\n    for expr in exprs {\n        match expr {\n            SExpr::Atom(a) => visit(a)?,\n            SExpr::List(l) => visit_validate_all_atoms(&l.t, visit)?,\n        }\n    }\n    Ok(())\n}\n\n#[allow(clippy::type_complexity)]\nfn visit_validate_all_atoms_peek_next(\n    exprs: &[SExpr],\n    visit: &mut dyn FnMut(&Spanned<String>, Option<&SExpr>) -> Result<()>,\n) -> Result<()> {\n    for (i, expr) in exprs.iter().enumerate() {\n        match expr {\n            SExpr::Atom(a) => visit(a, exprs.get(i + 1))?,\n            SExpr::List(l) => visit_validate_all_atoms_peek_next(&l.t, visit)?,\n        }\n    }\n    Ok(())\n}\n\nfn visit_mut_all_atoms(exprs: &mut [SExpr], visit: &mut dyn FnMut(&mut SExpr)) {\n    for expr in exprs {\n        match expr {\n            SExpr::Atom(_) => visit(expr),\n            SExpr::List(l) => visit_mut_all_atoms(&mut l.t, visit),\n        }\n    }\n}\n\nfn visit_mut_all_lists(exprs: &mut [SExpr], visit: &mut dyn FnMut(&mut SExpr) -> ChangeOccurred) {\n    for expr in exprs {\n        loop {\n            if let SExpr::Atom(_) = expr {\n                break;\n            }\n            // revisit until change did not happen to the list\n            if !visit(expr) {\n                if let SExpr::List(l) = expr {\n                    visit_mut_all_lists(&mut l.t, visit);\n                }\n                break;\n            }\n        }\n    }\n}\n\ntype ChangeOccurred = bool;\n\nfn evaluate_conditionals(exprs: &mut Vec<SExpr>) -> Result<ChangeOccurred> {\n    let mut replacements: Vec<Replacement> = vec![];\n    let mut expand_happened = false;\n    for (index, expr) in exprs.iter_mut().enumerate() {\n        if matches!(expr, SExpr::Atom(_)) {\n            continue;\n        }\n        // expr must be a list, visit it\n        if let Some(exprs) = if_equal_replacement(expr)? {\n            replacements.push(Replacement {\n                exprs,\n                insert_index: index,\n            })\n        } else if let Some(exprs) = if_not_equal_replacement(expr)? {\n            replacements.push(Replacement {\n                exprs,\n                insert_index: index,\n            })\n        } else if let Some(exprs) = if_in_list_replacement(expr)? {\n            replacements.push(Replacement {\n                exprs,\n                insert_index: index,\n            })\n        } else if let Some(exprs) = if_not_in_list_replacement(expr)? {\n            replacements.push(Replacement {\n                exprs,\n                insert_index: index,\n            })\n        } else {\n            expand_happened |= match expr {\n                SExpr::Atom(_) => unreachable!(),\n                SExpr::List(l) => evaluate_conditionals(&mut l.t)?,\n            };\n        }\n    }\n    // Ensure replacements are sorted. They probably are, but may as well make sure.\n    replacements.sort_by_key(|r| r.insert_index);\n    // Must replace last-first to keep unreplaced insertion points stable.\n    // perf_2 : could construct vec in one pass.\n    for replacement in replacements.iter().rev() {\n        let (before, after) = exprs.split_at(replacement.insert_index);\n        let after = after.iter().skip(1); // first element is `(if-equal ...)`\n        let new_vec = before\n            .iter()\n            .cloned()\n            .chain(replacement.exprs.iter().cloned())\n            .chain(after.cloned())\n            .collect();\n        *exprs = new_vec;\n    }\n    expand_happened |= !replacements.is_empty();\n    Ok(expand_happened)\n}\n\nfn if_equal_replacement(expr: &SExpr) -> Result<Option<Vec<SExpr>>> {\n    strings_compare_replacement(expr, \"if-equal\")\n}\n\nfn if_not_equal_replacement(expr: &SExpr) -> Result<Option<Vec<SExpr>>> {\n    strings_compare_replacement(expr, \"if-not-equal\")\n}\n\nfn if_in_list_replacement(expr: &SExpr) -> Result<Option<Vec<SExpr>>> {\n    string_list_compare_replacement(expr, \"if-in-list\")\n}\n\nfn if_not_in_list_replacement(expr: &SExpr) -> Result<Option<Vec<SExpr>>> {\n    string_list_compare_replacement(expr, \"if-not-in-list\")\n}\n\nfn strings_compare_replacement(expr: &SExpr, operation: &str) -> Result<Option<Vec<SExpr>>> {\n    match expr {\n        // Below should not be reached because only lists should be visited\n        SExpr::Atom(_) => unreachable!(),\n        SExpr::List(l) => Ok(match l.t.first() {\n            Some(SExpr::Atom(Spanned { t, .. })) if t.as_str() == operation => {\n                let first =\n                    l.t.get(1)\n                        .ok_or_else(|| {\n                            anyhow_expr!(\n                                &expr,\n                                \"{operation} expects a string comparand as the first parameter\"\n                            )\n                        })\n                        .and_then(|expr| {\n                            expr.atom(None).ok_or_else(|| {\n                                anyhow_expr!(&expr, \"comparands within {operation} must be strings\")\n                            })\n                        })?;\n                let second =\n                    l.t.get(2)\n                        .ok_or_else(|| {\n                            anyhow_expr!(\n                                &expr,\n                                \"{operation} expects a string comparand as the second parameter\"\n                            )\n                        })\n                        .and_then(|expr| {\n                            expr.atom(None).ok_or_else(|| {\n                                anyhow_expr!(&expr, \"comparands within {operation} must be strings\")\n                            })\n                        })?;\n                if match operation {\n                    \"if-equal\" => first == second,\n                    \"if-not-equal\" => first != second,\n                    _ => unreachable!(),\n                } {\n                    Some(l.t.iter().skip(3).cloned().collect())\n                } else {\n                    Some(vec![])\n                }\n            }\n            _ => None,\n        }),\n    }\n}\n\nfn string_list_compare_replacement(expr: &SExpr, operation: &str) -> Result<Option<Vec<SExpr>>> {\n    match expr {\n        // Below should not be reached because only lists should be visited\n        SExpr::Atom(_) => unreachable!(),\n        SExpr::List(l) => Ok(match l.t.first() {\n            Some(SExpr::Atom(Spanned { t, .. })) if t.as_str() == operation => {\n                let first =\n                    l.t.get(1)\n                        .ok_or_else(|| {\n                            anyhow_expr!(\n                                &expr,\n                                \"{operation} expects a string comparand as the first parameter\"\n                            )\n                        })\n                        .and_then(|expr| {\n                            expr.atom(None).ok_or_else(|| {\n                                anyhow_expr!(\n                                    &expr,\n                                    \"the first parameter of {operation} must be a string\"\n                                )\n                            })\n                        })?;\n                let second =\n                    l.t.get(2)\n                        .ok_or_else(|| {\n                            anyhow_expr!(\n                                &expr,\n                                \"{operation} expects a list comparand as the second parameter\"\n                            )\n                        })\n                        .and_then(|expr| {\n                            expr.list(None).ok_or_else(|| {\n                                anyhow_expr!(\n                                    &expr,\n                                    \"the second parameter of {operation} must be a list\"\n                                )\n                            })\n                        })?;\n                let mut in_list = false;\n                visit_validate_all_atoms(second, &mut |s| {\n                    in_list |= s.t == first;\n                    Ok(())\n                })?;\n                if match operation {\n                    \"if-in-list\" => in_list,\n                    \"if-not-in-list\" => !in_list,\n                    _ => unreachable!(),\n                } {\n                    Some(l.t.iter().skip(3).cloned().collect())\n                } else {\n                    Some(vec![])\n                }\n            }\n            _ => None,\n        }),\n    }\n}\n"
  },
  {
    "path": "parser/src/cfg/error.rs",
    "content": "// # Regarding #[allow(unused_assignments)]\n//\n// Seems the miette macros no longer trigger the compiler to find usage.\n\n#![allow(unused_assignments)]\n\nuse miette::{Diagnostic, NamedSource, SourceSpan};\nuse thiserror::Error;\n\nuse super::{sexpr::Span, *};\n\npub type MResult<T> = miette::Result<T>;\npub type Result<T> = std::result::Result<T, ParseError>;\n\n#[derive(Debug, Clone)]\npub struct ParseError {\n    pub msg: String,\n    pub span: Option<Span>,\n}\n\nimpl ParseError {\n    pub fn new(span: Span, err_msg: impl AsRef<str>) -> Self {\n        Self {\n            msg: err_msg.as_ref().to_string(),\n            span: Some(span),\n        }\n    }\n\n    pub fn new_without_span(err_msg: impl AsRef<str>) -> Self {\n        Self {\n            msg: err_msg.as_ref().to_string(),\n            span: None,\n        }\n    }\n\n    pub fn from_expr(expr: &sexpr::SExpr, err_msg: impl AsRef<str>) -> Self {\n        Self::new(expr.span(), err_msg)\n    }\n\n    pub fn from_spanned<T>(spanned: &Spanned<T>, err_msg: impl AsRef<str>) -> Self {\n        Self::new(spanned.span.clone(), err_msg)\n    }\n}\n\nimpl From<anyhow::Error> for ParseError {\n    fn from(value: anyhow::Error) -> Self {\n        Self::new_without_span(value.to_string())\n    }\n}\n\nimpl From<ParseError> for miette::Error {\n    fn from(val: ParseError) -> Self {\n        let diagnostic = CfgError {\n            err_span: val\n                .span\n                .as_ref()\n                .map(|s| SourceSpan::new(s.start().into(), (s.end() - s.start()).into())),\n            help_msg: help(val.msg),\n            file_name: val.span.as_ref().map(|s| s.file_name()),\n            file_content: val.span.as_ref().map(|s| s.file_content()),\n        };\n\n        let report: miette::Error = diagnostic.into();\n\n        if let Some(span) = val.span {\n            report.with_source_code(NamedSource::new(span.file_name(), span.file_content()))\n        } else {\n            report\n        }\n    }\n}\n\n#[derive(Error, Debug, Diagnostic, Clone)]\n#[error(\"Error in configuration\")]\n#[diagnostic()]\nstruct CfgError {\n    #[label(\"Error here\")]\n    err_span: Option<SourceSpan>,\n    #[help]\n    help_msg: String,\n    file_name: Option<String>,\n    file_content: Option<String>,\n}\n\npub(super) fn help(err_msg: impl AsRef<str>) -> String {\n    format!(\n        r\"{}\n\nFor more info, see the configuration guide:\nhttps://github.com/jtroo/kanata/blob/main/docs/config.adoc\",\n        err_msg.as_ref(),\n    )\n}\n"
  },
  {
    "path": "parser/src/cfg/fake_key.rs",
    "content": "use super::*;\n\nuse crate::{anyhow_expr, bail, bail_expr};\n\n#[allow(unused_variables)]\nfn set_virtual_key_reference_lsp_hint(vk_name_expr: &SExpr, s: &ParserState) {\n    #[cfg(feature = \"lsp\")]\n    {\n        let atom = match vk_name_expr {\n            SExpr::Atom(x) => x,\n            SExpr::List(_) => unreachable!(\"should be validated to be atom earlier\"),\n        };\n        s.lsp_hints\n            .borrow_mut()\n            .reference_locations\n            .virtual_key\n            .push_from_atom(atom);\n    }\n}\n\npub(crate) fn parse_fake_keys(exprs: &[&Vec<SExpr>], s: &mut ParserState) -> Result<()> {\n    for expr in exprs {\n        let mut subexprs = check_first_expr(expr.iter(), \"deffakekeys\")?;\n        // Read k-v pairs from the configuration\n        while let Some(key_name_expr) = subexprs.next() {\n            let key_name = key_name_expr\n                .atom(s.vars())\n                .ok_or_else(|| anyhow_expr!(key_name_expr, \"Fake key name must not be a list.\"))?\n                .to_owned();\n            let action = match subexprs.next() {\n                Some(v) => v,\n                None => bail_expr!(\n                    key_name_expr,\n                    \"Fake key name has no action - you should add an action.\"\n                ),\n            };\n            let action = parse_action(action, s)?;\n            let idx = s.virtual_keys.len();\n            log::trace!(\"inserting {key_name}->{idx}:{action:?}\");\n            if s.virtual_keys\n                .insert(key_name.clone(), (idx, action))\n                .is_some()\n            {\n                bail_expr!(key_name_expr, \"Duplicate fake key: {}\", key_name);\n            }\n            #[cfg(feature = \"lsp\")]\n            s.lsp_hints\n                .borrow_mut()\n                .definition_locations\n                .virtual_key\n                .insert(key_name, key_name_expr.span());\n        }\n    }\n    if s.virtual_keys.len() > KEYS_IN_ROW {\n        bail!(\n            \"Maximum number of fake keys is {KEYS_IN_ROW}, found {}\",\n            s.virtual_keys.len()\n        );\n    }\n    Ok(())\n}\n\npub(crate) fn parse_virtual_keys(exprs: &[&Vec<SExpr>], s: &mut ParserState) -> Result<()> {\n    s.pctx.is_within_defvirtualkeys = true;\n    for expr in exprs {\n        let mut subexprs = check_first_expr(expr.iter(), \"defvirtualkeys\")?;\n        // Read k-v pairs from the configuration\n        while let Some(key_name_expr) = subexprs.next() {\n            let key_name = key_name_expr\n                .atom(s.vars())\n                .ok_or_else(|| anyhow_expr!(key_name_expr, \"Virtual key name must not be a list.\"))?\n                .to_owned();\n            let action = match subexprs.next() {\n                Some(v) => v,\n                None => bail_expr!(\n                    key_name_expr,\n                    \"Virtual key name has no action - you must add an action.\"\n                ),\n            };\n            let action = parse_action(action, s)?;\n            let idx = s.virtual_keys.len();\n            log::trace!(\"inserting {key_name}->{idx}:{action:?}\");\n            if s.virtual_keys\n                .insert(key_name.clone(), (idx, action))\n                .is_some()\n            {\n                bail_expr!(key_name_expr, \"Duplicate virtual key: {}\", key_name);\n            };\n            #[cfg(feature = \"lsp\")]\n            s.lsp_hints\n                .borrow_mut()\n                .definition_locations\n                .virtual_key\n                .insert(key_name, key_name_expr.span());\n        }\n    }\n    s.pctx.is_within_defvirtualkeys = false;\n    if s.virtual_keys.len() > KEYS_IN_ROW {\n        bail!(\n            \"Maximum number of virtual keys is {KEYS_IN_ROW}, found {}\",\n            s.virtual_keys.len()\n        );\n    }\n    Ok(())\n}\n\npub(crate) fn parse_on_press_fake_key_op(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    let (coord, action) = parse_fake_key_op_coord_action(ac_params, s, ON_PRESS_FAKEKEY)?;\n    set_virtual_key_reference_lsp_hint(&ac_params[0], s);\n    Ok(s.a.sref(Action::Custom(\n        s.a.sref(s.a.sref_slice(CustomAction::FakeKey { coord, action })),\n    )))\n}\n\npub(crate) fn parse_on_release_fake_key_op(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    let (coord, action) = parse_fake_key_op_coord_action(ac_params, s, ON_RELEASE_FAKEKEY)?;\n    set_virtual_key_reference_lsp_hint(&ac_params[0], s);\n    Ok(s.a.sref(Action::Custom(s.a.sref(\n        s.a.sref_slice(CustomAction::FakeKeyOnRelease { coord, action }),\n    ))))\n}\n\npub(crate) fn parse_on_idle_fakekey(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"on-idle-fakekey expects three parameters:\\n<fake key name> <(tap|press|release)> <idle time>\\n\";\n    if ac_params.len() != 3 {\n        bail!(\"{ERR_MSG}\");\n    }\n    let y = match s\n        .virtual_keys\n        .get(ac_params[0].atom(s.vars()).ok_or_else(|| {\n            anyhow_expr!(\n                &ac_params[0],\n                \"{ERR_MSG}\\nInvalid first parameter: a fake key name cannot be a list\",\n            )\n        })?) {\n        Some((y, _)) => *y as u16, // cast should be safe; checked in `parse_fake_keys`\n        None => bail_expr!(\n            &ac_params[0],\n            \"{ERR_MSG}\\nInvalid first parameter: unknown fake key name {:?}\",\n            &ac_params[0]\n        ),\n    };\n    let action = ac_params[1]\n        .atom(s.vars())\n        .and_then(|a| match a {\n            \"tap\" => Some(FakeKeyAction::Tap),\n            \"press\" => Some(FakeKeyAction::Press),\n            \"release\" => Some(FakeKeyAction::Release),\n            _ => None,\n        })\n        .ok_or_else(|| {\n            anyhow_expr!(\n                &ac_params[1],\n                \"{ERR_MSG}\\nInvalid second parameter, it must be one of: tap, press, release\",\n            )\n        })?;\n    let idle_duration = parse_u16(&ac_params[2], s, \"idle time\").map_err(|mut e| {\n        e.msg = format!(\"{ERR_MSG}\\nInvalid third parameter: {}\", e.msg);\n        e\n    })?;\n    let (x, y) = get_fake_key_coords(y);\n    let coord = Coord { x, y };\n    set_virtual_key_reference_lsp_hint(&ac_params[0], s);\n    Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice(\n        CustomAction::FakeKeyOnIdle(FakeKeyOnIdle {\n            coord,\n            action,\n            idle_duration,\n        }),\n    )))))\n}\n\nfn parse_fake_key_op_coord_action(\n    ac_params: &[SExpr],\n    s: &ParserState,\n    ac_name: &str,\n) -> Result<(Coord, FakeKeyAction)> {\n    const ERR_MSG: &str = \"expects two parameters: <fake key name> <(tap|press|release|toggle)>\";\n    if ac_params.len() != 2 {\n        bail!(\"{ac_name} {ERR_MSG}\");\n    }\n    let y = match s\n        .virtual_keys\n        .get(ac_params[0].atom(s.vars()).ok_or_else(|| {\n            anyhow_expr!(\n                &ac_params[0],\n                \"{ac_name} {ERR_MSG}\\nInvalid first parameter: a fake key name cannot be a list\",\n            )\n        })?) {\n        Some((y, _)) => *y as u16, // cast should be safe; checked in `parse_fake_keys`\n        None => bail_expr!(\n            &ac_params[0],\n            \"{ac_name} {ERR_MSG}\\nInvalid first parameter: unknown fake key name {:?}\",\n            &ac_params[0]\n        ),\n    };\n    let action = ac_params[1]\n        .atom(s.vars())\n        .and_then(|a| match a {\n            \"tap\" => Some(FakeKeyAction::Tap),\n            \"press\" => Some(FakeKeyAction::Press),\n            \"release\" => Some(FakeKeyAction::Release),\n            \"toggle\" => Some(FakeKeyAction::Toggle),\n            _ => None,\n        })\n        .ok_or_else(|| {\n            anyhow_expr!(\n                &ac_params[1],\n                \"{ERR_MSG}\\nInvalid second parameter, it must be one of: tap, press, release\",\n            )\n        })?;\n    let (x, y) = get_fake_key_coords(y);\n    set_virtual_key_reference_lsp_hint(&ac_params[0], s);\n    Ok((Coord { x, y }, action))\n}\n\npub const NORMAL_KEY_ROW: u8 = 0;\npub const FAKE_KEY_ROW: u8 = 1;\n\npub(crate) fn get_fake_key_coords<T: Into<usize>>(y: T) -> (u8, u16) {\n    let y: usize = y.into();\n    (FAKE_KEY_ROW, y as u16)\n}\n\npub(crate) fn parse_fake_key_delay(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    parse_delay(ac_params, false, s)\n}\n\npub(crate) fn parse_on_release_fake_key_delay(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    parse_delay(ac_params, true, s)\n}\n\nfn parse_delay(\n    ac_params: &[SExpr],\n    is_release: bool,\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"delay expects a single number (ms, 0-65535)\";\n    let delay = ac_params[0]\n        .atom(s.vars())\n        .map(str::parse::<u16>)\n        .ok_or_else(|| anyhow!(\"{ERR_MSG}\"))?\n        .map_err(|e| anyhow!(\"{ERR_MSG}: {e}\"))?;\n    Ok(s.a\n        .sref(Action::Custom(s.a.sref(s.a.sref_slice(match is_release {\n            false => CustomAction::Delay(delay),\n            true => CustomAction::DelayOnRelease(delay),\n        })))))\n}\n\npub(crate) fn parse_vkey_coord(param: &SExpr, s: &ParserState) -> Result<Coord> {\n    let name = param\n        .atom(s.vars())\n        .ok_or_else(|| anyhow_expr!(param, \"key-name must not be a list\",))?;\n    let y = match s.virtual_keys.get(name) {\n        Some((y, _)) => *y as u16, // cast should be safe; checked in `parse_fake_keys`\n        None => bail_expr!(param, \"unknown virtual key name: {name}\",),\n    };\n    let coord = Coord { x: FAKE_KEY_ROW, y };\n    set_virtual_key_reference_lsp_hint(param, s);\n    Ok(coord)\n}\n\nfn parse_vkey_action(param: &SExpr, s: &ParserState) -> Result<FakeKeyAction> {\n    let action = param\n        .atom(s.vars())\n        .and_then(|ac| {\n            Some(match ac {\n                \"press-vkey\" | \"press-virtualkey\" => FakeKeyAction::Press,\n                \"release-vkey\" | \"release-virtualkey\" => FakeKeyAction::Release,\n                \"tap-vkey\" | \"tap-virtualkey\" => FakeKeyAction::Tap,\n                \"toggle-vkey\" | \"toggle-virtualkey\" => FakeKeyAction::Toggle,\n                _ => return None,\n            })\n        })\n        .ok_or_else(|| {\n            anyhow_expr!(\n                param,\n                \"action must be one of: (press|release|tap|toggle)-virtualkey\\n\\\n                 example: toggle-virtualkey\"\n            )\n        })?;\n    Ok(action)\n}\n\npub(crate) fn parse_on_press(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"on-press expects two parameters: <action> <key-name>\";\n    if ac_params.len() != 2 {\n        bail!(\"{ERR_MSG}\");\n    }\n    let action = parse_vkey_action(&ac_params[0], s)?;\n    let coord = parse_vkey_coord(&ac_params[1], s)?;\n\n    Ok(s.a.sref(Action::Custom(\n        s.a.sref(s.a.sref_slice(CustomAction::FakeKey { coord, action })),\n    )))\n}\n\npub(crate) fn parse_on_release(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"on-release expects two parameters: <action> <key-name>\";\n    if ac_params.len() != 2 {\n        bail!(\"{ERR_MSG}\");\n    }\n    let action = parse_vkey_action(&ac_params[0], s)?;\n    let coord = parse_vkey_coord(&ac_params[1], s)?;\n\n    Ok(s.a.sref(Action::Custom(s.a.sref(\n        s.a.sref_slice(CustomAction::FakeKeyOnRelease { coord, action }),\n    ))))\n}\n\npub(crate) fn parse_on_idle(ac_params: &[SExpr], s: &ParserState) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"on-idle expects three parameters: <timeout> <action> <key-name>\";\n    if ac_params.len() != 3 {\n        bail!(\"{ERR_MSG}\");\n    }\n    let idle_duration = parse_non_zero_u16(&ac_params[0], s, \"on-idle-timeout\")?;\n    let action = parse_vkey_action(&ac_params[1], s)?;\n    let coord = parse_vkey_coord(&ac_params[2], s)?;\n\n    Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice(\n        CustomAction::FakeKeyOnIdle(FakeKeyOnIdle {\n            coord,\n            action,\n            idle_duration,\n        }),\n    )))))\n}\n\npub(crate) fn parse_on_physical_idle(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str =\n        \"on-physical-idle expects three parameters: <timeout> <action> <key-name>\";\n    if ac_params.len() != 3 {\n        bail!(\"{ERR_MSG}\");\n    }\n    let idle_duration = parse_non_zero_u16(&ac_params[0], s, \"on-idle-timeout\")?;\n    let action = parse_vkey_action(&ac_params[1], s)?;\n    let coord = parse_vkey_coord(&ac_params[2], s)?;\n\n    Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice(\n        CustomAction::FakeKeyOnPhysicalIdle(FakeKeyOnIdle {\n            coord,\n            action,\n            idle_duration,\n        }),\n    )))))\n}\n\npub(crate) fn parse_hold_for_duration(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"hold-for-duration expects two parameters: <hold-duration> <key-name>\";\n    if ac_params.len() != 2 {\n        bail!(\"{ERR_MSG}\");\n    }\n    let hold_duration = parse_non_zero_u16(&ac_params[0], s, \"hold-duration\")?;\n    let coord = parse_vkey_coord(&ac_params[1], s)?;\n\n    Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice(\n        CustomAction::FakeKeyHoldForDuration(FakeKeyHoldForDuration {\n            coord,\n            hold_duration,\n        }),\n    )))))\n}\n"
  },
  {
    "path": "parser/src/cfg/fork.rs",
    "content": "use super::*;\n\nuse crate::bail;\n\npub(crate) fn parse_fork(ac_params: &[SExpr], s: &ParserState) -> Result<&'static KanataAction> {\n    const ERR_STR: &str =\n        \"fork expects 3 params: <left-action> <right-action> <right-trigger-keys>\";\n    if ac_params.len() != 3 {\n        bail!(\"{ERR_STR}\\nFound {} params instead of 3\", ac_params.len());\n    }\n    let left = *parse_action(&ac_params[0], s)?;\n    let right = *parse_action(&ac_params[1], s)?;\n    let right_triggers = s.a.sref_vec(\n        parse_key_list(&ac_params[2], s, \"right-trigger-keys\")?\n            .into_iter()\n            .map(KeyCode::from)\n            .collect::<Vec<_>>(),\n    );\n    Ok(s.a.sref(Action::Fork(s.a.sref(ForkConfig {\n        left,\n        right,\n        right_triggers,\n    }))))\n}\n"
  },
  {
    "path": "parser/src/cfg/is_a_button.rs",
    "content": "pub(crate) fn is_a_button(osc: u16) -> bool {\n    if cfg!(target_os = \"windows\") {\n        matches!(osc, 1..=6 | 256..)\n    } else {\n        osc >= 256\n    }\n}\n\n#[test]\nfn mouse_inputs_most_care_about_are_considered_buttons() {\n    use crate::keys::{OsCode, OsCode::*};\n    const MOUSE_INPUTS: &[OsCode] = &[\n        MouseWheelUp,\n        MouseWheelDown,\n        MouseWheelLeft,\n        MouseWheelRight,\n        BTN_LEFT,\n        BTN_RIGHT,\n        BTN_MIDDLE,\n        BTN_SIDE,\n        BTN_EXTRA,\n        BTN_FORWARD,\n        BTN_BACK,\n    ];\n    for input in MOUSE_INPUTS.iter().copied() {\n        println!(\"{input}\");\n        assert!(is_a_button(input.into()));\n    }\n}\n\n#[test]\nfn standard_keys_are_not_considered_buttons() {\n    use crate::keys::{OsCode, OsCode::*};\n    const KEY_INPUTS: &[OsCode] = &[\n        KEY_0,\n        KEY_1,\n        KEY_2,\n        KEY_3,\n        KEY_4,\n        KEY_5,\n        KEY_6,\n        KEY_7,\n        KEY_8,\n        KEY_9,\n        KEY_A,\n        KEY_B,\n        KEY_C,\n        KEY_D,\n        KEY_E,\n        KEY_F,\n        KEY_G,\n        KEY_H,\n        KEY_I,\n        KEY_J,\n        KEY_K,\n        KEY_L,\n        KEY_M,\n        KEY_N,\n        KEY_O,\n        KEY_P,\n        KEY_Q,\n        KEY_R,\n        KEY_S,\n        KEY_T,\n        KEY_U,\n        KEY_V,\n        KEY_W,\n        KEY_X,\n        KEY_Y,\n        KEY_Z,\n        KEY_SEMICOLON,\n        KEY_SLASH,\n        KEY_GRAVE,\n        KEY_LEFTBRACE,\n        KEY_BACKSLASH,\n        KEY_RIGHTBRACE,\n        KEY_APOSTROPHE,\n        KEY_MINUS,\n        KEY_DOT,\n        KEY_EQUAL,\n        KEY_BACKSPACE,\n        KEY_ESC,\n        KEY_TAB,\n        KEY_ENTER,\n        KEY_LEFTCTRL,\n        KEY_LEFTSHIFT,\n        KEY_COMMA,\n        KEY_RIGHTSHIFT,\n        KEY_KPASTERISK,\n        KEY_LEFTALT,\n        KEY_SPACE,\n        KEY_CAPSLOCK,\n        KEY_F1,\n        KEY_F2,\n        KEY_F3,\n        KEY_F4,\n        KEY_F5,\n        KEY_F6,\n        KEY_F7,\n        KEY_F8,\n        KEY_F9,\n        KEY_F10,\n        KEY_F11,\n        KEY_F12,\n        KEY_NUMLOCK,\n        KEY_SCROLLLOCK,\n        KEY_KP0,\n        KEY_KP1,\n        KEY_KP2,\n        KEY_KP3,\n        KEY_KP4,\n        KEY_KP5,\n        KEY_KP6,\n        KEY_KP7,\n        KEY_KP8,\n        KEY_KP9,\n        KEY_KPMINUS,\n        KEY_KPPLUS,\n        KEY_KPDOT,\n        KEY_KPENTER,\n        KEY_RIGHTCTRL,\n        KEY_KPSLASH,\n        KEY_RIGHTALT,\n        KEY_HOME,\n        KEY_UP,\n        KEY_PAGEUP,\n        KEY_LEFT,\n        KEY_RIGHT,\n        KEY_END,\n        KEY_DOWN,\n        KEY_PAGEDOWN,\n        KEY_INSERT,\n        KEY_DELETE,\n        KEY_MUTE,\n        KEY_VOLUMEDOWN,\n        KEY_VOLUMEUP,\n        KEY_EJECTCD,\n        KEY_PAUSE,\n        KEY_LEFTMETA,\n        KEY_RIGHTMETA,\n        KEY_COMPOSE,\n        KEY_BACK,\n        KEY_FORWARD,\n        KEY_NEXTSONG,\n        KEY_PLAYPAUSE,\n        KEY_PREVIOUSSONG,\n        KEY_STOP,\n        KEY_HOMEPAGE,\n        KEY_MAIL,\n        KEY_MEDIA,\n        KEY_REFRESH,\n        KEY_F13,\n        KEY_F14,\n        KEY_F15,\n        KEY_F16,\n        KEY_F17,\n        KEY_F18,\n        KEY_F19,\n        KEY_F20,\n        KEY_F21,\n        KEY_F22,\n        KEY_F23,\n        KEY_F24,\n        KEY_HANGEUL,\n        KEY_HANJA,\n        KEY_252,\n        KEY_102ND,\n        KEY_PLAY,\n        KEY_PRINT,\n        KEY_SEARCH,\n        KEY_RO,\n        KEY_HENKAN,\n        KEY_MUHENKAN,\n    ];\n    for input in KEY_INPUTS.iter().copied() {\n        assert!(!is_a_button(input.into()));\n    }\n}\n"
  },
  {
    "path": "parser/src/cfg/key_outputs.rs",
    "content": "use super::*;\n\n// Note: this uses a Vec inside the HashMap instead of a HashSet because ordering matters, e.g. for\n// chords like `S-b`, we want to ensure that `b` is checked first because key repeat for `b` is\n// useful while it is not useful for shift. The outputs should be iterated over in reverse order.\npub type KeyOutputs = Vec<HashMap<OsCode, Vec<OsCode>>>;\n\n/// Creates a `KeyOutputs` from `layers::LAYERS`.\npub(crate) fn create_key_outputs(\n    layers: &KLayers,\n    overrides: &Overrides,\n    chords_v2: &Option<ChordsV2<'static, KanataCustom>>,\n) -> KeyOutputs {\n    let mut outs = KeyOutputs::new();\n    for (layer_idx, layer) in layers.iter().enumerate() {\n        let mut layer_outputs = HashMap::default();\n        for (i, action) in layer[0].iter().enumerate() {\n            let osc_slot = match i.try_into() {\n                Ok(i) => i,\n                Err(_) => continue,\n            };\n            add_key_output_from_action_to_key_pos(osc_slot, action, &mut layer_outputs, overrides);\n            add_chordsv2_output_for_key_pos(\n                osc_slot,\n                layer_idx,\n                chords_v2,\n                &mut layer_outputs,\n                overrides,\n            );\n        }\n        outs.push(layer_outputs);\n    }\n    for layer_outs in outs.iter_mut() {\n        for keys_out in layer_outs.values_mut() {\n            keys_out.shrink_to_fit();\n        }\n        layer_outs.shrink_to_fit();\n    }\n    outs.shrink_to_fit();\n    outs\n}\n\npub(crate) fn add_chordsv2_output_for_key_pos(\n    osc_slot: OsCode,\n    layer_idx: usize,\n    chords_v2: &Option<ChordsV2<'static, KanataCustom>>,\n    outputs: &mut HashMap<OsCode, Vec<OsCode>>,\n    overrides: &Overrides,\n) {\n    assert!(layer_idx <= usize::from(u16::MAX));\n    let Some(chords_v2) = chords_v2.as_ref() else {\n        return;\n    };\n    let Some(chords_for_key) = chords_v2.chords().mapping.get(&u16::from(osc_slot)) else {\n        return;\n    };\n    for chord in chords_for_key.chords.iter() {\n        if !chord.disabled_layers.contains(&(layer_idx as u16)) {\n            add_key_output_from_action_to_key_pos(osc_slot, chord.action, outputs, overrides);\n        }\n    }\n}\n\npub(crate) fn add_key_output_from_action_to_key_pos(\n    osc_slot: OsCode,\n    action: &KanataAction,\n    outputs: &mut HashMap<OsCode, Vec<OsCode>>,\n    overrides: &Overrides,\n) {\n    match action {\n        Action::KeyCode(kc) => {\n            add_kc_output(osc_slot, kc.into(), outputs, overrides);\n        }\n        Action::HoldTap(HoldTapAction {\n            tap,\n            hold,\n            timeout_action,\n            ..\n        }) => {\n            add_key_output_from_action_to_key_pos(osc_slot, tap, outputs, overrides);\n            add_key_output_from_action_to_key_pos(osc_slot, hold, outputs, overrides);\n            add_key_output_from_action_to_key_pos(osc_slot, timeout_action, outputs, overrides);\n        }\n        Action::OneShot(OneShot { action: ac, .. }) => {\n            add_key_output_from_action_to_key_pos(osc_slot, ac, outputs, overrides);\n        }\n        Action::MultipleKeyCodes(kcs) => {\n            for kc in kcs.iter() {\n                add_kc_output(osc_slot, kc.into(), outputs, overrides);\n            }\n        }\n        Action::MultipleActions(actions) => {\n            for ac in actions.iter() {\n                add_key_output_from_action_to_key_pos(osc_slot, ac, outputs, overrides);\n            }\n        }\n        Action::TapDance(TapDance { actions, .. }) => {\n            for ac in actions.iter() {\n                add_key_output_from_action_to_key_pos(osc_slot, ac, outputs, overrides);\n            }\n        }\n        Action::Fork(ForkConfig { left, right, .. }) => {\n            add_key_output_from_action_to_key_pos(osc_slot, left, outputs, overrides);\n            add_key_output_from_action_to_key_pos(osc_slot, right, outputs, overrides);\n        }\n        Action::Chords(ChordsGroup { chords, .. }) => {\n            for (_, ac) in chords.iter() {\n                add_key_output_from_action_to_key_pos(osc_slot, ac, outputs, overrides);\n            }\n        }\n        Action::Switch(Switch { cases }) => {\n            for case in cases.iter() {\n                add_key_output_from_action_to_key_pos(osc_slot, case.1, outputs, overrides);\n            }\n        }\n        Action::Custom(cacs) => {\n            for ac in cacs.iter() {\n                match ac {\n                    CustomAction::Unmodded { keys, .. } | CustomAction::Unshifted { keys } => {\n                        for k in keys.iter() {\n                            add_kc_output(osc_slot, k.into(), outputs, overrides);\n                        }\n                    }\n                    _ => {}\n                }\n            }\n        }\n        Action::Src => {\n            add_kc_output(osc_slot, osc_slot, outputs, overrides);\n        }\n        Action::NoOp\n        | Action::Trans\n        | Action::Repeat\n        | Action::Layer(_)\n        | Action::DefaultLayer(_)\n        | Action::Sequence { .. }\n        | Action::RepeatableSequence { .. }\n        | Action::CancelSequences\n        | Action::OneShotIgnoreEventsTicks(_)\n        | Action::ReleaseState(_) => {}\n    };\n}\n\npub(crate) fn add_kc_output(\n    osc_slot: OsCode,\n    osc: OsCode,\n    outs: &mut HashMap<OsCode, Vec<OsCode>>,\n    overrides: &Overrides,\n) {\n    let outputs = match outs.entry(osc_slot) {\n        Entry::Occupied(o) => o.into_mut(),\n        Entry::Vacant(v) => v.insert(vec![]),\n    };\n    if !outputs.contains(&osc) {\n        outputs.push(osc);\n    }\n    for ov_osc in overrides\n        .output_non_mods_for_input_non_mod(osc)\n        .iter()\n        .copied()\n    {\n        if !outputs.contains(&ov_osc) {\n            outputs.push(ov_osc);\n        }\n    }\n}\n"
  },
  {
    "path": "parser/src/cfg/key_override.rs",
    "content": "//! Contains code to handle global override keys.\n\nuse anyhow::{Result, anyhow, bail};\nuse rustc_hash::FxHashMap as HashMap;\n\nuse crate::keys::*;\n\nuse kanata_keyberon::key_code::KeyCode;\nuse kanata_keyberon::layout::NORMAL_KEY_FLAG_CLEAR_ON_NEXT_ACTION;\nuse kanata_keyberon::layout::NORMAL_KEY_FLAG_CLEAR_ON_NEXT_RELEASE;\nuse kanata_keyberon::layout::State;\n\n/// Scratch space containing allocations used to process override information. Exists as an\n/// optimization to reuse allocations between iterations.\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct OverrideStates {\n    mods_pressed: u8,\n    oscs_to_remove: Vec<OsCode>,\n    oscs_to_add: Vec<OsCode>,\n}\n\nimpl Default for OverrideStates {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl OverrideStates {\n    pub fn new() -> Self {\n        Self {\n            mods_pressed: 0,\n            oscs_to_add: Vec::new(),\n            oscs_to_remove: Vec::new(),\n        }\n    }\n\n    fn cleanup(&mut self) {\n        self.oscs_to_add.clear();\n        self.oscs_to_remove.clear();\n        self.mods_pressed = 0;\n    }\n\n    fn update(&mut self, osc: OsCode, overrides: &Overrides, active_layer: u16) {\n        if let Some(mod_mask) = mask_for_key(osc) {\n            self.mods_pressed |= mod_mask;\n        } else {\n            overrides.update_keys(\n                osc,\n                self.mods_pressed,\n                &mut self.oscs_to_add,\n                &mut self.oscs_to_remove,\n                active_layer,\n            );\n        }\n    }\n\n    fn is_key_overridden(&self, osc: OsCode) -> bool {\n        self.oscs_to_remove.contains(&osc)\n    }\n\n    fn add_overrides(&self, oscs: &mut Vec<KeyCode>) {\n        oscs.extend(self.oscs_to_add.iter().copied().map(KeyCode::from));\n    }\n\n    pub fn removed_oscs(&self) -> impl Iterator<Item = OsCode> + '_ {\n        self.oscs_to_remove.iter().copied()\n    }\n}\n\n/// A collection of global key overrides.\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct Overrides {\n    overrides_by_osc: HashMap<OsCode, Vec<Override>>,\n}\n\nimpl Overrides {\n    pub fn new(overrides: &[Override]) -> Self {\n        let mut overrides_by_osc: HashMap<OsCode, Vec<Override>> = HashMap::default();\n        for o in overrides.iter() {\n            overrides_by_osc\n                .entry(o.in_non_mod_osc)\n                .and_modify(|ovd| ovd.push(o.clone()))\n                .or_insert_with(|| vec![o.clone()]);\n        }\n        for ovds in overrides_by_osc.values_mut() {\n            ovds.shrink_to_fit();\n        }\n        overrides_by_osc.shrink_to_fit();\n        Self { overrides_by_osc }\n    }\n\n    pub fn override_keys(\n        &self,\n        kcs: &mut Vec<KeyCode>,\n        states: &mut OverrideStates,\n        active_layer: u16,\n    ) {\n        if self.is_empty() {\n            return;\n        }\n        states.cleanup();\n        for kc in kcs.iter().copied() {\n            states.update(kc.into(), self, active_layer);\n        }\n        kcs.retain(|kc| !states.is_key_overridden((*kc).into()));\n        states.add_overrides(kcs);\n    }\n\n    pub fn output_non_mods_for_input_non_mod(&self, in_osc: OsCode) -> Vec<OsCode> {\n        let mut ret = Vec::new();\n        if let Some(ovds) = self.overrides_by_osc.get(&in_osc) {\n            for out_osc in ovds.iter().map(|ovd| ovd.out_non_mod_osc) {\n                ret.push(out_osc);\n            }\n        }\n        ret\n    }\n\n    fn is_empty(&self) -> bool {\n        self.overrides_by_osc.is_empty()\n    }\n\n    fn update_keys(\n        &self,\n        active_osc: OsCode,\n        active_mod_mask: u8,\n        oscs_to_add: &mut Vec<OsCode>,\n        oscs_to_remove: &mut Vec<OsCode>,\n        active_layer: u16,\n    ) {\n        let Some(ovds) = self.overrides_by_osc.get(&active_osc) else {\n            return;\n        };\n        if let Some(ovd) = ovds.iter().rfind(|ovd| {\n            if ovd\n                .excluded_layers\n                .as_ref()\n                .map(|excluded_layers| excluded_layers.iter().copied().any(|l| l == active_layer))\n                .unwrap_or(false)\n            {\n                return false;\n            }\n            let mask = ovd.get_mod_mask();\n            let exclude_mask = ovd.get_excluded_mod_mask();\n            mask & active_mod_mask == mask && exclude_mask & active_mod_mask == 0\n        }) {\n            log::debug!(\"using override {ovd:?}\");\n            ovd.add_override_keys(oscs_to_add);\n            ovd.add_removed_keys(oscs_to_remove);\n        }\n    }\n}\n\n/// A global key override.\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct Override {\n    in_non_mod_osc: OsCode,\n    out_non_mod_osc: OsCode,\n    in_mod_oscs: Box<[OsCode]>,\n    out_mod_oscs: Box<[OsCode]>,\n    excluded_mod_oscs: Option<Box<[OsCode]>>,\n    excluded_layers: Option<Box<[u16]>>,\n}\n\nimpl Override {\n    pub fn try_new(in_oscs: &[OsCode], out_oscs: &[OsCode]) -> Result<Self> {\n        let mut in_nmoscs = in_oscs\n            .iter()\n            .copied()\n            .filter(|osc| mask_for_key(*osc).is_none());\n        let in_non_mod_osc = in_nmoscs.next().ok_or_else(|| {\n            anyhow!(\"override must contain exactly one input non-modifier key; found none\")\n        })?;\n        if in_nmoscs.next().is_some() {\n            bail!(\"override must contain exactly one input non-modifier key; found multiple\");\n        }\n        let mut out_nmoscs = out_oscs\n            .iter()\n            .copied()\n            .filter(|osc| mask_for_key(*osc).is_none());\n        let out_non_mod_osc = out_nmoscs.next().ok_or_else(|| {\n            anyhow!(\"override must contain exactly one output non-modifier key; found none\")\n        })?;\n        if out_nmoscs.next().is_some() {\n            bail!(\"override must contain exactly one output non-modifier key; found multiple\");\n        }\n        let in_mod_oscs = in_oscs\n            .iter()\n            .copied()\n            .filter(|osc| osc.is_modifier())\n            .collect::<Vec<_>>();\n        let out_mod_oscs = out_oscs\n            .iter()\n            .copied()\n            .filter(|osc| osc.is_modifier())\n            .collect::<Vec<_>>();\n        Ok(Self {\n            in_non_mod_osc,\n            out_non_mod_osc,\n            in_mod_oscs: in_mod_oscs.into_boxed_slice(),\n            out_mod_oscs: out_mod_oscs.into_boxed_slice(),\n            excluded_mod_oscs: None,\n            excluded_layers: None,\n        })\n    }\n\n    pub fn try_new_v2(\n        in_oscs: &[OsCode],\n        out_oscs: &[OsCode],\n        excluded_mod_oscs: Box<[OsCode]>,\n        excluded_layers: Box<[u16]>,\n    ) -> Result<Self> {\n        let mut override_cfg = Self::try_new(in_oscs, out_oscs)?;\n        override_cfg.excluded_mod_oscs = Some(excluded_mod_oscs);\n        override_cfg.excluded_layers = Some(excluded_layers);\n        Ok(override_cfg)\n    }\n\n    fn get_mod_mask(&self) -> u8 {\n        let mut mask = 0;\n        for osc in self.in_mod_oscs.iter().copied() {\n            mask |= mask_for_key(osc).expect(\"mod only\");\n        }\n        mask\n    }\n\n    fn get_excluded_mod_mask(&self) -> u8 {\n        let mut mask = 0;\n        if let Some(mods) = self.excluded_mod_oscs.as_ref() {\n            for osc in mods.iter().copied() {\n                mask |= mask_for_key(osc).expect(\"mod only\");\n            }\n        }\n        mask\n    }\n\n    fn add_override_keys(&self, oscs_to_add: &mut Vec<OsCode>) {\n        for osc in self.out_mod_oscs.iter().copied() {\n            if !oscs_to_add.contains(&osc) {\n                oscs_to_add.push(osc);\n            }\n        }\n        if !oscs_to_add.contains(&self.out_non_mod_osc) {\n            oscs_to_add.push(self.out_non_mod_osc);\n        }\n    }\n\n    fn add_removed_keys(&self, oscs_to_remove: &mut Vec<OsCode>) {\n        for osc in self.in_mod_oscs.iter().copied() {\n            if !oscs_to_remove.contains(&osc) {\n                oscs_to_remove.push(osc);\n            }\n        }\n        if !oscs_to_remove.contains(&self.in_non_mod_osc) {\n            oscs_to_remove.push(self.in_non_mod_osc);\n        }\n    }\n}\n\nfn mask_for_key(osc: OsCode) -> Option<u8> {\n    match osc {\n        OsCode::KEY_LEFTCTRL => Some(1 << 0),\n        OsCode::KEY_LEFTSHIFT => Some(1 << 1),\n        OsCode::KEY_LEFTALT => Some(1 << 2),\n        OsCode::KEY_LEFTMETA => Some(1 << 3),\n        OsCode::KEY_RIGHTCTRL => Some(1 << 4),\n        OsCode::KEY_RIGHTSHIFT => Some(1 << 5),\n        OsCode::KEY_RIGHTALT => Some(1 << 6),\n        OsCode::KEY_RIGHTMETA => Some(1 << 7),\n        _ => None,\n    }\n}\n\n/// For every `OsCode` marked for removal by overrides that is not a modifier,\n/// mark its state in the keyberon layout\n/// with `NORMAL_KEY_FLAG_CLEAR_ON_NEXT_ACTION` and `NORMAL_KEY_FLAG_CLEAR_ON_NEXT_RELEASE`\n/// so that it gets eagerly cleared, avoiding weird character outputs.\npub fn mark_overridden_nonmodkeys_for_eager_erasure<T>(\n    override_states: &OverrideStates,\n    kb_states: &mut [State<T>],\n) {\n    for osc_to_mark in override_states\n        .removed_oscs()\n        .filter(|osc| !osc.is_modifier())\n    {\n        let kc: KeyCode = osc_to_mark.into();\n        for kbstate in kb_states.iter_mut() {\n            if let State::NormalKey {\n                mut flags,\n                keycode,\n                coord,\n            } = kbstate\n            {\n                if kc == *keycode {\n                    flags.0 |= NORMAL_KEY_FLAG_CLEAR_ON_NEXT_ACTION\n                        | NORMAL_KEY_FLAG_CLEAR_ON_NEXT_RELEASE;\n                    *kbstate = State::NormalKey {\n                        flags,\n                        keycode: *keycode,\n                        coord: *coord,\n                    };\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "parser/src/cfg/layer_opts.rs",
    "content": "use crate::cfg::*;\nuse crate::*;\n\npub(crate) const DEFLAYER_ICON: [&str; 3] = [\"icon\", \"🖻\", \"🖼\"];\npub(crate) type LayerIcons = HashMap<String, Option<String>>;\n\npub fn parse_layer_opts(list: &[SExpr]) -> Result<HashMap<String, String>> {\n    let mut layer_opts: HashMap<String, String> = HashMap::default();\n    let mut opts = list.chunks_exact(2);\n    for kv in opts.by_ref() {\n        let key_expr = &kv[0];\n        let val_expr = &kv[1];\n        // Read k-v pairs from the configuration\n        // todo: add hashmap for future options, currently only parse icons\n        let opt_key = key_expr.atom(None)\n            .ok_or_else(|| anyhow_expr!(key_expr, \"No lists are allowed in {DEFLAYER} options\"))\n            .and_then(|opt_key| {\n                if DEFLAYER_ICON.contains(&opt_key) {\n                    if layer_opts.contains_key(DEFLAYER_ICON[0]) {\n                        // separate dupe check since multi-keys are stored\n                        // with one \"canonical\" repr, so '🖻' → 'icon'\n                        // and this info will be lost after the loop\n                        bail_expr!(\n                            key_expr,\n                            \"Duplicate option found in {DEFLAYER}: {opt_key}, one of {DEFLAYER_ICON:?} already exists\"\n                        );\n                    }\n                    Ok(DEFLAYER_ICON[0])\n                } else {\n                    bail_expr!(key_expr, \"Invalid option in {DEFLAYER}: {opt_key}, expected one of {DEFLAYER_ICON:?}\")\n                }\n            })?;\n        if layer_opts.contains_key(opt_key) {\n            bail_expr!(key_expr, \"Duplicate option found in {DEFLAYER}: {opt_key}\");\n        }\n        let opt_val = val_expr.atom(None).ok_or_else(|| {\n            anyhow_expr!(\n                val_expr,\n                \"No lists are allowed in {DEFLAYER}'s option values\"\n            )\n        })?;\n        layer_opts.insert(opt_key.to_owned(), opt_val.to_owned());\n    }\n    let rem = opts.remainder();\n    if !rem.is_empty() {\n        bail_expr!(&rem[0], \"This option is missing a value.\");\n    }\n    Ok(layer_opts)\n}\n"
  },
  {
    "path": "parser/src/cfg/list_actions.rs",
    "content": "//! Contains all list action names and a function to check that an action name is that of a list\n//! action.\n\n// Note: changing any of these constants is a breaking change.\npub const LAYER_SWITCH: &str = \"layer-switch\";\npub const LAYER_TOGGLE: &str = \"layer-toggle\";\npub const LAYER_WHILE_HELD: &str = \"layer-while-held\";\npub const TAP_HOLD: &str = \"tap-hold\";\npub const TAP_HOLD_PRESS: &str = \"tap-hold-press\";\npub const TAP_HOLD_PRESS_A: &str = \"tap⬓↓\";\npub const TAP_HOLD_RELEASE: &str = \"tap-hold-release\";\npub const TAP_HOLD_RELEASE_A: &str = \"tap⬓↑\";\npub const TAP_HOLD_PRESS_TIMEOUT: &str = \"tap-hold-press-timeout\";\npub const TAP_HOLD_PRESS_TIMEOUT_A: &str = \"tap⬓↓timeout\";\npub const TAP_HOLD_RELEASE_TIMEOUT: &str = \"tap-hold-release-timeout\";\npub const TAP_HOLD_RELEASE_TIMEOUT_A: &str = \"tap⬓↑timeout\";\npub const TAP_HOLD_RELEASE_KEYS: &str = \"tap-hold-release-keys\";\npub const TAP_HOLD_RELEASE_KEYS_TAP_RELEASE: &str = \"tap-hold-release-tap-keys-release\";\npub const TAP_HOLD_RELEASE_KEYS_A: &str = \"tap⬓↑keys\";\npub const TAP_HOLD_EXCEPT_KEYS: &str = \"tap-hold-except-keys\";\npub const TAP_HOLD_EXCEPT_KEYS_A: &str = \"tap⬓⤫keys\";\npub const TAP_HOLD_TAP_KEYS: &str = \"tap-hold-tap-keys\";\npub const TAP_HOLD_TAP_KEYS_A: &str = \"tap⬓tapkeys\";\npub const MULTI: &str = \"multi\";\npub const MACRO: &str = \"macro\";\npub const MACRO_REPEAT: &str = \"macro-repeat\";\npub const MACRO_REPEAT_A: &str = \"macro⟳\";\npub const MACRO_RELEASE_CANCEL: &str = \"macro-release-cancel\";\npub const MACRO_RELEASE_CANCEL_A: &str = \"macro↑⤫\";\npub const MACRO_REPEAT_RELEASE_CANCEL: &str = \"macro-repeat-release-cancel\";\npub const MACRO_REPEAT_RELEASE_CANCEL_A: &str = \"macro⟳↑⤫\";\npub const MACRO_CANCEL_ON_NEXT_PRESS: &str = \"macro-cancel-on-press\";\npub const MACRO_REPEAT_CANCEL_ON_NEXT_PRESS: &str = \"macro-repeat-cancel-on-press\";\npub const MACRO_CANCEL_ON_NEXT_PRESS_CANCEL_ON_RELEASE: &str =\n    \"macro-release-cancel-and-cancel-on-press\";\npub const MACRO_REPEAT_CANCEL_ON_NEXT_PRESS_CANCEL_ON_RELEASE: &str =\n    \"macro-repeat-release-cancel-and-cancel-on-press\";\npub const UNICODE: &str = \"unicode\";\npub const SYM: &str = \"🔣\";\npub const ONE_SHOT: &str = \"one-shot\";\npub const ONE_SHOT_PRESS: &str = \"one-shot-press\";\npub const ONE_SHOT_PRESS_A: &str = \"one-shot↓\";\npub const ONE_SHOT_RELEASE: &str = \"one-shot-release\";\npub const ONE_SHOT_RELEASE_A: &str = \"one-shot↑\";\npub const ONE_SHOT_PRESS_PCANCEL: &str = \"one-shot-press-pcancel\";\npub const ONE_SHOT_PRESS_PCANCEL_A: &str = \"one-shot↓⤫\";\npub const ONE_SHOT_RELEASE_PCANCEL: &str = \"one-shot-release-pcancel\";\npub const ONE_SHOT_RELEASE_PCANCEL_A: &str = \"one-shot↑⤫\";\npub const ONE_SHOT_PAUSE_PROCESSING: &str = \"one-shot-pause-processing\";\npub const TAP_DANCE: &str = \"tap-dance\";\npub const TAP_DANCE_EAGER: &str = \"tap-dance-eager\";\npub const CHORD: &str = \"chord\";\npub const RELEASE_KEY: &str = \"release-key\";\npub const RELEASE_KEY_A: &str = \"key↑\";\npub const RELEASE_LAYER: &str = \"release-layer\";\npub const RELEASE_LAYER_A: &str = \"layer↑\";\npub const ON_PRESS_FAKEKEY: &str = \"on-press-fakekey\";\npub const ON_PRESS_FAKEKEY_A: &str = \"on↓fakekey\";\npub const ON_RELEASE_FAKEKEY: &str = \"on-release-fakekey\";\npub const ON_RELEASE_FAKEKEY_A: &str = \"on↑fakekey\";\npub const ON_PRESS_DELAY: &str = \"on-press-delay\";\npub const ON_RELEASE_DELAY: &str = \"on-release-delay\";\npub const ON_PRESS_FAKEKEY_DELAY: &str = \"on-press-fakekey-delay\";\npub const ON_PRESS_FAKEKEY_DELAY_A: &str = \"on↓fakekey-delay\";\npub const ON_RELEASE_FAKEKEY_DELAY: &str = \"on-release-fakekey-delay\";\npub const ON_RELEASE_FAKEKEY_DELAY_A: &str = \"on↑fakekey-delay\";\npub const ON_IDLE_FAKEKEY: &str = \"on-idle-fakekey\";\npub const MWHEEL_UP: &str = \"mwheel-up\";\npub const MWHEEL_DOWN: &str = \"mwheel-down\";\npub const MWHEEL_LEFT: &str = \"mwheel-left\";\npub const MWHEEL_RIGHT: &str = \"mwheel-right\";\npub const MWHEEL_UP_A: &str = \"🖱☸↑\";\npub const MWHEEL_DOWN_A: &str = \"🖱☸↓\";\npub const MWHEEL_LEFT_A: &str = \"🖱☸←\";\npub const MWHEEL_RIGHT_A: &str = \"🖱☸→\";\npub const MWHEEL_ACCEL_UP: &str = \"mwheel-accel-up\";\npub const MWHEEL_ACCEL_DOWN: &str = \"mwheel-accel-down\";\npub const MWHEEL_ACCEL_LEFT: &str = \"mwheel-accel-left\";\npub const MWHEEL_ACCEL_RIGHT: &str = \"mwheel-accel-right\";\npub const MOVEMOUSE_UP: &str = \"movemouse-up\";\npub const MOVEMOUSE_DOWN: &str = \"movemouse-down\";\npub const MOVEMOUSE_LEFT: &str = \"movemouse-left\";\npub const MOVEMOUSE_RIGHT: &str = \"movemouse-right\";\npub const MOVEMOUSE_ACCEL_UP: &str = \"movemouse-accel-up\";\npub const MOVEMOUSE_ACCEL_DOWN: &str = \"movemouse-accel-down\";\npub const MOVEMOUSE_ACCEL_LEFT: &str = \"movemouse-accel-left\";\npub const MOVEMOUSE_ACCEL_RIGHT: &str = \"movemouse-accel-right\";\npub const MOVEMOUSE_SPEED: &str = \"movemouse-speed\";\npub const MOVEMOUSE_UP_A: &str = \"🖱↑\";\npub const MOVEMOUSE_DOWN_A: &str = \"🖱↓\";\npub const MOVEMOUSE_LEFT_A: &str = \"🖱←\";\npub const MOVEMOUSE_RIGHT_A: &str = \"🖱→\";\npub const MOVEMOUSE_ACCEL_UP_A: &str = \"🖱accel↑\";\npub const MOVEMOUSE_ACCEL_DOWN_A: &str = \"🖱accel↓\";\npub const MOVEMOUSE_ACCEL_LEFT_A: &str = \"🖱accel←\";\npub const MOVEMOUSE_ACCEL_RIGHT_A: &str = \"🖱accel→\";\npub const MOVEMOUSE_SPEED_A: &str = \"🖱speed\";\npub const SETMOUSE: &str = \"setmouse\";\npub const SETMOUSE_A: &str = \"set🖱\";\npub const DYNAMIC_MACRO_RECORD: &str = \"dynamic-macro-record\";\npub const DYNAMIC_MACRO_PLAY: &str = \"dynamic-macro-play\";\npub const ARBITRARY_CODE: &str = \"arbitrary-code\";\npub const CMD: &str = \"cmd\";\npub const CMD_LOG: &str = \"cmd-log\";\npub const PUSH_MESSAGE: &str = \"push-msg\";\npub const CMD_OUTPUT_KEYS: &str = \"cmd-output-keys\";\npub const FORK: &str = \"fork\";\npub const CAPS_WORD: &str = \"caps-word\";\npub const CAPS_WORD_A: &str = \"word⇪\";\npub const CAPS_WORD_CUSTOM: &str = \"caps-word-custom\";\npub const CAPS_WORD_CUSTOM_A: &str = \"word⇪custom\";\npub const CAPS_WORD_TOGGLE: &str = \"caps-word-toggle\";\npub const CAPS_WORD_TOGGLE_A: &str = \"word⇪toggle\";\npub const CAPS_WORD_CUSTOM_TOGGLE: &str = \"caps-word-custom-toggle\";\npub const CAPS_WORD_CUSTOM_TOGGLE_A: &str = \"word⇪custom-toggle\";\npub const DYNAMIC_MACRO_RECORD_STOP_TRUNCATE: &str = \"dynamic-macro-record-stop-truncate\";\npub const SWITCH: &str = \"switch\";\npub const SEQUENCE: &str = \"sequence\";\npub const SEQUENCE_NOERASE: &str = \"sequence-noerase\";\npub const UNMOD: &str = \"unmod\";\npub const UNSHIFT: &str = \"unshift\";\npub const UNSHIFT_A: &str = \"un⇧\";\npub const LIVE_RELOAD_NUM: &str = \"lrld-num\";\npub const LIVE_RELOAD_FILE: &str = \"lrld-file\";\npub const ON_PRESS: &str = \"on-press\";\npub const ON_PRESS_A: &str = \"on↓\";\npub const ON_RELEASE: &str = \"on-release\";\npub const ON_RELEASE_A: &str = \"on↑\";\npub const ON_IDLE: &str = \"on-idle\";\npub const ON_PHYSICAL_IDLE: &str = \"on-physical-idle\";\npub const HOLD_FOR_DURATION: &str = \"hold-for-duration\";\npub const CLIPBOARD_SET: &str = \"clipboard-set\";\npub const CLIPBOARD_CMD_SET: &str = \"clipboard-cmd-set\";\npub const CLIPBOARD_SAVE: &str = \"clipboard-save\";\npub const CLIPBOARD_RESTORE: &str = \"clipboard-restore\";\npub const CLIPBOARD_SAVE_SET: &str = \"clipboard-save-set\";\npub const CLIPBOARD_SAVE_CMD_SET: &str = \"clipboard-save-cmd-set\";\npub const CLIPBOARD_SAVE_SWAP: &str = \"clipboard-save-swap\";\npub const TAP_HOLD_ORDER: &str = \"tap-hold-order\";\npub const TAP_HOLD_OPPOSITE_HAND: &str = \"tap-hold-opposite-hand\";\n\npub fn is_list_action(ac: &str) -> bool {\n    const LIST_ACTIONS: &[&str] = &[\n        LAYER_SWITCH,\n        LAYER_TOGGLE,\n        LAYER_WHILE_HELD,\n        TAP_HOLD,\n        TAP_HOLD_PRESS,\n        TAP_HOLD_PRESS_A,\n        TAP_HOLD_RELEASE,\n        TAP_HOLD_RELEASE_A,\n        TAP_HOLD_PRESS_TIMEOUT,\n        TAP_HOLD_PRESS_TIMEOUT_A,\n        TAP_HOLD_RELEASE_TIMEOUT,\n        TAP_HOLD_RELEASE_TIMEOUT_A,\n        TAP_HOLD_RELEASE_KEYS,\n        TAP_HOLD_RELEASE_KEYS_TAP_RELEASE,\n        TAP_HOLD_RELEASE_KEYS_A,\n        TAP_HOLD_EXCEPT_KEYS,\n        TAP_HOLD_EXCEPT_KEYS_A,\n        TAP_HOLD_TAP_KEYS,\n        TAP_HOLD_TAP_KEYS_A,\n        MULTI,\n        MACRO,\n        MACRO_REPEAT,\n        MACRO_REPEAT_A,\n        MACRO_RELEASE_CANCEL,\n        MACRO_RELEASE_CANCEL_A,\n        MACRO_REPEAT_RELEASE_CANCEL,\n        MACRO_REPEAT_RELEASE_CANCEL_A,\n        UNICODE,\n        SYM,\n        ONE_SHOT,\n        ONE_SHOT_PRESS,\n        ONE_SHOT_PRESS_A,\n        ONE_SHOT_RELEASE,\n        ONE_SHOT_RELEASE_A,\n        ONE_SHOT_PRESS_PCANCEL,\n        ONE_SHOT_PRESS_PCANCEL_A,\n        ONE_SHOT_RELEASE_PCANCEL,\n        ONE_SHOT_RELEASE_PCANCEL_A,\n        TAP_DANCE,\n        TAP_DANCE_EAGER,\n        CHORD,\n        RELEASE_KEY,\n        RELEASE_KEY_A,\n        RELEASE_LAYER,\n        RELEASE_LAYER_A,\n        ON_PRESS_FAKEKEY,\n        ON_PRESS_FAKEKEY_A,\n        ON_RELEASE_FAKEKEY,\n        ON_RELEASE_FAKEKEY_A,\n        ON_PRESS_DELAY,\n        ON_RELEASE_DELAY,\n        ON_PRESS_FAKEKEY_DELAY,\n        ON_PRESS_FAKEKEY_DELAY_A,\n        ON_RELEASE_FAKEKEY_DELAY,\n        ON_RELEASE_FAKEKEY_DELAY_A,\n        ON_IDLE_FAKEKEY,\n        MWHEEL_UP,\n        MWHEEL_UP_A,\n        MWHEEL_DOWN,\n        MWHEEL_DOWN_A,\n        MWHEEL_LEFT,\n        MWHEEL_LEFT_A,\n        MWHEEL_RIGHT,\n        MWHEEL_RIGHT_A,\n        MWHEEL_ACCEL_UP,\n        MWHEEL_ACCEL_DOWN,\n        MWHEEL_ACCEL_LEFT,\n        MWHEEL_ACCEL_RIGHT,\n        MOVEMOUSE_UP,\n        MOVEMOUSE_UP_A,\n        MOVEMOUSE_DOWN,\n        MOVEMOUSE_DOWN_A,\n        MOVEMOUSE_LEFT,\n        MOVEMOUSE_LEFT_A,\n        MOVEMOUSE_RIGHT,\n        MOVEMOUSE_RIGHT_A,\n        MOVEMOUSE_ACCEL_UP,\n        MOVEMOUSE_ACCEL_UP_A,\n        MOVEMOUSE_ACCEL_DOWN,\n        MOVEMOUSE_ACCEL_DOWN_A,\n        MOVEMOUSE_ACCEL_LEFT,\n        MOVEMOUSE_ACCEL_LEFT_A,\n        MOVEMOUSE_ACCEL_RIGHT,\n        MOVEMOUSE_ACCEL_RIGHT_A,\n        MOVEMOUSE_SPEED,\n        MOVEMOUSE_SPEED_A,\n        SETMOUSE,\n        SETMOUSE_A,\n        DYNAMIC_MACRO_RECORD,\n        DYNAMIC_MACRO_PLAY,\n        ARBITRARY_CODE,\n        CMD,\n        CMD_OUTPUT_KEYS,\n        CMD_LOG,\n        PUSH_MESSAGE,\n        FORK,\n        CAPS_WORD,\n        CAPS_WORD_A,\n        CAPS_WORD_TOGGLE,\n        CAPS_WORD_TOGGLE_A,\n        CAPS_WORD_CUSTOM,\n        CAPS_WORD_CUSTOM_A,\n        CAPS_WORD_CUSTOM_TOGGLE,\n        CAPS_WORD_CUSTOM_TOGGLE_A,\n        DYNAMIC_MACRO_RECORD_STOP_TRUNCATE,\n        SWITCH,\n        SEQUENCE,\n        SEQUENCE_NOERASE,\n        UNMOD,\n        UNSHIFT,\n        UNSHIFT_A,\n        LIVE_RELOAD_NUM,\n        LIVE_RELOAD_FILE,\n        ON_PRESS,\n        ON_PRESS_A,\n        ON_RELEASE,\n        ON_RELEASE_A,\n        ON_IDLE,\n        ON_PHYSICAL_IDLE,\n        HOLD_FOR_DURATION,\n        MACRO_CANCEL_ON_NEXT_PRESS,\n        MACRO_REPEAT_CANCEL_ON_NEXT_PRESS,\n        MACRO_CANCEL_ON_NEXT_PRESS_CANCEL_ON_RELEASE,\n        MACRO_REPEAT_CANCEL_ON_NEXT_PRESS_CANCEL_ON_RELEASE,\n        ONE_SHOT_PAUSE_PROCESSING,\n        CLIPBOARD_SET,\n        CLIPBOARD_CMD_SET,\n        CLIPBOARD_SAVE,\n        CLIPBOARD_RESTORE,\n        CLIPBOARD_SAVE_SET,\n        CLIPBOARD_SAVE_CMD_SET,\n        CLIPBOARD_SAVE_SWAP,\n        TAP_HOLD_ORDER,\n        TAP_HOLD_OPPOSITE_HAND,\n    ];\n    LIST_ACTIONS.contains(&ac)\n}\n"
  },
  {
    "path": "parser/src/cfg/live_reload.rs",
    "content": "use super::*;\n\nuse crate::bail;\nuse crate::bail_expr;\n\npub(crate) fn parse_live_reload_num(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"expects 1 parameter: <config argument position (1-65535)>\";\n    if ac_params.len() != 1 {\n        bail!(\"{LIVE_RELOAD_NUM} {ERR_MSG}, found {}\", ac_params.len());\n    }\n    let num = parse_non_zero_u16(&ac_params[0], s, \"config argument position\")?;\n    Ok(s.a.sref(Action::Custom(\n        // Note: for user-friendliness (hopefully), begin at 1 for parsing.\n        // But for use as an index when stored as data, subtract 1 for 0-based indexing.\n        s.a.sref(s.a.sref_slice(CustomAction::LiveReloadNum(num - 1))),\n    )))\n}\n\npub(crate) fn parse_live_reload_file(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"expects 1 parameter: <config argument (exact path)>\";\n    if ac_params.len() != 1 {\n        bail!(\"{LIVE_RELOAD_FILE} {ERR_MSG}, found {}\", ac_params.len());\n    }\n    let expr = &ac_params[0];\n    let spanned_filepath = match expr {\n        SExpr::Atom(filepath) => filepath,\n        SExpr::List(_) => {\n            bail_expr!(&expr, \"Filepath cannot be a list\")\n        }\n    };\n    let lrld_file_path = spanned_filepath.t.trim_atom_quotes();\n    Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice(\n        CustomAction::LiveReloadFile(s.a.sref_str(lrld_file_path.to_string())),\n    )))))\n}\n"
  },
  {
    "path": "parser/src/cfg/macro.rs",
    "content": "use super::*;\n\nuse crate::anyhow_expr;\nuse crate::bail;\nuse crate::bail_expr;\n\nconst MACRO_ERR: &str = \"Action macro only accepts delays, keys, chords, chorded sub-macros, and a subset of special actions.\\nThe macro section of the documentation describes this in more detail:\\nhttps://github.com/jtroo/kanata/blob/main/docs/config.adoc#macro\";\n\npub(crate) enum RepeatMacro {\n    Yes,\n    No,\n}\n\npub(crate) fn parse_macro(\n    ac_params: &[SExpr],\n    s: &ParserState,\n    repeat: RepeatMacro,\n) -> Result<&'static KanataAction> {\n    if ac_params.is_empty() {\n        bail!(\"macro expects at least one item after it\")\n    }\n    let mut all_events = Vec::with_capacity(256);\n    let mut params_remainder = ac_params;\n    while !params_remainder.is_empty() {\n        let mut events;\n        (events, params_remainder) = parse_macro_item(params_remainder, s)?;\n        all_events.append(&mut events);\n    }\n    if all_events.iter().any(|e| match e {\n        SequenceEvent::Tap(kc) | SequenceEvent::Press(kc) | SequenceEvent::Release(kc) => {\n            *kc == KEY_OVERLAP\n        }\n        _ => false,\n    }) {\n        bail!(\"macro contains O- which is only valid within defseq\")\n    }\n    all_events.push(SequenceEvent::Complete);\n    all_events.shrink_to_fit();\n    match repeat {\n        RepeatMacro::No => Ok(s.a.sref(Action::Sequence {\n            events: s.a.sref(s.a.sref(s.a.sref_vec(all_events))),\n        })),\n        RepeatMacro::Yes => Ok(s.a.sref(Action::RepeatableSequence {\n            events: s.a.sref(s.a.sref(s.a.sref_vec(all_events))),\n        })),\n    }\n}\n\npub(crate) fn parse_macro_release_cancel(\n    ac_params: &[SExpr],\n    s: &ParserState,\n    repeat: RepeatMacro,\n) -> Result<&'static KanataAction> {\n    let macro_action = parse_macro(ac_params, s, repeat)?;\n    Ok(s.a.sref(Action::MultipleActions(s.a.sref(s.a.sref_vec(vec![\n        *macro_action,\n        Action::Custom(s.a.sref(s.a.sref_slice(CustomAction::CancelMacroOnRelease))),\n    ])))))\n}\n\npub(crate) fn parse_macro_cancel_on_next_press(\n    ac_params: &[SExpr],\n    s: &ParserState,\n    repeat: RepeatMacro,\n) -> Result<&'static KanataAction> {\n    let macro_action = parse_macro(ac_params, s, repeat)?;\n    let macro_duration = match macro_action {\n        Action::RepeatableSequence { events } | Action::Sequence { events } => {\n            macro_sequence_event_total_duration(events)\n        }\n        _ => unreachable!(\"parse_macro should return sequence action\"),\n    };\n    Ok(s.a.sref(Action::MultipleActions(s.a.sref(s.a.sref_vec(vec![\n        *macro_action,\n        Action::Custom(\n            s.a.sref(s.a.sref_slice(CustomAction::CancelMacroOnNextPress(macro_duration))),\n        ),\n    ])))))\n}\n\npub(crate) fn parse_macro_cancel_on_next_press_cancel_on_release(\n    ac_params: &[SExpr],\n    s: &ParserState,\n    repeat: RepeatMacro,\n) -> Result<&'static KanataAction> {\n    let macro_action = parse_macro(ac_params, s, repeat)?;\n    let macro_duration = match macro_action {\n        Action::RepeatableSequence { events } | Action::Sequence { events } => {\n            macro_sequence_event_total_duration(events)\n        }\n        _ => unreachable!(\"parse_macro should return sequence action\"),\n    };\n    Ok(s.a.sref(Action::MultipleActions(s.a.sref(s.a.sref_vec(vec![\n        *macro_action,\n        Action::Custom(s.a.sref(s.a.sref_vec(vec![\n            &CustomAction::CancelMacroOnRelease,\n            s.a.sref(CustomAction::CancelMacroOnNextPress(macro_duration)),\n        ]))),\n    ])))))\n}\n\npub(crate) fn macro_sequence_event_total_duration<T>(events: &[SequenceEvent<T>]) -> u32 {\n    events.iter().fold(0, |duration, event| {\n        duration.saturating_add(match event {\n            SequenceEvent::Delay { duration: d } => *d,\n            _ => 1,\n        })\n    })\n}\n\npub(crate) fn parse_macro_record_stop_truncate(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_STR: &str =\n        \"dynamic-macro-record-stop-truncate expects 1 param: <num-keys-to-truncate>\";\n    if ac_params.len() != 1 {\n        bail!(\"{ERR_STR}\\nFound {} params instead of 1\", ac_params.len());\n    }\n    let num_to_truncate = parse_u16(&ac_params[0], s, \"num-keys-to-truncate\")?;\n    Ok(s.a.sref(Action::Custom(s.a.sref(\n        s.a.sref_slice(CustomAction::DynamicMacroRecordStop(num_to_truncate)),\n    ))))\n}\n\n#[derive(PartialEq)]\npub(crate) enum MacroNumberParseMode {\n    Delay,\n    Action,\n}\n\n#[allow(clippy::type_complexity)] // return type is not pub\npub(crate) fn parse_macro_item<'a>(\n    acs: &'a [SExpr],\n    s: &ParserState,\n) -> Result<(\n    Vec<SequenceEvent<'static, &'static &'static [&'static CustomAction]>>,\n    &'a [SExpr],\n)> {\n    parse_macro_item_impl(acs, s, MacroNumberParseMode::Delay)\n}\n\n#[allow(clippy::type_complexity)] // return type is not pub\npub(crate) fn parse_macro_item_impl<'a>(\n    acs: &'a [SExpr],\n    s: &ParserState,\n    num_parse_mode: MacroNumberParseMode,\n) -> Result<(\n    Vec<SequenceEvent<'static, &'static &'static [&'static CustomAction]>>,\n    &'a [SExpr],\n)> {\n    if num_parse_mode == MacroNumberParseMode::Delay {\n        if let Some(a) = acs[0].atom(s.vars()) {\n            match parse_non_zero_u16(&acs[0], s, \"delay\") {\n                Ok(duration) => {\n                    let duration = u32::from(duration);\n                    return Ok((vec![SequenceEvent::Delay { duration }], &acs[1..]));\n                }\n                Err(e) => {\n                    if a.chars().all(|c| c.is_ascii_digit()) {\n                        return Err(e);\n                    }\n                }\n            }\n        }\n    }\n    match parse_action(&acs[0], s) {\n        Ok(Action::KeyCode(kc)) => {\n            // Should note that I tried `SequenceEvent::Tap` initially but it seems to be buggy\n            // so I changed the code to use individual press and release. The SequenceEvent\n            // code is from a PR that (at the time of this writing) hasn't yet been merged into\n            // keyberon master and doesn't have tests written for it yet. This seems to work as\n            // expected right now though.\n            Ok((\n                vec![SequenceEvent::Press(*kc), SequenceEvent::Release(*kc)],\n                &acs[1..],\n            ))\n        }\n        Ok(Action::MultipleKeyCodes(kcs)) => {\n            // chord - press in order then release in the reverse order\n            let mut events = vec![];\n            for kc in kcs.iter() {\n                events.push(SequenceEvent::Press(*kc));\n            }\n            for kc in kcs.iter().rev() {\n                events.push(SequenceEvent::Release(*kc));\n            }\n            Ok((events, &acs[1..]))\n        }\n        Ok(Action::Custom(custom)) => Ok((vec![SequenceEvent::Custom(custom)], &acs[1..])),\n        Ok(_) => bail_expr!(&acs[0], \"{MACRO_ERR}\"),\n        Err(e) => {\n            if let Some(submacro) = acs[0].list(s.vars()) {\n                // If it's just a list that's not parsable as a usable action, try parsing the\n                // content.\n                let mut submacro_remainder = submacro;\n                let mut all_events = vec![];\n                while !submacro_remainder.is_empty() {\n                    let mut events;\n                    (events, submacro_remainder) =\n                        parse_macro_item(submacro_remainder, s).map_err(|_e| e.clone())?;\n                    all_events.append(&mut events);\n                }\n                return Ok((all_events, &acs[1..]));\n            }\n\n            let (held_mods, unparsed_str) =\n                parse_mods_held_for_submacro(&acs[0], s).map_err(|mut err| {\n                    if err.msg == MACRO_ERR {\n                        err.msg = format!(\"{}\\n{MACRO_ERR}\", &e.msg);\n                    }\n                    err\n                })?;\n            let mut all_events = vec![];\n\n            // First, press all of the modifiers\n            for kc in held_mods.iter().copied() {\n                all_events.push(SequenceEvent::Press(kc));\n            }\n\n            let mut rem_start = 1;\n            let maybe_list_var = SExpr::Atom(Spanned::new(unparsed_str.into(), acs[0].span()));\n            let submacro = match maybe_list_var.list(s.vars()) {\n                Some(l) => l,\n                None => {\n                    // Ensure that the unparsed text is empty since otherwise it means there is\n                    // invalid text there\n                    if !unparsed_str.is_empty() {\n                        bail_expr!(&acs[0], \"{}\\n{MACRO_ERR}\", &e.msg)\n                    }\n                    // Check for a follow-up list\n                    rem_start = 2;\n                    if acs.len() < 2 {\n                        bail_expr!(&acs[0], \"{}\\n{MACRO_ERR}\", &e.msg)\n                    }\n                    acs[1]\n                        .list(s.vars())\n                        .ok_or_else(|| anyhow_expr!(&acs[1], \"{MACRO_ERR}\"))?\n                }\n            };\n            let mut submacro_remainder = submacro;\n            let mut events;\n            while !submacro_remainder.is_empty() {\n                (events, submacro_remainder) = parse_macro_item(submacro_remainder, s)?;\n                all_events.append(&mut events);\n            }\n\n            // Lastly, release modifiers\n            for kc in held_mods.iter().copied() {\n                all_events.push(SequenceEvent::Release(kc));\n            }\n\n            Ok((all_events, &acs[rem_start..]))\n        }\n    }\n}\n\n/// Parses mod keys like `C-S-`. Returns the `KeyCode`s for the modifiers parsed and the unparsed\n/// text after any parsed modifier prefixes.\npub(crate) fn parse_mods_held_for_submacro<'a>(\n    held_mods: &'a SExpr,\n    s: &'a ParserState,\n) -> Result<(Vec<KeyCode>, &'a str)> {\n    let mods = held_mods\n        .atom(s.vars())\n        .ok_or_else(|| anyhow_expr!(held_mods, \"{MACRO_ERR}\"))?;\n    let (mod_keys, unparsed_str) = parse_mod_prefix(mods)?;\n    if mod_keys.is_empty() {\n        bail_expr!(held_mods, \"{MACRO_ERR}\");\n    }\n    Ok((mod_keys, unparsed_str))\n}\n\npub(crate) fn parse_dynamic_macro_record(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"dynamic-macro-record expects 1 parameter: <macro ID (0-65535)>\";\n    if ac_params.len() != 1 {\n        bail!(\"{ERR_MSG}, found {}\", ac_params.len());\n    }\n    let key = parse_u16(&ac_params[0], s, \"macro ID\")?;\n    Ok(s.a.sref(Action::Custom(\n        s.a.sref(s.a.sref_slice(CustomAction::DynamicMacroRecord(key))),\n    )))\n}\n\npub(crate) fn parse_dynamic_macro_play(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"dynamic-macro-play expects 1 parameter: <macro ID (number 0-65535)>\";\n    if ac_params.len() != 1 {\n        bail!(\"{ERR_MSG}, found {}\", ac_params.len());\n    }\n    let key = parse_u16(&ac_params[0], s, \"macro ID\")?;\n    Ok(s.a.sref(Action::Custom(\n        s.a.sref(s.a.sref_slice(CustomAction::DynamicMacroPlay(key))),\n    )))\n}\n"
  },
  {
    "path": "parser/src/cfg/mod.rs",
    "content": "//! This parses the configuration language to create a `kanata_keyberon::layout::Layout` as well as\n//! associated metadata to help with processing.\n//!\n//! How the configuration maps to keyberon:\n//!\n//! If the mapped keys are defined as:\n//!\n//! (defsrc\n//!     esc  1    2    3    4\n//! )\n//!\n//! and the layers are:\n//!\n//! (deflayer one\n//!     _   a    s    d    _\n//! )\n//!\n//! (deflayer two\n//!     _   a    o    e    _\n//! )\n//!\n//! Then the keyberon layers will be as follows:\n//!\n//! (xx means unimportant and _ means transparent)\n//!\n//! layers[0] = { xx, esc, a, s, d, 4, xx... }\n//! layers[1] = { xx, _  , a, s, d, _, xx... }\n//! layers[2] = { xx, esc, a, o, e, 4, xx... }\n//! layers[3] = { xx, _  , a, o, e, _, xx... }\n//!\n//! Note that this example isn't practical, but `(defsrc esc 1 2 3 4)` is used because these keys\n//! are at the beginning of the array. The column index for layers is the numerical value of\n//! the key from `keys::OsCode`.\n//!\n//! In addition, there are two versions of each layer. One version delegates transparent entries to\n//! the key defined in defsrc, while the other keeps them as actually transparent. This is to match\n//! the behaviour in kmonad.\n//!\n//! The specific values in example above applies to Linux, but the same logic applies to Windows.\n\npub(crate) mod alloc;\nuse alloc::*;\nmod arbitrary_code;\nuse arbitrary_code::*;\nmod caps_word;\nuse caps_word::*;\nmod chord;\nuse chord::*;\nmod chord_v1;\nuse chord_v1::*;\nmod clipboard;\nuse clipboard::*;\nmod cmd;\nuse cmd::*;\nmod custom_tap_hold;\nuse custom_tap_hold::*;\nmod defcfg;\npub use defcfg::*;\nmod defhands;\nuse defhands::{parse_defhands, parse_tap_hold_opposite_hand};\nmod deflocalkeys;\nuse deflocalkeys::*;\nmod defsrc;\nuse defsrc::*;\nmod deflayer;\nuse deflayer::*;\nmod deftemplate;\npub use deftemplate::*;\nmod error;\npub use error::*;\nmod fake_key;\nuse fake_key::*;\nmod fork;\npub use fake_key::{FAKE_KEY_ROW, NORMAL_KEY_ROW};\nuse fork::*;\nmod is_a_button;\nuse is_a_button::*;\nmod live_reload;\nuse live_reload::*;\nmod key_outputs;\npub use key_outputs::*;\nmod key_override;\npub use key_override::*;\npub mod layer_opts;\nuse layer_opts::*;\npub mod list_actions;\nuse list_actions::*;\nmod r#macro;\nuse r#macro::*;\nmod mouse;\nuse mouse::*;\nmod multi;\nuse multi::*;\nmod oneshot;\nuse oneshot::*;\nmod r#override;\nuse r#override::*;\nmod permutations;\nuse permutations::*;\nmod platform;\nuse platform::*;\nmod push_msg;\npub use push_msg::SimpleSExpr;\nuse push_msg::*;\nmod releases;\nuse releases::*;\nmod sequence;\nuse sequence::*;\npub mod sexpr;\nuse sexpr::*;\nmod str_ext;\npub use str_ext::*;\nmod switch;\npub use switch::*;\nmod tap_dance;\nuse tap_dance::*;\nmod tap_hold;\nuse tap_hold::*;\nmod unicode;\nuse unicode::*;\nmod unmod;\nuse unmod::*;\nmod vars;\nuse vars::*;\nmod zippychord;\npub use zippychord::*;\n\nuse crate::custom_action::*;\nuse crate::keys::*;\nuse crate::layers::*;\nuse crate::lsp_hints::{self, *};\nuse crate::sequences::*;\nuse crate::trie::Trie;\n\nuse anyhow::anyhow;\nuse ordered_float::OrderedFloat;\n\nuse std::cell::Cell;\nuse std::cell::RefCell;\nuse std::collections::hash_map::Entry;\nuse std::path::Path;\nuse std::path::PathBuf;\nuse std::sync::Arc;\n\nuse kanata_keyberon::action::*;\nuse kanata_keyberon::chord::ChordsV2;\nuse kanata_keyberon::key_code::*;\nuse kanata_keyberon::layout::*;\n\ntype HashSet<T> = rustc_hash::FxHashSet<T>;\ntype HashMap<K, V> = rustc_hash::FxHashMap<K, V>;\n\n#[cfg(test)]\nmod tests;\n#[cfg(test)]\npub use sexpr::parse;\n\n#[macro_export]\nmacro_rules! bail {\n    ($err:expr $(,)?) => {\n        return Err(ParseError::from(anyhow::anyhow!($err)))\n    };\n    ($fmt:expr, $($arg:tt)*) => {\n        return Err(ParseError::from(anyhow::anyhow!($fmt, $($arg)*)))\n    };\n}\n\n#[macro_export]\nmacro_rules! bail_expr {\n    ($expr:expr, $fmt:expr $(,)?) => {\n        return Err(ParseError::from_expr($expr, format!($fmt)))\n    };\n    ($expr:expr, $fmt:expr, $($arg:tt)*) => {\n        return Err(ParseError::from_expr($expr, format!($fmt, $($arg)*)))\n    };\n}\n\n#[macro_export]\nmacro_rules! err_expr {\n    ($expr:expr, $fmt:expr $(,)?) => {\n        Err(ParseError::from_expr($expr, format!($fmt)))\n    };\n    ($expr:expr, $fmt:expr, $($arg:tt)*) => {\n        Err(ParseError::from_expr($expr, format!($fmt, $($arg)*)))\n    };\n}\n\n#[macro_export]\nmacro_rules! bail_span {\n    ($expr:expr, $fmt:expr $(,)?) => {\n        return Err(ParseError::from_spanned($expr, format!($fmt)))\n    };\n    ($expr:expr, $fmt:expr, $($arg:tt)*) => {\n        return Err(ParseError::from_spanned($expr, format!($fmt, $($arg)*)))\n    };\n}\n\n#[macro_export]\nmacro_rules! err_span {\n    ($expr:expr, $fmt:expr $(,)?) => {\n        Err(ParseError::from_spanned($expr, format!($fmt)))\n    };\n    ($expr:expr, $fmt:expr, $($arg:tt)*) => {\n        Err(ParseError::from_spanned($expr, format!($fmt, $($arg)*)))\n    };\n}\n\n#[macro_export]\nmacro_rules! anyhow_expr {\n    ($expr:expr, $fmt:expr $(,)?) => {\n        ParseError::from_expr($expr, format!($fmt))\n    };\n    ($expr:expr, $fmt:expr, $($arg:tt)*) => {\n        ParseError::from_expr($expr, format!($fmt, $($arg)*))\n    };\n}\n\n#[macro_export]\nmacro_rules! anyhow_span {\n    ($expr:expr, $fmt:expr $(,)?) => {\n        ParseError::from_spanned($expr, format!($fmt))\n    };\n    ($expr:expr, $fmt:expr, $($arg:tt)*) => {\n        ParseError::from_spanned($expr, format!($fmt, $($arg)*))\n    };\n}\n\npub struct FileContentProvider<'a> {\n    /// A function to load content of a file from a filepath.\n    /// Optionally, it could implement caching and a mechanism preventing \"file\" and \"./file\"\n    /// from loading twice.\n    get_file_content_fn: &'a mut dyn FnMut(&Path) -> std::result::Result<String, String>,\n}\n\nimpl<'a> FileContentProvider<'a> {\n    pub fn new(\n        get_file_content_fn: &'a mut impl FnMut(&Path) -> std::result::Result<String, String>,\n    ) -> Self {\n        Self {\n            get_file_content_fn,\n        }\n    }\n    pub fn get_file_content(&mut self, filename: &Path) -> std::result::Result<String, String> {\n        (self.get_file_content_fn)(filename)\n    }\n}\n\npub type KanataCustom = &'static &'static [&'static CustomAction];\npub type KanataAction = Action<'static, KanataCustom>;\ntype KLayout = Layout<'static, KEYS_IN_ROW, 2, KanataCustom>;\n\ntype TapHoldCustomFunc = fn(&[OsCode], &Allocations) -> &'static custom_tap_hold::CustomTapHoldFn;\n\npub type BorrowedKLayout<'a> = Layout<'a, KEYS_IN_ROW, 2, &'a &'a [&'a CustomAction]>;\npub type KeySeqsToFKeys = Trie<(u8, u16)>;\n\npub struct KanataLayout {\n    layout: KLayout,\n    _allocations: Arc<Allocations>,\n}\n\nimpl KanataLayout {\n    fn new(layout: KLayout, a: Arc<Allocations>) -> Self {\n        Self {\n            layout,\n            _allocations: a,\n        }\n    }\n\n    /// bm stands for borrow mut.\n    pub fn bm(&mut self) -> &mut BorrowedKLayout<'_> {\n        // shrink the lifetime\n        unsafe { std::mem::transmute(&mut self.layout) }\n    }\n\n    /// b stands for borrow.\n    pub fn b(&self) -> &BorrowedKLayout<'_> {\n        // shrink the lifetime\n        unsafe { std::mem::transmute(&self.layout) }\n    }\n}\n\npub struct Cfg {\n    /// The list of keys that kanata should be processing. Keys that are missing from `mapped_keys`\n    /// that are received from the OS input mechanism will be forwarded to OS output mechanism\n    /// without going through kanata's processing.\n    pub mapped_keys: MappedKeys,\n    /// The potential outputs for a physical key position. The intention behind this is for sending\n    /// key repeats.\n    pub key_outputs: KeyOutputs,\n    /// Layer info used for printing to the logs.\n    pub layer_info: Vec<LayerInfo>,\n    /// Configuration items in `defcfg`.\n    pub options: CfgOptions,\n    /// The keyberon layout state machine struct.\n    pub layout: KanataLayout,\n    /// Sequences defined in `defseq`.\n    pub sequences: KeySeqsToFKeys,\n    /// Overrides defined in `defoverrides`.\n    pub overrides: Overrides,\n    /// Mapping of fake key name to its column in the fake key row.\n    pub fake_keys: HashMap<String, usize>,\n    /// The maximum value of switch's key-timing item in the configuration.\n    pub switch_max_key_timing: u16,\n    /// Zipchord-like configuration.\n    pub zippy: Option<(ZchPossibleChords, ZchConfig)>,\n}\n\n/// Parse a new configuration from a file.\npub fn new_from_file(p: &Path) -> MResult<Cfg> {\n    parse_cfg(p)\n}\n\npub fn new_from_str(cfg_text: &str, file_content: HashMap<String, String>) -> MResult<Cfg> {\n    let mut s = ParserState::default();\n    let icfg = parse_cfg_raw_string(\n        cfg_text,\n        &mut s,\n        &PathBuf::from(\"configuration\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut move |fname| match file_content\n                .get(fname.to_string_lossy().as_ref())\n            {\n                Some(s) => Ok(s.clone()),\n                None => Err(\"File is not known\".into()),\n            },\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"environment variables are not supported\".into()),\n    )?;\n    log::info!(\"config file is valid\");\n    Ok(populate_cfg_with_icfg(icfg, s))\n}\n\npub type MappedKeys = HashSet<OsCode>;\n\n#[derive(Debug)]\npub struct LayerInfo {\n    pub name: String,\n    pub cfg_text: String,\n    pub icon: Option<String>,\n}\n\n#[allow(clippy::type_complexity)] // return type is not pub\nfn parse_cfg(p: &Path) -> MResult<Cfg> {\n    let mut s = ParserState::default();\n    let icfg = parse_cfg_raw(p, &mut s)?;\n    log::info!(\"config file is valid\");\n    Ok(populate_cfg_with_icfg(icfg, s))\n}\n\nfn populate_cfg_with_icfg(icfg: IntermediateCfg, s: ParserState) -> Cfg {\n    let (layers, allocations) = icfg.klayers.get();\n    let key_outputs = create_key_outputs(&layers, &icfg.overrides, &icfg.chords_v2);\n    let switch_max_key_timing = s.switch_max_key_timing.get();\n    let mut layout = KanataLayout::new(\n        Layout::new_with_trans_action_settings(\n            s.a.sref(s.defsrc_layer),\n            layers,\n            icfg.options.trans_resolution_behavior_v2,\n            icfg.options.delegate_to_first_layer,\n        ),\n        allocations,\n    );\n    layout.bm().chords_v2 = icfg.chords_v2;\n    layout.bm().quick_tap_hold_timeout = icfg.options.concurrent_tap_hold;\n    layout.bm().tap_hold_require_prior_idle = icfg.options.tap_hold_require_prior_idle;\n    layout.bm().oneshot.pause_input_processing_delay = icfg.options.rapid_event_delay;\n    if let Some(s) = icfg.start_action {\n        layout\n            .bm()\n            .action_queue\n            .push_front(Some(((1, 0), 0, s, Default::default())));\n    }\n    let mut fake_keys: HashMap<String, usize> = s\n        .virtual_keys\n        .iter()\n        .map(|(k, v)| (k.clone(), v.0))\n        .collect();\n    fake_keys.shrink_to_fit();\n    Cfg {\n        options: icfg.options,\n        mapped_keys: icfg.mapped_keys,\n        layer_info: icfg.layer_info,\n        key_outputs,\n        layout,\n        sequences: icfg.sequences,\n        overrides: icfg.overrides,\n        fake_keys,\n        switch_max_key_timing,\n        zippy: icfg.zippy,\n    }\n}\n\n#[derive(Debug)]\npub struct IntermediateCfg {\n    pub options: CfgOptions,\n    pub mapped_keys: MappedKeys,\n    pub layer_info: Vec<LayerInfo>,\n    pub klayers: KanataLayers,\n    pub sequences: KeySeqsToFKeys,\n    pub overrides: Overrides,\n    pub chords_v2: Option<ChordsV2<'static, KanataCustom>>,\n    pub start_action: Option<&'static KanataAction>,\n    pub zippy: Option<(ZchPossibleChords, ZchConfig)>,\n}\n\n// A snapshot of enviroment variables, or an error message with an explanation\n// why env vars are not supported.\npub type EnvVars = std::result::Result<Vec<(String, String)>, String>;\n\n#[allow(clippy::type_complexity)] // return type is not pub\nfn parse_cfg_raw(p: &Path, s: &mut ParserState) -> MResult<IntermediateCfg> {\n    const INVALID_PATH_ERROR: &str = \"The provided config file path is not valid\";\n\n    let mut loaded_files: HashSet<PathBuf> = HashSet::default();\n\n    let mut get_file_content_fn_impl = |filepath: &Path| {\n        // Make the include paths relative to main config file instead of kanata executable.\n        let filepath_relative_to_loaded_kanata_cfg = if filepath.is_absolute() {\n            filepath.to_owned()\n        } else {\n            let relative_main_cfg_file_dir = p.parent().ok_or(INVALID_PATH_ERROR)?;\n            relative_main_cfg_file_dir.join(filepath)\n        };\n\n        let Ok(abs_filepath) = filepath_relative_to_loaded_kanata_cfg.canonicalize() else {\n            log::info!(\n                \"Failed to resolve relative path: {}. Ignoring this file.\",\n                filepath_relative_to_loaded_kanata_cfg.to_string_lossy()\n            );\n            return Ok(\"\".to_owned());\n        };\n\n        // Forbid loading the same file multiple times.\n        // This prevents a potential recursive infinite loop of includes\n        // (if includes within includes were to be allowed).\n        if !loaded_files.insert(abs_filepath.clone()) {\n            return Err(\"The provided config file was already included before\".to_string());\n        };\n\n        std::fs::read_to_string(abs_filepath.to_str().ok_or(INVALID_PATH_ERROR)?)\n            .map_err(|e| format!(\"Failed to include file: {e}\"))\n    };\n    let mut file_content_provider = FileContentProvider::new(&mut get_file_content_fn_impl);\n\n    // `get_file_content_fn_impl` already uses CWD of the main config path,\n    // so we need to provide only the name, not the whole path.\n    let cfg_file_name: PathBuf = p\n        .file_name()\n        .ok_or_else(|| miette::miette!(INVALID_PATH_ERROR))?\n        .into();\n    let text = file_content_provider\n        .get_file_content(&cfg_file_name)\n        .map_err(|e| miette::miette!(e))?;\n\n    let env_vars: EnvVars = Ok(std::env::vars().collect());\n\n    parse_cfg_raw_string(\n        &text,\n        s,\n        p,\n        &mut file_content_provider,\n        DEF_LOCAL_KEYS,\n        env_vars,\n    )\n    .map_err(|e| e.into())\n}\n\nfn expand_includes(\n    xs: Vec<TopLevel>,\n    file_content_provider: &mut FileContentProvider,\n    _lsp_hints: &mut LspHints,\n) -> Result<Vec<TopLevel>> {\n    let include_is_first_atom = gen_first_atom_filter(\"include\");\n    xs.iter().try_fold(Vec::new(), |mut acc, spanned_exprs| {\n        if include_is_first_atom(&&spanned_exprs.t) {\n            let mut exprs =\n                check_first_expr(spanned_exprs.t.iter(), \"include\").expect(\"can't fail\");\n\n            let expr = exprs.next().ok_or(anyhow_span!(\n                spanned_exprs,\n                \"Every include block must contain exactly one filepath\"\n            ))?;\n\n            let spanned_filepath = match expr {\n                SExpr::Atom(filepath) => filepath,\n                SExpr::List(_) => {\n                    bail_expr!(expr, \"Filepath cannot be a list\")\n                }\n            };\n\n            if let Some(expr) = exprs.next() {\n                bail_expr!(\n                    expr,\n                    \"Multiple filepaths are not allowed in include blocks. If you want to include multiple files, create a new include block for each of them.\"\n                )\n            };\n            let include_file_path = spanned_filepath.t.trim_atom_quotes();\n            let file_content = file_content_provider.get_file_content(Path::new(include_file_path))\n                .map_err(|e| anyhow_span!(spanned_filepath, \"{e}\"))?;\n            let tree = sexpr::parse(&file_content, include_file_path)?;\n            acc.extend(tree);\n\n            #[cfg(feature = \"lsp\")]\n            _lsp_hints.reference_locations.include.push_from_atom(spanned_filepath);\n\n            Ok(acc)\n        } else {\n            acc.push(spanned_exprs.clone());\n            Ok(acc)\n        }\n    })\n}\n\n#[cfg(feature = \"lsp\")]\nthread_local! {\n    pub(crate) static LSP_VARIABLE_REFERENCES: RefCell<crate::lsp_hints::ReferencesMap> =\n        RefCell::new(crate::lsp_hints::ReferencesMap::default());\n}\n\n#[allow(clippy::type_complexity)] // return type is not pub\npub fn parse_cfg_raw_string(\n    text: &str,\n    s: &mut ParserState,\n    cfg_path: &Path,\n    file_content_provider: &mut FileContentProvider,\n    def_local_keys_variant_to_apply: &str,\n    env_vars: EnvVars,\n) -> Result<IntermediateCfg> {\n    let mut lsp_hints: LspHints = Default::default();\n\n    let spanned_root_exprs = sexpr::parse(text, &cfg_path.to_string_lossy())\n        .and_then(|xs| expand_includes(xs, file_content_provider, &mut lsp_hints))\n        .and_then(|xs| {\n            filter_platform_specific_cfg(xs, def_local_keys_variant_to_apply, &mut lsp_hints)\n        })\n        .and_then(|xs| filter_env_specific_cfg(xs, &env_vars, &mut lsp_hints))\n        .and_then(|xs| expand_templates(xs, &mut lsp_hints))?;\n\n    if let Some(spanned) = spanned_root_exprs\n        .iter()\n        .find(gen_first_atom_filter_spanned(\"include\"))\n    {\n        bail_span!(spanned, \"Nested includes are not allowed.\")\n    }\n\n    let root_exprs: Vec<_> = spanned_root_exprs.iter().map(|t| t.t.clone()).collect();\n\n    error_on_unknown_top_level_atoms(&spanned_root_exprs)?;\n\n    let mut local_keys: Option<HashMap<String, OsCode>> = None;\n    clear_custom_str_oscode_mapping();\n    for def_local_keys_variant in DEFLOCALKEYS_VARIANTS {\n        let Some((result, _span)) = spanned_root_exprs\n            .iter()\n            .find(gen_first_atom_filter_spanned(def_local_keys_variant))\n            .map(|x| {\n                (\n                    parse_deflocalkeys(def_local_keys_variant, &x.t),\n                    x.span.clone(),\n                )\n            })\n        else {\n            continue;\n        };\n\n        let mapping = result?;\n        if def_local_keys_variant == &def_local_keys_variant_to_apply {\n            assert!(\n                local_keys.is_none(),\n                \">1 mutually exclusive deflocalkeys variants were parsed\"\n            );\n            local_keys = Some(mapping);\n        } else {\n            #[cfg(feature = \"lsp\")]\n            lsp_hints.inactive_code.push(lsp_hints::InactiveCode {\n                span: _span,\n                reason: format!(\n                    \"Another localkeys variant is currently active: {def_local_keys_variant_to_apply}\"\n                    ),\n            })\n        }\n\n        if let Some(spanned) = spanned_root_exprs\n            .iter()\n            .filter(gen_first_atom_filter_spanned(def_local_keys_variant))\n            .nth(1)\n        {\n            bail_span!(\n                spanned,\n                \"Only one {def_local_keys_variant} is allowed, found more. Delete the extras.\"\n            )\n        }\n    }\n    replace_custom_str_oscode_mapping(&local_keys.unwrap_or_default());\n\n    #[allow(unused_mut)]\n    let mut cfg = root_exprs\n        .iter()\n        .find(gen_first_atom_filter(\"defcfg\"))\n        .map(|cfg| parse_defcfg(cfg))\n        .transpose()?\n        .unwrap_or_else(|| {\n            log::warn!(\"No defcfg is defined. Consider whether the process-unmapped-keys defcfg option should be yes vs. no. Adding defcfg with process-unmapped-keys defined will remove this warning.\");\n            Default::default()\n        });\n    if let Some(spanned) = spanned_root_exprs\n        .iter()\n        .filter(gen_first_atom_filter_spanned(\"defcfg\"))\n        .nth(1)\n    {\n        bail_span!(\n            spanned,\n            \"Only one defcfg is allowed, found more. Delete the extras.\"\n        )\n    }\n    let src_expr = root_exprs\n        .iter()\n        .find(gen_first_atom_filter(\"defsrc\"))\n        .ok_or_else(|| anyhow!(\"Exactly one defsrc must exist; found none\"))?;\n    if let Some(spanned) = spanned_root_exprs\n        .iter()\n        .filter(gen_first_atom_filter_spanned(\"defsrc\"))\n        .nth(1)\n    {\n        bail_span!(\n            spanned,\n            \"Exactly one defsrc is allowed, found more. Delete the extras.\"\n        )\n    }\n    let (mut mapped_keys, mapping_order, _mouse_in_defsrc) = parse_defsrc(src_expr, &cfg)?;\n    #[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\n    if cfg.linux_opts.linux_device_detect_mode.is_none() {\n        cfg.linux_opts.linux_device_detect_mode = Some(match _mouse_in_defsrc {\n            MouseInDefsrc::MouseUsed => DeviceDetectMode::Any,\n            MouseInDefsrc::NoMouse => DeviceDetectMode::KeyboardMice,\n        });\n    }\n\n    let var_exprs = root_exprs\n        .iter()\n        .filter(gen_first_atom_filter(\"defvar\"))\n        .collect::<Vec<_>>();\n    let vars = parse_vars(&var_exprs, &mut lsp_hints)?;\n\n    let deflayer_labels = [DEFLAYER, DEFLAYER_MAPPED];\n    let deflayer_filter = |exprs: &&Vec<SExpr>| -> bool {\n        if exprs.is_empty() {\n            return false;\n        }\n        if let SExpr::Atom(atom) = &exprs[0] {\n            deflayer_labels.contains(&atom.t.as_str())\n        } else {\n            false\n        }\n    };\n    let deflayer_spanned_filter =\n        |exprs: &&Spanned<Vec<SExpr>>| -> bool { deflayer_filter(&&exprs.t) };\n    let layer_exprs = spanned_root_exprs\n        .iter()\n        .filter(deflayer_spanned_filter)\n        .map(|e| match e.t[0].atom(None).unwrap() {\n            DEFLAYER => SpannedLayerExprs::DefsrcMapping(e.clone()),\n            DEFLAYER_MAPPED => SpannedLayerExprs::CustomMapping(e.clone()),\n            _ => unreachable!(),\n        })\n        .collect::<Vec<_>>();\n    if layer_exprs.is_empty() {\n        bail!(\"No deflayer expressions exist. At least one layer must be defined.\")\n    }\n\n    let (layer_idxs, layer_icons) =\n        parse_layer_indexes(&layer_exprs, mapping_order.len(), &vars, &mut lsp_hints)?;\n    let mut sorted_idxs: Vec<(&String, &usize)> =\n        layer_idxs.iter().map(|tuple| (tuple.0, tuple.1)).collect();\n\n    sorted_idxs.sort_by_key(|f| f.1);\n\n    #[allow(clippy::needless_collect)]\n    // Clippy suggests using the sorted_idxs iter directly and manipulating it\n    // to produce the layer_names vec when creating Vec<LayerInfo> below\n    let layer_names = sorted_idxs\n        .into_iter()\n        .map(|(name, _)| (*name).clone())\n        .collect::<Vec<_>>();\n\n    let layer_strings = spanned_root_exprs\n        .iter()\n        .filter(|expr| deflayer_filter(&&expr.t))\n        .map(|expr| expr.span.file_content()[expr.span.clone()].to_string())\n        .collect::<Vec<_>>();\n\n    let layer_info: Vec<LayerInfo> = layer_names\n        .into_iter()\n        .zip(layer_strings)\n        .map(|(name, cfg_text)| LayerInfo {\n            name: name.clone(),\n            cfg_text,\n            icon: layer_icons.get(&name).unwrap_or(&None).clone(),\n        })\n        .collect();\n\n    let defsrc_layer = create_defsrc_layer();\n\n    let deflayer_filter = |exprs: &&Vec<SExpr>| -> bool {\n        if exprs.is_empty() {\n            return false;\n        }\n        if let SExpr::Atom(atom) = &exprs[0] {\n            deflayer_labels.contains(&atom.t.as_str())\n        } else {\n            false\n        }\n    };\n    let layer_exprs = root_exprs\n        .iter()\n        .filter(deflayer_filter)\n        .map(|e| match e[0].atom(None).unwrap() {\n            DEFLAYER => LayerExprs::DefsrcMapping(e.clone()),\n            DEFLAYER_MAPPED => LayerExprs::CustomMapping(e.clone()),\n            _ => unreachable!(),\n        })\n        .collect::<Vec<_>>();\n\n    *s = ParserState {\n        a: s.a.clone(),\n        layer_exprs,\n        layer_idxs,\n        mapping_order,\n        defsrc_layer,\n        is_cmd_enabled: {\n            #[cfg(feature = \"cmd\")]\n            {\n                if cfg.enable_cmd {\n                    log::warn!(\"DANGER! cmd action is enabled.\");\n                    true\n                } else {\n                    false\n                }\n            }\n            #[cfg(not(feature = \"cmd\"))]\n            {\n                log::info!(\"NOTE: kanata was compiled to never allow cmd\");\n                false\n            }\n        },\n        delegate_to_first_layer: cfg.delegate_to_first_layer,\n        default_sequence_timeout: cfg.sequence_timeout,\n        default_sequence_input_mode: cfg.sequence_input_mode,\n        block_unmapped_keys: cfg.block_unmapped_keys,\n        lsp_hints: RefCell::new(lsp_hints),\n        vars,\n        ..Default::default()\n    };\n\n    let defhands_exprs = root_exprs\n        .iter()\n        .filter(gen_first_atom_filter(\"defhands\"))\n        .collect::<Vec<_>>();\n    match defhands_exprs.len() {\n        0 => {}\n        1 => {\n            let hand_map = parse_defhands(defhands_exprs[0], s)?;\n            s.hand_map = Some(s.a.sref(hand_map));\n        }\n        _ => {\n            let spanned = spanned_root_exprs\n                .iter()\n                .filter(gen_first_atom_filter_spanned(\"defhands\"))\n                .nth(1)\n                .expect(\">= 2 defhands\");\n            bail_span!(\n                spanned,\n                \"Only one defhands block is allowed, found more. Delete the extras.\"\n            );\n        }\n    }\n\n    let chords_exprs = spanned_root_exprs\n        .iter()\n        .filter(gen_first_atom_filter_spanned(\"defchords\"))\n        .collect::<Vec<_>>();\n    parse_chord_groups(&chords_exprs, s)?;\n\n    let fake_keys_exprs = root_exprs\n        .iter()\n        .filter(gen_first_atom_filter(\"deffakekeys\"))\n        .collect::<Vec<_>>();\n    parse_fake_keys(&fake_keys_exprs, s)?;\n\n    let vkeys_exprs = root_exprs\n        .iter()\n        .filter(gen_first_atom_filter(\"defvirtualkeys\"))\n        .collect::<Vec<_>>();\n    parse_virtual_keys(&vkeys_exprs, s)?;\n\n    let sequence_exprs = root_exprs\n        .iter()\n        .filter(gen_first_atom_filter(\"defseq\"))\n        .collect::<Vec<_>>();\n    let sequences = parse_sequences(&sequence_exprs, s)?;\n\n    let alias_exprs = spanned_root_exprs\n        .iter()\n        .filter(gen_first_atom_start_filter_spanned(\"defalias\"))\n        .collect::<Vec<_>>();\n    parse_aliases(&alias_exprs, s, &env_vars)?;\n\n    let start_action = cfg\n        .start_alias\n        .as_ref()\n        .and_then(|start| s.aliases.get(start).copied());\n    if let (Some(_), None) = (cfg.start_alias.as_ref(), start_action) {\n        bail!(\"alias-to-trigger-on-load was given, but alias could not be found\")\n    }\n\n    let mut klayers = parse_layers(s, &mut mapped_keys, &cfg)?;\n\n    resolve_chord_groups(&mut klayers, s)?;\n    let layers = s.a.bref_slice(klayers);\n    s.layers = layers;\n\n    let override_exprs = root_exprs\n        .iter()\n        .filter(gen_first_atom_filter(\"defoverrides\"))\n        .collect::<Vec<_>>();\n    let (overrides, overrides_v1_exists) = match override_exprs.len() {\n        0 => (Overrides::new(&[]), false),\n        1 => (parse_overrides(override_exprs[0], s)?, true),\n        _ => {\n            let spanned = spanned_root_exprs\n                .iter()\n                .filter(gen_first_atom_filter_spanned(\"defoverrides\"))\n                .nth(1)\n                .expect(\">= 2 overrides\");\n            bail_span!(\n                spanned,\n                \"Only one defoverrides allowed, found more. Delete the extras.\"\n            )\n        }\n    };\n\n    let overridesv2_exprs = root_exprs\n        .iter()\n        .filter(gen_first_atom_filter(\"defoverridesv2\"))\n        .collect::<Vec<_>>();\n    let overrides = match overridesv2_exprs.len() {\n        0 => overrides,\n        1 => match overrides_v1_exists {\n            false => parse_overridesv2(overridesv2_exprs[0], s)?,\n            true => {\n                let spanned = spanned_root_exprs\n                    .iter()\n                    .find(gen_first_atom_filter_spanned(\"defoverridesv2\"))\n                    .expect(\"1 overridesv2\");\n                bail_span!(\n                    spanned,\n                    \"Only one of defoverrides or defoverridesv2 allowed, found both. Delete one of them.\"\n                )\n            }\n        },\n        _ => {\n            let spanned = spanned_root_exprs\n                .iter()\n                .filter(gen_first_atom_filter_spanned(\"defoverridesv2\"))\n                .nth(1)\n                .expect(\">= 2 overridesv2\");\n            bail_span!(\n                spanned,\n                \"Only one defoverridesv2 allowed, found more. Delete the extras.\"\n            )\n        }\n    };\n\n    let defchordsv2_filter = |exprs: &&Vec<SExpr>| -> bool {\n        if exprs.is_empty() {\n            return false;\n        }\n        if let SExpr::Atom(atom) = &exprs[0] {\n            matches!(atom.t.as_str(), \"defchordsv2\" | \"defchordsv2-experimental\")\n        } else {\n            false\n        }\n    };\n    let defchordsv2_spanned_filter =\n        |exprs: &&Spanned<Vec<SExpr>>| -> bool { defchordsv2_filter(&&exprs.t) };\n\n    s.pctx.trans_forbidden_reason = Some(\"Transparent action is forbidden within chordsv2\");\n    let chords_v2_exprs = root_exprs\n        .iter()\n        .filter(defchordsv2_filter)\n        .collect::<Vec<_>>();\n    let chords_v2 = match chords_v2_exprs.len() {\n        0 => None,\n        1 => {\n            let cfks = parse_defchordv2(chords_v2_exprs[0], s)?;\n            Some(ChordsV2::new(cfks, cfg.chords_v2_min_idle))\n        }\n        _ => {\n            let spanned = spanned_root_exprs\n                .iter()\n                .filter(defchordsv2_spanned_filter)\n                .nth(1)\n                .expect(\"> 2 overrides\");\n            bail_span!(\n                spanned,\n                \"Only one defchordsv2 allowed, found more.\\nDelete the extras.\"\n            )\n        }\n    };\n    s.pctx.trans_forbidden_reason = None;\n    if chords_v2.is_some() && !cfg.concurrent_tap_hold {\n        return Err(anyhow!(\n            \"With defchordsv2 defined, concurrent-tap-hold in defcfg must be true.\\n\\\n            It is currently false or unspecified.\"\n        )\n        .into());\n    }\n\n    let defzippy_filter = |exprs: &&Vec<SExpr>| -> bool {\n        if exprs.is_empty() {\n            return false;\n        }\n        if let SExpr::Atom(atom) = &exprs[0] {\n            matches!(atom.t.as_str(), \"defzippy\" | \"defzippy-experimental\")\n        } else {\n            false\n        }\n    };\n    let defzippy_spanned_filter =\n        |exprs: &&Spanned<Vec<SExpr>>| -> bool { defzippy_filter(&&exprs.t) };\n\n    let zippy_exprs = root_exprs\n        .iter()\n        .filter(defzippy_filter)\n        .collect::<Vec<_>>();\n    let zippy = match zippy_exprs.len() {\n        0 => None,\n        1 => {\n            let zippy = parse_zippy(zippy_exprs[0], s, file_content_provider)?;\n            Some(zippy)\n        }\n        _ => {\n            let spanned = spanned_root_exprs\n                .iter()\n                .filter(defzippy_spanned_filter)\n                .nth(1)\n                .expect(\"> 2 overrides\");\n            bail_span!(\n                spanned,\n                \"Only one defzippy allowed, found more.\\nDelete the extras.\"\n            )\n        }\n    };\n\n    #[cfg(feature = \"lsp\")]\n    LSP_VARIABLE_REFERENCES.with_borrow_mut(|refs| {\n        s.lsp_hints\n            .borrow_mut()\n            .reference_locations\n            .variable\n            .0\n            .extend(refs.0.drain());\n    });\n\n    let klayers = unsafe { KanataLayers::new(layers, s.a.clone()) };\n    Ok(IntermediateCfg {\n        options: cfg,\n        mapped_keys,\n        layer_info,\n        klayers,\n        sequences,\n        overrides,\n        chords_v2,\n        start_action,\n        zippy,\n    })\n}\n\nfn error_on_unknown_top_level_atoms(exprs: &[Spanned<Vec<SExpr>>]) -> Result<()> {\n    for expr in exprs {\n        expr.t\n            .first()\n            .ok_or_else(|| {\n                anyhow_span!(\n                    expr,\n                    \"Found empty list as a configuration item, you should delete this\"\n                )\n            })?\n            .atom(None)\n            .map(|a| match a {\n                \"defcfg\"\n                | \"defalias\"\n                | \"defaliasenvcond\"\n                | \"defsrc\"\n                | DEFLAYER\n                | DEFLAYER_MAPPED\n                | \"defoverrides\"\n                | \"defoverridesv2\"\n                | \"deflocalkeys-macos\"\n                | \"deflocalkeys-linux\"\n                | \"deflocalkeys-win\"\n                | \"deflocalkeys-winiov2\"\n                | \"deflocalkeys-wintercept\"\n                | \"deffakekeys\"\n                | \"defvirtualkeys\"\n                | \"defchords\"\n                | \"defvar\"\n                | \"deftemplate\"\n                | \"defchordsv2\"\n                | \"defchordsv2-experimental\"\n                | \"defzippy\"\n                | \"defzippy-experimental\"\n                | \"defseq\"\n                | \"defhands\" => Ok(()),\n                _ => err_span!(expr, \"Found unknown configuration item\"),\n            })\n            .ok_or_else(|| {\n                anyhow_expr!(\n                    expr.t.first().expect(\"not empty\"),\n                    \"Invalid: found list as first item in a configuration item\"\n                )\n            })??;\n    }\n    Ok(())\n}\n\n/// Return a closure that filters a root expression by the content of the first element. The\n/// closure returns true if the first element is an atom that matches the input `a` and false\n/// otherwise.\nfn gen_first_atom_filter(a: &str) -> impl Fn(&&Vec<SExpr>) -> bool {\n    let a = a.to_owned();\n    move |expr| {\n        if expr.is_empty() {\n            return false;\n        }\n        if let SExpr::Atom(atom) = &expr[0] {\n            atom.t == a\n        } else {\n            false\n        }\n    }\n}\n\n/// Return a closure that filters a root expression by the content of the first element. The\n/// closure returns true if the first element is an atom that starts with the input `a` and false\n/// otherwise.\nfn gen_first_atom_start_filter_spanned(a: &str) -> impl Fn(&&Spanned<Vec<SExpr>>) -> bool {\n    let a = a.to_owned();\n    move |expr| {\n        if expr.t.is_empty() {\n            return false;\n        }\n        if let SExpr::Atom(atom) = &expr.t[0] {\n            atom.t.starts_with(&a)\n        } else {\n            false\n        }\n    }\n}\n\n/// Return a closure that filters a root expression by the content of the first element. The\n/// closure returns true if the first element is an atom that matches the input `a` and false\n/// otherwise.\nfn gen_first_atom_filter_spanned(a: &str) -> impl Fn(&&Spanned<Vec<SExpr>>) -> bool {\n    let a = a.to_owned();\n    move |expr| {\n        if expr.t.is_empty() {\n            return false;\n        }\n        if let SExpr::Atom(atom) = &expr.t[0] {\n            atom.t == a\n        } else {\n            false\n        }\n    }\n}\n\n/// Consumes the first element and returns the rest of the iterator. Returns `Ok` if the first\n/// element is an atom and equals `expected_first`.\nfn check_first_expr<'a>(\n    mut exprs: impl Iterator<Item = &'a SExpr>,\n    expected_first: &str,\n) -> Result<impl Iterator<Item = &'a SExpr>> {\n    let first_atom = exprs\n        .next()\n        .ok_or_else(|| anyhow!(\"Passed empty list to {expected_first}\"))?\n        .atom(None)\n        .ok_or_else(|| anyhow!(\"First entry is expected to be an atom for {expected_first}\"))?;\n    if first_atom != expected_first {\n        bail!(\"Passed non-{expected_first} expression to {expected_first}: {first_atom}\");\n    }\n    Ok(exprs)\n}\n\n#[derive(Debug, Copy, Clone)]\nenum MouseInDefsrc {\n    MouseUsed,\n    NoMouse,\n}\n\ntype Aliases = HashMap<String, &'static KanataAction>;\n\n#[derive(Debug, Clone)]\nenum LayerExprs {\n    DefsrcMapping(Vec<SExpr>),\n    CustomMapping(Vec<SExpr>),\n}\n\n#[derive(Debug, Clone)]\nenum SpannedLayerExprs {\n    DefsrcMapping(Spanned<Vec<SExpr>>),\n    CustomMapping(Spanned<Vec<SExpr>>),\n}\n\n#[derive(Debug, Clone, Default)]\npub struct ParserContext {\n    is_within_defvirtualkeys: bool,\n    trans_forbidden_reason: Option<&'static str>,\n}\n\n#[derive(Debug)]\npub struct ParserState {\n    layers: KLayers,\n    layer_exprs: Vec<LayerExprs>,\n    aliases: Aliases,\n    layer_idxs: LayerIndexes,\n    mapping_order: Vec<usize>,\n    virtual_keys: HashMap<String, (usize, &'static KanataAction)>,\n    chord_groups: HashMap<String, ChordGroup>,\n    defsrc_layer: [KanataAction; KEYS_IN_ROW],\n    vars: HashMap<String, SExpr>,\n    is_cmd_enabled: bool,\n    delegate_to_first_layer: bool,\n    default_sequence_timeout: u16,\n    default_sequence_input_mode: SequenceInputMode,\n    block_unmapped_keys: bool,\n    switch_max_key_timing: Cell<u16>,\n    multi_action_nest_count: Cell<u16>,\n    pctx: ParserContext,\n    pub lsp_hints: RefCell<LspHints>,\n    hand_map: Option<&'static custom_tap_hold::HandMap>,\n    a: Arc<Allocations>,\n}\n\nimpl ParserState {\n    fn vars(&self) -> Option<&HashMap<String, SExpr>> {\n        Some(&self.vars)\n    }\n}\n\nimpl Default for ParserState {\n    fn default() -> Self {\n        let default_cfg = CfgOptions::default();\n        Self {\n            layers: Default::default(),\n            layer_exprs: Default::default(),\n            aliases: Default::default(),\n            layer_idxs: Default::default(),\n            mapping_order: Default::default(),\n            defsrc_layer: [KanataAction::NoOp; KEYS_IN_ROW],\n            virtual_keys: Default::default(),\n            chord_groups: Default::default(),\n            vars: Default::default(),\n            is_cmd_enabled: default_cfg.enable_cmd,\n            delegate_to_first_layer: default_cfg.delegate_to_first_layer,\n            default_sequence_timeout: default_cfg.sequence_timeout,\n            default_sequence_input_mode: default_cfg.sequence_input_mode,\n            block_unmapped_keys: default_cfg.block_unmapped_keys,\n            switch_max_key_timing: Cell::new(0),\n            multi_action_nest_count: Cell::new(0),\n            lsp_hints: Default::default(),\n            hand_map: None,\n            a: unsafe { Allocations::new() },\n            pctx: ParserContext::default(),\n        }\n    }\n}\n\n/// Parse alias->action mappings from multiple exprs starting with defalias.\n/// Mutates the input `s` by storing aliases inside.\nfn parse_aliases(\n    exprs: &[&Spanned<Vec<SExpr>>],\n    s: &mut ParserState,\n    env_vars: &EnvVars,\n) -> Result<()> {\n    for expr in exprs {\n        handle_standard_defalias(&expr.t, s)?;\n        handle_envcond_defalias(expr, s, env_vars)?;\n    }\n    Ok(())\n}\n\nfn handle_standard_defalias(expr: &[SExpr], s: &mut ParserState) -> Result<()> {\n    let subexprs = match check_first_expr(expr.iter(), \"defalias\") {\n        Ok(s) => s,\n        Err(_) => return Ok(()),\n    };\n    read_alias_name_action_pairs(subexprs, s)\n}\n\nfn handle_envcond_defalias(\n    exprs: &Spanned<Vec<SExpr>>,\n    s: &mut ParserState,\n    env_vars: &EnvVars,\n) -> Result<()> {\n    let mut subexprs = match check_first_expr(exprs.t.iter(), \"defaliasenvcond\") {\n        Ok(exprs) => exprs,\n        Err(_) => return Ok(()),\n    };\n\n    let conderr = \"defaliasenvcond must have a list with 2 strings as the first parameter:\\n\\\n            (<env var name> <env var value>)\";\n\n    // Check that there is a list containing the environment variable name and value that\n    // determines if this defalias entry should be used. If there is no match, return early.\n    match subexprs.next() {\n        Some(expr) => {\n            let envcond = expr.list(s.vars()).ok_or_else(|| {\n                anyhow_expr!(expr, \"Found a string, but expected a list.\\n{conderr}\")\n            })?;\n            if envcond.len() != 2 {\n                bail_expr!(expr, \"List has the incorrect number of items.\\n{conderr}\");\n            }\n            let env_var_name = envcond[0].atom(s.vars()).ok_or_else(|| {\n                anyhow_expr!(\n                    expr,\n                    \"Environment variable name must be a string, not a list.\\n{conderr}\"\n                )\n            })?;\n            let env_var_value = envcond[1].atom(s.vars()).ok_or_else(|| {\n                anyhow_expr!(\n                    expr,\n                    \"Environment variable value must be a string, not a list.\\n{conderr}\"\n                )\n            })?;\n            match env_vars {\n                Ok(vars) => {\n                    let values_of_matching_vars: Vec<_> = vars\n                        .iter()\n                        .filter_map(|(k, v)| if k == env_var_name { Some(v) } else { None })\n                        .collect();\n                    if values_of_matching_vars.is_empty() {\n                        let msg = format!(\"Env var '{env_var_name}' is not set\");\n                        #[cfg(feature = \"lsp\")]\n                        s.lsp_hints\n                            .borrow_mut()\n                            .inactive_code\n                            .push(lsp_hints::InactiveCode {\n                                span: exprs.span.clone(),\n                                reason: msg.clone(),\n                            });\n                        log::info!(\"{msg}, skipping associated aliases\");\n                        return Ok(());\n                    } else if !values_of_matching_vars.iter().any(|&v| v == env_var_value) {\n                        let msg =\n                            format!(\"Env var '{env_var_name}' is set, but value doesn't match\");\n                        #[cfg(feature = \"lsp\")]\n                        s.lsp_hints\n                            .borrow_mut()\n                            .inactive_code\n                            .push(lsp_hints::InactiveCode {\n                                span: exprs.span.clone(),\n                                reason: msg.clone(),\n                            });\n                        log::info!(\"{msg}, skipping associated aliases\");\n                        return Ok(());\n                    }\n                }\n                Err(err) => {\n                    bail_expr!(expr, \"{err}\");\n                }\n            }\n            log::info!(\"Found env var ({env_var_name} {env_var_value}), using associated aliases\");\n        }\n        None => bail_expr!(&exprs.t[0], \"Missing a list item.\\n{conderr}\"),\n    };\n    read_alias_name_action_pairs(subexprs, s)\n}\n\nfn read_alias_name_action_pairs<'a>(\n    mut exprs: impl Iterator<Item = &'a SExpr>,\n    s: &mut ParserState,\n) -> Result<()> {\n    // Read k-v pairs from the configuration\n    while let Some(alias_expr) = exprs.next() {\n        let alias = match alias_expr {\n            SExpr::Atom(a) => &a.t,\n            _ => bail_expr!(\n                alias_expr,\n                \"Alias names cannot be lists. Invalid alias: {:?}\",\n                alias_expr\n            ),\n        };\n        let action = match exprs.next() {\n            Some(v) => v,\n            None => bail_expr!(alias_expr, \"Found alias without an action - add an action\"),\n        };\n        let action = parse_action(action, s)?;\n        if s.aliases.insert(alias.into(), action).is_some() {\n            bail_expr!(alias_expr, \"Duplicate alias: {}\", alias);\n        }\n        #[cfg(feature = \"lsp\")]\n        s.lsp_hints\n            .borrow_mut()\n            .definition_locations\n            .alias\n            .insert(alias.into(), alias_expr.span());\n    }\n    Ok(())\n}\n\n/// Parse a `kanata_keyberon::action::Action` from a `SExpr`.\nfn parse_action(expr: &SExpr, s: &ParserState) -> Result<&'static KanataAction> {\n    expr.atom(s.vars())\n        .map(|a| parse_action_atom(&Spanned::new(a.into(), expr.span()), s))\n        .unwrap_or_else(|| {\n            expr.list(s.vars())\n                .map(|l| parse_action_list(l, s))\n                .expect(\"must be atom or list\")\n        })\n        .map_err(|mut e| {\n            if e.span.is_none() {\n                e.span = Some(expr.span())\n            };\n            e\n        })\n}\n\n/// Returns a single custom action in the proper wrapped type.\nfn custom(ca: CustomAction, a: &Allocations) -> Result<&'static KanataAction> {\n    Ok(a.sref(Action::Custom(a.sref(a.sref_slice(ca)))))\n}\n\n/// Parse a `kanata_keyberon::action::Action` from a string.\nfn parse_action_atom(ac_span: &Spanned<String>, s: &ParserState) -> Result<&'static KanataAction> {\n    let ac = &*ac_span.t;\n    if is_list_action(ac) {\n        bail_span!(\n            ac_span,\n            \"This is a list action and must be in parentheses: ({ac} ...)\"\n        );\n    }\n    match ac {\n        \"_\" | \"‗\" | \"≝\" => {\n            if let Some(trans_forbidden_reason) = s.pctx.trans_forbidden_reason {\n                bail_span!(ac_span, \"{trans_forbidden_reason}\");\n            } else {\n                return Ok(s.a.sref(Action::Trans));\n            }\n        }\n        \"XX\" | \"✗\" | \"∅\" | \"•\" => {\n            return Ok(s.a.sref(Action::NoOp));\n        }\n        \"lrld\" => return custom(CustomAction::LiveReload, &s.a),\n        \"lrld-next\" | \"lrnx\" => return custom(CustomAction::LiveReloadNext, &s.a),\n        \"lrld-prev\" | \"lrpv\" => return custom(CustomAction::LiveReloadPrev, &s.a),\n        \"sldr\" => {\n            return custom(\n                CustomAction::SequenceLeader(\n                    s.default_sequence_timeout,\n                    s.default_sequence_input_mode,\n                ),\n                &s.a,\n            );\n        }\n        \"scnl\" => return custom(CustomAction::SequenceCancel, &s.a),\n        \"mlft\" | \"mouseleft\" => return custom(CustomAction::Mouse(Btn::Left), &s.a),\n        \"mrgt\" | \"mouseright\" => return custom(CustomAction::Mouse(Btn::Right), &s.a),\n        \"mmid\" | \"mousemid\" => return custom(CustomAction::Mouse(Btn::Mid), &s.a),\n        \"mfwd\" | \"mouseforward\" => return custom(CustomAction::Mouse(Btn::Forward), &s.a),\n        \"mbck\" | \"mousebackward\" => return custom(CustomAction::Mouse(Btn::Backward), &s.a),\n        \"mltp\" | \"mousetapleft\" => return custom(CustomAction::MouseTap(Btn::Left), &s.a),\n        \"mrtp\" | \"mousetapright\" => return custom(CustomAction::MouseTap(Btn::Right), &s.a),\n        \"mmtp\" | \"mousetapmid\" => return custom(CustomAction::MouseTap(Btn::Mid), &s.a),\n        \"mftp\" | \"mousetapforward\" => return custom(CustomAction::MouseTap(Btn::Forward), &s.a),\n        \"mbtp\" | \"mousetapbackward\" => return custom(CustomAction::MouseTap(Btn::Backward), &s.a),\n        \"mwu\" | \"mousewheelup\" => {\n            return custom(\n                CustomAction::MWheelNotch {\n                    direction: MWheelDirection::Up,\n                },\n                &s.a,\n            );\n        }\n        \"mwd\" | \"mousewheeldown\" => {\n            return custom(\n                CustomAction::MWheelNotch {\n                    direction: MWheelDirection::Down,\n                },\n                &s.a,\n            );\n        }\n        \"mwl\" | \"mousewheelleft\" => {\n            return custom(\n                CustomAction::MWheelNotch {\n                    direction: MWheelDirection::Left,\n                },\n                &s.a,\n            );\n        }\n        \"mwr\" | \"mousewheelright\" => {\n            return custom(\n                CustomAction::MWheelNotch {\n                    direction: MWheelDirection::Right,\n                },\n                &s.a,\n            );\n        }\n        \"rpt\" | \"repeat\" | \"rpt-key\" => return custom(CustomAction::Repeat, &s.a),\n        \"rpt-any\" => return Ok(s.a.sref(Action::Repeat)),\n        \"dynamic-macro-record-stop\" => {\n            return custom(CustomAction::DynamicMacroRecordStop(0), &s.a);\n        }\n        \"reverse-release-order\" => match s.multi_action_nest_count.get() {\n            0 => bail_span!(\n                ac_span,\n                \"reverse-release-order is only allowed inside of a (multi ...) action list\"\n            ),\n            _ => return custom(CustomAction::ReverseReleaseOrder, &s.a),\n        },\n        \"use-defsrc\" => {\n            return Ok(s.a.sref(Action::Src));\n        }\n        \"mvmt\" | \"mousemovement\" | \"🖰mv\" => {\n            bail_span!(ac_span, \"{ac} can only be used as an input\")\n        }\n        _ => {}\n    };\n    if let Some(oscode) = str_to_oscode(ac) {\n        if matches!(ac, \"comp\" | \"cmp\") {\n            log::warn!(\n                \"comp/cmp/cmps is not actually a compose key even though its correpsonding code is KEY_COMPOSE. Its actual functionality is context menu which somewhat behaves like right-click.\\nTo remove this warning, replace this usage with an equivalent key name such as: menu\"\n            );\n        }\n        return Ok(s.a.sref(k(oscode.into())));\n    }\n    if let Some(alias) = ac.strip_prefix('@') {\n        return match s.aliases.get(alias) {\n            Some(ac) => {\n                #[cfg(feature = \"lsp\")]\n                s.lsp_hints\n                    .borrow_mut()\n                    .reference_locations\n                    .alias\n                    .push(alias, ac_span.span.clone());\n                Ok(*ac)\n            }\n            None => match s.pctx.is_within_defvirtualkeys {\n                true => bail_span!(\n                    ac_span,\n                    \"Aliases are not usable within defvirtualkeys. You may use vars or templates.\",\n                ),\n                false => bail_span!(\n                    ac_span,\n                    \"Referenced unknown alias {}. Note that order of declarations matter.\",\n                    alias\n                ),\n            },\n        };\n    }\n    if let Some(unisym) = ac.strip_prefix('🔣') {\n        // TODO: when unicode accepts multiple chars, change this to feed the whole string, not just the first char\n        return custom(\n            CustomAction::Unicode(unisym.chars().next().expect(\"1 char\")),\n            &s.a,\n        );\n    }\n    // Parse a sequence like `C-S-v` or `C-A-del`\n    let (mut keys, unparsed_str) = parse_mod_prefix(ac)?;\n    keys.push(\n        str_to_oscode(unparsed_str)\n            .ok_or_else(|| {\n                // check aliases\n                if s.aliases.contains_key(ac) {\n                    anyhow!(\"Unknown key/action: {ac}. If you meant to use an alias, prefix it with '@' symbol: @{ac}\")\n                } else if s.vars.contains_key(ac) {\n                    anyhow!(\"Unknown key/action: {ac}. If you meant to use a variable, prefix it with '$' symbol: ${ac}\")\n                } else {\n                    anyhow!(\"Unknown key/action: {ac}\")\n                }\n            })?\n            .into(),\n    );\n    if keys.contains(&KEY_OVERLAP) {\n        bail!(\"O- is only valid in sequences for lists of keys\");\n    }\n    Ok(s.a.sref(Action::MultipleKeyCodes(s.a.sref(s.a.sref_vec(keys)))))\n}\n\n/// Parse a `kanata_keyberon::action::Action` from a `SExpr::List`.\nfn parse_action_list(ac: &[SExpr], s: &ParserState) -> Result<&'static KanataAction> {\n    if ac.is_empty() {\n        return Ok(s.a.sref(Action::NoOp));\n    }\n    let ac_type = match &ac[0] {\n        SExpr::Atom(a) => &a.t,\n        _ => bail!(\"All list actions must start with string and not a list\"),\n    };\n    if !is_list_action(ac_type) {\n        bail_expr!(&ac[0], \"Unknown action type: {ac_type}\");\n    }\n    match ac_type.as_str() {\n        LAYER_SWITCH => parse_layer_base(&ac[1..], s),\n        LAYER_TOGGLE | LAYER_WHILE_HELD => parse_layer_toggle(&ac[1..], s),\n        TAP_HOLD => parse_tap_hold(&ac[1..], s, HoldTapConfig::Default),\n        TAP_HOLD_PRESS | TAP_HOLD_PRESS_A => {\n            parse_tap_hold(&ac[1..], s, HoldTapConfig::HoldOnOtherKeyPress)\n        }\n        TAP_HOLD_ORDER => parse_tap_hold_order(&ac[1..], s),\n        TAP_HOLD_RELEASE | TAP_HOLD_RELEASE_A => {\n            parse_tap_hold(&ac[1..], s, HoldTapConfig::PermissiveHold)\n        }\n        TAP_HOLD_PRESS_TIMEOUT | TAP_HOLD_PRESS_TIMEOUT_A => {\n            parse_tap_hold_timeout(&ac[1..], s, HoldTapConfig::HoldOnOtherKeyPress)\n        }\n        TAP_HOLD_RELEASE_TIMEOUT | TAP_HOLD_RELEASE_TIMEOUT_A => {\n            parse_tap_hold_timeout(&ac[1..], s, HoldTapConfig::PermissiveHold)\n        }\n        TAP_HOLD_RELEASE_KEYS_TAP_RELEASE => parse_tap_hold_keys_trigger_tap_release(&ac[1..], s),\n        TAP_HOLD_RELEASE_KEYS | TAP_HOLD_RELEASE_KEYS_A => {\n            parse_tap_hold_keys(&ac[1..], s, TAP_HOLD_RELEASE_KEYS, custom_tap_hold_release)\n        }\n        TAP_HOLD_EXCEPT_KEYS | TAP_HOLD_EXCEPT_KEYS_A => {\n            parse_tap_hold_keys(&ac[1..], s, TAP_HOLD_EXCEPT_KEYS, custom_tap_hold_except)\n        }\n        TAP_HOLD_TAP_KEYS | TAP_HOLD_TAP_KEYS_A => {\n            parse_tap_hold_keys(&ac[1..], s, TAP_HOLD_TAP_KEYS, custom_tap_hold_tap_keys)\n        }\n        TAP_HOLD_OPPOSITE_HAND => parse_tap_hold_opposite_hand(&ac[1..], s),\n        MULTI => parse_multi(&ac[1..], s),\n        MACRO => parse_macro(&ac[1..], s, RepeatMacro::No),\n        MACRO_REPEAT | MACRO_REPEAT_A => parse_macro(&ac[1..], s, RepeatMacro::Yes),\n        MACRO_RELEASE_CANCEL | MACRO_RELEASE_CANCEL_A => {\n            parse_macro_release_cancel(&ac[1..], s, RepeatMacro::No)\n        }\n        MACRO_REPEAT_RELEASE_CANCEL | MACRO_REPEAT_RELEASE_CANCEL_A => {\n            parse_macro_release_cancel(&ac[1..], s, RepeatMacro::Yes)\n        }\n        MACRO_CANCEL_ON_NEXT_PRESS => {\n            parse_macro_cancel_on_next_press(&ac[1..], s, RepeatMacro::No)\n        }\n        MACRO_REPEAT_CANCEL_ON_NEXT_PRESS => {\n            parse_macro_cancel_on_next_press(&ac[1..], s, RepeatMacro::Yes)\n        }\n        MACRO_CANCEL_ON_NEXT_PRESS_CANCEL_ON_RELEASE => {\n            parse_macro_cancel_on_next_press_cancel_on_release(&ac[1..], s, RepeatMacro::No)\n        }\n        MACRO_REPEAT_CANCEL_ON_NEXT_PRESS_CANCEL_ON_RELEASE => {\n            parse_macro_cancel_on_next_press_cancel_on_release(&ac[1..], s, RepeatMacro::Yes)\n        }\n        UNICODE | SYM => parse_unicode(&ac[1..], s),\n        ONE_SHOT | ONE_SHOT_PRESS | ONE_SHOT_PRESS_A => {\n            parse_one_shot(&ac[1..], s, OneShotEndConfig::EndOnFirstPress)\n        }\n        ONE_SHOT_RELEASE | ONE_SHOT_RELEASE_A => {\n            parse_one_shot(&ac[1..], s, OneShotEndConfig::EndOnFirstRelease)\n        }\n        ONE_SHOT_PRESS_PCANCEL | ONE_SHOT_PRESS_PCANCEL_A => {\n            parse_one_shot(&ac[1..], s, OneShotEndConfig::EndOnFirstPressOrRepress)\n        }\n        ONE_SHOT_RELEASE_PCANCEL | ONE_SHOT_RELEASE_PCANCEL_A => {\n            parse_one_shot(&ac[1..], s, OneShotEndConfig::EndOnFirstReleaseOrRepress)\n        }\n        ONE_SHOT_PAUSE_PROCESSING => parse_one_shot_pause_processing(&ac[1..], s),\n        TAP_DANCE => parse_tap_dance(&ac[1..], s, TapDanceConfig::Lazy),\n        TAP_DANCE_EAGER => parse_tap_dance(&ac[1..], s, TapDanceConfig::Eager),\n        CHORD => parse_chord(&ac[1..], s),\n        RELEASE_KEY | RELEASE_KEY_A => parse_release_key(&ac[1..], s),\n        RELEASE_LAYER | RELEASE_LAYER_A => parse_release_layer(&ac[1..], s),\n        ON_PRESS_FAKEKEY | ON_PRESS_FAKEKEY_A => parse_on_press_fake_key_op(&ac[1..], s),\n        ON_RELEASE_FAKEKEY | ON_RELEASE_FAKEKEY_A => parse_on_release_fake_key_op(&ac[1..], s),\n        ON_PRESS_DELAY | ON_PRESS_FAKEKEY_DELAY | ON_PRESS_FAKEKEY_DELAY_A => {\n            parse_fake_key_delay(&ac[1..], s)\n        }\n        ON_RELEASE_DELAY | ON_RELEASE_FAKEKEY_DELAY | ON_RELEASE_FAKEKEY_DELAY_A => {\n            parse_on_release_fake_key_delay(&ac[1..], s)\n        }\n        ON_IDLE_FAKEKEY => parse_on_idle_fakekey(&ac[1..], s),\n        ON_PRESS | ON_PRESS_A => parse_on_press(&ac[1..], s),\n        ON_RELEASE | ON_RELEASE_A => parse_on_release(&ac[1..], s),\n        ON_IDLE => parse_on_idle(&ac[1..], s),\n        ON_PHYSICAL_IDLE => parse_on_physical_idle(&ac[1..], s),\n        HOLD_FOR_DURATION => parse_hold_for_duration(&ac[1..], s),\n        MWHEEL_UP | MWHEEL_UP_A => parse_mwheel(&ac[1..], MWheelDirection::Up, s),\n        MWHEEL_DOWN | MWHEEL_DOWN_A => parse_mwheel(&ac[1..], MWheelDirection::Down, s),\n        MWHEEL_LEFT | MWHEEL_LEFT_A => parse_mwheel(&ac[1..], MWheelDirection::Left, s),\n        MWHEEL_RIGHT | MWHEEL_RIGHT_A => parse_mwheel(&ac[1..], MWheelDirection::Right, s),\n        MWHEEL_ACCEL_UP => parse_mwheel_accel(&ac[1..], MWheelDirection::Up, s),\n        MWHEEL_ACCEL_DOWN => parse_mwheel_accel(&ac[1..], MWheelDirection::Down, s),\n        MWHEEL_ACCEL_LEFT => parse_mwheel_accel(&ac[1..], MWheelDirection::Left, s),\n        MWHEEL_ACCEL_RIGHT => parse_mwheel_accel(&ac[1..], MWheelDirection::Right, s),\n        MOVEMOUSE_UP | MOVEMOUSE_UP_A => parse_move_mouse(&ac[1..], MoveDirection::Up, s),\n        MOVEMOUSE_DOWN | MOVEMOUSE_DOWN_A => parse_move_mouse(&ac[1..], MoveDirection::Down, s),\n        MOVEMOUSE_LEFT | MOVEMOUSE_LEFT_A => parse_move_mouse(&ac[1..], MoveDirection::Left, s),\n        MOVEMOUSE_RIGHT | MOVEMOUSE_RIGHT_A => parse_move_mouse(&ac[1..], MoveDirection::Right, s),\n        MOVEMOUSE_ACCEL_UP | MOVEMOUSE_ACCEL_UP_A => {\n            parse_move_mouse_accel(&ac[1..], MoveDirection::Up, s)\n        }\n        MOVEMOUSE_ACCEL_DOWN | MOVEMOUSE_ACCEL_DOWN_A => {\n            parse_move_mouse_accel(&ac[1..], MoveDirection::Down, s)\n        }\n        MOVEMOUSE_ACCEL_LEFT | MOVEMOUSE_ACCEL_LEFT_A => {\n            parse_move_mouse_accel(&ac[1..], MoveDirection::Left, s)\n        }\n        MOVEMOUSE_ACCEL_RIGHT | MOVEMOUSE_ACCEL_RIGHT_A => {\n            parse_move_mouse_accel(&ac[1..], MoveDirection::Right, s)\n        }\n        MOVEMOUSE_SPEED | MOVEMOUSE_SPEED_A => parse_move_mouse_speed(&ac[1..], s),\n        SETMOUSE | SETMOUSE_A => parse_set_mouse(&ac[1..], s),\n        DYNAMIC_MACRO_RECORD => parse_dynamic_macro_record(&ac[1..], s),\n        DYNAMIC_MACRO_PLAY => parse_dynamic_macro_play(&ac[1..], s),\n        ARBITRARY_CODE => parse_arbitrary_code(&ac[1..], s),\n        CMD => parse_cmd(&ac[1..], s, CmdType::Standard),\n        CMD_OUTPUT_KEYS => parse_cmd(&ac[1..], s, CmdType::OutputKeys),\n        CMD_LOG => parse_cmd_log(&ac[1..], s),\n        PUSH_MESSAGE => parse_push_message(&ac[1..], s),\n        FORK => parse_fork(&ac[1..], s),\n        CAPS_WORD | CAPS_WORD_A => {\n            parse_caps_word(&ac[1..], CapsWordRepressBehaviour::Overwrite, s)\n        }\n        CAPS_WORD_CUSTOM | CAPS_WORD_CUSTOM_A => {\n            parse_caps_word_custom(&ac[1..], CapsWordRepressBehaviour::Overwrite, s)\n        }\n        CAPS_WORD_TOGGLE | CAPS_WORD_TOGGLE_A => {\n            parse_caps_word(&ac[1..], CapsWordRepressBehaviour::Toggle, s)\n        }\n        CAPS_WORD_CUSTOM_TOGGLE | CAPS_WORD_CUSTOM_TOGGLE_A => {\n            parse_caps_word_custom(&ac[1..], CapsWordRepressBehaviour::Toggle, s)\n        }\n        DYNAMIC_MACRO_RECORD_STOP_TRUNCATE => parse_macro_record_stop_truncate(&ac[1..], s),\n        SWITCH => parse_switch(&ac[1..], s),\n        SEQUENCE => parse_sequence_start(&ac[1..], s),\n        SEQUENCE_NOERASE => parse_sequence_noerase(&ac[1..], s),\n        UNMOD => parse_unmod(UNMOD, &ac[1..], s),\n        UNSHIFT | UNSHIFT_A => parse_unmod(UNSHIFT, &ac[1..], s),\n        LIVE_RELOAD_NUM => parse_live_reload_num(&ac[1..], s),\n        LIVE_RELOAD_FILE => parse_live_reload_file(&ac[1..], s),\n        CLIPBOARD_SET => parse_clipboard_set(&ac[1..], s),\n        CLIPBOARD_CMD_SET => parse_cmd(&ac[1..], s, CmdType::ClipboardSet),\n        CLIPBOARD_SAVE => parse_clipboard_save(&ac[1..], s),\n        CLIPBOARD_RESTORE => parse_clipboard_restore(&ac[1..], s),\n        CLIPBOARD_SAVE_SET => parse_clipboard_save_set(&ac[1..], s),\n        CLIPBOARD_SAVE_CMD_SET => parse_cmd(&ac[1..], s, CmdType::ClipboardSaveSet),\n        CLIPBOARD_SAVE_SWAP => parse_clipboard_save_swap(&ac[1..], s),\n        _ => unreachable!(),\n    }\n}\n\nfn layer_idx(ac_params: &[SExpr], layers: &LayerIndexes, s: &ParserState) -> Result<usize> {\n    if ac_params.len() != 1 {\n        bail!(\n            \"Layer actions expect one item: the layer name, found {} items\",\n            ac_params.len()\n        )\n    }\n    let layer_name = ac_params[0]\n        .atom(s.vars())\n        .ok_or_else(|| anyhow_expr!(&ac_params[0], \"layer name should be a string not a list\",))?;\n    match layers.get(layer_name) {\n        Some(i) => Ok(*i),\n        None => err_expr!(\n            &ac_params[0],\n            \"layer name is not declared in any deflayer: {layer_name}\"\n        ),\n    }\n}\n\n/// Parse a list expression with length 2 having format:\n///     (name value)\n/// The items name and value must both be strings.\n/// The name string is validated to ensure it matches the input.\n/// The value is parsed into a u8.\n#[allow(unused)]\nfn parse_named_u8_param(name: &str, name_and_param: &SExpr, s: &ParserState) -> Result<u8> {\n    let err = || {\n        format!(\n            \"Expected a list with two items: {name} followed by a number. Example:\\n\\\n             ({name} 2)\"\n        )\n    };\n    let Some(list) = name_and_param.list(s.vars()) else {\n        bail_expr!(name_and_param, \"{}\", err());\n    };\n    if list.len() != 2 {\n        bail_expr!(name_and_param, \"{}\", err());\n    }\n    let Some(expr_name) = list[0].atom(s.vars()) else {\n        bail_expr!(&list[0], \"Expected {name}\");\n    };\n    if expr_name != name {\n        bail_expr!(&list[0], \"Expected {name}\");\n    }\n    parse_u8_with_range(&list[1], s, name, 0, 255)\n}\n\nfn parse_u8_with_range(expr: &SExpr, s: &ParserState, label: &str, min: u8, max: u8) -> Result<u8> {\n    expr.atom(s.vars())\n        .map(str::parse::<u8>)\n        .and_then(|u| u.ok())\n        .and_then(|u| {\n            assert!(min <= max);\n            if u >= min && u <= max { Some(u) } else { None }\n        })\n        .ok_or_else(|| anyhow_expr!(expr, \"{label} must be {min}-{max}\"))\n}\n\nfn parse_u16(expr: &SExpr, s: &ParserState, label: &str) -> Result<u16> {\n    expr.atom(s.vars())\n        .map(str::parse::<u16>)\n        .and_then(|u| u.ok())\n        .ok_or_else(|| anyhow_expr!(expr, \"{label} must be 0-65535\"))\n}\n\nfn parse_f32(\n    expr: &SExpr,\n    s: &ParserState,\n    label: &str,\n    min: f32,\n    max: f32,\n) -> Result<OrderedFloat<f32>> {\n    expr.atom(s.vars())\n        .map(str::parse::<f32>)\n        .and_then(|u| {\n            u.ok().and_then(|v| {\n                if v >= min && v <= max {\n                    Some(v.into())\n                } else {\n                    None\n                }\n            })\n        })\n        .ok_or_else(|| anyhow_expr!(expr, \"{label} must be {min:.2}-{max:.2}\"))\n}\n\n// Note on allows:\n// - macOS CI is behind on Rust version.\n// - Clippy bug in new lint of Rust v1.86.\n#[allow(unknown_lints)]\n#[allow(clippy::manual_ok_err)]\nfn parse_non_zero_u16(expr: &SExpr, s: &ParserState, label: &str) -> Result<u16> {\n    expr.atom(s.vars())\n        .map(str::parse::<u16>)\n        .and_then(|u| match u {\n            Ok(u @ 1..) => Some(u),\n            _ => None,\n        })\n        .ok_or_else(|| anyhow_expr!(expr, \"{label} must be 1-65535\"))\n}\n\nfn parse_key_list(expr: &SExpr, s: &ParserState, label: &str) -> Result<Vec<OsCode>> {\n    expr.list(s.vars())\n        .map(|keys| {\n            keys.iter().try_fold(vec![], |mut keys, key| {\n                key.atom(s.vars())\n                    .map(|a| -> Result<()> {\n                        keys.push(str_to_oscode(a).ok_or_else(|| {\n                            anyhow_expr!(key, \"string of a known key is expected\")\n                        })?);\n                        Ok(())\n                    })\n                    .ok_or_else(|| {\n                        anyhow_expr!(key, \"string of a known key is expected, found list instead\")\n                    })??;\n                Ok(keys)\n            })\n        })\n        .ok_or_else(|| anyhow_expr!(expr, \"{label} must be a list of keys\"))?\n}\n\nstatic KEYMODI: &[(&str, KeyCode)] = &[\n    (\"S-\", KeyCode::LShift),\n    (\"‹⇧\", KeyCode::LShift),\n    (\"⇧›\", KeyCode::RShift),\n    (\"RS-\", KeyCode::RShift),\n    (\"C-\", KeyCode::LCtrl),\n    (\"‹⎈\", KeyCode::LCtrl),\n    (\"‹⌃\", KeyCode::LCtrl),\n    (\"⎈›\", KeyCode::RCtrl),\n    (\"⌃›\", KeyCode::RCtrl),\n    (\"RC-\", KeyCode::RCtrl),\n    (\"M-\", KeyCode::LGui),\n    (\"‹◆\", KeyCode::LGui),\n    (\"‹⌘\", KeyCode::LGui),\n    (\"‹❖\", KeyCode::LGui),\n    (\"◆›\", KeyCode::RGui),\n    (\"⌘›\", KeyCode::RGui),\n    (\"❖›\", KeyCode::RGui),\n    (\"RM-\", KeyCode::RGui),\n    (\"‹⎇\", KeyCode::LAlt),\n    (\"A-\", KeyCode::LAlt),\n    (\"‹⌥\", KeyCode::LAlt),\n    (\"AG-\", KeyCode::RAlt),\n    (\"RA-\", KeyCode::RAlt),\n    (\"⎇›\", KeyCode::RAlt),\n    (\"⌥›\", KeyCode::RAlt),\n    (\"⎈\", KeyCode::LCtrl), // Shorter indicators should be at the end to only get matched after\n    // indicators with sides have had a chance\n    (\"⌥\", KeyCode::LAlt),\n    (\"⎇\", KeyCode::LAlt),\n    (\"◆\", KeyCode::LGui),\n    (\"⌘\", KeyCode::LGui),\n    (\"❖\", KeyCode::LGui),\n    (\"O-\", KEY_OVERLAP),\n];\n\n/// Parses mod keys like `C-S-`. Returns the `KeyCode`s for the modifiers parsed and the unparsed\n/// text after any parsed modifier prefixes.\npub fn parse_mod_prefix(mods: &str) -> Result<(Vec<KeyCode>, &str)> {\n    let mut key_stack = Vec::new();\n    let mut rem = mods;\n    loop {\n        let mut found_none = true;\n        for (key_s, key_code) in KEYMODI {\n            if let Some(rest) = rem.strip_prefix(key_s) {\n                if key_stack.contains(key_code) {\n                    bail!(\"Redundant \\\"{key_code:?}\\\" in {mods:?}\");\n                }\n                key_stack.push(*key_code);\n                rem = rest;\n                found_none = false;\n            }\n        }\n        if found_none {\n            break;\n        }\n    }\n    Ok((key_stack, rem))\n}\n"
  },
  {
    "path": "parser/src/cfg/mouse.rs",
    "content": "use super::*;\n\nuse crate::anyhow_expr;\nuse crate::bail;\n\npub(crate) fn parse_distance(expr: &SExpr, s: &ParserState, label: &str) -> Result<u16> {\n    expr.atom(s.vars())\n        .map(str::parse::<u16>)\n        .and_then(|d| d.ok())\n        .ok_or_else(|| anyhow_expr!(expr, \"{label} must be 1-30000\"))\n}\n\npub(crate) fn parse_mwheel(\n    ac_params: &[SExpr],\n    direction: MWheelDirection,\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"mwheel expects 2 parameters: <interval (ms)> <distance>\";\n    if ac_params.len() != 2 {\n        bail!(\"{ERR_MSG}, found {}\", ac_params.len());\n    }\n    let interval = parse_non_zero_u16(&ac_params[0], s, \"interval\")?;\n    let distance = parse_distance(&ac_params[1], s, \"distance\")?;\n    Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice(\n        CustomAction::MWheel {\n            direction,\n            interval,\n            distance,\n            inertial_scroll_params: None,\n        },\n    )))))\n}\n\npub(crate) fn parse_mwheel_accel(\n    ac_params: &[SExpr],\n    direction: MWheelDirection,\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"mwheel-accel expects 4 float32 parameters:\\n\\\n                           - initial velocity\\n- maximum velocity\\n\\\n                           - acceleration multiplier\\n- deceleration multiplier\";\n    if ac_params.len() != 4 {\n        bail!(\"{ERR_MSG}, found {}\", ac_params.len());\n    }\n    let initial_velocity = parse_f32(&ac_params[0], s, \"initial velocity\", 1.0, 12000.0)?;\n    let maximum_velocity = parse_f32(&ac_params[1], s, \"maximum velocity\", 1.0, 12000.0)?;\n    let acceleration_multiplier =\n        parse_f32(&ac_params[2], s, \"acceleration multiplier\", 1.0, 1000.0)?;\n    let deceleration_multiplier =\n        parse_f32(&ac_params[3], s, \"deceleration multiplier\", 0.0, 0.99)?;\n    Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice(\n        CustomAction::MWheel {\n            direction,\n            interval: 16,\n            distance: 1,\n            inertial_scroll_params: Some(s.a.sref(MWheelInertial {\n                initial_velocity,\n                maximum_velocity,\n                acceleration_multiplier,\n                deceleration_multiplier,\n            })),\n        },\n    )))))\n}\n\npub(crate) fn parse_move_mouse(\n    ac_params: &[SExpr],\n    direction: MoveDirection,\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"movemouse expects 2 parameters: <interval (ms)> <distance (px)>\";\n    if ac_params.len() != 2 {\n        bail!(\"{ERR_MSG}, found {}\", ac_params.len());\n    }\n    let interval = parse_non_zero_u16(&ac_params[0], s, \"interval\")?;\n    let distance = parse_distance(&ac_params[1], s, \"distance\")?;\n    Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice(\n        CustomAction::MoveMouse {\n            direction,\n            interval,\n            distance,\n        },\n    )))))\n}\n\npub(crate) fn parse_move_mouse_accel(\n    ac_params: &[SExpr],\n    direction: MoveDirection,\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    if ac_params.len() != 4 {\n        bail!(\n            \"movemouse-accel expects four parameters, found {}\\n<interval (ms)> <acceleration time (ms)> <min_distance> <max_distance>\",\n            ac_params.len()\n        );\n    }\n    let interval = parse_non_zero_u16(&ac_params[0], s, \"interval\")?;\n    let accel_time = parse_non_zero_u16(&ac_params[1], s, \"acceleration time\")?;\n    let min_distance = parse_distance(&ac_params[2], s, \"min distance\")?;\n    let max_distance = parse_distance(&ac_params[3], s, \"max distance\")?;\n    if min_distance > max_distance {\n        bail!(\"min distance should be less than max distance\")\n    }\n    Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice(\n        CustomAction::MoveMouseAccel {\n            direction,\n            interval,\n            accel_time,\n            min_distance,\n            max_distance,\n        },\n    )))))\n}\n\npub(crate) fn parse_move_mouse_speed(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    if ac_params.len() != 1 {\n        bail!(\n            \"movemouse-speed expects one parameter, found {}\\n<speed scaling % (1-65535)>\",\n            ac_params.len()\n        );\n    }\n    let speed = parse_non_zero_u16(&ac_params[0], s, \"speed scaling %\")?;\n    Ok(s.a.sref(Action::Custom(\n        s.a.sref(s.a.sref_slice(CustomAction::MoveMouseSpeed { speed })),\n    )))\n}\n\npub(crate) fn parse_set_mouse(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    if ac_params.len() != 2 {\n        bail!(\n            \"movemouse-accel expects two parameters, found {}: <x> <y>\",\n            ac_params.len()\n        );\n    }\n    let x = parse_u16(&ac_params[0], s, \"x\")?;\n    let y = parse_u16(&ac_params[1], s, \"y\")?;\n    Ok(s.a.sref(Action::Custom(\n        s.a.sref(s.a.sref_slice(CustomAction::SetMouse { x, y })),\n    )))\n}\n"
  },
  {
    "path": "parser/src/cfg/multi.rs",
    "content": "use super::*;\n\nuse crate::bail;\n\npub(crate) fn parse_multi(ac_params: &[SExpr], s: &ParserState) -> Result<&'static KanataAction> {\n    if ac_params.is_empty() {\n        bail!(\"multi expects at least one item after it\")\n    }\n    s.multi_action_nest_count\n        .replace(s.multi_action_nest_count.get().saturating_add(1));\n    let mut actions = Vec::new();\n    let mut custom_actions: Vec<&'static CustomAction> = Vec::new();\n    for expr in ac_params {\n        let ac = parse_action(expr, s)?;\n        match ac {\n            Action::Custom(acs) => {\n                for ac in acs.iter() {\n                    custom_actions.push(ac);\n                }\n            }\n            // Flatten multi actions\n            Action::MultipleActions(acs) => {\n                for ac in acs.iter() {\n                    match ac {\n                        Action::Custom(acs) => {\n                            for ac in acs.iter() {\n                                custom_actions.push(ac);\n                            }\n                        }\n                        _ => actions.push(*ac),\n                    }\n                }\n            }\n            _ => actions.push(*ac),\n        }\n    }\n\n    if !custom_actions.is_empty() {\n        actions.push(Action::Custom(s.a.sref(s.a.sref_vec(custom_actions))));\n    }\n\n    if actions\n        .iter()\n        .filter(|ac| {\n            matches!(\n                ac,\n                Action::TapDance(TapDance {\n                    config: TapDanceConfig::Lazy,\n                    ..\n                }) | Action::HoldTap { .. }\n                    | Action::Chords { .. }\n            )\n        })\n        .count()\n        > 1\n    {\n        bail!(\"Cannot combine multiple tap-hold/tap-dance/chord\");\n    }\n\n    s.multi_action_nest_count\n        .replace(s.multi_action_nest_count.get().saturating_sub(1));\n    Ok(s.a.sref(Action::MultipleActions(s.a.sref(s.a.sref_vec(actions)))))\n}\n"
  },
  {
    "path": "parser/src/cfg/oneshot.rs",
    "content": "use super::*;\n\nuse crate::bail;\n\npub(crate) fn parse_one_shot(\n    ac_params: &[SExpr],\n    s: &ParserState,\n    end_config: OneShotEndConfig,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"one-shot expects a timeout followed by a key or action\";\n    if ac_params.len() != 2 {\n        bail!(ERR_MSG);\n    }\n\n    let timeout = parse_non_zero_u16(&ac_params[0], s, \"timeout\")?;\n    let action = parse_action(&ac_params[1], s)?;\n    if !matches!(\n        action,\n        Action::Layer(..) | Action::KeyCode(..) | Action::MultipleKeyCodes(..)\n    ) {\n        bail!(\"one-shot is only allowed to contain layer-while-held, a keycode, or a chord\");\n    }\n\n    Ok(s.a.sref(Action::OneShot(s.a.sref(OneShot {\n        timeout,\n        action,\n        end_config,\n    }))))\n}\n\npub(crate) fn parse_one_shot_pause_processing(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"one-shot-pause-processing expects a time\";\n    if ac_params.len() != 1 {\n        bail!(ERR_MSG);\n    }\n    let timeout = parse_non_zero_u16(&ac_params[0], s, \"time (milliseconds)\")?;\n    Ok(s.a.sref(Action::OneShotIgnoreEventsTicks(timeout)))\n}\n"
  },
  {
    "path": "parser/src/cfg/override.rs",
    "content": "use super::*;\n\nuse crate::anyhow_expr;\nuse crate::bail_expr;\n\npub(crate) fn parse_overrides(exprs: &[SExpr], s: &ParserState) -> Result<Overrides> {\n    const ERR_MSG: &str =\n        \"defoverrides expects pairs of parameters: <input key list> <output key list>\";\n    let mut subexprs = check_first_expr(exprs.iter(), \"defoverrides\")?;\n\n    let mut overrides = Vec::<Override>::new();\n    while let Some(in_keys_expr) = subexprs.next() {\n        let out_keys_expr = subexprs\n            .next()\n            .ok_or_else(|| anyhow_expr!(in_keys_expr, \"Missing output keys for input keys\"))?;\n        let (in_keys, out_keys) = parse_override_inout_keys(in_keys_expr, out_keys_expr, s)?;\n        overrides\n            .push(Override::try_new(&in_keys, &out_keys).map_err(|e| anyhow!(\"{ERR_MSG}: {e}\"))?);\n    }\n    log::debug!(\"All overrides:\\n{overrides:#?}\");\n    Ok(Overrides::new(&overrides))\n}\n\npub(crate) fn parse_override_inout_keys(\n    in_keys_expr: &SExpr,\n    out_keys_expr: &SExpr,\n    s: &ParserState,\n) -> Result<(Vec<OsCode>, Vec<OsCode>)> {\n    let out_keys = out_keys_expr\n        .list(s.vars())\n        .ok_or_else(|| anyhow_expr!(out_keys_expr, \"Output keys must be a list\"))?;\n    let in_keys = in_keys_expr\n        .list(s.vars())\n        .ok_or_else(|| anyhow_expr!(in_keys_expr, \"Input keys must be a list\"))?;\n    let in_keys = in_keys\n        .iter()\n        .try_fold(vec![], |mut keys, key_expr| -> Result<Vec<OsCode>> {\n            let key = key_expr\n                .atom(s.vars())\n                .and_then(str_to_oscode)\n                .ok_or_else(|| {\n                    anyhow_expr!(key_expr, \"Unknown input key name, must use known keys\")\n                })?;\n            keys.push(key);\n            Ok(keys)\n        })?;\n    let out_keys =\n        out_keys\n            .iter()\n            .try_fold(vec![], |mut keys, key_expr| -> Result<Vec<OsCode>> {\n                let key = key_expr\n                    .atom(s.vars())\n                    .and_then(str_to_oscode)\n                    .ok_or_else(|| {\n                        anyhow_expr!(key_expr, \"Unknown output key name, must use known keys\")\n                    })?;\n                keys.push(key);\n                Ok(keys)\n            })?;\n    Ok((in_keys, out_keys))\n}\n\npub(crate) fn parse_overridesv2(exprs: &[SExpr], s: &ParserState) -> Result<Overrides> {\n    const ERR_MSG: &str = \"defoverridesv2 expects 4-tuples of parameters: <input key list> <output key list> <without mods> <excluded layers>\";\n    let mut subexprs = check_first_expr(exprs.iter(), \"defoverridesv2\")?;\n\n    let mut overrides = Vec::<Override>::new();\n    while let Some(in_keys_expr) = subexprs.next() {\n        let out_keys_expr = subexprs\n            .next()\n            .ok_or_else(|| anyhow_expr!(in_keys_expr, \"Missing output keys for input keys\"))?;\n        let (in_keys, out_keys) = parse_override_inout_keys(in_keys_expr, out_keys_expr, s)?;\n\n        let without_mods_expr = subexprs\n            .next()\n            .ok_or_else(|| anyhow_expr!(in_keys_expr, \"Missing without mods list\"))?;\n        let without_mods = without_mods_expr.list(s.vars()).ok_or_else(|| {\n            anyhow_expr!(\n                without_mods_expr,\n                \"Without mods configuration must be a list\"\n            )\n        })?;\n\n        let excluded_layers_expr = subexprs\n            .next()\n            .ok_or_else(|| anyhow_expr!(in_keys_expr, \"Missing excluded layers\"))?;\n        let excluded_layers = excluded_layers_expr\n            .list(s.vars())\n            .ok_or_else(|| anyhow_expr!(excluded_layers_expr, \"Excluded layers must be a list\"))?;\n\n        let without_mods =\n            without_mods\n                .iter()\n                .try_fold(vec![], |mut keys, key_expr| -> Result<Vec<OsCode>> {\n                    let key = key_expr\n                        .atom(s.vars())\n                        .and_then(str_to_oscode)\n                        .ok_or_else(|| {\n                            anyhow_expr!(key_expr, \"Unknown key name, must use known keys\")\n                        })\n                        .and_then(|osc| match osc.is_modifier() {\n                            true => Ok(osc),\n                            false => bail_expr!(\n                                key_expr,\n                                \"Keys in without mods must be modifiers, e.g. lctl, ralt\"\n                            ),\n                        })?;\n                    keys.push(key);\n                    Ok(keys)\n                })?;\n\n        let excluded_layers = excluded_layers.iter().try_fold(\n            vec![],\n            |mut layers, layer_expr| -> Result<Vec<u16>> {\n                let layer = layer_expr\n                    .atom(s.vars())\n                    .and_then(|l| s.layer_idxs.get(l))\n                    .ok_or_else(|| anyhow_expr!(layer_expr, \"Unknown layer name\"))?;\n                layers.push(*layer as u16);\n                Ok(layers)\n            },\n        )?;\n\n        overrides.push(\n            Override::try_new_v2(\n                &in_keys,\n                &out_keys,\n                without_mods.into(),\n                excluded_layers.into(),\n            )\n            .map_err(|e| anyhow!(\"{ERR_MSG}: {e}\"))?,\n        );\n    }\n    log::debug!(\"All overrides:\\n{overrides:#?}\");\n    Ok(Overrides::new(&overrides))\n}\n"
  },
  {
    "path": "parser/src/cfg/permutations.rs",
    "content": "//! Implements Heap's algorithm.\n\n/*\nFrom Wikipedia:\n\nprocedure generate(k: integer, A : array of any):\n    if k = 1 then\n        output(A)\n    else\n        // Generate permutations with k-th unaltered\n        // Initially k = length(A)\n        generate(k - 1, A)\n\n        // Generate permutations for k-th swapped with each k-1 initial\n        for i := 0; i < k-1; i += 1 do\n            // Swap choice dependent on parity of k (even or odd)\n            if k is even then\n                swap(A[i], A[k-1]) // zero-indexed, the k-th is at k-1\n            else\n                swap(A[0], A[k-1])\n            end if\n            generate(k - 1, A)\n        end for\n    end if\n*/\n\n/// Heap's algorithm\npub fn gen_permutations<T: Clone + Default>(a: &[T]) -> Vec<Vec<T>> {\n    let mut a2 = vec![Default::default(); a.len()];\n    a2.clone_from_slice(a);\n    let mut outs = vec![];\n    heaps_alg(a.len(), &mut a2, &mut outs);\n    outs\n}\n\nfn heaps_alg<T: Clone>(k: usize, a: &mut [T], outs: &mut Vec<Vec<T>>) {\n    if k == 1 {\n        outs.push(a.to_vec());\n    } else {\n        heaps_alg(k - 1, a, outs);\n        for i in 0..k - 1 {\n            if k.is_multiple_of(2) {\n                a.swap(i, k - 1);\n            } else {\n                a.swap(0, k - 1);\n            }\n            heaps_alg(k - 1, a, outs);\n        }\n    }\n}\n"
  },
  {
    "path": "parser/src/cfg/platform.rs",
    "content": "use super::*;\n\nuse crate::anyhow_expr;\nuse crate::bail_expr;\nuse crate::bail_span;\nuse crate::err_expr;\n\npub(crate) fn filter_platform_specific_cfg(\n    top_levels: Vec<TopLevel>,\n    deflocalkeys_variant_to_apply: &str,\n    _lsp_hints: &mut lsp_hints::LspHints,\n) -> Result<Vec<TopLevel>> {\n    let valid_platform_names = DEFLOCALKEYS_VARIANTS\n        .iter()\n        .map(|dfl| dfl.trim_start_matches(\"deflocalkeys-\"))\n        .collect::<Vec<_>>();\n    let current_platform = deflocalkeys_variant_to_apply.trim_start_matches(\"deflocalkeys-\");\n    top_levels\n        .into_iter()\n        .try_fold(vec![], |mut tles, tle| -> Result<Vec<TopLevel>> {\n            if !matches!(tle.t.first().and_then(|m| m.atom(None)), Some(\"platform\")) {\n                tles.push(tle);\n                return Ok(tles);\n            }\n\n            if tle.t.len() != 3 {\n                bail_span!(\n                    &tle,\n                    \"platform requires exactly two parameters:\\n\\\n                      applicable-platforms, configuration-item\"\n                );\n            }\n\n            let configuration = tle.t[2]\n                .span_list(None)\n                .ok_or_else(|| anyhow_expr!(&tle.t[2], \"configuration-item must be a list\"))?;\n\n            let applicable_platforms = tle.t[1]\n                .list(None)\n                .ok_or_else(|| anyhow_expr!(&tle.t[1], \"applicable-platforms must be a list\"))\n                .and_then(|pf_list| {\n                    pf_list.iter().try_fold(vec![], |mut pfs, pf_expr| {\n                        let good_pf = pf_expr\n                            .atom(None)\n                            .ok_or_else(|| anyhow_expr!(pf_expr, \"platform must be a string\"))\n                            .and_then(|pf| {\n                                if valid_platform_names.contains(&pf) {\n                                    Ok(pf)\n                                } else {\n                                    err_expr!(\n                                        pf_expr,\n                                        \"Unknown platform. Valid platforms:\\n{}\",\n                                        valid_platform_names.join(\" \")\n                                    )\n                                }\n                            })?;\n                        pfs.push(good_pf);\n                        Ok(pfs)\n                    })\n                })?;\n\n            if applicable_platforms.contains(&current_platform) {\n                tles.push(configuration.clone());\n            } else {\n                #[cfg(feature = \"lsp\")]\n                _lsp_hints.inactive_code.push(lsp_hints::InactiveCode {\n                    span: tle.span.clone(),\n                    reason: format!(\n                        \"Current platform \\\"{current_platform}\\\" doesn't match any of: {}\",\n                        applicable_platforms.join(\" \")\n                    ),\n                })\n            }\n\n            Ok(tles)\n        })\n}\n\npub(crate) fn filter_env_specific_cfg(\n    top_levels: Vec<TopLevel>,\n    env: &EnvVars,\n    _lsp_hints: &mut lsp_hints::LspHints,\n) -> Result<Vec<TopLevel>> {\n    top_levels\n        .into_iter()\n        .try_fold(vec![], |mut tles, tle| -> Result<Vec<TopLevel>> {\n            if !matches!(\n                tle.t.first().and_then(|m| m.atom(None)),\n                Some(\"environment\")\n            ) {\n                tles.push(tle);\n                return Ok(tles);\n            }\n            let env = match env.as_ref() {\n                Ok(v) => v,\n                Err(e) => Err(anyhow!(\"{e}\"))?,\n            };\n            if tle.t.len() != 3 {\n                bail_span!(\n                    &tle,\n                    \"environment requires exactly two parameters:\\n\\\n                     varname-varvalue, configuration-item\"\n                );\n            }\n\n            let configuration = tle.t[2]\n                .span_list(None)\n                .ok_or_else(|| anyhow_expr!(&tle.t[2], \"configuration-item must be a list\"))?;\n\n            let (env_var_name, env_var_val) = tle.t[1]\n                .list(None)\n                .ok_or_else(|| anyhow_expr!(&tle.t[1], \"varname-varvalue must be a list\"))\n                .and_then(|varnameval| {\n                    if varnameval.len() != 2 {\n                        bail_expr!(\n                            &tle.t[1],\n                            \"varname-varvalue must be a list of two elements:\\n\\\n                                               varname, varvalue\"\n                        );\n                    }\n                    Ok((\n                        varnameval[0].atom(None).ok_or_else(|| {\n                            anyhow_expr!(&varnameval[0], \"varname must be a string\")\n                        })?,\n                        varnameval[1].atom(None).ok_or_else(|| {\n                            anyhow_expr!(&varnameval[1], \"varvalue must be a string\")\n                        })?,\n                    ))\n                })?;\n            let env_var_val = env_var_val.trim_atom_quotes();\n            match (\n                env.iter().find_map(|(name, val)| {\n                    if name == env_var_name {\n                        Some(val)\n                    } else {\n                        None\n                    }\n                }),\n                env_var_val.is_empty(),\n            ) {\n                (None, false) => {\n                    #[cfg(feature = \"lsp\")]\n                    _lsp_hints.inactive_code.push(lsp_hints::InactiveCode {\n                        span: tle.span.clone(),\n                        reason: format!(\n                            \"Active if env var {env_var_name} is {env_var_val}. It is unset.\"\n                        ),\n                    });\n                }\n                (None, true) => {\n                    tles.push(configuration.clone());\n                }\n                (Some(val), true) if val.is_empty() => {\n                    tles.push(configuration.clone());\n                }\n                (Some(_val), true) => {\n                    #[cfg(feature = \"lsp\")]\n                    _lsp_hints.inactive_code.push(lsp_hints::InactiveCode {\n                        span: tle.span.clone(),\n                        reason: format!(\n                            \"Active if {env_var_name} is empty or unset. It has value {_val}\"\n                        ),\n                    });\n                }\n                (Some(val), false) => {\n                    if val == env_var_val {\n                        tles.push(configuration.clone());\n                    } else {\n                        #[cfg(feature = \"lsp\")]\n                        _lsp_hints.inactive_code.push(lsp_hints::InactiveCode {\n                            span: tle.span.clone(),\n                            reason: format!(\n                                \"Active if {env_var_name} is {env_var_val}. It has value {val}\"\n                            ),\n                        })\n                    }\n                }\n            }\n\n            Ok(tles)\n        })\n}\n"
  },
  {
    "path": "parser/src/cfg/push_msg.rs",
    "content": "use super::*;\n\nuse crate::bail;\n\npub(crate) fn parse_push_message(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    if ac_params.is_empty() {\n        bail!(\n            \"{PUSH_MESSAGE} expects at least one item, an item can be a list or an atom, found 0, none\"\n        );\n    }\n    let message = to_simple_expr(ac_params, s);\n    custom(CustomAction::PushMessage(s.a.sref_vec(message)), &s.a)\n}\n\npub(crate) fn to_simple_expr(params: &[SExpr], s: &ParserState) -> Vec<SimpleSExpr> {\n    let mut result: Vec<SimpleSExpr> = Vec::new();\n    for param in params {\n        if let Some(a) = param.atom(s.vars()) {\n            result.push(SimpleSExpr::Atom(a.trim_atom_quotes().to_owned()));\n        } else {\n            // unwrap: this must be a list, since it's not an atom.\n            let sexps = param.list(s.vars()).unwrap();\n            let value = to_simple_expr(sexps, s);\n            let list = SimpleSExpr::List(value);\n            result.push(list);\n        }\n    }\n    result\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub enum SimpleSExpr {\n    Atom(String),\n    List(Vec<SimpleSExpr>),\n}\n"
  },
  {
    "path": "parser/src/cfg/releases.rs",
    "content": "use super::*;\n\nuse crate::bail;\nuse crate::err_expr;\n\npub(crate) fn parse_release_key(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"release-key expects exactly one keycode (e.g. lalt)\";\n    if ac_params.len() != 1 {\n        bail!(\"{ERR_MSG}: found {} items\", ac_params.len());\n    }\n    let ac = parse_action(&ac_params[0], s)?;\n    match ac {\n        Action::KeyCode(kc) => {\n            Ok(s.a.sref(Action::ReleaseState(ReleasableState::KeyCode(*kc))))\n        }\n        _ => err_expr!(&ac_params[0], \"{}\", ERR_MSG),\n    }\n}\n\npub(crate) fn parse_release_layer(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    Ok(s.a\n        .sref(Action::ReleaseState(ReleasableState::Layer(layer_idx(\n            ac_params,\n            &s.layer_idxs,\n            s,\n        )?))))\n}\n"
  },
  {
    "path": "parser/src/cfg/sequence.rs",
    "content": "use super::*;\n\nuse crate::anyhow_expr;\nuse crate::bail;\nuse crate::bail_expr;\n\nconst SEQ_ERR: &str = \"defseq expects pairs of parameters: <virtual_key_name> <key_list>\";\n\npub(crate) fn parse_sequence_start(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str =\n        \"sequence expects one or two params: <timeout-override> <?input-mode-override>\";\n    if !matches!(ac_params.len(), 1 | 2) {\n        bail!(\"{ERR_MSG}\\nfound {} items\", ac_params.len());\n    }\n    let timeout = parse_non_zero_u16(&ac_params[0], s, \"timeout-override\")?;\n    let input_mode = if ac_params.len() > 1 {\n        if let Some(Ok(input_mode)) = ac_params[1]\n            .atom(s.vars())\n            .map(SequenceInputMode::try_from_str)\n        {\n            input_mode\n        } else {\n            bail_expr!(&ac_params[1], \"{ERR_MSG}\\n{}\", SequenceInputMode::err_msg());\n        }\n    } else {\n        s.default_sequence_input_mode\n    };\n    Ok(s.a.sref(Action::Custom(s.a.sref(\n        s.a.sref_slice(CustomAction::SequenceLeader(timeout, input_mode)),\n    ))))\n}\n\npub(crate) fn parse_sequence_noerase(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"sequence-noerase expects one: <noerase-count>\";\n    if ac_params.len() != 1 {\n        bail!(\"{ERR_MSG}\\nfound {} items\", ac_params.len());\n    }\n    let count = parse_non_zero_u16(&ac_params[0], s, \"noerase-count\")?;\n    Ok(s.a.sref(Action::Custom(\n        s.a.sref(s.a.sref_slice(CustomAction::SequenceNoerase(count))),\n    )))\n}\n\npub(crate) fn parse_sequences(exprs: &[&Vec<SExpr>], s: &ParserState) -> Result<KeySeqsToFKeys> {\n    let mut sequences = Trie::new();\n    for expr in exprs {\n        let mut subexprs = check_first_expr(expr.iter(), \"defseq\")?.peekable();\n\n        while let Some(vkey_expr) = subexprs.next() {\n            let vkey = vkey_expr.atom(s.vars()).ok_or_else(|| {\n                anyhow_expr!(vkey_expr, \"{SEQ_ERR}\\nvirtual_key_name must not be a list\")\n            })?;\n            #[cfg(feature = \"lsp\")]\n            s.lsp_hints\n                .borrow_mut()\n                .reference_locations\n                .virtual_key\n                .push(vkey, vkey_expr.span());\n            if !s.virtual_keys.contains_key(vkey) {\n                bail_expr!(\n                    vkey_expr,\n                    \"{SEQ_ERR}\\nThe referenced key does not exist: {vkey}\"\n                );\n            }\n            let key_seq_expr = subexprs\n                .next()\n                .ok_or_else(|| anyhow_expr!(vkey_expr, \"{SEQ_ERR}\\nMissing key_list for {vkey}\"))?;\n            let key_seq = key_seq_expr.list(s.vars()).ok_or_else(|| {\n                anyhow_expr!(key_seq_expr, \"{SEQ_ERR}\\nGot a non-list for key_list\")\n            })?;\n            if key_seq.is_empty() {\n                bail_expr!(key_seq_expr, \"{SEQ_ERR}\\nkey_list cannot be empty\");\n            }\n\n            let keycode_seq = parse_sequence_keys(key_seq, s)?;\n\n            // Generate permutations of sequences for overlapping keys.\n            let mut permutations = vec![vec![]];\n            let mut vals = keycode_seq.iter().copied();\n            while let Some(val) = vals.next() {\n                if val & KEY_OVERLAP_MARKER == 0 {\n                    for p in permutations.iter_mut() {\n                        p.push(val);\n                    }\n                    continue;\n                }\n\n                if val == 0x0400 {\n                    bail_expr!(\n                        key_seq_expr,\n                        \"O-(...) lists must have a minimum of 2 elements\"\n                    );\n                }\n                let mut values_to_permute = vec![val];\n                for val in vals.by_ref() {\n                    if val == 0x0400 {\n                        break;\n                    }\n                    values_to_permute.push(val);\n                }\n\n                let ps = match values_to_permute.len() {\n                    0 | 1 => bail_expr!(\n                        key_seq_expr,\n                        \"O-(...) lists must have a minimum of 2 elements\"\n                    ),\n                    2..=6 => gen_permutations(&values_to_permute[..]),\n                    _ => bail_expr!(\n                        key_seq_expr,\n                        \"O-(...) lists must have a maximum of 6 elements\"\n                    ),\n                };\n\n                let mut new_permutations: Vec<Vec<u16>> = vec![];\n                for p in permutations.iter() {\n                    for p2 in ps.iter() {\n                        new_permutations.push(\n                            p.iter()\n                                .copied()\n                                .chain(p2.iter().copied().chain([KEY_OVERLAP_MARKER]))\n                                .collect(),\n                        );\n                    }\n                }\n                permutations = new_permutations;\n            }\n\n            for p in permutations.into_iter() {\n                if sequences.ancestor_exists(&p) {\n                    bail_expr!(\n                        key_seq_expr,\n                        \"Sequence has a conflict: its sequence contains an earlier defined sequence\"\n                    );\n                }\n                if sequences.descendant_exists(&p) {\n                    bail_expr!(\n                        key_seq_expr,\n                        \"Sequence has a conflict: its sequence is contained within an earlier defined seqence\"\n                    );\n                }\n                sequences.insert(\n                    p,\n                    s.virtual_keys\n                        .get(vkey)\n                        .map(|(y, _)| get_fake_key_coords(*y))\n                        .expect(\"vk exists, checked earlier\"),\n                );\n            }\n        }\n    }\n    Ok(sequences)\n}\n\npub(crate) fn parse_sequence_keys(exprs: &[SExpr], s: &ParserState) -> Result<Vec<u16>> {\n    use SequenceEvent::*;\n\n    // Reuse macro parsing but do some other processing since sequences don't support everything\n    // that can go in a macro, and also change error messages of course.\n    let mut exprs_remaining = exprs;\n    let mut all_keys = Vec::new();\n    while !exprs_remaining.is_empty() {\n        let (mut keys, exprs_remaining_tmp) =\n            match parse_macro_item_impl(exprs_remaining, s, MacroNumberParseMode::Action) {\n                Ok(res) => {\n                    if res.0.iter().any(|k| !matches!(k, Press(..) | Release(..))) {\n                        // Determine the bad expression depending on how many expressions were consumed\n                        // by parse_macro_item_impl.\n                        let bad_expr = if exprs_remaining.len() - res.1.len() == 1 {\n                            &exprs_remaining[0]\n                        } else {\n                            // This error message will have an imprecise span since it will take the\n                            // whole chorded list instead of the single element inside that's not a\n                            // standard key. Oh well, should still be helpful. I'm too lazy to write\n                            // the code to find the exact expr to use right now.\n                            &exprs_remaining[1]\n                        };\n                        bail_expr!(bad_expr, \"{SEQ_ERR}\\nFound invalid key/chord in key_list\");\n                    }\n\n                    // The keys are currenty in the form of SequenceEvent::{Press, Release}. This is\n                    // not what we want.\n                    //\n                    // The trivial and incorrect way to parse this would be to just take all of the\n                    // presses. However, we need to transform chorded keys/lists like S-a or S-(a b) to\n                    // have the upper bits set, to be able to differentiate (S-a b) from (S-(a b)).\n                    //\n                    // The order of presses and releases reveals whether or not a key is chorded with\n                    // some modifier. When a chord starts, there are multiple presses in a row, whereas\n                    // non-chords will always be a press followed by a release. Likewise, a chord\n                    // ending is marked by multiple releases in a row.\n                    let mut mods_currently_held = vec![];\n                    let mut key_actions = res.0.iter().peekable();\n                    let mut seq = vec![];\n                    let mut do_release_mod = false;\n                    while let Some(action) = key_actions.next() {\n                        match action {\n                            Press(pressed) => {\n                                if matches!(key_actions.peek(), Some(Press(..))) {\n                                    // press->press: current press is mod\n                                    mods_currently_held.push(*pressed);\n                                }\n                                let mut seq_num = u16::from(OsCode::from(pressed));\n                                for modk in mods_currently_held.iter().copied() {\n                                    seq_num |= mod_mask_for_keycode(modk);\n                                }\n                                if seq_num & KEY_OVERLAP_MARKER == KEY_OVERLAP_MARKER\n                                    && seq_num & MASK_MODDED != KEY_OVERLAP_MARKER\n                                {\n                                    bail_expr!(\n                                        &exprs_remaining[0],\n                                        \"O-(...) lists cannot be combined with other modifiers.\"\n                                    );\n                                }\n                                if *pressed != KEY_OVERLAP {\n                                    // Note: key overlap item is special and goes at the end,\n                                    // not the beginning\n                                    seq.push(seq_num);\n                                }\n                            }\n                            Release(released) => {\n                                if *released == KEY_OVERLAP {\n                                    seq.push(KEY_OVERLAP_MARKER);\n                                }\n                                if do_release_mod {\n                                    mods_currently_held.remove(\n                                        mods_currently_held\n                                            .iter()\n                                            .position(|modk| modk == released)\n                                            .expect(\"had to be pressed to be released\"),\n                                    );\n                                }\n                                // release->release: next release is mod\n                                do_release_mod = matches!(key_actions.peek(), Some(Release(..)));\n                            }\n                            _ => unreachable!(\"should be filtered out\"),\n                        }\n                    }\n\n                    (seq, res.1)\n                }\n                Err(mut e) => {\n                    e.msg = format!(\"{SEQ_ERR}\\nFound invalid key/chord in key_list\");\n                    return Err(e);\n                }\n            };\n        all_keys.append(&mut keys);\n        exprs_remaining = exprs_remaining_tmp;\n    }\n    Ok(all_keys)\n}\n"
  },
  {
    "path": "parser/src/cfg/sexpr.rs",
    "content": "use std::ops::Index;\nuse std::rc::Rc;\nuse std::str::Bytes;\nuse std::{fmt::Debug, iter};\n\ntype HashMap<K, V> = rustc_hash::FxHashMap<K, V>;\n\nuse super::{ParseError, Result};\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]\npub struct Position {\n    /// The position (since the beginning of the file), in bytes.\n    pub absolute: usize,\n    /// The number of newline characters since the beginning of the file.\n    pub line: usize,\n    /// The position of beginning of line, in bytes.\n    pub line_beginning: usize,\n}\n\nimpl Position {\n    pub fn new(absolute: usize, line: usize, line_beginning: usize) -> Self {\n        assert!(line <= absolute);\n        assert!(line_beginning <= absolute);\n        Self {\n            absolute,\n            line,\n            line_beginning,\n        }\n    }\n}\n\n#[derive(Clone, PartialEq, Eq, Hash)]\npub struct Span {\n    pub start: Position,\n    pub end: Position,\n    pub file_name: Rc<str>,\n    pub file_content: Rc<str>,\n}\n\nimpl Debug for Span {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Span\")\n            .field(\"start\", &self.start)\n            .field(\"end\", &self.end)\n            .field(\"file_name\", &self.file_name)\n            .field(\"file_content [len]\", &self.file_content.len())\n            .finish()\n    }\n}\n\nimpl Default for Span {\n    fn default() -> Self {\n        Self {\n            start: Position::default(),\n            end: Position::default(),\n            file_name: Rc::from(\"\"),\n            file_content: Rc::from(\"\"),\n        }\n    }\n}\n\nimpl Span {\n    pub fn new(start: Position, end: Position, file_name: Rc<str>, file_content: Rc<str>) -> Span {\n        assert!(start.absolute <= end.absolute);\n        assert!(start.line <= end.line);\n        Span {\n            start,\n            end,\n            file_name,\n            file_content,\n        }\n    }\n\n    pub fn cover(&self, other: &Span) -> Span {\n        assert!(self.file_name == other.file_name);\n\n        let start: Position = if self.start() <= other.start() {\n            self.start\n        } else {\n            other.start\n        };\n\n        let end: Position = if self.end() >= other.end() {\n            self.end\n        } else {\n            other.end\n        };\n\n        Span::new(\n            start,\n            end,\n            self.file_name.clone(),\n            self.file_content.clone(),\n        )\n    }\n\n    pub fn start(&self) -> usize {\n        self.start.absolute\n    }\n\n    pub fn end(&self) -> usize {\n        self.end.absolute\n    }\n\n    pub fn file_name(&self) -> String {\n        self.file_name.clone().to_string()\n    }\n\n    pub fn file_content(&self) -> String {\n        self.file_content.clone().to_string()\n    }\n}\n\nimpl Index<Span> for str {\n    type Output = str;\n    fn index(&self, span: Span) -> &Self::Output {\n        &self[span.start()..span.end()]\n    }\n}\n\nimpl Index<Span> for String {\n    type Output = str;\n    fn index(&self, span: Span) -> &Self::Output {\n        &self[span.start()..span.end()]\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct Spanned<T> {\n    pub t: T,\n    pub span: Span,\n}\n\nimpl<T> Spanned<T> {\n    pub fn new(t: T, span: Span) -> Spanned<T> {\n        Spanned { t, span }\n    }\n}\n\n#[derive(Clone, PartialEq, Eq, Hash)]\n/// I know this isn't the classic definition of an S-Expression which uses cons cell and atom, but\n/// this is more convenient to work with (I find).\npub enum SExpr {\n    Atom(Spanned<String>),\n    List(Spanned<Vec<SExpr>>),\n}\n\nimpl SExpr {\n    pub fn atom<'a>(&'a self, vars: Option<&'a HashMap<String, SExpr>>) -> Option<&'a str> {\n        match self {\n            SExpr::Atom(a) => {\n                let s = a.t.as_str();\n                match (s.strip_prefix('$'), vars) {\n                    (Some(varname), Some(vars)) => match vars.get(varname) {\n                        Some(var) => {\n                            #[cfg(feature = \"lsp\")]\n                            super::LSP_VARIABLE_REFERENCES.with_borrow_mut(|refs| {\n                                refs.push(varname, a.span.clone());\n                            });\n                            var.atom(Some(vars))\n                        }\n                        None => Some(s),\n                    },\n                    _ => Some(s),\n                }\n            }\n            _ => None,\n        }\n    }\n\n    pub fn list<'a>(&'a self, vars: Option<&'a HashMap<String, SExpr>>) -> Option<&'a [SExpr]> {\n        match self {\n            SExpr::List(l) => Some(&l.t),\n            SExpr::Atom(a) => match (a.t.strip_prefix('$'), vars) {\n                (Some(varname), Some(vars)) => match vars.get(varname) {\n                    Some(var) => {\n                        #[cfg(feature = \"lsp\")]\n                        super::LSP_VARIABLE_REFERENCES.with_borrow_mut(|refs| {\n                            refs.push(varname, a.span.clone());\n                        });\n                        var.list(Some(vars))\n                    }\n                    None => None,\n                },\n                _ => None,\n            },\n        }\n    }\n\n    pub fn span_list<'a>(\n        &'a self,\n        vars: Option<&'a HashMap<String, SExpr>>,\n    ) -> Option<&'a Spanned<Vec<SExpr>>> {\n        match self {\n            SExpr::List(l) => Some(l),\n            SExpr::Atom(a) => match (a.t.strip_prefix('$'), vars) {\n                (Some(varname), Some(vars)) => match vars.get(varname) {\n                    Some(var) => {\n                        #[cfg(feature = \"lsp\")]\n                        super::LSP_VARIABLE_REFERENCES.with_borrow_mut(|refs| {\n                            refs.push(varname, a.span.clone());\n                        });\n                        var.span_list(Some(vars))\n                    }\n                    None => None,\n                },\n                _ => None,\n            },\n        }\n    }\n\n    pub fn span(&self) -> Span {\n        match self {\n            SExpr::Atom(a) => a.span.clone(),\n            SExpr::List(l) => l.span.clone(),\n        }\n    }\n}\n\nimpl std::fmt::Debug for SExpr {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            SExpr::Atom(a) => write!(f, \"{}\", &a.t),\n            SExpr::List(l) => {\n                write!(f, \"(\")?;\n                for i in 0..l.t.len() - 1 {\n                    write!(f, \"{:?} \", &l.t[i])?;\n                }\n                if let Some(last) = &l.t.last() {\n                    write!(f, \"{last:?}\")?;\n                }\n                write!(f, \")\")?;\n                Ok(())\n            }\n        }\n    }\n}\n\n#[derive(Clone, PartialEq, Eq, Debug)]\n/// Complementary to SExpr metadata items.\npub enum SExprMetaData {\n    LineComment(Spanned<String>),\n    BlockComment(Spanned<String>),\n    Whitespace(Spanned<String>),\n}\n\nimpl SExprMetaData {\n    pub fn span(&self) -> Span {\n        match self {\n            Self::LineComment(x) => x.span.clone(),\n            Self::BlockComment(x) => x.span.clone(),\n            Self::Whitespace(x) => x.span.clone(),\n        }\n    }\n}\n\n#[derive(Debug)]\nenum Token {\n    Open,\n    Close,\n    StringTok,\n    BlockComment,\n    LineComment,\n    Whitespace,\n}\n\n#[derive(Clone)]\n/// A wrapper around [`Bytes`] that keeps track of current [`Position`].\nstruct PositionCountingBytesIterator<'a> {\n    bytes: Bytes<'a>,\n    source_length: usize,\n    line: usize,\n    line_beginning: usize,\n}\n\nimpl<'a> PositionCountingBytesIterator<'a> {\n    fn new(s: &'a str) -> Self {\n        Self {\n            bytes: s.bytes(),\n            source_length: s.len(),\n            line: 0,\n            line_beginning: 0,\n        }\n    }\n\n    fn pos(&self) -> Position {\n        let absolute = self.source_length - self.bytes.len();\n        Position::new(absolute, self.line, self.line_beginning)\n    }\n}\n\nimpl Iterator for PositionCountingBytesIterator<'_> {\n    type Item = u8;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        self.bytes.next().inspect(|&b| {\n            if b == b'\\n' {\n                self.line += 1;\n                self.line_beginning = self.source_length - self.bytes.len()\n            }\n        })\n    }\n}\n\npub struct Lexer<'a> {\n    bytes: PositionCountingBytesIterator<'a>,\n    ignore_whitespace_and_comments: bool,\n}\n\nfn is_start(b: u8) -> bool {\n    matches!(b, b'(' | b')' | b'\"') || b.is_ascii_whitespace()\n}\n\ntype TokenRes = std::result::Result<Token, String>;\n\nimpl<'a> Lexer<'a> {\n    #[allow(clippy::new_ret_no_self)]\n    /// `file_name` is used only for indicating a file, where\n    /// a fragment of `source` that caused parsing error came from.\n    fn new(\n        source: &'a str,\n        file_name: &'a str,\n        ignore_whitespace_and_comments: bool,\n    ) -> impl Iterator<Item = Spanned<TokenRes>> + 'a {\n        let _bytes = source.bytes().next();\n\n        let mut lexer = Lexer {\n            bytes: PositionCountingBytesIterator::new(source),\n            ignore_whitespace_and_comments,\n        };\n        let file_name: Rc<str> = Rc::from(file_name);\n        let file_content: Rc<str> = Rc::from(source);\n        iter::from_fn(move || {\n            lexer.next_token().map(|(start, t)| {\n                let end = lexer.bytes.pos();\n                Spanned::new(\n                    t,\n                    Span::new(start, end, file_name.clone(), file_content.clone()),\n                )\n            })\n        })\n    }\n\n    fn next_while(&mut self, f: impl Fn(u8) -> bool) {\n        for b in self.bytes.clone() {\n            if f(b) {\n                // Iterating over a clone of this iterator - this is guaranteed to be Some\n                self.bytes.next().expect(\"iter lag\");\n            } else {\n                break;\n            }\n        }\n    }\n\n    /// Looks for \"#, consuming bytes until found. If not found, returns Err(...);\n    fn read_until_multiline_string_end(&mut self) -> TokenRes {\n        for b2 in self.bytes.clone().skip(1) {\n            // Iterating over a clone of this iterator that's 1 item ahead - this is guaranteed to\n            // be Some.\n            let b1 = self.bytes.next().expect(\"iter lag\");\n            if b1 == b'\"' && b2 == b'#' {\n                self.bytes.next();\n                return Ok(Token::StringTok);\n            }\n        }\n        Err(\"Unterminated multiline string. Add \\\"# after the end of your string.\".to_string())\n    }\n\n    /// Looks for \"|#\", consuming bytes until found. If not found, returns Err(...);\n    fn read_until_multiline_comment_end(&mut self) -> TokenRes {\n        for b2 in self.bytes.clone().skip(1) {\n            // Iterating over a clone of this iterator that's 1 item ahead - this is guaranteed to\n            // be Some.\n            let b1 = self.bytes.next().expect(\"iter lag\");\n            if b1 == b'|' && b2 == b'#' {\n                self.bytes.next();\n                return Ok(Token::BlockComment);\n            }\n        }\n        Err(\"Unterminated multiline comment. Add |# after the end of your comment.\".to_string())\n    }\n\n    fn next_token(&mut self) -> Option<(Position, TokenRes)> {\n        use Token::*;\n        loop {\n            let start = self.bytes.pos();\n            break match self.bytes.next() {\n                Some(b) => Some((\n                    start,\n                    Ok(match b {\n                        b'(' => Open,\n                        b')' => Close,\n                        b'\"' => {\n                            self.next_while(|b| b != b'\"' && b != b'\\n');\n                            match self.bytes.next() {\n                                Some(b'\"') => StringTok,\n                                _ => return Some((start, Err(\"Unterminated string\".to_string()))),\n                            }\n                        }\n                        b';' => match self.bytes.clone().next() {\n                            Some(b';') => {\n                                self.next_while(|b| b != b'\\n');\n                                // possibly consume the newline (or EOF handled in next iteration)\n                                self.bytes.next();\n                                if self.ignore_whitespace_and_comments {\n                                    continue;\n                                }\n                                Token::LineComment\n                            }\n                            _ => self.next_string(),\n                        },\n                        b'r' => {\n                            match (self.bytes.clone().next(), self.bytes.clone().nth(1)) {\n                                (Some(b'#'), Some(b'\"')) => {\n                                    // consume the # and \"\n                                    self.bytes.next();\n                                    self.bytes.next();\n                                    let tok: Token = match self.read_until_multiline_string_end() {\n                                        Ok(t) => t,\n                                        e @ Err(_) => return Some((start, e)),\n                                    };\n                                    tok\n                                }\n                                _ => self.next_string(),\n                            }\n                        }\n                        b'#' => match self.bytes.clone().next() {\n                            Some(b'|') => {\n                                // consume the '|'\n                                self.bytes.next();\n                                let tok: Token = match self.read_until_multiline_comment_end() {\n                                    Ok(t) => t,\n                                    e @ Err(_) => return Some((start, e)),\n                                };\n                                if self.ignore_whitespace_and_comments {\n                                    continue;\n                                }\n                                tok\n                            }\n                            _ => self.next_string(),\n                        },\n                        b if b.is_ascii_whitespace() => {\n                            let tok = self.next_whitespace();\n                            if self.ignore_whitespace_and_comments {\n                                continue;\n                            }\n                            tok\n                        }\n                        _ => self.next_string(),\n                    }),\n                )),\n                None => None,\n            };\n        }\n    }\n\n    fn next_string(&mut self) -> Token {\n        // might want to limit this to ascii or XID_START/XID_CONTINUE\n        self.next_while(|b| !is_start(b));\n        Token::StringTok\n    }\n\n    fn next_whitespace(&mut self) -> Token {\n        self.next_while(|b| b.is_ascii_whitespace());\n        Token::Whitespace\n    }\n}\n\npub type TopLevel = Spanned<Vec<SExpr>>;\n\npub fn parse(cfg: &str, file_name: &str) -> std::result::Result<Vec<TopLevel>, ParseError> {\n    let ignore_whitespace_and_comments = true;\n    parse_(cfg, file_name, ignore_whitespace_and_comments).map(|(x, _)| x)\n}\n\npub fn parse_(\n    cfg: &str,\n    file_name: &str,\n    ignore_whitespace_and_comments: bool,\n) -> Result<(Vec<TopLevel>, Vec<SExprMetaData>)> {\n    let cfg = strip_utf8_bom(cfg);\n    parse_with(\n        cfg,\n        Lexer::new(cfg, file_name, ignore_whitespace_and_comments),\n    )\n    .map_err(|e| {\n        if e.msg.contains(\"Unterminated multiline comment\") {\n            if let Some(mut span) = e.span {\n                span.end = span.start;\n                span.end.absolute += 2;\n                ParseError::new(span, e.msg)\n            } else {\n                e\n            }\n        } else {\n            e\n        }\n    })\n}\n\nfn strip_utf8_bom(s: &str) -> &str {\n    match s.as_bytes().strip_prefix(&[0xef, 0xbb, 0xbf]) {\n        Some(stripped) => std::str::from_utf8(stripped).expect(\"valid input\"),\n        None => s,\n    }\n}\n\nfn parse_with(\n    s: &str,\n    mut tokens: impl Iterator<Item = Spanned<TokenRes>>,\n) -> Result<(Vec<TopLevel>, Vec<SExprMetaData>)> {\n    use Token::*;\n    let mut stack = vec![Spanned::new(vec![], Span::default())];\n    let mut metadata: Vec<SExprMetaData> = vec![];\n    loop {\n        match tokens.next() {\n            None => break,\n            Some(Spanned { t, span }) => match t.map_err(|s| ParseError::new(span.clone(), s))? {\n                Open => stack.push(Spanned::new(vec![], span)),\n                Close => {\n                    let Spanned {\n                        t: exprs,\n                        span: stack_span,\n                        // There is a placeholder at the bottom of the stack to allow this unwrap;\n                        // if the stack is ever empty, return an error.\n                    } = stack.pop().expect(\"placeholder unpopped\");\n                    if stack.is_empty() {\n                        return Err(ParseError::new(span, \"Unexpected closing parenthesis\"));\n                    }\n                    let expr = SExpr::List(Spanned::new(exprs, stack_span.cover(&span)));\n                    stack.last_mut().expect(\"not empty\").t.push(expr);\n                }\n                StringTok => stack\n                    .last_mut()\n                    .expect(\"not empty\")\n                    .t\n                    .push(SExpr::Atom(Spanned::new(s[span.clone()].to_string(), span))),\n                BlockComment => metadata.push(SExprMetaData::BlockComment(Spanned::new(\n                    s[span.clone()].to_string(),\n                    span,\n                ))),\n                LineComment => metadata.push(SExprMetaData::LineComment(Spanned::new(\n                    s[span.clone()].to_string(),\n                    span,\n                ))),\n                Whitespace => metadata.push(SExprMetaData::Whitespace(Spanned::new(\n                    s[span.clone()].to_string(),\n                    span,\n                ))),\n            },\n        }\n    }\n    // There is a placeholder at the bottom of the stack to allow this unwrap; if the stack is ever\n    // empty, return an error.\n    let Spanned { t: exprs, span: sp } = stack.pop().expect(\"placeholder unpopped\");\n    if !stack.is_empty() {\n        return Err(ParseError::new(sp, \"Unclosed opening parenthesis\"));\n    }\n    let exprs = exprs\n        .into_iter()\n        .map(|expr| match expr {\n            SExpr::List(es) => Ok(es),\n            SExpr::Atom(s) => Err(ParseError::new(s.span, \"Everything must be in a list\")),\n        })\n        .collect::<Result<_>>()?;\n    Ok((exprs, metadata))\n}\n"
  },
  {
    "path": "parser/src/cfg/str_ext.rs",
    "content": "pub trait TrimAtomQuotes {\n    fn trim_atom_quotes(&self) -> &str;\n}\n\nimpl TrimAtomQuotes for str {\n    fn trim_atom_quotes(&self) -> &str {\n        match self.strip_prefix(\"r#\\\"\") {\n            Some(a) => a.strip_suffix(\"\\\"#\").unwrap_or(a),\n            None => self\n                .strip_prefix('\"')\n                .unwrap_or(self)\n                .strip_suffix('\"')\n                .unwrap_or(self),\n        }\n    }\n}\n\nimpl TrimAtomQuotes for String {\n    fn trim_atom_quotes(&self) -> &str {\n        match self.as_str().strip_prefix(\"r#\\\"\") {\n            Some(a) => a.strip_suffix(\"\\\"#\").unwrap_or(a),\n            None => self\n                .strip_prefix('\"')\n                .unwrap_or(self)\n                .strip_suffix('\"')\n                .unwrap_or(self),\n        }\n    }\n}\n"
  },
  {
    "path": "parser/src/cfg/switch.rs",
    "content": "use super::*;\nuse crate::{anyhow_expr, bail, bail_expr};\n\npub fn parse_switch(ac_params: &[SExpr], s: &ParserState) -> Result<&'static KanataAction> {\n    const ERR_STR: &str =\n        \"switch expects triples of params: <key match> <action> <break|fallthrough>\";\n\n    let mut cases = vec![];\n\n    let mut params = ac_params.iter();\n    loop {\n        let Some(key_match) = params.next() else {\n            break;\n        };\n        let Some(action) = params.next() else {\n            bail!(\"{ERR_STR}\\nMissing <action> and <break|fallthrough> for the final triple\");\n        };\n        let Some(break_or_fallthrough_expr) = params.next() else {\n            bail!(\"{ERR_STR}\\nMissing <break|fallthrough> for the final triple\");\n        };\n\n        let Some(key_match) = key_match.list(s.vars()) else {\n            bail_expr!(key_match, \"{ERR_STR}\\n<key match> must be a list\")\n        };\n        let mut ops = vec![];\n        for op in key_match.iter() {\n            parse_switch_case_bool(1, op, &mut ops, s)?;\n        }\n\n        let action = parse_action(action, s)?;\n\n        let Some(break_or_fallthrough) = break_or_fallthrough_expr.atom(s.vars()) else {\n            bail_expr!(\n                break_or_fallthrough_expr,\n                \"{ERR_STR}\\nthis must be one of: break, fallthrough\"\n            );\n        };\n        let break_or_fallthrough = match break_or_fallthrough {\n            \"break\" => BreakOrFallthrough::Break,\n            \"fallthrough\" => BreakOrFallthrough::Fallthrough,\n            _ => bail_expr!(\n                break_or_fallthrough_expr,\n                \"{ERR_STR}\\nthis must be one of: break, fallthrough\"\n            ),\n        };\n        cases.push((s.a.sref_vec(ops), action, break_or_fallthrough));\n    }\n    Ok(s.a.sref(Action::Switch(s.a.sref(Switch {\n        cases: s.a.sref_vec(cases),\n    }))))\n}\n\npub fn parse_switch_case_bool(\n    depth: u8,\n    op_expr: &SExpr,\n    ops: &mut Vec<OpCode>,\n    s: &ParserState,\n) -> Result<()> {\n    if ops.len() > MAX_OPCODE_LEN as usize {\n        bail_expr!(\n            op_expr,\n            \"maximum key match size of {MAX_OPCODE_LEN} items is exceeded\"\n        );\n    }\n    if usize::from(depth) > MAX_BOOL_EXPR_DEPTH {\n        bail_expr!(\n            op_expr,\n            \"maximum key match expression depth {MAX_BOOL_EXPR_DEPTH} is exceeded\"\n        );\n    }\n    if let Some(a) = op_expr.atom(s.vars()) {\n        let osc = str_to_oscode(a).ok_or_else(|| anyhow_expr!(op_expr, \"invalid key name\"))?;\n        ops.push(OpCode::new_key(osc.into()));\n        Ok(())\n    } else {\n        let l = op_expr\n            .list(s.vars())\n            .expect(\"must be a list, checked atom\");\n        if l.is_empty() {\n            bail_expr!(op_expr, \"switch logic cannot contain empty lists inside\");\n        }\n        #[derive(PartialEq)]\n        enum AllowedListOps {\n            Or,\n            And,\n            Not,\n            KeyHistory,\n            KeyTiming,\n            Input,\n            InputHistory,\n            Layer,\n            BaseLayer,\n        }\n        #[derive(Copy, Clone)]\n        enum InputType {\n            Real,\n            Virtual,\n        }\n        impl InputType {\n            fn to_row(self) -> u8 {\n                match self {\n                    InputType::Real => 0,\n                    InputType::Virtual => 1,\n                }\n            }\n        }\n        let op = l[0]\n            .atom(s.vars())\n            .and_then(|s| match s {\n                \"or\" => Some(AllowedListOps::Or),\n                \"and\" => Some(AllowedListOps::And),\n                \"not\" => Some(AllowedListOps::Not),\n                \"key-history\" => Some(AllowedListOps::KeyHistory),\n                \"key-timing\" => Some(AllowedListOps::KeyTiming),\n                \"input\" => Some(AllowedListOps::Input),\n                \"input-history\" => Some(AllowedListOps::InputHistory),\n                \"layer\" => Some(AllowedListOps::Layer),\n                \"base-layer\" => Some(AllowedListOps::BaseLayer),\n                _ => None,\n            })\n            .ok_or_else(|| {\n                anyhow_expr!(\n                    op_expr,\n                    \"lists inside switch logic must begin with one of:\\n\\\n                    or | and | not | key-history | key-timing\\n\\\n                    | input | input-history | layer | base-layer\",\n                )\n            })?;\n\n        match op {\n            AllowedListOps::KeyHistory => {\n                if l.len() != 3 {\n                    bail_expr!(\n                        op_expr,\n                        \"key-history must have 2 parameters: key, key-recency\"\n                    );\n                }\n                let osc = l[1]\n                    .atom(s.vars())\n                    .and_then(str_to_oscode)\n                    .ok_or_else(|| anyhow_expr!(&l[1], \"invalid key name\"))?;\n                let key_recency = parse_u8_with_range(&l[2], s, \"key-recency\", 1, 8)? - 1;\n                ops.push(OpCode::new_key_history(osc.into(), key_recency));\n                Ok(())\n            }\n            AllowedListOps::Input => {\n                if l.len() != 3 {\n                    bail_expr!(\n                        op_expr,\n                        \"input must have 2 parameters: key-type(virtual|real), key\"\n                    );\n                }\n\n                let input_type = match l[1]\n                    .atom(s.vars())\n                    .ok_or_else(|| anyhow_expr!(&l[1], \"key-type must be virtual|real\"))?\n                {\n                    \"real\" => InputType::Real,\n                    \"fake\" | \"virtual\" => InputType::Virtual,\n                    _ => bail_expr!(op_expr, \"key-type must be virtual|real\"),\n                };\n                let input = match input_type {\n                    InputType::Real => {\n                        let key = l[2].atom(s.vars()).ok_or_else(|| {\n                            anyhow_expr!(&l[2], \"input key name must not be a list\")\n                        })?;\n                        u16::from(\n                            str_to_oscode(key)\n                                .ok_or_else(|| anyhow_expr!(&l[2], \"invalid input key name\"))?,\n                        )\n                    }\n                    InputType::Virtual => parse_vkey_coord(&l[2], s)?.y,\n                };\n                let (op1, op2) = OpCode::new_active_input((input_type.to_row(), input));\n                ops.extend(&[op1, op2]);\n                Ok(())\n            }\n            AllowedListOps::InputHistory => {\n                if l.len() != 4 {\n                    bail_expr!(\n                        op_expr,\n                        \"input-history must have 3 parameters: key-type(virtual|real), key, key-recency\"\n                    );\n                }\n\n                let input_type = match l[1]\n                    .atom(s.vars())\n                    .ok_or_else(|| anyhow_expr!(&l[1], \"key-type must be virtual|real\"))?\n                {\n                    \"real\" => InputType::Real,\n                    \"fake\" | \"virtual\" => InputType::Virtual,\n                    _ => bail_expr!(&l[1], \"key-type must be virtual|real\"),\n                };\n                let input = match input_type {\n                    InputType::Real => {\n                        let key = l[2].atom(s.vars()).ok_or_else(|| {\n                            anyhow_expr!(&l[2], \"input key name must not be a list\")\n                        })?;\n                        u16::from(\n                            str_to_oscode(key)\n                                .ok_or_else(|| anyhow_expr!(&l[2], \"invalid input key name\"))?,\n                        )\n                    }\n                    InputType::Virtual => parse_vkey_coord(&l[2], s)?.y,\n                };\n                let key_recency = parse_u8_with_range(&l[3], s, \"key-recency\", 1, 8)? - 1;\n                let (op1, op2) =\n                    OpCode::new_historical_input((input_type.to_row(), input), key_recency);\n                ops.extend(&[op1, op2]);\n                Ok(())\n            }\n            AllowedListOps::KeyTiming => {\n                if l.len() != 4 {\n                    bail_expr!(\n                        op_expr,\n                        \"key-timing must have 3 parameters: key-recency, lt|gt|less-than|greater-than, milliseconds (0-65535)\"\n                    );\n                }\n                let nth_key = parse_u8_with_range(&l[1], s, \"key-recency\", 1, 8)? - 1;\n                let ticks_since = parse_u16(&l[3], s, \"milliseconds\")?;\n                match l[2].atom(s.vars()).ok_or_else(|| {\n                    anyhow_expr!(\n                        &l[2],\n                        \"key-timing 2nd parameter must be one of: lt|gt|less-than|greater-than\"\n                    )\n                })? {\n                    \"less-than\" | \"lt\" => {\n                        ops.push(OpCode::new_ticks_since_lt(nth_key, ticks_since));\n                    }\n                    \"greater-than\" | \"gt\" => {\n                        ops.push(OpCode::new_ticks_since_gt(nth_key, ticks_since));\n                    }\n                    _ => {\n                        bail_expr!(\n                            &l[2],\n                            \"key-timing 2nd parameter must be one of: lt|gt|less-than|greater-than\"\n                        );\n                    }\n                };\n                s.switch_max_key_timing\n                    .set(std::cmp::max(s.switch_max_key_timing.get(), ticks_since));\n                Ok(())\n            }\n            AllowedListOps::Layer | AllowedListOps::BaseLayer => {\n                if l.len() != 2 {\n                    bail_expr!(\n                        op_expr,\n                        \"{} must have 1 parameter: layer-name\",\n                        match op {\n                            AllowedListOps::Layer => \"layer\",\n                            AllowedListOps::BaseLayer => \"base-layer\",\n                            _ => unreachable!(),\n                        }\n                    );\n                }\n                let layer = l[1]\n                    .atom(s.vars())\n                    .and_then(|atom| s.layer_idxs.get(atom))\n                    .map(|idx| {\n                        assert!(*idx < MAX_LAYERS);\n                        *idx as u16\n                    })\n                    .ok_or_else(|| anyhow_expr!(&l[1], \"not a known layer name\"))?;\n                let (op1, op2) = match op {\n                    AllowedListOps::Layer => OpCode::new_layer(layer),\n                    AllowedListOps::BaseLayer => OpCode::new_base_layer(layer),\n                    _ => unreachable!(),\n                };\n                ops.extend(&[op1, op2]);\n                Ok(())\n            }\n            AllowedListOps::Or | AllowedListOps::And | AllowedListOps::Not => {\n                let op = match op {\n                    AllowedListOps::Or => BooleanOperator::Or,\n                    AllowedListOps::And => BooleanOperator::And,\n                    AllowedListOps::Not => BooleanOperator::Not,\n                    _ => unreachable!(),\n                };\n                // insert a placeholder for now, don't know the end index yet.\n                let placeholder_index = ops.len() as u16;\n                ops.push(OpCode::new_bool(op, placeholder_index));\n                for op in l.iter().skip(1) {\n                    parse_switch_case_bool(depth + 1, op, ops, s)?;\n                }\n                if ops.len() > usize::from(MAX_OPCODE_LEN) {\n                    bail_expr!(op_expr, \"switch logic length has been exceeded\");\n                }\n                ops[placeholder_index as usize] = OpCode::new_bool(op, ops.len() as u16);\n                Ok(())\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "parser/src/cfg/tap_dance.rs",
    "content": "use super::*;\n\nuse crate::anyhow_expr;\nuse crate::bail;\n\npub(crate) fn parse_tap_dance(\n    ac_params: &[SExpr],\n    s: &ParserState,\n    config: TapDanceConfig,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"tap-dance expects a timeout (number) followed by a list of actions\";\n    if ac_params.len() != 2 {\n        bail!(ERR_MSG);\n    }\n\n    let timeout = parse_non_zero_u16(&ac_params[0], s, \"timeout\")?;\n    let actions = ac_params[1]\n        .list(s.vars())\n        .map(|tap_dance_actions| -> Result<Vec<&'static KanataAction>> {\n            let mut actions = Vec::new();\n            for expr in tap_dance_actions {\n                let ac = parse_action(expr, s)?;\n                actions.push(ac);\n            }\n            Ok(actions)\n        })\n        .ok_or_else(|| anyhow_expr!(&ac_params[1], \"{ERR_MSG}: expected a list\"))??;\n\n    Ok(s.a.sref(Action::TapDance(s.a.sref(TapDance {\n        timeout,\n        actions: s.a.sref_vec(actions),\n        config,\n    }))))\n}\n"
  },
  {
    "path": "parser/src/cfg/tap_hold.rs",
    "content": "use super::*;\n\nuse crate::anyhow_expr;\nuse crate::bail;\nuse crate::bail_expr;\n\n/// Options that can be specified as trailing `(keyword value)` lists on any tap-hold action.\n#[derive(Default)]\npub(crate) struct TapHoldOptions {\n    pub(crate) require_prior_idle: Option<u16>,\n}\n\n/// Parse the value of a `(require-prior-idle <ms>)` option list.\n/// Validates that the list has exactly 2 items and the value is a u16.\npub(crate) fn parse_require_prior_idle_option(\n    option: &[SExpr],\n    option_expr: &SExpr,\n    s: &ParserState,\n) -> Result<u16> {\n    if option.len() != 2 {\n        bail_expr!(\n            option_expr,\n            \"require-prior-idle option expects exactly 2 items: \\\n            `(require-prior-idle <ms>)`\"\n        );\n    }\n    parse_u16(&option[1], s, \"require-prior-idle\")\n}\n\n/// Parse trailing `(keyword value)` option lists from tap-hold action parameters.\n/// Returns the parsed options. Errors on unknown or duplicate options.\npub(crate) fn parse_tap_hold_options(\n    option_exprs: &[SExpr],\n    s: &ParserState,\n) -> Result<TapHoldOptions> {\n    let mut opts = TapHoldOptions::default();\n    let mut seen_options: HashSet<&str> = HashSet::default();\n\n    for option_expr in option_exprs {\n        let Some(option) = option_expr.list(s.vars()) else {\n            bail_expr!(\n                option_expr,\n                \"expected option list, e.g. `(require-prior-idle 150)`\"\n            );\n        };\n        if option.is_empty() {\n            bail_expr!(option_expr, \"option list cannot be empty\");\n        }\n        let kw = option[0]\n            .atom(s.vars())\n            .ok_or_else(|| anyhow_expr!(&option[0], \"option name must be a string\"))?;\n        if !seen_options.insert(kw) {\n            bail_expr!(&option[0], \"duplicate option '{}'\", kw);\n        }\n        match kw {\n            \"require-prior-idle\" => {\n                opts.require_prior_idle =\n                    Some(parse_require_prior_idle_option(option, option_expr, s)?);\n            }\n            _ => bail_expr!(\n                &option[0],\n                \"unknown tap-hold option '{}'. \\\n                Valid options: require-prior-idle\",\n                kw\n            ),\n        }\n    }\n    Ok(opts)\n}\n\nconst TAP_HOLD_OPTION_KEYWORDS: &[&str] = &[\"require-prior-idle\"];\n\n/// Count how many trailing expressions are tap-hold option lists.\n/// An option list is a list whose first element is a known option keyword.\n/// Stops at the first non-option expression (scanning from the end).\nfn count_trailing_options(ac_params: &[SExpr], s: &ParserState) -> usize {\n    let mut count = 0;\n    for expr in ac_params.iter().rev() {\n        if let Some(list) = expr.list(s.vars()) {\n            if let Some(kw) = list.first().and_then(|e| e.atom(s.vars())) {\n                if TAP_HOLD_OPTION_KEYWORDS.contains(&kw) {\n                    count += 1;\n                    continue;\n                }\n            }\n        }\n        break;\n    }\n    count\n}\n\npub(crate) fn parse_tap_hold(\n    ac_params: &[SExpr],\n    s: &ParserState,\n    config: HoldTapConfig<'static>,\n) -> Result<&'static KanataAction> {\n    let n_opts = count_trailing_options(ac_params, s);\n    let n_positional = ac_params.len() - n_opts;\n    if n_positional != 4 {\n        bail!(\n            r\"tap-hold expects 4 items after it, got {}.\nParams in order:\n<tap-repress-timeout> <hold-timeout> <tap-action> <hold-action>\",\n            n_positional,\n        )\n    }\n    let tap_repress_timeout = parse_u16(&ac_params[0], s, \"tap repress timeout\")?;\n    let hold_timeout = parse_non_zero_u16(&ac_params[1], s, \"hold timeout\")?;\n    let tap_action = parse_action(&ac_params[2], s)?;\n    let hold_action = parse_action(&ac_params[3], s)?;\n    if matches!(tap_action, Action::HoldTap { .. }) {\n        bail!(\"tap-hold does not work in the tap-action of tap-hold\")\n    }\n    let opts = parse_tap_hold_options(&ac_params[n_positional..], s)?;\n    Ok(s.a.sref(Action::HoldTap(s.a.sref(HoldTapAction {\n        config,\n        tap_hold_interval: tap_repress_timeout,\n        timeout: hold_timeout,\n        tap: *tap_action,\n        hold: *hold_action,\n        timeout_action: *hold_action,\n        on_press_reset_timeout_to: None,\n        require_prior_idle: opts.require_prior_idle,\n    }))))\n}\n\npub(crate) fn parse_tap_hold_timeout(\n    ac_params: &[SExpr],\n    s: &ParserState,\n    config: HoldTapConfig<'static>,\n) -> Result<&'static KanataAction> {\n    const PARAMS_FOR_RELEASE: &str = \"Params in order:\\n\\\n       <tap-repress-timeout> <hold-timeout> <tap-action> <hold-action> <timeout-action> [?reset-timeout-on-press]\";\n    let n_opts = count_trailing_options(ac_params, s);\n    let n_positional = ac_params.len() - n_opts;\n    match config {\n        HoldTapConfig::PermissiveHold => {\n            if n_positional != 5 && n_positional != 6 {\n                bail!(\n                    \"tap-hold-release-timeout expects at least 5 items after it, got {}.\\n\\\n                    {PARAMS_FOR_RELEASE}\",\n                    n_positional,\n                )\n            }\n        }\n        HoldTapConfig::HoldOnOtherKeyPress => {\n            if n_positional != 5 {\n                bail!(\n                    \"tap-hold-press-timeout expects 5 items after it, got {}.\\n\\\n                    Params in order:\\n\\\n                    <tap-repress-timeout> <hold-timeout> <tap-action> <hold-action> <timeout-action>\",\n                    n_positional,\n                )\n            }\n        }\n        _ => unreachable!(\"other configs not expected\"),\n    };\n    let tap_repress_timeout = parse_u16(&ac_params[0], s, \"tap repress timeout\")?;\n    let hold_timeout = parse_non_zero_u16(&ac_params[1], s, \"hold timeout\")?;\n    let tap_action = parse_action(&ac_params[2], s)?;\n    let hold_action = parse_action(&ac_params[3], s)?;\n    let timeout_action = parse_action(&ac_params[4], s)?;\n    if matches!(tap_action, Action::HoldTap { .. }) {\n        bail!(\"tap-hold does not work in the tap-action of tap-hold\")\n    }\n    let on_press_reset_timeout_to = match config {\n        HoldTapConfig::PermissiveHold => match n_positional {\n            6 => match ac_params[5].atom(s.vars()) {\n                Some(\"reset-timeout-on-press\") => std::num::NonZeroU16::new(hold_timeout),\n                _ => bail_expr!(&ac_params[5], \"Unexpected parameter.\\n{PARAMS_FOR_RELEASE}\"),\n            },\n            5 => None,\n            _ => unreachable!(\"other lengths not expected\"),\n        },\n        HoldTapConfig::HoldOnOtherKeyPress => None,\n        _ => unreachable!(\"other configs not expected\"),\n    };\n    let opts = parse_tap_hold_options(&ac_params[n_positional..], s)?;\n    Ok(s.a.sref(Action::HoldTap(s.a.sref(HoldTapAction {\n        config,\n        tap_hold_interval: tap_repress_timeout,\n        timeout: hold_timeout,\n        tap: *tap_action,\n        hold: *hold_action,\n        timeout_action: *timeout_action,\n        on_press_reset_timeout_to,\n        require_prior_idle: opts.require_prior_idle,\n    }))))\n}\n\npub(crate) fn parse_tap_hold_order(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    let n_opts = count_trailing_options(ac_params, s);\n    let n_positional = ac_params.len() - n_opts;\n    if n_positional != 4 {\n        bail!(\n            r\"tap-hold-order expects 4 items after it, got {}.\nParams in order:\n<tap-repress-timeout> <buffer-ms> <tap-action> <hold-action>\",\n            n_positional,\n        )\n    }\n    let tap_repress_timeout = parse_u16(&ac_params[0], s, \"tap repress timeout\")?;\n    let buffer = parse_u16(&ac_params[1], s, \"buffer\")?;\n    let tap_action = parse_action(&ac_params[2], s)?;\n    let hold_action = parse_action(&ac_params[3], s)?;\n    if matches!(tap_action, Action::HoldTap { .. }) {\n        bail!(\"tap-hold does not work in the tap-action of tap-hold\")\n    }\n    let opts = parse_tap_hold_options(&ac_params[n_positional..], s)?;\n    Ok(s.a.sref(Action::HoldTap(s.a.sref(HoldTapAction {\n        config: HoldTapConfig::Order { buffer },\n        tap_hold_interval: tap_repress_timeout,\n        timeout: u16::MAX, // Resolution is purely event-driven, not timeout-based.\n        tap: *tap_action,\n        hold: *hold_action,\n        timeout_action: *tap_action,\n        on_press_reset_timeout_to: None,\n        require_prior_idle: opts.require_prior_idle,\n    }))))\n}\n\npub(crate) fn parse_tap_hold_keys(\n    ac_params: &[SExpr],\n    s: &ParserState,\n    custom_name: &str,\n    custom_func: TapHoldCustomFunc,\n) -> Result<&'static KanataAction> {\n    let n_opts = count_trailing_options(ac_params, s);\n    let n_positional = ac_params.len() - n_opts;\n    if n_positional != 5 {\n        bail!(\n            r\"{} expects 5 items after it, got {}.\nParams in order:\n<tap-repress-timeout> <hold-timeout> <tap-action> <hold-action> <tap-trigger-keys>\",\n            custom_name,\n            n_positional,\n        )\n    }\n    let tap_repress_timeout = parse_u16(&ac_params[0], s, \"tap repress timeout\")?;\n    let hold_timeout = parse_non_zero_u16(&ac_params[1], s, \"hold timeout\")?;\n    let tap_action = parse_action(&ac_params[2], s)?;\n    let hold_action = parse_action(&ac_params[3], s)?;\n    let tap_trigger_keys = parse_key_list(&ac_params[4], s, \"tap-trigger-keys\")?;\n    if matches!(tap_action, Action::HoldTap { .. }) {\n        bail!(\"tap-hold does not work in the tap-action of tap-hold\")\n    }\n    let opts = parse_tap_hold_options(&ac_params[n_positional..], s)?;\n    Ok(s.a.sref(Action::HoldTap(s.a.sref(HoldTapAction {\n        config: HoldTapConfig::Custom(custom_func(&tap_trigger_keys, &s.a)),\n        tap_hold_interval: tap_repress_timeout,\n        timeout: hold_timeout,\n        tap: *tap_action,\n        hold: *hold_action,\n        timeout_action: *hold_action,\n        on_press_reset_timeout_to: None,\n        require_prior_idle: opts.require_prior_idle,\n    }))))\n}\n\npub(crate) fn parse_tap_hold_keys_trigger_tap_release(\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    let n_opts = count_trailing_options(ac_params, s);\n    let n_positional = ac_params.len() - n_opts;\n    if n_positional != 6 {\n        bail!(\n            r\"{} expects 6 items after it, got {}.\nParams in order:\n<tap-repress-timeout> <hold-timeout> <tap-action> <hold-action> <tap-trigger-keys-on-press> <tap-trigger-keys-on-press-then-release>\",\n            TAP_HOLD_RELEASE_KEYS_TAP_RELEASE,\n            n_positional,\n        )\n    }\n    let tap_repress_timeout = parse_u16(&ac_params[0], s, \"tap repress timeout\")?;\n    let hold_timeout = parse_non_zero_u16(&ac_params[1], s, \"hold timeout\")?;\n    let tap_action = parse_action(&ac_params[2], s)?;\n    let hold_action = parse_action(&ac_params[3], s)?;\n    let tap_trigger_keys_on_press =\n        parse_key_list(&ac_params[4], s, \"tap-trigger-keys-on-multi-press\")?;\n    let tap_trigger_keys_on_press_then_release =\n        parse_key_list(&ac_params[5], s, \"tap-trigger-keys-on-release\")?;\n    if matches!(tap_action, Action::HoldTap { .. }) {\n        bail!(\"tap-hold does not work in the tap-action of tap-hold\")\n    }\n    let opts = parse_tap_hold_options(&ac_params[n_positional..], s)?;\n    Ok(s.a.sref(Action::HoldTap(s.a.sref(HoldTapAction {\n        config: HoldTapConfig::Custom(custom_tap_hold_release_trigger_tap_release(\n            &tap_trigger_keys_on_press,\n            &tap_trigger_keys_on_press_then_release,\n            &s.a,\n        )),\n        tap_hold_interval: tap_repress_timeout,\n        timeout: hold_timeout,\n        tap: *tap_action,\n        hold: *hold_action,\n        timeout_action: *hold_action,\n        on_press_reset_timeout_to: None,\n        require_prior_idle: opts.require_prior_idle,\n    }))))\n}\n"
  },
  {
    "path": "parser/src/cfg/tests/ambiguous.rs",
    "content": "use super::*;\n\n#[test]\nfn parse_double_dollar_var() {\n    let source = r#\"\n(defsrc)\n(deflayer base)\n(defvar $$num 100\n         $num 99\n          num not-a-number-or-key)\n(defalias test\n         (movemouse-accel-up $$num $$$num $$num $$$num))\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn parse_double_at_alias() {\n    let source = r#\"\n(defsrc)\n(deflayer base)\n          ;; alias cannot be used in macro, @alias can\n(defalias @alias 0\n           alias (tap-hold 9 9 a b)\n           test (macro @@alias))\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n"
  },
  {
    "path": "parser/src/cfg/tests/defcfg.rs",
    "content": "use super::*;\n\n#[test]\nfn disallow_same_key_in_defsrc_unmapped_except() {\n    let source = \"\n(defcfg process-unmapped-keys (all-except bspc))\n(defsrc bspc)\n(deflayermap (name) 0 0)\n\";\n    parse_cfg(source)\n        .map(|_| ())\n        //.map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect_err(\"fails\");\n}\n\n#[test]\nfn unmapped_except_keys_cannot_have_dupes() {\n    let source = \"\n(defcfg process-unmapped-keys (all-except bspc bspc))\n(defsrc)\n(deflayermap (name) 0 0)\n\";\n    parse_cfg(source)\n        .map(|_| ())\n        //.map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect_err(\"fails\");\n}\n\n#[test]\nfn unmapped_except_keys_must_be_known() {\n    let source = \"\n(defcfg process-unmapped-keys (all-except notakey))\n(defsrc)\n(deflayermap (name) 0 0)\n\";\n    parse_cfg(source)\n        .map(|_| ())\n        //.map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect_err(\"fails\");\n}\n\n#[test]\nfn unmapped_except_keys_respects_deflocalkeys() {\n    let source = \"\n(deflocalkeys-win         lkey90 555)\n(deflocalkeys-winiov2     lkey90 555)\n(deflocalkeys-wintercept  lkey90 555)\n(deflocalkeys-linux       lkey90 555)\n(deflocalkeys-macos       lkey90 555)\n(defcfg process-unmapped-keys (all-except lkey90))\n(defsrc)\n(deflayermap (name) 0 0)\n\";\n    let cfg = parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"passes\");\n    assert!(!cfg.mapped_keys.contains(&OsCode::from(555u16)));\n    assert!(cfg.mapped_keys.contains(&OsCode::KEY_ENTER));\n    for osc in 0..KEYS_IN_ROW as u16 {\n        if let Some(osc) = OsCode::from_u16(osc) {\n            match KeyCode::from(osc) {\n                KeyCode::No | KeyCode::K555 => {\n                    assert!(!cfg.mapped_keys.contains(&osc));\n                }\n                _ if osc.is_mouse_code() => {\n                    assert!(!cfg.mapped_keys.contains(&osc));\n                }\n                _ => {\n                    assert!(cfg.mapped_keys.contains(&osc));\n                }\n            }\n        }\n    }\n}\n\n#[test]\nfn unmapped_except_keys_is_removed_from_mapping() {\n    let source = \"\n(defcfg process-unmapped-keys (all-except 1 2 3))\n(defsrc mlft mmid)\n(deflayermap (name) 0 0)\n\";\n    let cfg = parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"passes\");\n    assert!(cfg.mapped_keys.contains(&OsCode::KEY_A));\n    assert!(cfg.mapped_keys.contains(&OsCode::KEY_0));\n    assert!(!cfg.mapped_keys.contains(&OsCode::KEY_1));\n    assert!(!cfg.mapped_keys.contains(&OsCode::KEY_2));\n    assert!(!cfg.mapped_keys.contains(&OsCode::KEY_3));\n    assert!(cfg.mapped_keys.contains(&OsCode::KEY_4));\n    for osc in 0..KEYS_IN_ROW as u16 {\n        if let Some(osc) = OsCode::from_u16(osc) {\n            match KeyCode::from(osc) {\n                KeyCode::No | KeyCode::Kb1 | KeyCode::Kb2 | KeyCode::Kb3 => {\n                    assert!(!cfg.mapped_keys.contains(&osc));\n                }\n                // mlft, mmid\n                KeyCode::K272 | KeyCode::K274 => {\n                    assert!(cfg.mapped_keys.contains(&osc));\n                }\n                _ if osc.is_mouse_code() => {\n                    assert!(!cfg.mapped_keys.contains(&osc));\n                }\n                _ => {\n                    assert!(cfg.mapped_keys.contains(&osc));\n                }\n            }\n        }\n    }\n}\n\n#[test]\nfn non_applicable_os_deflocalkeys_always_succeeds_parsing() {\n    let source = \"\n(deflocalkeys-linux å 26 ' 43)\n(defsrc)\n(deflayer base)\n\";\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"passes\");\n}\n\n#[test]\nfn tap_hold_require_prior_idle_parses_valid_value() {\n    let source = \"\n(defcfg tap-hold-require-prior-idle 150)\n(defsrc a)\n(deflayer base a)\n\";\n    let cfg = parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"passes\");\n    assert_eq!(cfg.options.tap_hold_require_prior_idle, 150);\n}\n\n#[test]\nfn tap_hold_require_prior_idle_allows_zero() {\n    let source = \"\n(defcfg tap-hold-require-prior-idle 0)\n(defsrc a)\n(deflayer base a)\n\";\n    let cfg = parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"passes\");\n    assert_eq!(cfg.options.tap_hold_require_prior_idle, 0);\n}\n\n#[test]\nfn tap_hold_require_prior_idle_rejects_non_numeric() {\n    let source = \"\n(defcfg tap-hold-require-prior-idle nope)\n(defsrc a)\n(deflayer base a)\n\";\n    parse_cfg(source)\n        .map(|_| ())\n        //.map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect_err(\"fails\");\n}\n\n#[test]\nfn per_action_require_prior_idle_parses() {\n    let source = \"\n(defsrc a)\n(deflayer base @a)\n(defalias a (tap-hold 200 200 a lctl (require-prior-idle 100)))\n\";\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"passes\");\n}\n\n#[test]\nfn per_action_require_prior_idle_zero_parses() {\n    let source = \"\n(defsrc a)\n(deflayer base @a)\n(defalias a (tap-hold 200 200 a lctl (require-prior-idle 0)))\n\";\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"passes\");\n}\n\n#[test]\nfn per_action_require_prior_idle_rejects_non_numeric() {\n    let source = \"\n(defsrc a)\n(deflayer base @a)\n(defalias a (tap-hold 200 200 a lctl (require-prior-idle nope)))\n\";\n    parse_cfg(source).map(|_| ()).expect_err(\"fails\");\n}\n\n#[test]\nfn per_action_require_prior_idle_rejects_unknown_option() {\n    let source = \"\n(defsrc a)\n(deflayer base @a)\n(defalias a (tap-hold 200 200 a lctl (unknown-option 100)))\n\";\n    parse_cfg(source).map(|_| ()).expect_err(\"fails\");\n}\n\n#[test]\nfn per_action_require_prior_idle_rejects_duplicate() {\n    let source = \"\n(defsrc a)\n(deflayer base @a)\n(defalias a (tap-hold 200 200 a lctl (require-prior-idle 100) (require-prior-idle 50)))\n\";\n    parse_cfg(source).map(|_| ()).expect_err(\"fails\");\n}\n\n#[test]\nfn per_action_require_prior_idle_on_tap_hold_press() {\n    let source = \"\n(defsrc a)\n(deflayer base @a)\n(defalias a (tap-hold-press 200 200 a lctl (require-prior-idle 100)))\n\";\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"passes\");\n}\n\n#[test]\nfn per_action_require_prior_idle_on_tap_hold_release() {\n    let source = \"\n(defsrc a)\n(deflayer base @a)\n(defalias a (tap-hold-release 200 200 a lctl (require-prior-idle 100)))\n\";\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"passes\");\n}\n\n#[test]\nfn per_action_require_prior_idle_on_tap_hold_release_timeout() {\n    let source = \"\n(defsrc a)\n(deflayer base @a)\n(defalias a (tap-hold-release-timeout 200 200 a lctl lalt (require-prior-idle 100)))\n\";\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"passes\");\n}\n\n#[test]\nfn per_action_require_prior_idle_on_tap_hold_press_timeout() {\n    let source = \"\n(defsrc a)\n(deflayer base @a)\n(defalias a (tap-hold-press-timeout 200 200 a lctl lalt (require-prior-idle 100)))\n\";\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"passes\");\n}\n\n#[test]\nfn per_action_require_prior_idle_on_tap_hold_release_keys() {\n    let source = \"\n(defsrc a b)\n(deflayer base @a b)\n(defalias a (tap-hold-release-keys 200 200 a lctl (b) (require-prior-idle 100)))\n\";\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"passes\");\n}\n\n#[test]\nfn per_action_require_prior_idle_on_tap_hold_except_keys() {\n    let source = \"\n(defsrc a b)\n(deflayer base @a b)\n(defalias a (tap-hold-except-keys 200 200 a lctl (b) (require-prior-idle 100)))\n\";\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"passes\");\n}\n\n#[test]\nfn per_action_require_prior_idle_on_tap_hold_opposite_hand() {\n    let source = \"\n(defhands (left a s d f g) (right h j k l ;))\n(defsrc a)\n(deflayer base @a)\n(defalias a (tap-hold-opposite-hand 200 a lctl (require-prior-idle 100)))\n\";\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"passes\");\n}\n\n#[test]\nfn per_action_require_prior_idle_on_tap_hold_release_tap_keys_release() {\n    let source = \"\n(defsrc a b c)\n(deflayer base @a b c)\n(defalias a (tap-hold-release-tap-keys-release 200 200 a lctl (b) (c) (require-prior-idle 100)))\n\";\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"passes\");\n}\n"
  },
  {
    "path": "parser/src/cfg/tests/defhands.rs",
    "content": "use super::*;\n\n#[test]\nfn opposite_hand_no_args() {\n    let source = \"\n(defhands (left a s d f) (right j k l ;))\n(defsrc a)\n(deflayer base (tap-hold-opposite-hand))\n\";\n    parse_cfg(source)\n        .map(|_| ())\n        .expect_err(\"tap-hold-opposite-hand with zero args should fail\");\n}\n\n#[test]\nfn opposite_hand_one_arg() {\n    let source = \"\n(defhands (left a s d f) (right j k l ;))\n(defsrc a)\n(deflayer base (tap-hold-opposite-hand 180))\n\";\n    parse_cfg(source)\n        .map(|_| ())\n        .expect_err(\"tap-hold-opposite-hand with one arg should fail\");\n}\n\n#[test]\nfn opposite_hand_two_args() {\n    let source = \"\n(defhands (left a s d f) (right j k l ;))\n(defsrc a)\n(deflayer base (tap-hold-opposite-hand 180 a))\n\";\n    parse_cfg(source)\n        .map(|_| ())\n        .expect_err(\"tap-hold-opposite-hand with two args should fail\");\n}\n\n#[test]\nfn defhands_missing_for_opposite_hand() {\n    let source = \"\n(defsrc a)\n(deflayer base (tap-hold-opposite-hand 180 a lctl))\n\";\n    parse_cfg(source)\n        .map(|_| ())\n        .expect_err(\"tap-hold-opposite-hand without defhands should fail\");\n}\n\n#[test]\nfn defhands_duplicate_blocks() {\n    let source = \"\n(defhands (left a s d f) (right j k l ;))\n(defhands (left q w e r))\n(defsrc a)\n(deflayer base a)\n\";\n    parse_cfg(source)\n        .map(|_| ())\n        .expect_err(\"duplicate defhands blocks should fail\");\n}\n\n#[test]\nfn defhands_key_in_both_groups() {\n    let source = \"\n(defhands (left a s d f) (right a j k l))\n(defsrc a)\n(deflayer base a)\n\";\n    parse_cfg(source)\n        .map(|_| ())\n        .expect_err(\"same key in both left and right should fail\");\n}\n\n#[test]\nfn defhands_duplicate_group_name() {\n    let source = \"\n(defhands (left a s d f) (left q w e r))\n(defsrc a)\n(deflayer base a)\n\";\n    parse_cfg(source)\n        .map(|_| ())\n        .expect_err(\"duplicate left group in defhands should fail\");\n}\n\n#[test]\nfn defhands_invalid_group_name() {\n    let source = \"\n(defhands (center a s d f))\n(defsrc a)\n(deflayer base a)\n\";\n    parse_cfg(source)\n        .map(|_| ())\n        .expect_err(\"invalid group name 'center' should fail\");\n}\n\n#[test]\nfn opposite_hand_unknown_option() {\n    let source = \"\n(defhands (left a s d f) (right j k l ;))\n(defsrc a)\n(deflayer base (tap-hold-opposite-hand 180 a lctl (foo bar)))\n\";\n    parse_cfg(source)\n        .map(|_| ())\n        .expect_err(\"unknown option foo should fail\");\n}\n\n#[test]\nfn opposite_hand_trailing_keyword() {\n    let source = \"\n(defhands (left a s d f) (right j k l ;))\n(defsrc a)\n(deflayer base (tap-hold-opposite-hand 180 a lctl (timeout)))\n\";\n    parse_cfg(source)\n        .map(|_| ())\n        .expect_err(\"option list without value should fail\");\n}\n\n#[test]\nfn opposite_hand_invalid_behavior() {\n    let source = \"\n(defhands (left a s d f) (right j k l ;))\n(defsrc a)\n(deflayer base (tap-hold-opposite-hand 180 a lctl (same-hand maybe)))\n\";\n    parse_cfg(source)\n        .map(|_| ())\n        .expect_err(\"invalid behavior 'maybe' should fail\");\n}\n\n#[test]\nfn opposite_hand_duplicate_option() {\n    let source = \"\n(defhands (left a s d f) (right j k l ;))\n(defsrc a)\n(deflayer base (tap-hold-opposite-hand 180 a lctl (timeout tap) (timeout hold)))\n\";\n    parse_cfg(source)\n        .map(|_| ())\n        .expect_err(\"duplicate option timeout should fail\");\n}\n\n#[test]\nfn defhands_valid_partial() {\n    let source = \"\n(defhands (left a s d f))\n(defsrc a)\n(deflayer base (tap-hold-opposite-hand 180 a lctl))\n\";\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"partial defhands with only left should succeed\");\n}\n"
  },
  {
    "path": "parser/src/cfg/tests/device_detect.rs",
    "content": "#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\nmod linux {\n    use super::super::*;\n\n    #[test]\n    fn linux_device_parses_properly() {\n        let source = r#\"\n(defcfg linux-device-detect-mode any)\n(defsrc) (deflayer base)\"#;\n        let icfg = parse_cfg(source)\n            .map_err(|e| log::info!(\"{:?}\", miette::Error::from(e)))\n            .expect(\"no error\");\n        assert_eq!(\n            icfg.options.linux_opts.linux_device_detect_mode,\n            Some(DeviceDetectMode::Any)\n        );\n\n        let source = r#\"\n(defcfg linux-device-detect-mode keyboard-only)\n(defsrc) (deflayer base)\"#;\n        let icfg = parse_cfg(source)\n            .map_err(|e| log::info!(\"{:?}\", miette::Error::from(e)))\n            .expect(\"no error\");\n        assert_eq!(\n            icfg.options.linux_opts.linux_device_detect_mode,\n            Some(DeviceDetectMode::KeyboardOnly)\n        );\n\n        let source = r#\"\n(defcfg linux-device-detect-mode keyboard-mice)\n(defsrc) (deflayer base)\"#;\n        let icfg = parse_cfg(source)\n            .map_err(|e| log::info!(\"{:?}\", miette::Error::from(e)))\n            .expect(\"no error\");\n        assert_eq!(\n            icfg.options.linux_opts.linux_device_detect_mode,\n            Some(DeviceDetectMode::KeyboardMice)\n        );\n\n        let source = r#\"(defsrc mmid) (deflayer base 1)\"#;\n        let icfg = parse_cfg(source)\n            .map_err(|e| log::info!(\"{:?}\", miette::Error::from(e)))\n            .expect(\"no error\");\n        assert_eq!(\n            icfg.options.linux_opts.linux_device_detect_mode,\n            Some(DeviceDetectMode::Any)\n        );\n\n        let source = r#\"(defsrc a) (deflayer base b)\"#;\n        let icfg = parse_cfg(source)\n            .map_err(|e| log::info!(\"{:?}\", miette::Error::from(e)))\n            .expect(\"no error\");\n        assert_eq!(\n            icfg.options.linux_opts.linux_device_detect_mode,\n            Some(DeviceDetectMode::KeyboardMice)\n        );\n\n        let source = r#\"\n(defcfg linux-device-detect-mode not an opt)\n(defsrc) (deflayer base)\"#;\n        parse_cfg(source)\n            .map(|_| ())\n            .map_err(|e| log::info!(\"{:?}\", miette::Error::from(e)))\n            .expect_err(\"error should happen\");\n    }\n}\n"
  },
  {
    "path": "parser/src/cfg/tests/environment.rs",
    "content": "use super::*;\n\nfn parse_cfg_env(cfg: &str, env_vars: Vec<(String, String)>) -> Result<IntermediateCfg> {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let mut s = ParserState::default();\n    parse_cfg_raw_string(\n        cfg,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Ok(env_vars),\n    )\n}\n\n#[test]\nfn parse_env() {\n    parse_cfg_env(\n        r#\"\n        (environment (hello \"\") (defsrc a))\n        (environment (goodbye \"\") (deflayer 1 (layer-switch 2)))\n        (environment (farewell val) (deflayer 2 (layer-switch 1)))\n        ;; below would conflict if environment did not cancel\n        (environment (hello yea) (defsrc))\n        (environment (goodbye yea) (deflayer 1))\n        (environment (farewell notval) (deflayer 2))\n        \"#,\n        vec![\n            (\"goodbye\".into(), \"\".into()),\n            (\"farewell\".into(), \"val\".into()),\n        ],\n    )\n    .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n    .unwrap();\n}\n"
  },
  {
    "path": "parser/src/cfg/tests/macros.rs",
    "content": "use super::*;\n\n#[test]\nfn unsupported_action_in_macro_triggers_error() {\n    let source = r#\"\n(defsrc)\n(deflayer base)\n(defalias a (macro (multi a b c))) \"#;\n    parse_cfg(source)\n        .map(|_| ())\n        .map_err(|e| log::info!(\"{:?}\", miette::Error::from(e)))\n        .expect_err(\"errors\");\n}\n\n#[test]\nfn incorrectly_configured_supported_action_in_macro_triggers_useful_error() {\n    let source = r#\"\n(defsrc)\n(deflayer base)\n(defalias a (macro (on-press press-vkey does-not-exist))) \"#;\n    parse_cfg(source)\n        .map(|_| ())\n        .map_err(|e| {\n            let e = miette::Error::from(e);\n            let msg = format!(\"{e:?}\");\n            log::info!(\"{msg}\");\n            assert!(msg.contains(\"unknown virtual key name: does-not-exist\"));\n        })\n        .expect_err(\"errors\");\n}\n"
  },
  {
    "path": "parser/src/cfg/tests.rs",
    "content": "use super::*;\n#[allow(unused_imports)]\nuse crate::cfg::sexpr::{Span, parse};\nuse kanata_keyberon::action::BooleanOperator::*;\n\nuse std::sync::{Mutex, MutexGuard};\n\nmod ambiguous;\nmod defcfg;\nmod defhands;\nmod device_detect;\nmod environment;\nmod macros;\n\nstatic CFG_PARSE_LOCK: Mutex<()> = Mutex::new(());\n\nfn init_log() {\n    use simplelog::*;\n    use std::sync::OnceLock;\n    static LOG_INIT: OnceLock<()> = OnceLock::new();\n    LOG_INIT.get_or_init(|| {\n        let mut log_cfg = ConfigBuilder::new();\n        if let Err(e) = log_cfg.set_time_offset_to_local() {\n            eprintln!(\"WARNING: could not set log TZ to local: {e:?}\");\n        };\n        log_cfg.set_time_format_rfc3339();\n        CombinedLogger::init(vec![TermLogger::new(\n            // Note: set to a different level to see logs in tests.\n            // Also, not all tests call init_log so you might have to add the call there too.\n            LevelFilter::Error,\n            log_cfg.build(),\n            TerminalMode::Stderr,\n            ColorChoice::AlwaysAnsi,\n        )])\n        .expect(\"logger can init\");\n    });\n}\n\nfn lock<T>(lk: &Mutex<T>) -> MutexGuard<'_, T> {\n    match lk.lock() {\n        Ok(guard) => guard,\n        Err(poisoned) => poisoned.into_inner(),\n    }\n}\n\nfn parse_cfg(cfg: &str) -> Result<IntermediateCfg> {\n    init_log();\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let mut s = ParserState::default();\n    let icfg = parse_cfg_raw_string(\n        cfg,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    );\n    if let Ok(ref icfg) = icfg {\n        assert!(icfg.klayers.layers.iter().all(|layer| {\n            layer[usize::from(NORMAL_KEY_ROW)]\n                .iter()\n                .all(|action| *action != DEFAULT_ACTION)\n        }));\n        #[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\n        assert!(icfg.options.linux_opts.linux_device_detect_mode.is_some());\n    }\n    icfg\n}\n\n#[test]\nfn sizeof_action_is_two_usizes() {\n    assert_eq!(\n        std::mem::size_of::<KanataAction>(),\n        std::mem::size_of::<usize>() * 2\n    );\n}\n\n#[test]\nfn test_span_absolute_ranges() {\n    let s = \"(hello world my oyster)\\n(row two)\";\n    let tlevel = parse(s, \"test\").unwrap();\n    assert_eq!(\n        &s[tlevel[0].span.start()..tlevel[0].span.end()],\n        \"(hello world my oyster)\"\n    );\n    assert_eq!(\n        &s[tlevel[1].span.start()..tlevel[1].span.end()],\n        \"(row two)\"\n    );\n}\n\n#[test]\nfn span_works_with_unicode_characters() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let mut s = ParserState::default();\n    let source = r#\"(defsrc a) ;; 😊\n(deflayer base @😊)\n\"#;\n    let span = parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    )\n    .expect_err(\"should be an error because @😊 is not defined\")\n    .span\n    .expect(\"span should be Some\");\n\n    assert_eq!(&source[span.start()..span.end()], \"@😊\");\n\n    assert_eq!(span.start.line, 1);\n    assert_eq!(span.end.line, 1);\n\n    assert_eq!(\"😊\".len(), 4);\n    assert_eq!(\"(defsrc a) ;; 😊\\n\".len(), 19);\n    assert_eq!(span.start.line_beginning, 19);\n    assert_eq!(span.end.line_beginning, 19);\n}\n\n#[test]\nfn test_multiline_error_span() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let mut s = ParserState::default();\n    let source = r#\"(defsrc a)\n(\n  🍍\n  🍕\n)\n(defalias a b)\n\"#;\n    let span = parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    )\n    .expect_err(\"should error on unknown top level block\")\n    .span\n    .expect(\"span should be Some\");\n\n    assert_eq!(&source[span.start()..span.end()], \"(\\n  🍍\\n  🍕\\n)\");\n\n    assert_eq!(span.start.line, 1);\n    assert_eq!(span.end.line, 4);\n\n    assert_eq!(span.start.line_beginning, \"(defsrc a)\\n\".len());\n    assert_eq!(span.end.line_beginning, \"(defsrc a)\\n(\\n  🍍\\n  🍕\\n\".len());\n}\n\n#[test]\nfn test_span_of_an_unterminated_block_comment_error() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let mut s = ParserState::default();\n    let source = r#\"(defsrc a) |# I'm an unterminated block comment...\"#;\n    let span = parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    )\n    .expect_err(\"should be an unterminated comment error\")\n    .span\n    .expect(\"span should be Some\");\n\n    assert_eq!(&source[span.start()..span.end()], \"|#\");\n\n    assert_eq!(span.start.line, 0);\n    assert_eq!(span.end.line, 0);\n\n    assert_eq!(span.start.line_beginning, 0);\n    assert_eq!(span.end.line_beginning, 0);\n}\n\n#[test]\nfn parse_action_vars() {\n    let source = r#\"\n(defvirtualkeys\n  ctl lctl\n  sft lsft\n  met lmet\n  alt lalt\n)\n(defvar\n  one 1\n  two 2\n  a a\n  base base\n  three ($a b c)\n  chr C-S-v\n  td ($a b $chr)\n  four (lctl d)\n)\n(defvar\n  five (lsft e)\n  rel release\n  e  example\n  e2 example2\n  chord1 (chord $e $one)\n  chord2 (chord $e2 $one)\n  1 (1)\n  full-action (tap-dance $one $three)\n)\n(defalias\n  tdl (tap-dance $two $td)\n  tde (tap-dance-eager $two $td)\n  unc (unicode $one)\n  rlk (release-key $one)\n  mul (multi $two $one)\n  mwu (mwheel-up $one $two)\n  mwua (🖱☸↑ $one $two)\n  mwda (🖱☸↓ $one $two)\n  mwla (🖱☸← $one $two)\n  mwra (🖱☸→ $one $two)\n  mmua (🖱↑ $one $two)\n  mmda (🖱↓ $one $two)\n  mmla (🖱← $one $two)\n  mmra (🖱→ $one $two)\n  mmu (movemouse-up $one $two)\n  mau (movemouse-accel-up $one $two $one $two)\n  maua (🖱accel↑ $one $two $one $two)\n  mada (🖱accel↓ $one $two $one $two)\n  mala (🖱accel← $one $two $one $two)\n  mara (🖱accel→ $one $two $one $two)\n  ons (one-shot $one $two)\n  thd (tap-hold $one $two $chr $two)\n  tht (tap-hold-release-timeout $one $two $chr $two $one)\n  thk (tap-hold-release-keys $one $two $chr $two $three)\n  the (tap-hold-except-keys $one $two $chr $two $three)\n  thtt (tap-hold-tap-keys $one $two $chr $two $three)\n  thta (tap⬓↑timeout $one $two $chr $two $one)\n  thka (tap⬓↑keys $one $two $chr $two $three)\n  thea (tap⬓⤫keys $one $two $chr $two $three)\n  thtta (tap⬓tapkeys $one $two $chr $two $three)\n  mac (macro $one $two $one $two $chr C-S-$three $one)\n  rmc (macro-repeat $one $two $one $two $chr C-S-$three $one)\n  mrca (macro↑⤫ $one 500 bspc S-1 500 bspc S-2)\n  mrra (macro⟳↑⤫ mltp)\n  oat (tap⬓↓timeout   200 200 o $one bspc)\n  fsta (🖱speed 200)\n  psfa (on↓ press-virtualkey   sft)\n  rsfa (on↑ release-virtualkey sft)\n  os2a (one-shot↓ 2000 lsft)\n  os3a (one-shot↑ 2000 lctl)\n  os4a (one-shot↓⤫ 2000 lalt)\n  os5a (one-shot↑⤫ 2000 lmet)\n  oara (tap⬓↓ 200 200 o $two)\n  echa (tap⬓↑ 200 200 e $two)\n  rmca (macro⟳ $one $two $one $two $chr C-S-$three $one)\n  dr1 (dynamic-macro-record $one)\n  dp1 (dynamic-macro-play $one)\n  abc (arbitrary-code $one)\n  opf (on-press-fakekey $one $rel)\n  orf (on-release-fakekey $one $rel)\n  opfa (on↓fakekey $one $rel)\n  orfa (on↑fakekey $one $rel)\n  opfda (on↓fakekey-delay 200)\n  orfda (on↑fakekey-delay 200)\n  relka (key↑ $one)\n  rella (layer↑ base)\n  fla $full-action\n  frk (fork $one $two $five)\n  cpw (caps-word-custom $one $three $four)\n  cwa (word⇪ 2000)\n  cpwa (word⇪custom $one $three $four)\n  rst (dynamic-macro-record-stop-truncate $one)\n  stm (setmouse $one $two)\n  stma (set🖱 $one $two)\n)\n(defsrc a b c d)\n(deflayer base $chord1 $chord2 $chr @tdl)\n(defoverrides\n  ($two) ($one)\n  ($one) $four\n  $five ($two)\n  $four $five\n)\n(deffakekeys\n  $one $two\n)\n(defseq $one $three)\n(defchords $e $one $1 $two)\n(defchords $e2 $one ($one) $two)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn parse_multiline_comment() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    new_from_file(&std::path::PathBuf::from(\n        \"./test_cfgs/multiline_comment.kbd\",\n    ))\n    .unwrap();\n}\n\n#[test]\nfn parse_all_keys() {\n    init_log();\n    let _lk = match CFG_PARSE_LOCK.lock() {\n        Ok(guard) => guard,\n        Err(poisoned) => poisoned.into_inner(),\n    };\n    new_from_file(&std::path::PathBuf::from(\n        \"./test_cfgs/all_keys_in_defsrc.kbd\",\n    ))\n    .unwrap();\n}\n\n#[test]\nfn parse_file_with_utf8_bom() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    new_from_file(&std::path::PathBuf::from(\"./test_cfgs/utf8bom.kbd\")).unwrap();\n}\n\n#[test]\n#[cfg(feature = \"zippychord\")]\nfn parse_zippychord_file() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    new_from_file(&std::path::PathBuf::from(\"./test_cfgs/testzch.kbd\")).unwrap();\n}\n\n#[test]\nfn disallow_nested_tap_hold() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    match new_from_file(&std::path::PathBuf::from(\"./test_cfgs/nested_tap_hold.kbd\"))\n        .map_err(|e| format!(\"{}\", e.help().unwrap()))\n    {\n        Ok(_) => panic!(\"invalid nested tap-hold in tap action was Ok'd\"),\n        Err(e) => assert!(e.contains(\"tap-hold\"), \"real e: {e}\"),\n    }\n}\n\n#[test]\nfn disallow_ancestor_seq() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    match new_from_file(&std::path::PathBuf::from(\"./test_cfgs/ancestor_seq.kbd\"))\n        .map_err(|e| format!(\"{e:?}\"))\n    {\n        Ok(_) => panic!(\"invalid ancestor seq was Ok'd\"),\n        Err(e) => assert!(e.contains(\"is contained\")),\n    }\n}\n\n#[test]\nfn disallow_descendent_seq() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    match new_from_file(&std::path::PathBuf::from(\"./test_cfgs/descendant_seq.kbd\"))\n        .map_err(|e| format!(\"{e:?}\"))\n    {\n        Ok(_) => panic!(\"invalid descendant seq was Ok'd\"),\n        Err(e) => assert!(e.contains(\"contains\")),\n    }\n}\n\n#[test]\nfn disallow_multiple_waiting_actions() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    match new_from_file(&std::path::PathBuf::from(\"./test_cfgs/bad_multi.kbd\"))\n        .map_err(|e| format!(\"{}\", e.help().unwrap()))\n    {\n        Ok(_) => panic!(\"invalid multiple waiting actions Ok'd\"),\n        Err(e) => assert!(e.contains(\"Cannot combine multiple\")),\n    }\n}\n\n#[test]\nfn chord_in_macro_dont_panic() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    new_from_file(&std::path::PathBuf::from(\n        \"./test_cfgs/macro-chord-dont-panic.kbd\",\n    ))\n    .map(|_| ())\n    .expect_err(\"graceful failure, no panic, also no success\");\n}\n\n#[test]\nfn unknown_defcfg_item_fails() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    new_from_file(&std::path::PathBuf::from(\n        \"./test_cfgs/unknown_defcfg_opt.kbd\",\n    ))\n    .map(|_| ())\n    .expect_err(\"graceful failure, no panic, also no success\");\n}\n\n#[test]\nfn recursive_multi_is_flattened() {\n    macro_rules! atom {\n        ($e:expr) => {\n            SExpr::Atom(Spanned::new($e.into(), Span::default()))\n        };\n    }\n    macro_rules! list {\n        ($e:tt) => {\n            SExpr::List(Spanned::new(vec! $e, Span::default()))\n        };\n    }\n    use sexpr::*;\n    let params = [\n        atom!(\"a\"),\n        atom!(\"mmtp\"),\n        list!([\n            atom!(\"multi\"),\n            atom!(\"b\"),\n            atom!(\"mltp\"),\n            list!([atom!(\"multi\"), atom!(\"c\"), atom!(\"mrtp\"),])\n        ]),\n    ];\n    let s = ParserState::default();\n    if let KanataAction::MultipleActions(parsed_multi) = parse_multi(&params, &s).unwrap() {\n        assert_eq!(parsed_multi.len(), 4);\n        assert_eq!(parsed_multi[0], Action::KeyCode(KeyCode::A));\n        assert_eq!(parsed_multi[1], Action::KeyCode(KeyCode::B));\n        assert_eq!(parsed_multi[2], Action::KeyCode(KeyCode::C));\n        assert_eq!(\n            parsed_multi[3],\n            Action::Custom(\n                &&[\n                    &CustomAction::MouseTap(Btn::Mid),\n                    &CustomAction::MouseTap(Btn::Left),\n                    &CustomAction::MouseTap(Btn::Right),\n                ][..]\n            )\n        );\n    } else {\n        panic!(\"multi did not parse into multi\");\n    }\n}\n\n#[test]\nfn test_parse_sequence_a_b() {\n    let seq = parse_sequence_keys(\n        &parse(\"(a b)\", \"test\").expect(\"parses\")[0].t,\n        &ParserState::default(),\n    )\n    .expect(\"parses\");\n    assert_eq!(seq.len(), 2);\n    assert_eq!(&seq[0], &u16::from(OsCode::KEY_A));\n    assert_eq!(&seq[1], &u16::from(OsCode::KEY_B));\n}\n\n#[test]\nfn test_parse_sequence_sa_b() {\n    let seq = parse_sequence_keys(\n        &parse(\"(S-a b)\", \"test\").expect(\"parses\")[0].t,\n        &ParserState::default(),\n    )\n    .expect(\"parses\");\n    assert_eq!(seq.len(), 3);\n    assert_eq!(&seq[0], &(u16::from(OsCode::KEY_LEFTSHIFT) | 0x8000));\n    assert_eq!(&seq[1], &(u16::from(OsCode::KEY_A) | 0x8000));\n    assert_eq!(&seq[2], &u16::from(OsCode::KEY_B));\n}\n\n#[test]\nfn test_parse_sequence_sab() {\n    let seq = parse_sequence_keys(\n        &parse(\"(S-(a b))\", \"test\").expect(\"parses\")[0].t,\n        &ParserState::default(),\n    )\n    .expect(\"parses\");\n    assert_eq!(seq.len(), 3);\n    assert_eq!(&seq[0], &(u16::from(OsCode::KEY_LEFTSHIFT) | 0x8000));\n    assert_eq!(&seq[1], &(u16::from(OsCode::KEY_A) | 0x8000));\n    assert_eq!(&seq[2], &(u16::from(OsCode::KEY_B) | 0x8000));\n}\n\n#[test]\nfn test_parse_sequence_bigchord() {\n    let seq = parse_sequence_keys(\n        &parse(\"(AG-A-M-C-S-(a b) c)\", \"test\").expect(\"parses\")[0].t,\n        &ParserState::default(),\n    )\n    .expect(\"parses\");\n    assert_eq!(seq.len(), 8);\n    assert_eq!(&seq[0], &(u16::from(OsCode::KEY_RIGHTALT) | 0x1000));\n    assert_eq!(&seq[1], &(u16::from(OsCode::KEY_LEFTALT) | 0x3000));\n    assert_eq!(&seq[2], &(u16::from(OsCode::KEY_LEFTMETA) | 0x3800));\n    assert_eq!(&seq[3], &(u16::from(OsCode::KEY_LEFTCTRL) | 0x7800));\n    assert_eq!(&seq[4], &(u16::from(OsCode::KEY_LEFTSHIFT) | 0xF800));\n    assert_eq!(&seq[5], &(u16::from(OsCode::KEY_A) | 0xF800));\n    assert_eq!(&seq[6], &(u16::from(OsCode::KEY_B) | 0xF800));\n    assert_eq!(&seq[7], &(u16::from(OsCode::KEY_C)));\n}\n\n#[test]\nfn test_parse_sequence_inner_chord() {\n    let seq = parse_sequence_keys(\n        &parse(\"(S-(a b C-c) d)\", \"test\").expect(\"parses\")[0].t,\n        &ParserState::default(),\n    )\n    .expect(\"parses\");\n    assert_eq!(seq.len(), 6);\n    assert_eq!(&seq[0], &(u16::from(OsCode::KEY_LEFTSHIFT) | 0x8000));\n    assert_eq!(&seq[1], &(u16::from(OsCode::KEY_A) | 0x8000));\n    assert_eq!(&seq[2], &(u16::from(OsCode::KEY_B) | 0x8000));\n    assert_eq!(&seq[3], &(u16::from(OsCode::KEY_LEFTCTRL) | 0xC000));\n    assert_eq!(&seq[4], &(u16::from(OsCode::KEY_C) | 0xC000));\n    assert_eq!(&seq[5], &(u16::from(OsCode::KEY_D)));\n}\n\n#[test]\nfn test_parse_sequence_earlier_inner_chord() {\n    let seq = parse_sequence_keys(\n        &parse(\"(S-(a C-b c) d)\", \"test\").expect(\"parses\")[0].t,\n        &ParserState::default(),\n    )\n    .expect(\"parses\");\n    assert_eq!(seq.len(), 6);\n    assert_eq!(&seq[0], &(u16::from(OsCode::KEY_LEFTSHIFT) | 0x8000));\n    assert_eq!(&seq[1], &(u16::from(OsCode::KEY_A) | 0x8000));\n    assert_eq!(&seq[2], &(u16::from(OsCode::KEY_LEFTCTRL) | 0xC000));\n    assert_eq!(&seq[3], &(u16::from(OsCode::KEY_B) | 0xC000));\n    assert_eq!(&seq[4], &(u16::from(OsCode::KEY_C) | 0x8000));\n    assert_eq!(&seq[5], &(u16::from(OsCode::KEY_D)));\n}\n\n#[test]\nfn test_parse_sequence_numbers() {\n    let seq = parse_sequence_keys(\n        &parse(\"(0 1 2 3 4 5 6 7 8 9)\", \"test\").expect(\"parses\")[0].t,\n        &ParserState::default(),\n    )\n    .expect(\"parses\");\n    assert_eq!(seq.len(), 10);\n    assert_eq!(&seq[0], &u16::from(OsCode::KEY_0));\n    assert_eq!(&seq[1], &u16::from(OsCode::KEY_1));\n    assert_eq!(&seq[2], &u16::from(OsCode::KEY_2));\n    assert_eq!(&seq[3], &u16::from(OsCode::KEY_3));\n    assert_eq!(&seq[4], &u16::from(OsCode::KEY_4));\n    assert_eq!(&seq[5], &u16::from(OsCode::KEY_5));\n    assert_eq!(&seq[6], &u16::from(OsCode::KEY_6));\n    assert_eq!(&seq[7], &u16::from(OsCode::KEY_7));\n    assert_eq!(&seq[8], &u16::from(OsCode::KEY_8));\n    assert_eq!(&seq[9], &u16::from(OsCode::KEY_9));\n}\n\n#[test]\nfn test_parse_macro_numbers() {\n    // Note, can't test zero in this way because a delay of 0 isn't allowed by the parsing.\n    let exprs = parse(\"(1 2 3 4 5 6 7 8 9)\", \"test\").expect(\"parses\")[0]\n        .t\n        .clone();\n    let mut expr_rem = exprs.as_slice();\n    let mut i = 1;\n    while !expr_rem.is_empty() {\n        let (macro_events, expr_rem_tmp) =\n            parse_macro_item(expr_rem, &ParserState::default()).expect(\"parses\");\n        expr_rem = expr_rem_tmp;\n        assert_eq!(macro_events.len(), 1);\n        match &macro_events[0] {\n            SequenceEvent::Delay { duration } => assert_eq!(duration, &i),\n            ev => panic!(\"expected delay, {ev:?}\"),\n        }\n        i += 1;\n    }\n\n    let exprs = parse(\"(0)\", \"test\").expect(\"parses\")[0].t.clone();\n    parse_macro_item(exprs.as_slice(), &ParserState::default()).expect_err(\"errors\");\n}\n\n#[test]\nfn test_include_good() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    new_from_file(&std::path::PathBuf::from(\"./test_cfgs/include-good.kbd\")).unwrap();\n}\n\n#[test]\nfn test_include_bad_has_filename_included() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let err = format!(\n        \"{:?}\",\n        new_from_file(\n            &std::path::Path::new(\".\")\n                .join(\"test_cfgs\")\n                .join(\"include-bad.kbd\")\n        )\n        .map(|_| ())\n        .unwrap_err()\n    );\n    assert!(err.contains(\"included-bad.kbd\"));\n    assert!(!err.contains(&format!(\n        \"test_cfgs{}include-bad.kbd\",\n        std::path::MAIN_SEPARATOR\n    )));\n}\n\n#[test]\nfn test_include_bad2_has_original_filename() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let err = format!(\n        \"{:?}\",\n        new_from_file(\n            &std::path::Path::new(\".\")\n                .join(\"test_cfgs\")\n                .join(\"include-bad2.kbd\")\n        )\n        .map(|_| ())\n        .unwrap_err()\n    );\n    assert!(!err.contains(&format!(\n        \"test_cfgs{}included-bad2.kbd\",\n        std::path::MAIN_SEPARATOR\n    )));\n    assert!(err.contains(&format!(\n        \"test_cfgs{}include-bad2.kbd\",\n        std::path::MAIN_SEPARATOR\n    )));\n}\n\n#[test]\nfn test_include_ignore_optional_filename() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    new_from_file(&std::path::PathBuf::from(\n        \"./test_cfgs/include-good-optional-absent.kbd\",\n    ))\n    .unwrap();\n}\n\n#[test]\nfn parse_bad_submacro() {\n    // Test exists since it used to crash. It should not crash.\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let mut s = ParserState::default();\n    let source = r#\"\n(defsrc a)\n(deflayer base\n  (macro M-s-())\n)\n\"#;\n    parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    )\n    .map_err(|_e| {\n        // uncomment to see what this looks like when running test\n        // eprintln!(\"{:?}\", _e);\n        \"\"\n    })\n    .unwrap_err();\n}\n\n#[test]\nfn parse_bad_submacro_2() {\n    // Test exists since it used to crash. It should not crash.\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let mut s = ParserState::default();\n    let source = r#\"\n(defsrc a)\n(deflayer base\n  (macro M-s-g)\n)\n\"#;\n    parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    )\n    .map_err(|_e| {\n        // uncomment to see what this looks like when running test\n        // eprintln!(\"{:?}\", _e);\n        \"\"\n    })\n    .unwrap_err();\n}\n\n#[test]\nfn parse_nested_macro() {\n    let source = r#\"\n(defvar m1 (a b c))\n(defsrc a b)\n(deflayer base\n  (macro $m1)\n  (macro bspc bspc $m1)\n)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn parse_switch() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let mut s = ParserState::default();\n    let source = r#\"\n(defvar var1 a)\n(defsrc a)\n(deffakekeys vk1 XX)\n(defvirtualkeys vk2 XX)\n(deflayer base\n  (switch\n    ((and a b (or c d) (or e f))) XX break\n    ((not (and a b (not (or c (not d))) (or e f)))) XX break\n    () _ fallthrough\n    (a b c) $var1 fallthrough\n    ((or (or (or (or (or (or (or (or))))))))) $var1 fallthrough\n    ((key-history a 1) (key-history b 5) (key-history c 8)) $var1 fallthrough\n    ((not\n      (key-timing 1 less-than 200)\n      (key-timing 4 greater-than 500)\n      (key-timing 7 lt 1000)\n      (key-timing 8 gt 20000)\n    )) $var1 fallthrough\n    ((input virtual vk1)) $var1 break\n    ((input real lctl)) $var1 break\n    ((input-history virtual vk2 1)) $var1 break\n    ((input-history real lsft 8)) $var1 break\n  )\n)\n\"#;\n    let res = parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    )\n    .unwrap();\n    let (op1, op2) = OpCode::new_active_input((FAKE_KEY_ROW, 0));\n    let (op3, op4) = OpCode::new_active_input((NORMAL_KEY_ROW, u16::from(OsCode::KEY_LEFTCTRL)));\n    let (op5, op6) = OpCode::new_historical_input((FAKE_KEY_ROW, 1), 0);\n    let (op7, op8) =\n        OpCode::new_historical_input((NORMAL_KEY_ROW, u16::from(OsCode::KEY_LEFTSHIFT)), 7);\n    let (klayers, _) = res.klayers.get();\n    assert_eq!(\n        klayers[0][0][OsCode::KEY_A.as_u16() as usize],\n        Action::Switch(&Switch {\n            cases: &[\n                (\n                    &[\n                        OpCode::new_bool(And, 9),\n                        OpCode::new_key(KeyCode::A),\n                        OpCode::new_key(KeyCode::B),\n                        OpCode::new_bool(Or, 6),\n                        OpCode::new_key(KeyCode::C),\n                        OpCode::new_key(KeyCode::D),\n                        OpCode::new_bool(Or, 9),\n                        OpCode::new_key(KeyCode::E),\n                        OpCode::new_key(KeyCode::F),\n                    ],\n                    &Action::NoOp,\n                    BreakOrFallthrough::Break\n                ),\n                (\n                    &[\n                        OpCode::new_bool(Not, 12),\n                        OpCode::new_bool(And, 12),\n                        OpCode::new_key(KeyCode::A),\n                        OpCode::new_key(KeyCode::B),\n                        OpCode::new_bool(Not, 9),\n                        OpCode::new_bool(Or, 9),\n                        OpCode::new_key(KeyCode::C),\n                        OpCode::new_bool(Not, 9),\n                        OpCode::new_key(KeyCode::D),\n                        OpCode::new_bool(Or, 12),\n                        OpCode::new_key(KeyCode::E),\n                        OpCode::new_key(KeyCode::F),\n                    ],\n                    &Action::NoOp,\n                    BreakOrFallthrough::Break\n                ),\n                (&[], &Action::Trans, BreakOrFallthrough::Fallthrough),\n                (\n                    &[\n                        OpCode::new_key(KeyCode::A),\n                        OpCode::new_key(KeyCode::B),\n                        OpCode::new_key(KeyCode::C),\n                    ],\n                    &Action::KeyCode(KeyCode::A),\n                    BreakOrFallthrough::Fallthrough\n                ),\n                (\n                    &[\n                        OpCode::new_bool(Or, 8),\n                        OpCode::new_bool(Or, 8),\n                        OpCode::new_bool(Or, 8),\n                        OpCode::new_bool(Or, 8),\n                        OpCode::new_bool(Or, 8),\n                        OpCode::new_bool(Or, 8),\n                        OpCode::new_bool(Or, 8),\n                        OpCode::new_bool(Or, 8),\n                    ],\n                    &Action::KeyCode(KeyCode::A),\n                    BreakOrFallthrough::Fallthrough\n                ),\n                (\n                    &[\n                        OpCode::new_key_history(KeyCode::A, 0),\n                        OpCode::new_key_history(KeyCode::B, 4),\n                        OpCode::new_key_history(KeyCode::C, 7),\n                    ],\n                    &Action::KeyCode(KeyCode::A),\n                    BreakOrFallthrough::Fallthrough\n                ),\n                (\n                    &[\n                        OpCode::new_bool(Not, 5),\n                        OpCode::new_ticks_since_lt(0, 200),\n                        OpCode::new_ticks_since_gt(3, 500),\n                        OpCode::new_ticks_since_lt(6, 1000),\n                        OpCode::new_ticks_since_gt(7, 20000),\n                    ],\n                    &Action::KeyCode(KeyCode::A),\n                    BreakOrFallthrough::Fallthrough\n                ),\n                (\n                    &[op1, op2],\n                    &Action::KeyCode(KeyCode::A),\n                    BreakOrFallthrough::Break\n                ),\n                (\n                    &[op3, op4],\n                    &Action::KeyCode(KeyCode::A),\n                    BreakOrFallthrough::Break\n                ),\n                (\n                    &[op5, op6],\n                    &Action::KeyCode(KeyCode::A),\n                    BreakOrFallthrough::Break\n                ),\n                (\n                    &[op7, op8],\n                    &Action::KeyCode(KeyCode::A),\n                    BreakOrFallthrough::Break\n                ),\n            ]\n        })\n    );\n}\n\n#[test]\nfn parse_switch_exceed_depth() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let mut s = ParserState::default();\n    let source = r#\"\n(defsrc a)\n(deflayer base\n  (switch\n    ((or (or (or (or (or (or (or (or (or)))))))))) XX break\n  )\n)\n\"#;\n    parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    )\n    .map_err(|_e| {\n        // uncomment to see what this looks like when running test\n        // eprintln!(\"{:?}\", _e);\n        \"\"\n    })\n    .unwrap_err();\n}\n\n#[test]\nfn parse_virtualkeys() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let mut s = ParserState::default();\n    let source = r#\"\n(defvar var1 a)\n(defsrc a b c d e f g h i j k l m n o p)\n(deffakekeys hello a)\n(defvirtualkeys bye a)\n(deflayer base\n  (on-press   press-virtualkey hello)\n  (on-press release-virtualkey hello)\n  (on-press  toggle-virtualkey hello)\n  (on-press     tap-virtualkey hello)\n  (on-press   press-vkey bye)\n  (on-press release-vkey bye)\n  (on-press  toggle-vkey bye)\n  (on-press     tap-vkey bye)\n  (on-release   press-virtualkey hello)\n  (on-release release-virtualkey hello)\n  (on-release  toggle-virtualkey hello)\n  (on-release     tap-virtualkey hello)\n  (on-release   press-vkey bye)\n  (on-release release-vkey bye)\n  (on-release  toggle-vkey bye)\n  (on-release     tap-vkey bye)\n)\n\"#;\n    let res = parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    )\n    .map_err(|e| {\n        eprintln!(\"{:?}\", miette::Error::from(e));\n        \"\"\n    })\n    .unwrap();\n    let (klayers, _) = res.klayers.get();\n    assert_eq!(\n        klayers[0][0][OsCode::KEY_A.as_u16() as usize],\n        Action::Custom(\n            &[&CustomAction::FakeKey {\n                coord: Coord { x: 1, y: 0 },\n                action: FakeKeyAction::Press,\n            }]\n            .as_ref()\n        ),\n    );\n    assert_eq!(\n        klayers[0][0][OsCode::KEY_F.as_u16() as usize],\n        Action::Custom(\n            &[&CustomAction::FakeKey {\n                coord: Coord { x: 1, y: 1 },\n                action: FakeKeyAction::Release,\n            }]\n            .as_ref()\n        ),\n    );\n    assert_eq!(\n        klayers[0][0][OsCode::KEY_K.as_u16() as usize],\n        Action::Custom(\n            &[&CustomAction::FakeKeyOnRelease {\n                coord: Coord { x: 1, y: 0 },\n                action: FakeKeyAction::Toggle,\n            }]\n            .as_ref()\n        ),\n    );\n    assert_eq!(\n        klayers[0][0][OsCode::KEY_P.as_u16() as usize],\n        Action::Custom(\n            &[&CustomAction::FakeKeyOnRelease {\n                coord: Coord { x: 1, y: 1 },\n                action: FakeKeyAction::Tap,\n            }]\n            .as_ref()\n        ),\n    );\n}\n\n#[test]\nfn parse_on_idle_fakekey() {\n    let source = r#\"\n(defvar var1 a)\n(defsrc a b c d e f g h i)\n(deffakekeys hello a)\n(defvirtualkeys bye a)\n(deflayer base\n  (on-idle-fakekey hello tap 200)\n  (on-idle 100   press-virtualkey hello)\n  (on-idle 100 release-virtualkey hello)\n  (on-idle 100  toggle-virtualkey hello)\n  (on-idle 100     tap-virtualkey hello)\n  (on-idle 100   press-vkey bye)\n  (on-idle 100 release-vkey bye)\n  (on-idle 100  toggle-vkey bye)\n  (on-idle 200     tap-vkey bye)\n)\n\"#;\n    let res = parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n    let (klayers, _) = res.klayers.get();\n    assert_eq!(\n        klayers[0][0][OsCode::KEY_A.as_u16() as usize],\n        Action::Custom(\n            &[&CustomAction::FakeKeyOnIdle(FakeKeyOnIdle {\n                coord: Coord { x: 1, y: 0 },\n                action: FakeKeyAction::Tap,\n                idle_duration: 200\n            })]\n            .as_ref()\n        ),\n    );\n}\n\n#[test]\nfn parse_on_idle_fakekey_errors() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let mut s = ParserState::default();\n    let source = r#\"\n(defvar var1 a)\n(defsrc a)\n(deffakekeys hello a)\n(deflayer base\n  (on-idle-fakekey hello bap 200)\n)\n\"#;\n    parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    )\n    .map_err(|_e| {\n        // comment out to see what this looks like when running test\n        // eprintln!(\"{:?}\", _e);\n        \"\"\n    })\n    .unwrap_err();\n\n    let source = r#\"\n(defvar var1 a)\n(defsrc a)\n(deffakekeys hello a)\n(deflayer base\n  (on-idle-fakekey jello tap 200)\n)\n\"#;\n    parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    )\n    .map_err(|_e| {\n        // uncomment to see what this looks like when running test\n        // eprintln!(\"{:?}\", _e);\n        \"\"\n    })\n    .unwrap_err();\n\n    let source = r#\"\n(defvar var1 a)\n(defsrc a)\n(deffakekeys hello a)\n(deflayer base\n  (on-idle-fakekey (hello) tap 200)\n)\n\"#;\n    parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    )\n    .map_err(|_e| {\n        // uncomment to see what this looks like when running test\n        // eprintln!(\"{:?}\", _e);\n        \"\"\n    })\n    .unwrap_err();\n\n    let source = r#\"\n(defvar var1 a)\n(defsrc a)\n(deffakekeys hello a)\n(deflayer base\n  (on-idle-fakekey hello tap -1)\n)\n\"#;\n    parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    )\n    .map_err(|_e| {\n        // uncomment to see what this looks like when running test\n        // eprintln!(\"{:?}\", _e);\n        \"\"\n    })\n    .unwrap_err();\n}\n\n#[test]\nfn parse_fake_keys_errors_on_too_many() {\n    let mut s = ParserState::default();\n    let mut checked_for_err = false;\n    for n in 0..1000 {\n        let exprs = [&vec![\n            SExpr::Atom(Spanned {\n                t: \"deffakekeys\".to_string(),\n                span: Default::default(),\n            }),\n            SExpr::Atom(Spanned {\n                t: \"a\".repeat(n),\n                span: Default::default(),\n            }),\n            SExpr::Atom(Spanned {\n                t: \"a\".to_string(),\n                span: Default::default(),\n            }),\n        ]];\n        if n < 500 {\n            // fill up fake keys, expect first bunch to succeed\n            parse_fake_keys(&exprs, &mut s).unwrap();\n        } else if n < 999 {\n            // at some point they start failing, ignore result\n            let _ = parse_fake_keys(&exprs, &mut s);\n        } else {\n            // last iteration, check for error. probably happened before this, but just check here\n            let _ = parse_fake_keys(&exprs, &mut s).unwrap_err();\n            checked_for_err = true;\n        }\n    }\n    assert!(checked_for_err);\n}\n\n#[test]\nfn parse_deflocalkeys_overridden() {\n    let source = r#\"\n(deflocalkeys-win\n+   300\n[   301\n]   302\n{   303\n}   304\n/   305\n;   306\n`   307\n=   308\n-   309\n'   310\n,   311\n.   312\n\\   313\nyen 314\n¥   315\nnew 316\n)\n(deflocalkeys-winiov2\n+   300\n[   301\n]   302\n{   303\n}   304\n/   305\n;   306\n`   307\n=   308\n-   309\n'   310\n,   311\n.   312\n\\   313\nyen 314\n¥   315\nnew 316\n)\n(deflocalkeys-wintercept\n+   300\n[   301\n]   302\n{   303\n}   304\n/   305\n;   306\n`   307\n=   308\n-   309\n'   310\n,   311\n.   312\n\\   313\nyen 314\n¥   315\nnew 316\n)\n(deflocalkeys-linux\n+   300\n[   301\n]   302\n{   303\n}   304\n/   305\n;   306\n`   307\n=   308\n-   309\n'   310\n,   311\n.   312\n\\   313\nyen 314\n¥   315\nnew 316\n)\n(deflocalkeys-macos\n+   300\n[   301\n]   302\n{   303\n}   304\n/   305\n;   306\n`   307\n=   308\n-   309\n'   310\n,   311\n.   312\n\\   313\nyen 314\n¥   315\nnew 316\n)\n(defsrc + [  ]  {  }  /  ;  `  =  -  '  ,  .  \\  yen ¥ new)\n(deflayer base + [  ]  {  }  /  ;  `  =  -  '  ,  .  \\  yen ¥ new)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn use_default_overridable_mappings() {\n    let source = r#\"\n(defsrc + [  ]  a  b  /  ;  `  =  -  '  ,  .  9  yen ¥  )\n(deflayer base + [  ]  {  }  /  ;  `  =  -  '  ,  .  \\  yen ¥  )\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn list_action_not_in_list_error_message_is_good() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let mut s = ParserState::default();\n    let source = r#\"\n(defsrc a)\n(defalias hello\n  one-shot 1 2\n)\n(deflayer base hello)\n\"#;\n    parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    )\n    .map_err(|e| {\n        assert_eq!(\n            e.msg,\n            \"This is a list action and must be in parentheses: (one-shot ...)\"\n        );\n    })\n    .unwrap_err();\n}\n\n#[test]\nfn parse_device_paths() {\n    assert_eq!(parse_colon_separated_text(\"\"), [\"\"]);\n    assert_eq!(parse_colon_separated_text(\"device1\"), [\"device1\"]);\n    assert_eq!(parse_colon_separated_text(\"h:w\"), [\"h\", \"w\"]);\n    assert_eq!(parse_colon_separated_text(\"h\\\\:w\"), [\"h:w\"]);\n    assert_eq!(parse_colon_separated_text(\"h\\\\:w\\\\\"), [\"h:w\\\\\"]);\n}\n\n#[test]\n#[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\nfn test_parse_dev() {\n    // The old colon separated devices format\n    assert_eq!(\n        parse_dev(&SExpr::Atom(Spanned {\n            t: \"\\\"Keyboard2:Input Device 1:pci-0000\\\\:00\\\\:14.0-usb-0\\\\:1\\\\:1.0-event\\\"\"\n                .to_string(),\n            span: Span::default(),\n        }))\n        .expect(\"succeeds\"),\n        [\n            \"Keyboard2\",\n            \"Input Device 1\",\n            \"pci-0000:00:14.0-usb-0:1:1.0-event\"\n        ]\n    );\n    parse_dev(&SExpr::Atom(Spanned {\n        t: \"\\\"\\\"\".to_string(),\n        span: Span::default(),\n    }))\n    .expect_err(\"'' is not a valid device name/path, this should fail\");\n\n    // The new device list format\n    assert_eq!(\n        parse_dev(&SExpr::List(Spanned {\n            t: vec![\n                SExpr::Atom(Spanned {\n                    t: \"Keyboard2\".to_string(),\n                    span: Span::default(),\n                }),\n                SExpr::Atom(Spanned {\n                    t: \"\\\"Input Device 1\\\"\".to_string(),\n                    span: Span::default(),\n                }),\n                SExpr::Atom(Spanned {\n                    t: \"pci-0000:00:14.0-usb-0:1:1.0-event\".to_string(),\n                    span: Span::default(),\n                }),\n                SExpr::Atom(Spanned {\n                    t: r\"backslashes\\do\\not\\escape\\:\\anything\".to_string(),\n                    span: Span::default(),\n                }),\n            ],\n            span: Span::default(),\n        }))\n        .expect(\"succeeds\"),\n        [\n            \"Keyboard2\",\n            \"Input Device 1\",\n            \"pci-0000:00:14.0-usb-0:1:1.0-event\",\n            r\"backslashes\\do\\not\\escape\\:\\anything\"\n        ]\n    );\n    parse_dev(&SExpr::List(Spanned {\n        t: vec![\n            SExpr::Atom(Spanned {\n                t: \"Device1\".to_string(),\n                span: Span::default(),\n            }),\n            SExpr::List(Spanned {\n                t: vec![],\n                span: Span::default(),\n            }),\n        ],\n        span: Span::default(),\n    }))\n    .expect_err(\"nested lists in path list shouldn't be allowed\");\n}\n\n#[test]\nfn parse_all_defcfg() {\n    let source = r#\"\n(defcfg\n  process-unmapped-keys yes\n  danger-enable-cmd yes\n  sequence-timeout 2000\n  sequence-input-mode visible-backspaced\n  sequence-backtrack-modcancel no\n  log-layer-changes no\n  delegate-to-first-layer yes\n  movemouse-inherit-accel-state yes\n  movemouse-smooth-diagonals yes\n  override-release-on-activation yes\n  dynamic-macro-max-presses 1000\n  concurrent-tap-hold yes\n  rapid-event-delay 5\n  linux-dev /dev/input/dev1:/dev/input/dev2\n  linux-dev-names-include \"Name 1:Name 2\"\n  linux-dev-names-exclude \"Name 3:Name 4\"\n  linux-continue-if-no-devs-found yes\n  linux-unicode-u-code v\n  linux-unicode-termination space\n  linux-x11-repeat-delay-rate 400,50\n  linux-use-trackpoint-property yes\n  linux-output-device-name \"Kanata Test\"\n  linux-output-device-bus-type USB\n  tray-icon symbols.ico\n  icon-match-layer-name no\n  tooltip-layer-changes yes\n  tooltip-show-blank yes\n  tooltip-no-base yes\n  tooltip-duration 300\n  tooltip-size 24,24\n  notify-cfg-reload yes\n  notify-cfg-reload-silent no\n  notify-error yes\n  windows-altgr add-lctl-release\n  windows-interception-mouse-hwid \"70, 0, 60, 0\"\n  windows-interception-mouse-hwids (\"0, 0, 0\" \"1, 1, 1\")\n  windows-interception-keyboard-hwids (\"0, 0, 0\" \"1, 1, 1\")\n)\n(defsrc a)\n(deflayer base a)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn parse_defcfg_linux_output_bus() {\n    let source = r#\"\n(defcfg linux-output-device-bus-type USB)\n(defsrc a)\n(deflayer base a)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n    let source = r#\"\n(defcfg linux-output-device-bus-type I8042)\n(defsrc a)\n(deflayer base a)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n    let source = r#\"\n(defcfg linux-output-device-bus-type INVALID)\n(defsrc a)\n(deflayer base a)\n\"#;\n    let err = parse_cfg(source).expect_err(\"should err\");\n    assert!(\n        err.msg\n            .contains(\"Invalid value for linux-output-device-bus-type\")\n    );\n}\n\n#[test]\nfn parse_unmod() {\n    let source = r#\"\n(defsrc a b c d e)\n(deflayer base\n  (unmod a)\n  (unmod a b)\n  (unshift a)\n  (un⇧ a)\n  (unshift a b)\n)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn using_parentheses_in_deflayer_directly_fails_with_custom_message() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let mut s = ParserState::default();\n    let source = r#\"\n(defsrc a b)\n(deflayer base ( ))\n\"#;\n    let err = parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    )\n    .expect_err(\"should err\");\n    assert!(\n        err.msg\n            .contains(\"You can't put parentheses in deflayer directly\")\n    );\n}\n\n#[test]\nfn using_escaped_parentheses_in_deflayer_fails_with_custom_message() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let mut s = ParserState::default();\n    let source = r#\"\n(defsrc a b)\n(deflayer base \\( \\))\n\"#;\n    let err = parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    )\n    .expect_err(\"should err\");\n    assert!(\n        err.msg\n            .contains(\"Escaping shifted characters with `\\\\` is currently not supported\")\n    );\n}\n\n#[test]\n#[cfg(feature = \"cmd\")]\nfn parse_cmd() {\n    let source = r#\"\n(defcfg danger-enable-cmd yes)\n(defsrc a)\n(deflayer base a)\n(defvar\n    x blah\n    y (nyoom)\n    z (squish squash (splish splosh))\n)\n(defalias\n    1 (cmd hello world)\n    2 (cmd (hello world))\n    3 (cmd $x $y ($z))\n    4 (clipboard-cmd-set powershell.exe -c \"echo 'hello world'\")\n    5 (clipboard-save-cmd-set 0 bash -c \"echo 'goodbye'\")\n)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\n#[cfg(feature = \"cmd\")]\nfn parse_cmd_log() {\n    let source = r#\"\n(defcfg danger-enable-cmd yes)\n(defsrc a)\n(deflayer base a)\n(defvar\n    x blah\n    y (nyoom)\n    z (squish squash (splish splosh))\n)\n(defalias\n    1 (cmd-log debug debug hello world)\n    2 (cmd-log error warn (hello world))\n    3 (cmd-log info debug $x $y ($z))\n    4 (cmd-log none none hello world)\n)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn parse_defvar_concat() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let source = r#\"\n(defsrc a)\n(deflayer base a)\n(defvar\n    x (concat a b c)\n    y (concat d (e f))\n    z (squish squash (splish splosh))\n    xx (concat $x $y)\n    xy (concat $x ($y))\n    xz (notconcat a b \" \" c \" d\")\n    yx (concat a b \" \" c \" d\" (\"efg\" \" hij \") \"kl\")\n    yz (concat \"abc\"def\"ghi\"\"jkl\")\n\n    rootpath \"/home/myuser/mysubdir\"\n    ;; $otherpath will be the string: /home/myuser/mysubdir/helloworld\n    otherpath (concat $rootpath \"/helloworld\")\n)\n\"#;\n    let mut s = ParserState::default();\n    parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    )\n    .expect(\"succeeds\");\n    match s.vars().unwrap().get(\"x\").unwrap() {\n        SExpr::Atom(a) => assert_eq!(&a.t, \"abc\"),\n        SExpr::List(l) => panic!(\"expected string not list: {l:?}\"),\n    }\n    match s.vars().unwrap().get(\"y\").unwrap() {\n        SExpr::Atom(a) => assert_eq!(&a.t, \"def\"),\n        SExpr::List(l) => panic!(\"expected string not list: {l:?}\"),\n    }\n    match s.vars().unwrap().get(\"z\").unwrap() {\n        SExpr::Atom(a) => panic!(\"expected list not string: {a:?}\"),\n        SExpr::List(_) => {}\n    }\n    match s.vars().unwrap().get(\"xx\").unwrap() {\n        SExpr::Atom(a) => assert_eq!(&a.t, \"abcdef\"),\n        SExpr::List(l) => panic!(\"expected string not list: {l:?}\"),\n    }\n    match s.vars().unwrap().get(\"xy\").unwrap() {\n        SExpr::Atom(a) => assert_eq!(&a.t, \"abcdef\"),\n        SExpr::List(l) => panic!(\"expected string not list: {l:?}\"),\n    }\n    match s.vars().unwrap().get(\"xz\").unwrap() {\n        SExpr::Atom(a) => panic!(\"expected list not string {a:?}\"),\n        SExpr::List(_) => {}\n    }\n    match s.vars().unwrap().get(\"yx\").unwrap() {\n        SExpr::Atom(a) => assert_eq!(&a.t, \"ab c defg hij kl\"),\n        SExpr::List(l) => panic!(\"expected string not list: {l:?}\"),\n    }\n    match s.vars().unwrap().get(\"yz\").unwrap() {\n        SExpr::Atom(a) => assert_eq!(&a.t, \"abcdefghijkl\"),\n        SExpr::List(l) => panic!(\"expected string not list: {l:?}\"),\n    }\n    match s.vars().unwrap().get(\"otherpath\").unwrap() {\n        SExpr::Atom(a) => assert_eq!(&a.t, \"/home/myuser/mysubdir/helloworld\"),\n        SExpr::List(l) => panic!(\"expected string not list: {l:?}\"),\n    }\n}\n\n#[test]\nfn parse_template_1() {\n    let source = r#\"\n(deftemplate home-row (j-behaviour)\n  a s d f g h $j-behaviour k l ; '\n)\n\n(defsrc\n  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n  caps (template-expand home-row j)                            ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n\n(deflayer base\n  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n  caps (template-expand home-row (tap-hold 200 200 j lctl))    ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn parse_template_2() {\n    let source = r#\"\n(defvar chord-timeout 200)\n(defcfg process-unmapped-keys yes)\n\n;; This template defines a chord group and aliases that use the chord group.\n;; The purpose is to easily define the same chord position behaviour\n;; for multiple layers that have different underlying keys.\n(deftemplate left-hand-chords (chordgroupname k1 k2 k3 k4 alias1 alias2 alias3 alias4)\n  (defalias\n    $alias1 (chord $chordgroupname $k1)\n    $alias2 (chord $chordgroupname $k2)\n    $alias3 (chord $chordgroupname $k3)\n    $alias4 (chord $chordgroupname $k4)\n  )\n  (defchords $chordgroupname $chord-timeout\n    ($k1) $k1\n    ($k2) $k2\n    ($k3) $k3\n    ($k4) $k4\n    ($k1 $k2) lctl\n    ($k3 $k4) lsft\n  )\n)\n\n(template-expand left-hand-chords qwerty a s d f qwa qws qwd qwf)\n(template-expand left-hand-chords dvorak a o e u dva dvo dve dvu)\n\n(defsrc a s d f)\n(deflayer dvorak @dva @dvo @dve @dvu)\n(deflayer qwerty @qwa @qws @qwd @qwf)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn parse_template_3() {\n    let source = r#\"\n(deftemplate home-row (version)\n  a s d f g h\n  (if-equal $version v1 j)\n  (if-equal $version v2 (tap-hold 200 200 j (if-equal $version v2 k)))\n   k l ; '\n)\n\n(defsrc\n  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n  caps (template-expand home-row v1)                            ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n\n(deflayer base\n  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc\n  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \\\n  caps (template-expand home-row v2)                            ret\n  lsft z    x    c    v    b    n    m    ,    .    /    rsft\n  lctl lmet lalt           spc            ralt rmet rctl\n)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn parse_template_4() {\n    let source = r#\"\n(deftemplate home-row (version)\n  a s d f g h\n  (if-not-equal $version v2 j)\n  (if-not-equal $version v1 (tap-hold 200 200 j (if-not-equal $version v1 k)))\n   k l ; '\n)\n\n(defsrc\n  (template-expand home-row v1)\n)\n\n(deflayer base\n  (template-expand home-row v2)\n)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn parse_template_5() {\n    let source = r#\"\n(deftemplate home-row (version)\n  a s d f g h\n  (if-in-list $version (v0 v3 v1 v4) j)\n  (if-in-list $version (v0 v2 v3 v4) (tap-hold 200 200 j (if-in-list $version (v0 v3 v4 v2) k)))\n   k l ; '\n)\n\n(defsrc\n  (template-expand home-row v1)\n)\n\n(deflayer base\n  (template-expand home-row v2)\n)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn parse_template_6() {\n    let source = r#\"\n(deftemplate home-row (version)\n  a s d f g h\n  (if-not-in-list $version (v2 v3 v4) j)\n  (if-not-in-list $version (v1 v3 v4) (tap-hold 200 200 j (if-not-in-list $version (v1 v3 v4) k)))\n   k l ; '\n)\n\n(defsrc\n  (template-expand home-row v1)\n)\n\n(deflayer base\n  (template-expand home-row v2)\n)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn parse_template_7() {\n    let source = r#\"\n(deftemplate home-row (version)\n  a s d f g h\n  (if-in-list $version (v0 v3 (concat v (((1)))) v4) (concat j))\n  (if-in-list $version ((concat v 0) (concat v (2)) v3 v4) (tap-hold 200 200 (concat j) (if-in-list $version (v0 v3 v4 v2) (concat \"k\"))))\n   k l ; '\n)\n\n(defsrc\n  (template-expand home-row v1)\n)\n\n(deflayer base\n  (template-expand home-row v2)\n)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn parse_template_8() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n\n    let source = r#\"\n(deftemplate home-row (version)\n  a s d f g h\n  (if-in-list $version (v0 v3 (concat v (((1)))) v4) (concat j))\n  (if-in-list $version ((concat v 0) (concat v (2)) v3 v4) (tap-hold 200 200 (concat j) (if-in-list $version (v0 v3 v4 v2) (concat \"k\"))))\n   k l ; '\n)\n(deftemplate var () (defvar num 200))\n(defsrc\n  (t! home-row v1)\n)\n(deflayer base\n  (t! home-row v2)\n)\n(t! var)\n(defalias a (tap-dance $num (a)))\n\"#;\n    let mut s = ParserState::default();\n    parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(\"env vars not implemented\".into()),\n    )\n    .map_err(|e| {\n        eprintln!(\"{:?}\", miette::Error::from(e));\n    })\n    .expect(\"parses\");\n}\n\n#[test]\nfn test_deflayermap() {\n    let source = r#\"\n(defsrc a b l)\n(deflayermap (blah)\n  d   (macro a b c)\n  e   e\n  f   0\n  j   1\n  k   2\n  l   3\n  m   4\n  =   a\n  a   =\n)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn test_defaliasenvcond() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    let source = r#\"\n(defsrc spc)\n(deflayer base _)\n(defaliasenvcond (ENV_TEST 1) a b)\n\"#;\n\n    let env_var_err = \"env vars not implemented\";\n    let mut s = ParserState::default();\n    let parse_err = parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Err(env_var_err.into()),\n    )\n    .expect_err(\"should err\");\n    assert_eq!(parse_err.msg, env_var_err);\n\n    // now test with env vars implemented\n\n    let mut s = ParserState::default();\n    parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Ok(vec![(\"ENV_TEST\".into(), \"1\".into())]),\n    )\n    .map_err(|e| {\n        eprintln!(\"{:?}\", miette::Error::from(e));\n        \"\"\n    })\n    .expect(\"parses\");\n    assert!(s.aliases[\"a\"].key_codes().eq(vec![KeyCode::B]));\n\n    // test env var set but to a different value\n\n    let mut s = ParserState::default();\n    parse_cfg_raw_string(\n        source,\n        &mut s,\n        &PathBuf::from(\"test\"),\n        &mut FileContentProvider {\n            get_file_content_fn: &mut |_| unimplemented!(),\n        },\n        DEF_LOCAL_KEYS,\n        Ok(vec![(\"ENV_TEST\".into(), \"asdf\".into())]),\n    )\n    .map_err(|e| {\n        eprintln!(\"{:?}\", miette::Error::from(e));\n        \"\"\n    })\n    .expect(\"parses\");\n    assert!(!s.aliases.contains_key(\"a\"));\n}\n\n#[test]\nfn parse_platform_specific() {\n    let source = r#\"\n(platform () (invalid config but is not used anywhere))\n(defsrc)\n(deflayermap (base)\n  a (layer-switch 2)\n)\n;; layer 2 must exist on all platforms, all in one list\n(platform (win winiov2 wintercept linux macos)\n  (deflayermap (2)\n    a (layer-switch 3)\n  )\n)\n;; layer 3 must exist on all platforms, in individual lists\n;; Tests for no duplication.\n(platform (win)\n  (deflayermap (3)\n    a (layer-switch 3)\n  )\n)\n(platform (winiov2)\n  (deflayermap (3)\n    a (layer-switch 3)\n  )\n)\n(platform (wintercept)\n  (deflayermap (3)\n    a (layer-switch 3)\n  )\n)\n(platform (linux)\n  (deflayermap (3)\n    a (layer-switch 3)\n  )\n)\n(platform (macos)\n  (deflayermap (3)\n    a (layer-switch 3)\n  )\n)\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn parse_defseq_overlap_limits() {\n    let source = r#\"\n(defsrc)\n(deflayer base)\n(defvirtualkeys v v)\n(defseq v (O-(a b c d e f)))\n(defseq v (O-(a b)))\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parses\");\n}\n\n#[test]\nfn parse_defseq_overlap_too_many() {\n    let source = r#\"\n(defsrc)\n(deflayer base)\n(defvirtualkeys v v)\n(defseq v (O-(a b c d e f g)))\n\"#;\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect_err(\"fails\");\n}\n\n#[test]\nfn parse_layer_opts_icon() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    new_from_file(&std::path::PathBuf::from(\"./test_cfgs/icon_good.kbd\")).unwrap();\n}\n\n#[test]\nfn disallow_dupe_layer_opts_icon_layernonmap() {\n    let _lk = lock(&CFG_PARSE_LOCK);\n    new_from_file(&std::path::PathBuf::from(\"./test_cfgs/icon_bad_dupe.kbd\"))\n        .map(|_| ())\n        .expect_err(\"fails\");\n}\n\n#[test]\nfn disallow_dupe_layer_opts_icon_layermap() {\n    let source = \"\n(defcfg)\n(defsrc)\n(deflayermap (base icon base.png 🖻 n.ico) 0 0)\n\";\n    parse_cfg(source).map(|_| ()).expect_err(\"fails\");\n}\n\n#[test]\nfn layer_name_allows_var() {\n    let source = \"\n(defvar l1name base)\n(defvar l2name l2)\n(defvar l3name (concat $l1name $l2name))\n(defvar l4name (concat $l3name actually-4))\n(defsrc a)\n(deflayer $l1name (layer-while-held $l2name))\n(deflayermap ($l2name)\n  a (layer-while-held $l3name))\n(deflayer ($l3name) (layer-while-held $l4name))\n(deflayer ($l4name icon icon.ico) (layer-while-held $l1name))\n\";\n    parse_cfg(source)\n        .map_err(|e| eprintln!(\"{:?}\", miette::Error::from(e)))\n        .expect(\"parse succeeds\");\n}\n\n#[test]\nfn disallow_whitespace_in_tooltip_size() {\n    let source = \"\n(defcfg\n  tooltip-size 24 24\t;; should be 24,24\n)\n(defsrc 1)\n(deflayer test 1)\n\";\n    parse_cfg(source).map(|_| ()).expect_err(\"fails\");\n}\n\n#[test]\nfn reverse_release_order_must_be_within_multi() {\n    let source = \"\n(defsrc a)\n(deflayer base reverse-release-order)\n\";\n    let e = parse_cfg(source).map(|_| ()).expect_err(\"fails\");\n    assert_eq!(\n        e.msg,\n        \"reverse-release-order is only allowed inside of a (multi ...) action list\"\n    );\n}\n\n#[test]\nfn parse_clipboard_actions() {\n    let source = \"\n(defsrc)\n(deflayermap (base)\n a (clipboard-set       clip)\n b (clipboard-save      0)\n c (clipboard-restore   0)\n d (clipboard-save-swap 0 65535)\n)\n\";\n    parse_cfg(source).map(|_| ()).expect(\"success\");\n}\n"
  },
  {
    "path": "parser/src/cfg/unicode.rs",
    "content": "use super::*;\n\nuse crate::anyhow_expr;\nuse crate::bail;\nuse crate::bail_expr;\n\npub(crate) fn parse_unicode(ac_params: &[SExpr], s: &ParserState) -> Result<&'static KanataAction> {\n    const ERR_STR: &str = \"unicode expects exactly one (not combos looking like one) unicode character as an argument\\nor a unicode hex number, prefixed by U+. Example: U+1F686.\";\n    if ac_params.len() != 1 {\n        bail!(ERR_STR)\n    }\n    ac_params[0]\n        .atom(s.vars())\n        .map(|a| {\n            let a = a.trim_atom_quotes();\n            let unicode_char = match a.chars().count() {\n                0 => bail_expr!(&ac_params[0], \"{ERR_STR}\"),\n                1 => a.chars().next().expect(\"1 char\"),\n                _ => {\n                    let normalized = a.to_uppercase();\n                    let Some(hexnum) = normalized.strip_prefix(\"U+\") else {\n                        bail_expr!(&ac_params[0], \"{ERR_STR}.\\nMust begin with U+\")\n                    };\n                    let Ok(u_val) = u32::from_str_radix(hexnum, 16) else {\n                        bail_expr!(&ac_params[0], \"{ERR_STR}.\\nInvalid number after U+\")\n                    };\n                    match char::from_u32(u_val) {\n                        Some(v) => v,\n                        None => bail_expr!(&ac_params[0], \"{ERR_STR}.\\nInvalid char.\"),\n                    }\n                }\n            };\n            Ok(s.a.sref(Action::Custom(\n                s.a.sref(s.a.sref_slice(CustomAction::Unicode(unicode_char))),\n            )))\n        })\n        .ok_or_else(|| anyhow_expr!(&ac_params[0], \"{ERR_STR}\"))?\n}\n"
  },
  {
    "path": "parser/src/cfg/unmod.rs",
    "content": "use super::*;\n\nuse crate::anyhow_expr;\nuse crate::bail;\nuse crate::bail_expr;\n\npub(crate) fn parse_unmod(\n    unmod_type: &str,\n    ac_params: &[SExpr],\n    s: &ParserState,\n) -> Result<&'static KanataAction> {\n    const ERR_MSG: &str = \"expects expects at least one key name\";\n    if ac_params.is_empty() {\n        bail!(\"{unmod_type} {ERR_MSG}\\nfound {} items\", ac_params.len());\n    }\n\n    let mut mods = UnmodMods::all();\n    let mut params = ac_params;\n    // Parse the optional first-list that specifies the mod keys to use.\n    if let Some(mod_list) = ac_params[0].list(s.vars()) {\n        if unmod_type != UNMOD {\n            bail_expr!(\n                &ac_params[0],\n                \"{unmod_type} only expects key names but found a list\"\n            );\n        }\n        mods = mod_list\n            .iter()\n            .try_fold(UnmodMods::empty(), |mod_flags, mod_key| {\n                let flag = mod_key\n                    .atom(s.vars())\n                    .and_then(str_to_oscode)\n                    .and_then(|osc| match osc {\n                        OsCode::KEY_LEFTSHIFT => Some(UnmodMods::LSft),\n                        OsCode::KEY_RIGHTSHIFT => Some(UnmodMods::RSft),\n                        OsCode::KEY_LEFTCTRL => Some(UnmodMods::LCtl),\n                        OsCode::KEY_RIGHTCTRL => Some(UnmodMods::RCtl),\n                        OsCode::KEY_LEFTMETA => Some(UnmodMods::LMet),\n                        OsCode::KEY_RIGHTMETA => Some(UnmodMods::RMet),\n                        OsCode::KEY_LEFTALT => Some(UnmodMods::LAlt),\n                        OsCode::KEY_RIGHTALT => Some(UnmodMods::RAlt),\n                        _ => None,\n                    })\n                    .ok_or_else(|| {\n                        anyhow_expr!(\n                            mod_key,\n                            \"{UNMOD} expects modifier key names within the modifier list.\"\n                        )\n                    })?;\n                if !(mod_flags & flag).is_empty() {\n                    bail_expr!(\n                        mod_key,\n                        \"Duplicate key name in modifier key list is not allowed.\"\n                    );\n                }\n                Ok::<_, ParseError>(mod_flags | flag)\n            })?;\n        if mods.is_empty() {\n            bail_expr!(&ac_params[0], \"an empty modifier key list is invalid\");\n        }\n        if ac_params[1..].is_empty() {\n            bail!(\"at least one key is required after the modifier key list\");\n        }\n        params = &ac_params[1..];\n    }\n\n    let keys: Vec<KeyCode> = params.iter().try_fold(Vec::new(), |mut keys, param| {\n        keys.push(\n            param\n                .atom(s.vars())\n                .and_then(str_to_oscode)\n                .ok_or_else(|| {\n                    anyhow_expr!(\n                        &ac_params[0],\n                        \"{unmod_type} {ERR_MSG}\\nfound invalid key name\"\n                    )\n                })?\n                .into(),\n        );\n        Ok::<_, ParseError>(keys)\n    })?;\n    let keys = s.a.sref_vec(keys);\n    match unmod_type {\n        UNMOD => Ok(s.a.sref(Action::Custom(\n            s.a.sref(s.a.sref_slice(CustomAction::Unmodded { keys, mods })),\n        ))),\n        UNSHIFT => Ok(s.a.sref(Action::Custom(\n            s.a.sref(s.a.sref_slice(CustomAction::Unshifted { keys })),\n        ))),\n        _ => panic!(\"Unknown unmod type {unmod_type}\"),\n    }\n}\n"
  },
  {
    "path": "parser/src/cfg/vars.rs",
    "content": "use super::*;\n\nuse crate::bail_expr;\n\npub(crate) fn parse_vars(\n    exprs: &[&Vec<SExpr>],\n    _lsp_hints: &mut LspHints,\n) -> Result<HashMap<String, SExpr>> {\n    let mut vars: HashMap<String, SExpr> = Default::default();\n    for expr in exprs {\n        let mut subexprs = check_first_expr(expr.iter(), \"defvar\")?;\n        // Read k-v pairs from the configuration\n        while let Some(var_name_expr) = subexprs.next() {\n            let var_name = match var_name_expr {\n                SExpr::Atom(a) => &a.t,\n                _ => bail_expr!(var_name_expr, \"variable name must not be a list\"),\n            };\n            let var_expr = match subexprs.next() {\n                Some(v) => match v {\n                    SExpr::Atom(_) => v.clone(),\n                    SExpr::List(l) => parse_list_var(l, &vars),\n                },\n                None => bail_expr!(var_name_expr, \"variable name must have a subsequent value\"),\n            };\n            #[cfg(feature = \"lsp\")]\n            _lsp_hints\n                .definition_locations\n                .variable\n                .insert(var_name.to_owned(), var_name_expr.span());\n            if vars.insert(var_name.into(), var_expr).is_some() {\n                bail_expr!(var_name_expr, \"duplicate variable name: {}\", var_name);\n            }\n        }\n    }\n    Ok(vars)\n}\n\npub(crate) fn parse_list_var(expr: &Spanned<Vec<SExpr>>, vars: &HashMap<String, SExpr>) -> SExpr {\n    let ret = match expr.t.first() {\n        Some(SExpr::Atom(a)) => match a.t.as_str() {\n            \"concat\" => {\n                let mut concat_str = String::new();\n                let visitees = &expr.t[1..];\n                push_all_atoms(visitees, vars, &mut concat_str);\n                SExpr::Atom(Spanned {\n                    span: expr.span.clone(),\n                    t: concat_str,\n                })\n            }\n            _ => SExpr::List(expr.clone()),\n        },\n        _ => SExpr::List(expr.clone()),\n    };\n    ret\n}\n\npub(crate) fn push_all_atoms(exprs: &[SExpr], vars: &HashMap<String, SExpr>, pusheen: &mut String) {\n    for expr in exprs {\n        if let Some(a) = expr.atom(Some(vars)) {\n            pusheen.push_str(a.trim_atom_quotes());\n        } else if let Some(l) = expr.list(Some(vars)) {\n            push_all_atoms(l, vars, pusheen);\n        }\n    }\n}\n"
  },
  {
    "path": "parser/src/cfg/zippychord.rs",
    "content": "//! Zipchord-like parsing. Probably not 100% compatible.\n//!\n//! Example lines in input file.\n//! The \" => \" string represents a tab character.\n//!\n//! \"dy => day\"\n//!   -> chord: (d y)\n//!   -> output: \"day\"\n//!\n//! \"dy => day\"\n//! \"dy 1 => Monday\"\n//!   -> chord: (d y)\n//!   -> output: \"day\"\n//!   -> chord: (d y)\n//!   -> output: \"Monday\"; \"day\" gets erased\n//!\n//! \" abc => Alphabet\"\n//!   -> chord: (space a b c)\n//!   -> output: \"Alphabet\"\n//!\n//! \"r df => recipient\"\n//!   -> chord: (r)\n//!   -> output: nothing yet, just type r\n//!   -> chord: (d f)\n//!   -> output: \"recipient\"\n//!\n//! \" w  a => Washington\"\n//!   -> chord: (space w)\n//!   -> output: nothing yet, type spacebar+w in whatever true order they were pressed\n//!   -> chord: (space a)\n//!   -> output: \"Washington\"\n//!   -> note: do observe the two spaces between 'w' and 'a'\nuse super::*;\nuse crate::bail_expr;\n\n#[cfg(not(feature = \"zippychord\"))]\n#[derive(Debug, Clone, Default)]\npub struct ZchPossibleChords();\n#[cfg(not(feature = \"zippychord\"))]\n#[derive(Debug, Clone, Default)]\npub struct ZchConfig();\n#[cfg(not(feature = \"zippychord\"))]\nfn parse_zippy_inner(\n    exprs: &[SExpr],\n    _s: &ParserState,\n    _f: &mut FileContentProvider,\n) -> Result<(ZchPossibleChords, ZchConfig)> {\n    bail_expr!(\n        &exprs[0],\n        \"Kanata was not compiled with the \\\"zippychord\\\" feature. This configuration is unsupported\"\n    )\n}\n\npub(crate) fn parse_zippy(\n    exprs: &[SExpr],\n    s: &ParserState,\n    f: &mut FileContentProvider,\n) -> Result<(ZchPossibleChords, ZchConfig)> {\n    parse_zippy_inner(exprs, s, f)\n}\n\n#[cfg(feature = \"zippychord\")]\npub use inner::*;\n#[cfg(feature = \"zippychord\")]\nmod inner {\n    use super::*;\n\n    use crate::anyhow_expr;\n    use crate::subset::*;\n\n    use parking_lot::Mutex;\n\n    /// All possible chords.\n    #[derive(Debug, Clone, Default)]\n    pub struct ZchPossibleChords(pub SubsetMap<u16, Arc<ZchChordOutput>>);\n    impl ZchPossibleChords {\n        pub fn is_empty(&self) -> bool {\n            self.0.is_empty()\n        }\n    }\n\n    /// Tracks current input to check against possible chords.\n    /// This does not store by the input order;\n    /// instead it is by some consistent ordering for\n    /// hashing into the possible chord map.\n    #[derive(Debug, Clone, Default, PartialEq, Eq)]\n    pub struct ZchInputKeys {\n        zch_inputs: ZchSortedChord,\n    }\n    impl ZchInputKeys {\n        pub fn zchik_new() -> Self {\n            Self {\n                zch_inputs: ZchSortedChord {\n                    zch_keys: Vec::new(),\n                },\n            }\n        }\n        pub fn zchik_contains(&self, osc: OsCode) -> bool {\n            self.zch_inputs.zch_keys.contains(&osc.into())\n        }\n        pub fn zchik_insert(&mut self, osc: OsCode) {\n            self.zch_inputs.zch_insert(osc.into());\n        }\n        pub fn zchik_remove(&mut self, osc: OsCode) {\n            self.zch_inputs.zch_keys.retain(|k| *k != osc.into());\n        }\n        pub fn zchik_len(&self) -> usize {\n            self.zch_inputs.zch_keys.len()\n        }\n        pub fn zchik_clear(&mut self) {\n            self.zch_inputs.zch_keys.clear()\n        }\n        pub fn zchik_keys(&self) -> &[u16] {\n            &self.zch_inputs.zch_keys\n        }\n        pub fn zchik_is_empty(&self) -> bool {\n            self.zch_inputs.zch_keys.is_empty()\n        }\n    }\n\n    #[derive(Debug, Default, Clone, Hash, PartialEq, Eq)]\n    /// Sorted consistently by some arbitrary key order;\n    /// as opposed to, for example, simply the user press order.\n    pub struct ZchSortedChord {\n        zch_keys: Vec<u16>,\n    }\n    impl ZchSortedChord {\n        pub fn zch_insert(&mut self, key: u16) {\n            match self.zch_keys.binary_search(&key) {\n                // Q: what is the meaning of Ok vs. Err?\n                // A: Ok means the element already in vector @ `pos`. Normally this wouldn't be\n                // expected to happen but it turns out that key repeat might get in the way of this\n                // assumption. Err means element does not exist and returns the correct insert position.\n                Ok(_pos) => {}\n                Err(pos) => self.zch_keys.insert(pos, key),\n            }\n        }\n    }\n\n    /// A chord.\n    ///\n    /// If any followups exist it will be Some.\n    /// E.g. with:\n    /// - dy   -> day\n    /// - dy 1 -> Monday\n    /// - dy 2 -> Tuesday\n    ///\n    /// the output will be \"day\" and the Monday+Tuesday chords will be in `followups`.\n    #[derive(Debug, Clone)]\n    pub struct ZchChordOutput {\n        pub zch_output: Box<[ZchOutput]>,\n        pub zch_followups: Option<Arc<Mutex<ZchPossibleChords>>>,\n    }\n\n    /// Zch output can be uppercase, lowercase, altgr, and shift-altgr characters.\n    /// The parser should ensure all `OsCode`s in variants containing them\n    /// are visible characters that are backspacable.\n    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n    pub enum ZchOutput {\n        Lowercase(OsCode),\n        Uppercase(OsCode),\n        AltGr(OsCode),\n        ShiftAltGr(OsCode),\n        NoEraseLowercase(OsCode),\n        NoEraseUppercase(OsCode),\n        NoEraseAltGr(OsCode),\n        NoEraseShiftAltGr(OsCode),\n    }\n\n    impl ZchOutput {\n        pub fn osc(self) -> OsCode {\n            use ZchOutput::*;\n            match self {\n                Lowercase(osc)\n                | Uppercase(osc)\n                | AltGr(osc)\n                | ShiftAltGr(osc)\n                | NoEraseLowercase(osc)\n                | NoEraseUppercase(osc)\n                | NoEraseAltGr(osc)\n                | NoEraseShiftAltGr(osc) => osc,\n            }\n        }\n        pub fn osc_and_is_noerase(self) -> (OsCode, bool) {\n            use ZchOutput::*;\n            match self {\n                Lowercase(osc) | Uppercase(osc) | AltGr(osc) | ShiftAltGr(osc) => (osc, false),\n                NoEraseLowercase(osc)\n                | NoEraseUppercase(osc)\n                | NoEraseAltGr(osc)\n                | NoEraseShiftAltGr(osc) => (osc, true),\n            }\n        }\n        pub fn display_len(outs: impl AsRef<[Self]>) -> i16 {\n            outs.as_ref().iter().copied().fold(0i16, |mut len, out| {\n                len += out.output_char_count();\n                len\n            })\n        }\n        pub fn output_char_count(self) -> i16 {\n            match self.osc_and_is_noerase() {\n                (OsCode::KEY_BACKSPACE, _) => -1,\n                (_, false) => 1,\n                (_, true) => 0,\n            }\n        }\n    }\n\n    /// User configuration for smart space.\n    ///\n    /// - `Full`         = add spaces after words, remove these spaces after typing punctuation.\n    /// - `AddSpaceOnly` = add spaces after words\n    /// - `Disabled`     = do nothing\n    #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n    pub enum ZchSmartSpaceCfg {\n        Full,\n        AddSpaceOnly,\n        Disabled,\n    }\n\n    #[derive(Debug)]\n    pub struct ZchConfig {\n        /// When, during typing, chord fails to activate, zippychord functionality becomes temporarily\n        /// disabled. This is to avoid accidental chord activations when typing normally, as opposed to\n        /// intentionally trying to activate a chord. The duration of temporary disabling is determined\n        /// by this configuration item. Re-enabling also happens when word-splitting characters are\n        /// typed, for example typing  a space or a comma, but a pause of all typing activity lasting a\n        /// number of milliseconds equal to this configuration will also re-enable chording even if\n        /// typing within a single word.\n        pub zch_cfg_ticks_wait_enable: u16,\n\n        /// Assuming zippychording is enabled, when the first press happens this deadline will begin\n        /// and if no chords are completed within the deadline, zippychording will be disabled\n        /// temporarily (see `zch_cfg_ticks_wait_enable`). You may want a long or short deadline\n        /// depending on your use case. If you are primarily typing normally, with chords being used\n        /// occasionally being used, you may want a short deadline so that regular typing will be\n        /// unlikely to activate any chord. However, if you primarily type with chords, you may want a\n        /// longer deadline to give you more time to complete the intended chord (e.g. in case of\n        /// overlaps). With a long deadline you should be very intentional about pressing and releasing\n        /// an individual key to begin a sequence of regular typing to trigger the disabling of\n        /// zippychord. If, after the first press, a chord activates, this deadline will reset to\n        /// enable further chord activations.\n        pub zch_cfg_ticks_chord_deadline: u16,\n\n        /// User configuration for smart space. See `pub enum ZchSmartSpaceCfg`.\n        pub zch_cfg_smart_space: ZchSmartSpaceCfg,\n\n        /// Define keys for punctuation, which is relevant to smart space auto-erasure of added spaces.\n        pub zch_cfg_smart_space_punctuation: HashSet<ZchOutput>,\n    }\n\n    impl Default for ZchConfig {\n        fn default() -> Self {\n            Self {\n                zch_cfg_ticks_wait_enable: 500,\n                zch_cfg_ticks_chord_deadline: 500,\n                zch_cfg_smart_space: ZchSmartSpaceCfg::Disabled,\n                zch_cfg_smart_space_punctuation: {\n                    let mut puncs = HashSet::default();\n                    puncs.insert(ZchOutput::Lowercase(OsCode::KEY_DOT));\n                    puncs.insert(ZchOutput::Lowercase(OsCode::KEY_COMMA));\n                    puncs.insert(ZchOutput::Lowercase(OsCode::KEY_SEMICOLON));\n                    puncs.shrink_to_fit();\n                    puncs\n                },\n            }\n        }\n    }\n\n    const NO_ERASE: &str = \"no-erase\";\n    const SINGLE_OUTPUT_MULTI_KEY: &str = \"single-output\";\n\n    enum ZchIoMappingType {\n        NoErase,\n        SingleOutput,\n    }\n    impl ZchIoMappingType {\n        fn try_parse(expr: &SExpr, vars: Option<&HashMap<String, SExpr>>) -> Result<Self> {\n            use ZchIoMappingType::*;\n            expr.atom(vars)\n                .and_then(|name| match name {\n                    NO_ERASE => Some(NoErase),\n                    SINGLE_OUTPUT_MULTI_KEY => Some(SingleOutput),\n                    _ => None,\n                })\n                .ok_or_else(|| {\n                    anyhow_expr!(\n                        &expr,\n                        \"Unknown output type. Must be one of:\\nno-erase | single-output\"\n                    )\n                })\n        }\n    }\n\n    #[cfg(feature = \"zippychord\")]\n    pub(super) fn parse_zippy_inner(\n        exprs: &[SExpr],\n        s: &ParserState,\n        f: &mut FileContentProvider,\n    ) -> Result<(ZchPossibleChords, ZchConfig)> {\n        use crate::subset::GetOrIsSubsetOfKnownKey::*;\n\n        if exprs[0].atom(None).expect(\"should be atom\") == \"defzippy-experimental\" {\n            log::warn!(\n                \"You should replace defzippy-experimental with defzippy.\\n\\\n             Using -experimental will be invalid in the future.\"\n            );\n        }\n\n        if exprs.len() < 2 {\n            bail_expr!(\n                &exprs[0],\n                \"There must be a filename following the zippy definition.\\nFound {}\",\n                exprs.len() - 1\n            );\n        }\n\n        let Some(file_name) = exprs[1].atom(s.vars()) else {\n            bail_expr!(&exprs[1], \"Filename must be a string, not a list.\");\n        };\n\n        let mut config = ZchConfig::default();\n\n        const KEY_NAME_MAPPINGS: &str = \"output-character-mappings\";\n        const IDLE_REACTIVATE_TIME: &str = \"idle-reactivate-time\";\n        const CHORD_DEADLINE: &str = \"on-first-press-chord-deadline\";\n        const SMART_SPACE: &str = \"smart-space\";\n        const SMART_SPACE_PUNCTUATION: &str = \"smart-space-punctuation\";\n\n        let mut idle_reactivate_time_seen = false;\n        let mut key_name_mappings_seen = false;\n        let mut chord_deadline_seen = false;\n        let mut smart_space_seen = false;\n        let mut smart_space_punctuation_seen = false;\n        let mut smart_space_punctuation_val_expr = None;\n\n        let mut user_cfg_char_to_output: HashMap<char, Vec<ZchOutput>> = HashMap::default();\n        let mut pairs = exprs[2..].chunks_exact(2);\n        for pair in pairs.by_ref() {\n            let config_name = &pair[0];\n            let config_value = &pair[1];\n\n            match config_name.atom(s.vars()).ok_or_else(|| {\n                anyhow_expr!(\n                    config_name,\n                    \"A configuration name must be a string, not a list\"\n                )\n            })? {\n                IDLE_REACTIVATE_TIME => {\n                    if idle_reactivate_time_seen {\n                        bail_expr!(\n                            config_name,\n                            \"This is the 2nd instance; it can only be defined once\"\n                        );\n                    }\n                    idle_reactivate_time_seen = true;\n                    config.zch_cfg_ticks_wait_enable =\n                        parse_u16(config_value, s, IDLE_REACTIVATE_TIME)?;\n                }\n\n                CHORD_DEADLINE => {\n                    if chord_deadline_seen {\n                        bail_expr!(\n                            config_name,\n                            \"This is the 2nd instance; it can only be defined once\"\n                        );\n                    }\n                    chord_deadline_seen = true;\n                    config.zch_cfg_ticks_chord_deadline =\n                        parse_u16(config_value, s, CHORD_DEADLINE)?;\n                }\n\n                SMART_SPACE => {\n                    if smart_space_seen {\n                        bail_expr!(\n                            config_name,\n                            \"This is the 2nd instance; it can only be defined once\"\n                        );\n                    }\n                    smart_space_seen = true;\n                    config.zch_cfg_smart_space = config_value\n                        .atom(s.vars())\n                        .and_then(|val| match val {\n                            \"none\" => Some(ZchSmartSpaceCfg::Disabled),\n                            \"full\" => Some(ZchSmartSpaceCfg::Full),\n                            \"add-space-only\" => Some(ZchSmartSpaceCfg::AddSpaceOnly),\n                            _ => None,\n                        })\n                        .ok_or_else(|| {\n                            anyhow_expr!(&config_value, \"Must be: none | full | add-space-only\")\n                        })?;\n                }\n\n                SMART_SPACE_PUNCTUATION => {\n                    if smart_space_punctuation_seen {\n                        bail_expr!(\n                            config_name,\n                            \"This is the 2nd instance; it can only be defined once\"\n                        );\n                    }\n                    smart_space_punctuation_seen = true;\n                    // Need to save and parse this later since it makes use of KEY_NAME_MAPPINGS.\n                    smart_space_punctuation_val_expr = Some(config_value);\n                }\n\n                KEY_NAME_MAPPINGS => {\n                    if key_name_mappings_seen {\n                        bail_expr!(\n                            config_name,\n                            \"This is the 2nd instance; it can only be defined once\"\n                        );\n                    }\n                    key_name_mappings_seen = true;\n                    let mut mappings = config_value\n                        .list(s.vars())\n                        .ok_or_else(|| {\n                            anyhow_expr!(\n                                config_value,\n                                \"{KEY_NAME_MAPPINGS} must be followed by a list\"\n                            )\n                        })?\n                        .chunks_exact(2);\n\n                    for mapping_pair in mappings.by_ref() {\n                        let input = mapping_pair[0]\n                            .atom(None)\n                            .ok_or_else(|| {\n                                anyhow_expr!(\n                                    &mapping_pair[0],\n                                    \"key mapping input does not use lists\"\n                                )\n                            })?\n                            .trim_atom_quotes();\n                        if input.chars().count() != 1 {\n                            bail_expr!(&mapping_pair[0], \"Inputs should be exactly one character\");\n                        }\n                        let input_char = input.chars().next().expect(\"count is 1\");\n\n                        let output = match mapping_pair[1].atom(s.vars()) {\n                            Some(o) => vec![parse_single_zippy_output_mapping(\n                                o,\n                                &mapping_pair[1],\n                                false,\n                            )?],\n                            None => {\n                                // note for unwrap below: must be list if not atom\n                                let output_list = mapping_pair[1].list(s.vars()).unwrap();\n                                if output_list.is_empty() {\n                                    bail_expr!(\n                                        &mapping_pair[1],\n                                        \"Empty list is invalid for zippy output mapping.\"\n                                    );\n                                }\n                                let output_type =\n                                    ZchIoMappingType::try_parse(&output_list[0], s.vars())?;\n                                match output_type {\n                                    ZchIoMappingType::NoErase => {\n                                        const ERR: &str = \"expects a single key or output chord.\";\n                                        if output_list.len() != 2 {\n                                            anyhow_expr!(&output_list[1], \"{NO_ERASE} {ERR}\");\n                                        }\n                                        let output =\n                                            output_list[1].atom(s.vars()).ok_or_else(|| {\n                                                anyhow_expr!(&output_list[1], \"{NO_ERASE} {ERR}\")\n                                            })?;\n                                        vec![parse_single_zippy_output_mapping(\n                                            output,\n                                            &output_list[1],\n                                            true,\n                                        )?]\n                                    }\n                                    ZchIoMappingType::SingleOutput => {\n                                        if output_list.len() < 2 {\n                                            anyhow_expr!(\n                                                &output_list[1],\n                                                \"{SINGLE_OUTPUT_MULTI_KEY} expects one or more keys or output chords.\"\n                                            );\n                                        }\n                                        let all_params_except_last =\n                                            &output_list[1..output_list.len() - 1];\n                                        let mut outs = vec![];\n                                        for expr in all_params_except_last {\n                                            let output = expr\n                                            .atom(s.vars())\n                                            .ok_or_else(|| {\n                                                anyhow_expr!(&output_list[1], \"{SINGLE_OUTPUT_MULTI_KEY} does not allow list parameters.\")\n                                            })?;\n                                            let out = parse_single_zippy_output_mapping(\n                                                output,\n                                                &output_list[1],\n                                                true,\n                                            )?;\n                                            outs.push(out);\n                                        }\n                                        let last_expr = &output_list.last().unwrap(); // non-empty, checked length already\n                                        let last_out = last_expr\n                                        .atom(s.vars())\n                                        .ok_or_else(|| {\n                                            anyhow_expr!(last_expr, \"{SINGLE_OUTPUT_MULTI_KEY} does not allow list parameters.\")\n                                        })?;\n                                        outs.push(parse_single_zippy_output_mapping(\n                                            last_out, last_expr, false,\n                                        )?);\n                                        outs\n                                    }\n                                }\n                            }\n                        };\n\n                        if user_cfg_char_to_output.insert(input_char, output).is_some() {\n                            bail_expr!(&mapping_pair[0], \"Duplicate character, not allowed\");\n                        }\n                    }\n\n                    let rem = mappings.remainder();\n                    if !rem.is_empty() {\n                        bail_expr!(&rem[0], \"zippy input is missing its output mapping\");\n                    }\n                }\n                _ => bail_expr!(config_name, \"Unknown zippy configuration name\"),\n            }\n        }\n\n        let rem = pairs.remainder();\n        if !rem.is_empty() {\n            bail_expr!(&rem[0], \"zippy config name is missing its value\");\n        }\n\n        if let Some(val) = smart_space_punctuation_val_expr {\n            config.zch_cfg_smart_space_punctuation = val\n                .list(s.vars())\n                .ok_or_else(|| {\n                    anyhow_expr!(val, \"{SMART_SPACE_PUNCTUATION} must be followed by a list\")\n                })?\n                .iter()\n                .try_fold(vec![], |mut puncs, punc_expr| -> Result<Vec<ZchOutput>> {\n                    let punc = punc_expr\n                        .atom(s.vars())\n                        .ok_or_else(|| anyhow_expr!(&punc_expr, \"Lists are not allowed\"))?;\n\n                    if punc.chars().count() == 1 {\n                        let c = punc.chars().next().unwrap(); // checked count above\n                        if let Some(out) = user_cfg_char_to_output.get(&c) {\n                            if out.len() > 1 {\n                                bail_expr!(\n                                    punc_expr,\n                                    \"This character is a single-output with multiple keys\\n\n                                       and is not yet supported as use for punctuation.\"\n                                );\n                            }\n                            puncs.push(out[0]);\n                            return Ok(puncs);\n                        }\n                    }\n\n                    let osc = str_to_oscode(punc)\n                        .ok_or_else(|| anyhow_expr!(&punc_expr, \"Unknown key name\"))?;\n                    puncs.push(ZchOutput::Lowercase(osc));\n\n                    Ok(puncs)\n                })?\n                .into_iter()\n                .collect();\n            config.zch_cfg_smart_space_punctuation.shrink_to_fit();\n        }\n\n        // process zippy file\n        let input_data = f\n            .get_file_content(file_name.as_ref())\n            .map_err(|e| anyhow_expr!(&exprs[1], \"Failed to read file:\\n{e}\"))?;\n        let res = input_data\n            .lines()\n            .enumerate()\n            .filter(|(_, line)| !line.trim().is_empty() && !line.trim().starts_with(\"//\"))\n            .try_fold(\n                Arc::new(Mutex::new(ZchPossibleChords(SubsetMap::ssm_new()))),\n                |zch, (line_number, line)| {\n                    let Some((input, output)) = line.split_once('\\t') else {\n                        bail_expr!(\n                        &exprs[1],\n                        \"Input and output are separated by a tab, but found no tab:\\n{}: {line}\",\n                        line_number + 1\n                    );\n                    };\n                    if input.is_empty() {\n                        bail_expr!(\n                            &exprs[1],\n                            \"No input defined; line must not begin with a tab:\\n{}: {line}\",\n                            line_number + 1\n                        );\n                    }\n\n                    let mut char_buf: [u8; 4] = [0; 4];\n                    let output = {\n                        output\n                            .chars()\n                            .try_fold(vec![], |mut zch_output, out_char| -> Result<_> {\n                                if let Some(out) = user_cfg_char_to_output.get(&out_char) {\n                                    zch_output.extend(out.iter());\n                                    return Ok(zch_output);\n                                }\n\n                                let out_key = out_char.to_lowercase().next().unwrap();\n                                let key_name = out_key.encode_utf8(&mut char_buf);\n                                let osc = match key_name as &str {\n                                    \" \" => OsCode::KEY_SPACE,\n                                    _ => str_to_oscode(key_name).ok_or_else(|| {\n                                        anyhow_expr!(\n                                            &exprs[1],\n                                            \"Unknown output key name '{}':\\n{}: {line}\",\n                                            out_char,\n                                            line_number + 1,\n                                        )\n                                    })?,\n                                };\n                                let out = match out_char.is_uppercase() {\n                                    true => ZchOutput::Uppercase(osc),\n                                    false => ZchOutput::Lowercase(osc),\n                                };\n                                zch_output.push(out);\n                                Ok(zch_output)\n                            })?\n                            .into_boxed_slice()\n                    };\n                    let mut input_left_to_parse = input;\n                    let mut chord_chars;\n                    let mut input_chord = ZchInputKeys::zchik_new();\n                    let mut is_space_included;\n                    let mut possible_chords_map = zch.clone();\n                    let mut next_map: Option<Arc<Mutex<_>>>;\n\n                    while !input_left_to_parse.is_empty() {\n                        input_chord.zchik_clear();\n\n                        // Check for a starting space.\n                        (is_space_included, input_left_to_parse) =\n                            match input_left_to_parse.strip_prefix(' ') {\n                                None => (false, input_left_to_parse),\n                                Some(i) => (true, i),\n                            };\n                        if is_space_included {\n                            input_chord.zchik_insert(OsCode::KEY_SPACE);\n                        }\n\n                        // Parse chord until next space.\n                        (chord_chars, input_left_to_parse) =\n                            match input_left_to_parse.split_once(' ') {\n                                Some(split) => split,\n                                None => (input_left_to_parse, \"\"),\n                            };\n\n                        chord_chars\n                            .chars()\n                            .try_fold((), |_, chord_char| -> Result<()> {\n                                let key_name = chord_char.encode_utf8(&mut char_buf);\n                                let osc = str_to_oscode(key_name).ok_or_else(|| {\n                                    anyhow_expr!(\n                                        &exprs[1],\n                                        \"Unknown input key name: '{key_name}':\\n{}: {line}\",\n                                        line_number + 1\n                                    )\n                                })?;\n                                input_chord.zchik_insert(osc);\n                                Ok(())\n                            })?;\n\n                        let output_for_input_chord = possible_chords_map\n                            .lock()\n                            .0\n                            .ssm_get_or_is_subset_ksorted(input_chord.zchik_keys());\n                        match (input_left_to_parse.is_empty(), output_for_input_chord) {\n                            (true, HasValue(_)) => {\n                                bail_expr!(\n                            &exprs[1],\n                            \"Found duplicate input chord, which is disallowed {input}:\\n{}: {line}\",\n                            line_number + 1\n                        );\n                            }\n                            (true, _) => {\n                                possible_chords_map.lock().0.ssm_insert_ksorted(\n                                    input_chord.zchik_keys(),\n                                    Arc::new(ZchChordOutput {\n                                        zch_output: output,\n                                        zch_followups: None,\n                                    }),\n                                );\n                                break;\n                            }\n                            (false, HasValue(next_nested_map)) => {\n                                match &next_nested_map.zch_followups {\n                                    None => {\n                                        let map = Arc::new(Mutex::new(ZchPossibleChords(\n                                            SubsetMap::ssm_new(),\n                                        )));\n                                        next_map = Some(map.clone());\n                                        possible_chords_map.lock().0.ssm_insert_ksorted(\n                                            input_chord.zchik_keys(),\n                                            ZchChordOutput {\n                                                zch_output: next_nested_map.zch_output.clone(),\n                                                zch_followups: Some(map),\n                                            }\n                                            .into(),\n                                        );\n                                    }\n                                    Some(followup) => {\n                                        next_map = Some(followup.clone());\n                                    }\n                                }\n                            }\n                            (false, _) => {\n                                let map =\n                                    Arc::new(Mutex::new(ZchPossibleChords(SubsetMap::ssm_new())));\n                                next_map = Some(map.clone());\n                                possible_chords_map.lock().0.ssm_insert_ksorted(\n                                    input_chord.zchik_keys(),\n                                    Arc::new(ZchChordOutput {\n                                        zch_output: Box::new([]),\n                                        zch_followups: Some(map),\n                                    }),\n                                );\n                            }\n                        };\n                        if let Some(map) = next_map.take() {\n                            possible_chords_map = map;\n                        }\n                    }\n                    Ok(zch)\n                },\n            )?;\n        Ok((\n            Arc::into_inner(res).expect(\"no other refs\").into_inner(),\n            config,\n        ))\n    }\n\n    fn parse_single_zippy_output_mapping(\n        output: &str,\n        output_expr: &SExpr,\n        is_noerase: bool,\n    ) -> Result<ZchOutput> {\n        let (output_mods, output_key) = parse_mod_prefix(output)?;\n        if output_mods.contains(&KeyCode::LShift) && output_mods.contains(&KeyCode::RShift) {\n            bail_expr!(\n                output_expr,\n                \"Both shifts are used which is redundant, use only one.\"\n            );\n        }\n        if output_mods\n            .iter()\n            .any(|m| !matches!(m, KeyCode::LShift | KeyCode::RShift | KeyCode::RAlt))\n        {\n            bail_expr!(output_expr, \"Only S- and AG- are supported.\");\n        }\n        let output_osc = str_to_oscode(output_key)\n            .ok_or_else(|| anyhow_expr!(output_expr, \"unknown key name\"))?;\n        let output = match output_mods.len() {\n            0 => match is_noerase {\n                false => ZchOutput::Lowercase(output_osc),\n                true => ZchOutput::NoEraseLowercase(output_osc),\n            },\n            1 => match output_mods[0] {\n                KeyCode::LShift | KeyCode::RShift => match is_noerase {\n                    false => ZchOutput::Uppercase(output_osc),\n                    true => ZchOutput::NoEraseUppercase(output_osc),\n                },\n                KeyCode::RAlt => match is_noerase {\n                    false => ZchOutput::AltGr(output_osc),\n                    true => ZchOutput::NoEraseAltGr(output_osc),\n                },\n                _ => unreachable!(\"forbidden by earlier parsing\"),\n            },\n            2 => match is_noerase {\n                false => ZchOutput::ShiftAltGr(output_osc),\n                true => ZchOutput::NoEraseShiftAltGr(output_osc),\n            },\n            _ => {\n                unreachable!(\"contains at most: altgr and one of the shifts\")\n            }\n        };\n        Ok(output)\n    }\n}\n"
  },
  {
    "path": "parser/src/custom_action.rs",
    "content": "//! This module contains the \"Custom\" actions that are used with the keyberon layout.\n//!\n//! When adding a new custom action, the macro section of the config.adoc documentation may need to\n//! be updated, to include the new action to the documented list of supported actions in macro.\n\nuse anyhow::{Result, anyhow};\nuse core::fmt;\nuse kanata_keyberon::key_code::KeyCode;\n\nuse crate::{cfg::SimpleSExpr, keys::OsCode};\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub enum CustomAction {\n    Cmd(&'static [&'static str]),\n    CmdLog(LogLevel, LogLevel, &'static [&'static str]),\n    CmdOutputKeys(&'static [&'static str]),\n    PushMessage(&'static [SimpleSExpr]),\n    Unicode(char),\n    Mouse(Btn),\n    MouseTap(Btn),\n    FakeKey {\n        coord: Coord,\n        action: FakeKeyAction,\n    },\n    FakeKeyOnRelease {\n        coord: Coord,\n        action: FakeKeyAction,\n    },\n    FakeKeyOnIdle(FakeKeyOnIdle),\n    FakeKeyOnPhysicalIdle(FakeKeyOnIdle),\n    FakeKeyHoldForDuration(FakeKeyHoldForDuration),\n    Delay(u16),\n    DelayOnRelease(u16),\n    MWheel {\n        direction: MWheelDirection,\n        interval: u16,\n        distance: u16,\n        inertial_scroll_params: Option<&'static MWheelInertial>,\n    },\n    MWheelNotch {\n        direction: MWheelDirection,\n    },\n    MoveMouse {\n        direction: MoveDirection,\n        interval: u16,\n        distance: u16,\n    },\n    MoveMouseAccel {\n        direction: MoveDirection,\n        interval: u16,\n        accel_time: u16,\n        min_distance: u16,\n        max_distance: u16,\n    },\n    MoveMouseSpeed {\n        speed: u16,\n    },\n    SequenceCancel,\n    SequenceLeader(u16, SequenceInputMode),\n    /// Purpose:\n    /// In case the user has dead keys in their OS layout, they may wish to send fewer backspaces upon\n    /// a successful completion of visible-backspaced sequences, because the number of key events\n    /// is larger than the number of backspace-able symbols typed within the application.\n    /// This custom action is a marker to accomplish the use case.\n    SequenceNoerase(u16),\n    LiveReload,\n    LiveReloadNext,\n    LiveReloadPrev,\n    /// Live-reload the n'th configuration file provided on the CLI. This should begin with 0 as\n    /// the first configuration file provided. The rest of the parser code is free to choose 0 or 1\n    /// as the user-facing value though.\n    LiveReloadNum(u16),\n    LiveReloadFile(&'static str),\n    Repeat,\n    CancelMacroOnRelease,\n    CancelMacroOnNextPress(u32),\n    DynamicMacroRecord(u16),\n    DynamicMacroRecordStop(u16),\n    DynamicMacroPlay(u16),\n    SendArbitraryCode(u16),\n    CapsWord(CapsWordCfg),\n    SetMouse {\n        x: u16,\n        y: u16,\n    },\n    Unmodded {\n        keys: &'static [KeyCode],\n        mods: UnmodMods,\n    },\n    Unshifted {\n        keys: &'static [KeyCode],\n    },\n    ReverseReleaseOrder,\n    ClipboardSet(&'static str),\n    ClipboardCmdSet(&'static [&'static str]),\n    ClipboardSave(u16),\n    ClipboardRestore(u16),\n    ClipboardSaveSet(u16, &'static str),\n    ClipboardSaveCmdSet(u16, &'static [&'static str]),\n    ClipboardSaveSwap(u16, u16),\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum Btn {\n    Left,\n    Right,\n    Mid,\n    Forward,\n    Backward,\n}\n\nimpl fmt::Display for Btn {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        match self {\n            Btn::Left => write!(f, \"‹🖰\"),\n            Btn::Right => write!(f, \"🖰›\"),\n            Btn::Mid => write!(f, \"🖱\"),\n            Btn::Backward => write!(f, \"⎌🖰\"),\n            Btn::Forward => write!(f, \"🖰↷\"),\n        }\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct Coord {\n    pub x: u8,\n    pub y: u16,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum FakeKeyAction {\n    Press,\n    Release,\n    Tap,\n    Toggle,\n}\n\n/// An active waiting-for-idle state.\n#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]\npub struct FakeKeyOnIdle {\n    pub coord: Coord,\n    pub action: FakeKeyAction,\n    pub idle_duration: u16,\n}\n\n/// Information for an action that presses a fake key / vkey that becomes released on a\n/// renewable-when-reactivated deadline.\n#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]\npub struct FakeKeyHoldForDuration {\n    pub coord: Coord,\n    pub hold_duration: u16,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum MWheelDirection {\n    Up,\n    Down,\n    Left,\n    Right,\n}\nimpl fmt::Display for MWheelDirection {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        match self {\n            MWheelDirection::Up => write!(f, \"🖱↑\"),\n            MWheelDirection::Down => write!(f, \"🖱↓\"),\n            MWheelDirection::Left => write!(f, \"🖱←\"),\n            MWheelDirection::Right => write!(f, \"🖱→\"),\n        }\n    }\n}\n\nimpl TryFrom<OsCode> for MWheelDirection {\n    type Error = ();\n    fn try_from(value: OsCode) -> Result<Self, Self::Error> {\n        use OsCode::*;\n        Ok(match value {\n            MouseWheelUp => MWheelDirection::Up,\n            MouseWheelDown => MWheelDirection::Down,\n            MouseWheelLeft => MWheelDirection::Left,\n            MouseWheelRight => MWheelDirection::Right,\n            _ => return Err(()),\n        })\n    }\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)]\npub struct MWheelInertial {\n    pub initial_velocity: ordered_float::OrderedFloat<f32>,\n    pub maximum_velocity: ordered_float::OrderedFloat<f32>,\n    pub acceleration_multiplier: ordered_float::OrderedFloat<f32>,\n    pub deceleration_multiplier: ordered_float::OrderedFloat<f32>,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum MoveDirection {\n    Up,\n    Down,\n    Left,\n    Right,\n}\nimpl fmt::Display for MoveDirection {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        match self {\n            MoveDirection::Up => write!(f, \"↑\"),\n            MoveDirection::Down => write!(f, \"↓\"),\n            MoveDirection::Left => write!(f, \"←\"),\n            MoveDirection::Right => write!(f, \"→\"),\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct CapsWordCfg {\n    pub keys_to_capitalize: &'static [KeyCode],\n    pub keys_nonterminal: &'static [KeyCode],\n    pub timeout: u16,\n    pub repress_behaviour: CapsWordRepressBehaviour,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub enum CapsWordRepressBehaviour {\n    Overwrite,\n    Toggle,\n}\n\n/// This controls the behaviour of kanata when sequence mode is initiated by the sequence leader\n/// action.\n///\n/// - `HiddenSuppressed` hides the keys typed as part of the sequence and does not output the keys\n///   typed when an invalid sequence is the result of an invalid sequence character or a timeout.\n/// - `HiddenDelayType` hides the keys typed as part of the sequence and outputs the keys when an\n///   typed when an invalid sequence is the result of an invalid sequence character or a timeout.\n/// - `VisibleBackspaced` will type the keys that are typed as part of the sequence but will\n///   backspace the typed sequence keys before performing the fake key tap when a valid sequence is\n///   the result.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum SequenceInputMode {\n    HiddenSuppressed,\n    HiddenDelayType,\n    VisibleBackspaced,\n}\n\nconst SEQ_VISIBLE_BACKSPACED: &str = \"visible-backspaced\";\nconst SEQ_HIDDEN_SUPPRESSED: &str = \"hidden-suppressed\";\nconst SEQ_HIDDEN_DELAY_TYPE: &str = \"hidden-delay-type\";\n\nimpl SequenceInputMode {\n    pub fn try_from_str(s: &str) -> Result<Self> {\n        match s {\n            SEQ_VISIBLE_BACKSPACED => Ok(SequenceInputMode::VisibleBackspaced),\n            SEQ_HIDDEN_SUPPRESSED => Ok(SequenceInputMode::HiddenSuppressed),\n            SEQ_HIDDEN_DELAY_TYPE => Ok(SequenceInputMode::HiddenDelayType),\n            _ => Err(anyhow!(SequenceInputMode::err_msg())),\n        }\n    }\n\n    pub fn err_msg() -> String {\n        format!(\n            \"sequence input mode must be one of: {SEQ_VISIBLE_BACKSPACED}, {SEQ_HIDDEN_SUPPRESSED}, {SEQ_HIDDEN_DELAY_TYPE}\"\n        )\n    }\n}\n\nconst LOG_LEVEL_DEBUG: &str = \"debug\";\nconst LOG_LEVEL_INFO: &str = \"info\";\nconst LOG_LEVEL_WARN: &str = \"warn\";\nconst LOG_LEVEL_ERROR: &str = \"error\";\nconst LOG_LEVEL_NONE: &str = \"none\";\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum LogLevel {\n    // No trace here because that wouldn't make sense\n    Debug,\n    Info,\n    Warn,\n    Error,\n    None,\n}\n\nimpl LogLevel {\n    pub fn try_from_str(s: &str) -> Result<Self> {\n        match s {\n            LOG_LEVEL_DEBUG => Ok(LogLevel::Debug),\n            LOG_LEVEL_INFO => Ok(LogLevel::Info),\n            LOG_LEVEL_WARN => Ok(LogLevel::Warn),\n            LOG_LEVEL_ERROR => Ok(LogLevel::Error),\n            LOG_LEVEL_NONE => Ok(LogLevel::None),\n            _ => Err(anyhow!(LogLevel::err_msg())),\n        }\n    }\n\n    pub fn get_level(&self) -> Option<log::Level> {\n        match self {\n            LogLevel::Debug => Some(log::Level::Debug),\n            LogLevel::Info => Some(log::Level::Info),\n            LogLevel::Warn => Some(log::Level::Warn),\n            LogLevel::Error => Some(log::Level::Error),\n            LogLevel::None => None,\n        }\n    }\n\n    pub fn err_msg() -> String {\n        format!(\n            \"log level must be one of: {LOG_LEVEL_DEBUG}, {LOG_LEVEL_INFO}, {LOG_LEVEL_WARN}, {LOG_LEVEL_ERROR}, {LOG_LEVEL_NONE}\"\n        )\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct UnmodMods(u8);\n\nbitflags::bitflags! {\n    impl UnmodMods: u8 {\n        const LSft = 0b00000001;\n        const RSft = 0b00000010;\n        const LAlt = 0b00000100;\n        const RAlt = 0b00001000;\n        const LCtl = 0b00010000;\n        const RCtl = 0b00100000;\n        const LMet = 0b01000000;\n        const RMet = 0b10000000;\n    }\n}\n"
  },
  {
    "path": "parser/src/keys/linux.rs",
    "content": "// This file is taken from the original ktrl project's keys.rs file with modifications.\n\nuse super::OsCode;\n\nimpl OsCode {\n    pub(super) const fn as_u16_linux(self) -> u16 {\n        self as u16\n    }\n    pub(super) const fn from_u16_linux(code: u16) -> Option<Self> {\n        match code {\n            0 => Some(OsCode::KEY_RESERVED),\n            1 => Some(OsCode::KEY_ESC),\n            2 => Some(OsCode::KEY_1),\n            3 => Some(OsCode::KEY_2),\n            4 => Some(OsCode::KEY_3),\n            5 => Some(OsCode::KEY_4),\n            6 => Some(OsCode::KEY_5),\n            7 => Some(OsCode::KEY_6),\n            8 => Some(OsCode::KEY_7),\n            9 => Some(OsCode::KEY_8),\n            10 => Some(OsCode::KEY_9),\n            11 => Some(OsCode::KEY_0),\n            12 => Some(OsCode::KEY_MINUS),\n            13 => Some(OsCode::KEY_EQUAL),\n            14 => Some(OsCode::KEY_BACKSPACE),\n            15 => Some(OsCode::KEY_TAB),\n            16 => Some(OsCode::KEY_Q),\n            17 => Some(OsCode::KEY_W),\n            18 => Some(OsCode::KEY_E),\n            19 => Some(OsCode::KEY_R),\n            20 => Some(OsCode::KEY_T),\n            21 => Some(OsCode::KEY_Y),\n            22 => Some(OsCode::KEY_U),\n            23 => Some(OsCode::KEY_I),\n            24 => Some(OsCode::KEY_O),\n            25 => Some(OsCode::KEY_P),\n            26 => Some(OsCode::KEY_LEFTBRACE),\n            27 => Some(OsCode::KEY_RIGHTBRACE),\n            28 => Some(OsCode::KEY_ENTER),\n            29 => Some(OsCode::KEY_LEFTCTRL),\n            30 => Some(OsCode::KEY_A),\n            31 => Some(OsCode::KEY_S),\n            32 => Some(OsCode::KEY_D),\n            33 => Some(OsCode::KEY_F),\n            34 => Some(OsCode::KEY_G),\n            35 => Some(OsCode::KEY_H),\n            36 => Some(OsCode::KEY_J),\n            37 => Some(OsCode::KEY_K),\n            38 => Some(OsCode::KEY_L),\n            39 => Some(OsCode::KEY_SEMICOLON),\n            40 => Some(OsCode::KEY_APOSTROPHE),\n            41 => Some(OsCode::KEY_GRAVE),\n            42 => Some(OsCode::KEY_LEFTSHIFT),\n            43 => Some(OsCode::KEY_BACKSLASH),\n            44 => Some(OsCode::KEY_Z),\n            45 => Some(OsCode::KEY_X),\n            46 => Some(OsCode::KEY_C),\n            47 => Some(OsCode::KEY_V),\n            48 => Some(OsCode::KEY_B),\n            49 => Some(OsCode::KEY_N),\n            50 => Some(OsCode::KEY_M),\n            51 => Some(OsCode::KEY_COMMA),\n            52 => Some(OsCode::KEY_DOT),\n            53 => Some(OsCode::KEY_SLASH),\n            54 => Some(OsCode::KEY_RIGHTSHIFT),\n            55 => Some(OsCode::KEY_KPASTERISK),\n            56 => Some(OsCode::KEY_LEFTALT),\n            57 => Some(OsCode::KEY_SPACE),\n            58 => Some(OsCode::KEY_CAPSLOCK),\n            59 => Some(OsCode::KEY_F1),\n            60 => Some(OsCode::KEY_F2),\n            61 => Some(OsCode::KEY_F3),\n            62 => Some(OsCode::KEY_F4),\n            63 => Some(OsCode::KEY_F5),\n            64 => Some(OsCode::KEY_F6),\n            65 => Some(OsCode::KEY_F7),\n            66 => Some(OsCode::KEY_F8),\n            67 => Some(OsCode::KEY_F9),\n            68 => Some(OsCode::KEY_F10),\n            69 => Some(OsCode::KEY_NUMLOCK),\n            70 => Some(OsCode::KEY_SCROLLLOCK),\n            71 => Some(OsCode::KEY_KP7),\n            72 => Some(OsCode::KEY_KP8),\n            73 => Some(OsCode::KEY_KP9),\n            74 => Some(OsCode::KEY_KPMINUS),\n            75 => Some(OsCode::KEY_KP4),\n            76 => Some(OsCode::KEY_KP5),\n            77 => Some(OsCode::KEY_KP6),\n            78 => Some(OsCode::KEY_KPPLUS),\n            79 => Some(OsCode::KEY_KP1),\n            80 => Some(OsCode::KEY_KP2),\n            81 => Some(OsCode::KEY_KP3),\n            82 => Some(OsCode::KEY_KP0),\n            83 => Some(OsCode::KEY_KPDOT),\n            84 => Some(OsCode::KEY_84),\n            85 => Some(OsCode::KEY_ZENKAKUHANKAKU),\n            86 => Some(OsCode::KEY_102ND),\n            87 => Some(OsCode::KEY_F11),\n            88 => Some(OsCode::KEY_F12),\n            89 => Some(OsCode::KEY_RO),\n            90 => Some(OsCode::KEY_KATAKANA),\n            91 => Some(OsCode::KEY_HIRAGANA),\n            92 => Some(OsCode::KEY_HENKAN),\n            93 => Some(OsCode::KEY_KATAKANAHIRAGANA),\n            94 => Some(OsCode::KEY_MUHENKAN),\n            95 => Some(OsCode::KEY_KPJPCOMMA),\n            96 => Some(OsCode::KEY_KPENTER),\n            97 => Some(OsCode::KEY_RIGHTCTRL),\n            98 => Some(OsCode::KEY_KPSLASH),\n            99 => Some(OsCode::KEY_SYSRQ),\n            100 => Some(OsCode::KEY_RIGHTALT),\n            101 => Some(OsCode::KEY_LINEFEED),\n            102 => Some(OsCode::KEY_HOME),\n            103 => Some(OsCode::KEY_UP),\n            104 => Some(OsCode::KEY_PAGEUP),\n            105 => Some(OsCode::KEY_LEFT),\n            106 => Some(OsCode::KEY_RIGHT),\n            107 => Some(OsCode::KEY_END),\n            108 => Some(OsCode::KEY_DOWN),\n            109 => Some(OsCode::KEY_PAGEDOWN),\n            110 => Some(OsCode::KEY_INSERT),\n            111 => Some(OsCode::KEY_DELETE),\n            112 => Some(OsCode::KEY_MACRO),\n            113 => Some(OsCode::KEY_MUTE),\n            114 => Some(OsCode::KEY_VOLUMEDOWN),\n            115 => Some(OsCode::KEY_VOLUMEUP),\n            116 => Some(OsCode::KEY_POWER),\n            117 => Some(OsCode::KEY_KPEQUAL),\n            118 => Some(OsCode::KEY_KPPLUSMINUS),\n            119 => Some(OsCode::KEY_PAUSE),\n            120 => Some(OsCode::KEY_SCALE),\n            121 => Some(OsCode::KEY_KPCOMMA),\n            122 => Some(OsCode::KEY_HANGEUL),\n            123 => Some(OsCode::KEY_HANJA),\n            124 => Some(OsCode::KEY_YEN),\n            125 => Some(OsCode::KEY_LEFTMETA),\n            126 => Some(OsCode::KEY_RIGHTMETA),\n            127 => Some(OsCode::KEY_COMPOSE),\n            128 => Some(OsCode::KEY_STOP),\n            129 => Some(OsCode::KEY_AGAIN),\n            130 => Some(OsCode::KEY_PROPS),\n            131 => Some(OsCode::KEY_UNDO),\n            132 => Some(OsCode::KEY_FRONT),\n            133 => Some(OsCode::KEY_COPY),\n            134 => Some(OsCode::KEY_OPEN),\n            135 => Some(OsCode::KEY_PASTE),\n            136 => Some(OsCode::KEY_FIND),\n            137 => Some(OsCode::KEY_CUT),\n            138 => Some(OsCode::KEY_HELP),\n            139 => Some(OsCode::KEY_MENU),\n            140 => Some(OsCode::KEY_CALC),\n            141 => Some(OsCode::KEY_SETUP),\n            142 => Some(OsCode::KEY_SLEEP),\n            143 => Some(OsCode::KEY_WAKEUP),\n            144 => Some(OsCode::KEY_FILE),\n            145 => Some(OsCode::KEY_SENDFILE),\n            146 => Some(OsCode::KEY_DELETEFILE),\n            147 => Some(OsCode::KEY_XFER),\n            148 => Some(OsCode::KEY_PROG1),\n            149 => Some(OsCode::KEY_PROG2),\n            150 => Some(OsCode::KEY_WWW),\n            151 => Some(OsCode::KEY_MSDOS),\n            152 => Some(OsCode::KEY_COFFEE),\n            153 => Some(OsCode::KEY_ROTATE_DISPLAY),\n            154 => Some(OsCode::KEY_CYCLEWINDOWS),\n            155 => Some(OsCode::KEY_MAIL),\n            156 => Some(OsCode::KEY_BOOKMARKS),\n            157 => Some(OsCode::KEY_COMPUTER),\n            158 => Some(OsCode::KEY_BACK),\n            159 => Some(OsCode::KEY_FORWARD),\n            160 => Some(OsCode::KEY_CLOSECD),\n            161 => Some(OsCode::KEY_EJECTCD),\n            162 => Some(OsCode::KEY_EJECTCLOSECD),\n            163 => Some(OsCode::KEY_NEXTSONG),\n            164 => Some(OsCode::KEY_PLAYPAUSE),\n            165 => Some(OsCode::KEY_PREVIOUSSONG),\n            166 => Some(OsCode::KEY_STOPCD),\n            167 => Some(OsCode::KEY_RECORD),\n            168 => Some(OsCode::KEY_REWIND),\n            169 => Some(OsCode::KEY_PHONE),\n            170 => Some(OsCode::KEY_ISO),\n            171 => Some(OsCode::KEY_CONFIG),\n            172 => Some(OsCode::KEY_HOMEPAGE),\n            173 => Some(OsCode::KEY_REFRESH),\n            174 => Some(OsCode::KEY_EXIT),\n            175 => Some(OsCode::KEY_MOVE),\n            176 => Some(OsCode::KEY_EDIT),\n            177 => Some(OsCode::KEY_SCROLLUP),\n            178 => Some(OsCode::KEY_SCROLLDOWN),\n            179 => Some(OsCode::KEY_KPLEFTPAREN),\n            180 => Some(OsCode::KEY_KPRIGHTPAREN),\n            181 => Some(OsCode::KEY_NEW),\n            182 => Some(OsCode::KEY_REDO),\n            183 => Some(OsCode::KEY_F13),\n            184 => Some(OsCode::KEY_F14),\n            185 => Some(OsCode::KEY_F15),\n            186 => Some(OsCode::KEY_F16),\n            187 => Some(OsCode::KEY_F17),\n            188 => Some(OsCode::KEY_F18),\n            189 => Some(OsCode::KEY_F19),\n            190 => Some(OsCode::KEY_F20),\n            191 => Some(OsCode::KEY_F21),\n            192 => Some(OsCode::KEY_F22),\n            193 => Some(OsCode::KEY_F23),\n            194 => Some(OsCode::KEY_F24),\n            195 => Some(OsCode::KEY_195),\n            196 => Some(OsCode::KEY_196),\n            197 => Some(OsCode::KEY_197),\n            198 => Some(OsCode::KEY_198),\n            199 => Some(OsCode::KEY_199),\n            200 => Some(OsCode::KEY_PLAYCD),\n            201 => Some(OsCode::KEY_PAUSECD),\n            202 => Some(OsCode::KEY_PROG3),\n            203 => Some(OsCode::KEY_PROG4),\n            204 => Some(OsCode::KEY_DASHBOARD),\n            205 => Some(OsCode::KEY_SUSPEND),\n            206 => Some(OsCode::KEY_CLOSE),\n            207 => Some(OsCode::KEY_PLAY),\n            208 => Some(OsCode::KEY_FASTFORWARD),\n            209 => Some(OsCode::KEY_BASSBOOST),\n            210 => Some(OsCode::KEY_PRINT),\n            211 => Some(OsCode::KEY_HP),\n            212 => Some(OsCode::KEY_CAMERA),\n            213 => Some(OsCode::KEY_SOUND),\n            214 => Some(OsCode::KEY_QUESTION),\n            215 => Some(OsCode::KEY_EMAIL),\n            216 => Some(OsCode::KEY_CHAT),\n            217 => Some(OsCode::KEY_SEARCH),\n            218 => Some(OsCode::KEY_CONNECT),\n            219 => Some(OsCode::KEY_FINANCE),\n            220 => Some(OsCode::KEY_SPORT),\n            221 => Some(OsCode::KEY_SHOP),\n            222 => Some(OsCode::KEY_ALTERASE),\n            223 => Some(OsCode::KEY_CANCEL),\n            224 => Some(OsCode::KEY_BRIGHTNESSDOWN),\n            225 => Some(OsCode::KEY_BRIGHTNESSUP),\n            226 => Some(OsCode::KEY_MEDIA),\n            227 => Some(OsCode::KEY_SWITCHVIDEOMODE),\n            228 => Some(OsCode::KEY_KBDILLUMTOGGLE),\n            229 => Some(OsCode::KEY_KBDILLUMDOWN),\n            230 => Some(OsCode::KEY_KBDILLUMUP),\n            231 => Some(OsCode::KEY_SEND),\n            232 => Some(OsCode::KEY_REPLY),\n            233 => Some(OsCode::KEY_FORWARDMAIL),\n            234 => Some(OsCode::KEY_SAVE),\n            235 => Some(OsCode::KEY_DOCUMENTS),\n            236 => Some(OsCode::KEY_BATTERY),\n            237 => Some(OsCode::KEY_BLUETOOTH),\n            238 => Some(OsCode::KEY_WLAN),\n            239 => Some(OsCode::KEY_UWB),\n            240 => Some(OsCode::KEY_UNKNOWN),\n            241 => Some(OsCode::KEY_VIDEO_NEXT),\n            242 => Some(OsCode::KEY_VIDEO_PREV),\n            243 => Some(OsCode::KEY_BRIGHTNESS_CYCLE),\n            244 => Some(OsCode::KEY_BRIGHTNESS_AUTO),\n            245 => Some(OsCode::KEY_DISPLAY_OFF),\n            246 => Some(OsCode::KEY_WWAN),\n            247 => Some(OsCode::KEY_RFKILL),\n            248 => Some(OsCode::KEY_MICMUTE),\n            249 => Some(OsCode::KEY_249),\n            250 => Some(OsCode::KEY_250),\n            251 => Some(OsCode::KEY_251),\n            252 => Some(OsCode::KEY_252),\n            253 => Some(OsCode::KEY_253),\n            254 => Some(OsCode::KEY_254),\n            255 => Some(OsCode::KEY_255),\n            256 => Some(OsCode::BTN_0),\n            257 => Some(OsCode::BTN_1),\n            258 => Some(OsCode::BTN_2),\n            259 => Some(OsCode::BTN_3),\n            260 => Some(OsCode::BTN_4),\n            261 => Some(OsCode::BTN_5),\n            262 => Some(OsCode::BTN_6),\n            263 => Some(OsCode::BTN_7),\n            264 => Some(OsCode::BTN_8),\n            265 => Some(OsCode::BTN_9),\n            266 => Some(OsCode::KEY_266),\n            267 => Some(OsCode::KEY_267),\n            268 => Some(OsCode::KEY_268),\n            269 => Some(OsCode::KEY_269),\n            270 => Some(OsCode::KEY_270),\n            271 => Some(OsCode::KEY_271),\n            272 => Some(OsCode::BTN_LEFT),\n            273 => Some(OsCode::BTN_RIGHT),\n            274 => Some(OsCode::BTN_MIDDLE),\n            275 => Some(OsCode::BTN_SIDE),\n            276 => Some(OsCode::BTN_EXTRA),\n            277 => Some(OsCode::BTN_FORWARD),\n            278 => Some(OsCode::BTN_BACK),\n            279 => Some(OsCode::BTN_TASK),\n            280 => Some(OsCode::KEY_280),\n            281 => Some(OsCode::KEY_281),\n            282 => Some(OsCode::KEY_282),\n            283 => Some(OsCode::KEY_283),\n            284 => Some(OsCode::KEY_284),\n            285 => Some(OsCode::KEY_285),\n            286 => Some(OsCode::KEY_286),\n            287 => Some(OsCode::KEY_287),\n            288 => Some(OsCode::BTN_TRIGGER),\n            289 => Some(OsCode::BTN_THUMB),\n            290 => Some(OsCode::BTN_THUMB2),\n            291 => Some(OsCode::BTN_TOP),\n            292 => Some(OsCode::BTN_TOP2),\n            293 => Some(OsCode::BTN_PINKIE),\n            294 => Some(OsCode::BTN_BASE),\n            295 => Some(OsCode::BTN_BASE2),\n            296 => Some(OsCode::BTN_BASE3),\n            297 => Some(OsCode::BTN_BASE4),\n            298 => Some(OsCode::BTN_BASE5),\n            299 => Some(OsCode::BTN_BASE6),\n            300 => Some(OsCode::KEY_300),\n            301 => Some(OsCode::KEY_301),\n            302 => Some(OsCode::KEY_302),\n            303 => Some(OsCode::BTN_DEAD),\n            304 => Some(OsCode::BTN_SOUTH),\n            305 => Some(OsCode::BTN_EAST),\n            306 => Some(OsCode::BTN_C),\n            307 => Some(OsCode::BTN_NORTH),\n            308 => Some(OsCode::BTN_WEST),\n            309 => Some(OsCode::BTN_Z),\n            310 => Some(OsCode::BTN_TL),\n            311 => Some(OsCode::BTN_TR),\n            312 => Some(OsCode::BTN_TL2),\n            313 => Some(OsCode::BTN_TR2),\n            314 => Some(OsCode::BTN_SELECT),\n            315 => Some(OsCode::BTN_START),\n            316 => Some(OsCode::BTN_MODE),\n            317 => Some(OsCode::BTN_THUMBL),\n            318 => Some(OsCode::BTN_THUMBR),\n            319 => Some(OsCode::KEY_319),\n            320 => Some(OsCode::BTN_TOOL_PEN),\n            321 => Some(OsCode::BTN_TOOL_RUBBER),\n            322 => Some(OsCode::BTN_TOOL_BRUSH),\n            323 => Some(OsCode::BTN_TOOL_PENCIL),\n            324 => Some(OsCode::BTN_TOOL_AIRBRUSH),\n            325 => Some(OsCode::BTN_TOOL_FINGER),\n            326 => Some(OsCode::BTN_TOOL_MOUSE),\n            327 => Some(OsCode::BTN_TOOL_LENS),\n            328 => Some(OsCode::BTN_TOOL_QUINTTAP),\n            329 => Some(OsCode::BTN_STYLUS3),\n            330 => Some(OsCode::BTN_TOUCH),\n            331 => Some(OsCode::BTN_STYLUS),\n            332 => Some(OsCode::BTN_STYLUS2),\n            333 => Some(OsCode::BTN_TOOL_DOUBLETAP),\n            334 => Some(OsCode::BTN_TOOL_TRIPLETAP),\n            335 => Some(OsCode::BTN_TOOL_QUADTAP),\n            336 => Some(OsCode::BTN_GEAR_DOWN),\n            337 => Some(OsCode::BTN_GEAR_UP),\n            338 => Some(OsCode::KEY_338),\n            339 => Some(OsCode::KEY_339),\n            340 => Some(OsCode::KEY_340),\n            341 => Some(OsCode::KEY_341),\n            342 => Some(OsCode::KEY_342),\n            343 => Some(OsCode::KEY_343),\n            344 => Some(OsCode::KEY_344),\n            345 => Some(OsCode::KEY_345),\n            346 => Some(OsCode::KEY_346),\n            347 => Some(OsCode::KEY_347),\n            348 => Some(OsCode::KEY_348),\n            349 => Some(OsCode::KEY_349),\n            350 => Some(OsCode::KEY_350),\n            351 => Some(OsCode::KEY_351),\n            352 => Some(OsCode::KEY_OK),\n            353 => Some(OsCode::KEY_SELECT),\n            354 => Some(OsCode::KEY_GOTO),\n            355 => Some(OsCode::KEY_CLEAR),\n            356 => Some(OsCode::KEY_POWER2),\n            357 => Some(OsCode::KEY_OPTION),\n            358 => Some(OsCode::KEY_INFO),\n            359 => Some(OsCode::KEY_TIME),\n            360 => Some(OsCode::KEY_VENDOR),\n            361 => Some(OsCode::KEY_ARCHIVE),\n            362 => Some(OsCode::KEY_PROGRAM),\n            363 => Some(OsCode::KEY_CHANNEL),\n            364 => Some(OsCode::KEY_FAVORITES),\n            365 => Some(OsCode::KEY_EPG),\n            366 => Some(OsCode::KEY_PVR),\n            367 => Some(OsCode::KEY_MHP),\n            368 => Some(OsCode::KEY_LANGUAGE),\n            369 => Some(OsCode::KEY_TITLE),\n            370 => Some(OsCode::KEY_SUBTITLE),\n            371 => Some(OsCode::KEY_ANGLE),\n            372 => Some(OsCode::KEY_FULL_SCREEN),\n            373 => Some(OsCode::KEY_MODE),\n            374 => Some(OsCode::KEY_KEYBOARD),\n            375 => Some(OsCode::KEY_ASPECT_RATIO),\n            376 => Some(OsCode::KEY_PC),\n            377 => Some(OsCode::KEY_TV),\n            378 => Some(OsCode::KEY_TV2),\n            379 => Some(OsCode::KEY_VCR),\n            380 => Some(OsCode::KEY_VCR2),\n            381 => Some(OsCode::KEY_SAT),\n            382 => Some(OsCode::KEY_SAT2),\n            383 => Some(OsCode::KEY_CD),\n            384 => Some(OsCode::KEY_TAPE),\n            385 => Some(OsCode::KEY_RADIO),\n            386 => Some(OsCode::KEY_TUNER),\n            387 => Some(OsCode::KEY_PLAYER),\n            388 => Some(OsCode::KEY_TEXT),\n            389 => Some(OsCode::KEY_DVD),\n            390 => Some(OsCode::KEY_AUX),\n            391 => Some(OsCode::KEY_MP3),\n            392 => Some(OsCode::KEY_AUDIO),\n            393 => Some(OsCode::KEY_VIDEO),\n            394 => Some(OsCode::KEY_DIRECTORY),\n            395 => Some(OsCode::KEY_LIST),\n            396 => Some(OsCode::KEY_MEMO),\n            397 => Some(OsCode::KEY_CALENDAR),\n            398 => Some(OsCode::KEY_RED),\n            399 => Some(OsCode::KEY_GREEN),\n            400 => Some(OsCode::KEY_YELLOW),\n            401 => Some(OsCode::KEY_BLUE),\n            402 => Some(OsCode::KEY_CHANNELUP),\n            403 => Some(OsCode::KEY_CHANNELDOWN),\n            404 => Some(OsCode::KEY_FIRST),\n            405 => Some(OsCode::KEY_LAST),\n            406 => Some(OsCode::KEY_AB),\n            407 => Some(OsCode::KEY_NEXT),\n            408 => Some(OsCode::KEY_RESTART),\n            409 => Some(OsCode::KEY_SLOW),\n            410 => Some(OsCode::KEY_SHUFFLE),\n            411 => Some(OsCode::KEY_BREAK),\n            412 => Some(OsCode::KEY_PREVIOUS),\n            413 => Some(OsCode::KEY_DIGITS),\n            414 => Some(OsCode::KEY_TEEN),\n            415 => Some(OsCode::KEY_TWEN),\n            416 => Some(OsCode::KEY_VIDEOPHONE),\n            417 => Some(OsCode::KEY_GAMES),\n            418 => Some(OsCode::KEY_ZOOMIN),\n            419 => Some(OsCode::KEY_ZOOMOUT),\n            420 => Some(OsCode::KEY_ZOOMRESET),\n            421 => Some(OsCode::KEY_WORDPROCESSOR),\n            422 => Some(OsCode::KEY_EDITOR),\n            423 => Some(OsCode::KEY_SPREADSHEET),\n            424 => Some(OsCode::KEY_GRAPHICSEDITOR),\n            425 => Some(OsCode::KEY_PRESENTATION),\n            426 => Some(OsCode::KEY_DATABASE),\n            427 => Some(OsCode::KEY_NEWS),\n            428 => Some(OsCode::KEY_VOICEMAIL),\n            429 => Some(OsCode::KEY_ADDRESSBOOK),\n            430 => Some(OsCode::KEY_MESSENGER),\n            431 => Some(OsCode::KEY_DISPLAYTOGGLE),\n            432 => Some(OsCode::KEY_SPELLCHECK),\n            433 => Some(OsCode::KEY_LOGOFF),\n            434 => Some(OsCode::KEY_DOLLAR),\n            435 => Some(OsCode::KEY_EURO),\n            436 => Some(OsCode::KEY_FRAMEBACK),\n            437 => Some(OsCode::KEY_FRAMEFORWARD),\n            438 => Some(OsCode::KEY_CONTEXT_MENU),\n            439 => Some(OsCode::KEY_MEDIA_REPEAT),\n            440 => Some(OsCode::KEY_10CHANNELSUP),\n            441 => Some(OsCode::KEY_10CHANNELSDOWN),\n            442 => Some(OsCode::KEY_IMAGES),\n            443 => Some(OsCode::KEY_443),\n            444 => Some(OsCode::KEY_444),\n            445 => Some(OsCode::KEY_445),\n            446 => Some(OsCode::KEY_446),\n            447 => Some(OsCode::KEY_447),\n            448 => Some(OsCode::KEY_DEL_EOL),\n            449 => Some(OsCode::KEY_DEL_EOS),\n            450 => Some(OsCode::KEY_INS_LINE),\n            451 => Some(OsCode::KEY_DEL_LINE),\n            452 => Some(OsCode::KEY_452),\n            453 => Some(OsCode::KEY_453),\n            454 => Some(OsCode::KEY_454),\n            455 => Some(OsCode::KEY_455),\n            456 => Some(OsCode::KEY_456),\n            457 => Some(OsCode::KEY_457),\n            458 => Some(OsCode::KEY_458),\n            459 => Some(OsCode::KEY_459),\n            460 => Some(OsCode::KEY_460),\n            461 => Some(OsCode::KEY_461),\n            462 => Some(OsCode::KEY_462),\n            463 => Some(OsCode::KEY_463),\n            464 => Some(OsCode::KEY_FN),\n            465 => Some(OsCode::KEY_FN_ESC),\n            466 => Some(OsCode::KEY_FN_F1),\n            467 => Some(OsCode::KEY_FN_F2),\n            468 => Some(OsCode::KEY_FN_F3),\n            469 => Some(OsCode::KEY_FN_F4),\n            470 => Some(OsCode::KEY_FN_F5),\n            471 => Some(OsCode::KEY_FN_F6),\n            472 => Some(OsCode::KEY_FN_F7),\n            473 => Some(OsCode::KEY_FN_F8),\n            474 => Some(OsCode::KEY_FN_F9),\n            475 => Some(OsCode::KEY_FN_F10),\n            476 => Some(OsCode::KEY_FN_F11),\n            477 => Some(OsCode::KEY_FN_F12),\n            478 => Some(OsCode::KEY_FN_1),\n            479 => Some(OsCode::KEY_FN_2),\n            480 => Some(OsCode::KEY_FN_D),\n            481 => Some(OsCode::KEY_FN_E),\n            482 => Some(OsCode::KEY_FN_F),\n            483 => Some(OsCode::KEY_FN_S),\n            484 => Some(OsCode::KEY_FN_B),\n            485 => Some(OsCode::KEY_485),\n            486 => Some(OsCode::KEY_486),\n            487 => Some(OsCode::KEY_487),\n            488 => Some(OsCode::KEY_488),\n            489 => Some(OsCode::KEY_489),\n            490 => Some(OsCode::KEY_490),\n            491 => Some(OsCode::KEY_491),\n            492 => Some(OsCode::KEY_492),\n            493 => Some(OsCode::KEY_493),\n            494 => Some(OsCode::KEY_494),\n            495 => Some(OsCode::KEY_495),\n            496 => Some(OsCode::KEY_496),\n            497 => Some(OsCode::KEY_BRL_DOT1),\n            498 => Some(OsCode::KEY_BRL_DOT2),\n            499 => Some(OsCode::KEY_BRL_DOT3),\n            500 => Some(OsCode::KEY_BRL_DOT4),\n            501 => Some(OsCode::KEY_BRL_DOT5),\n            502 => Some(OsCode::KEY_BRL_DOT6),\n            503 => Some(OsCode::KEY_BRL_DOT7),\n            504 => Some(OsCode::KEY_BRL_DOT8),\n            505 => Some(OsCode::KEY_BRL_DOT9),\n            506 => Some(OsCode::KEY_BRL_DOT10),\n            507 => Some(OsCode::KEY_507),\n            508 => Some(OsCode::KEY_508),\n            509 => Some(OsCode::KEY_509),\n            510 => Some(OsCode::KEY_510),\n            511 => Some(OsCode::KEY_511),\n            512 => Some(OsCode::KEY_NUMERIC_0),\n            513 => Some(OsCode::KEY_NUMERIC_1),\n            514 => Some(OsCode::KEY_NUMERIC_2),\n            515 => Some(OsCode::KEY_NUMERIC_3),\n            516 => Some(OsCode::KEY_NUMERIC_4),\n            517 => Some(OsCode::KEY_NUMERIC_5),\n            518 => Some(OsCode::KEY_NUMERIC_6),\n            519 => Some(OsCode::KEY_NUMERIC_7),\n            520 => Some(OsCode::KEY_NUMERIC_8),\n            521 => Some(OsCode::KEY_NUMERIC_9),\n            522 => Some(OsCode::KEY_NUMERIC_STAR),\n            523 => Some(OsCode::KEY_NUMERIC_POUND),\n            524 => Some(OsCode::KEY_NUMERIC_A),\n            525 => Some(OsCode::KEY_NUMERIC_B),\n            526 => Some(OsCode::KEY_NUMERIC_C),\n            527 => Some(OsCode::KEY_NUMERIC_D),\n            528 => Some(OsCode::KEY_CAMERA_FOCUS),\n            529 => Some(OsCode::KEY_WPS_BUTTON),\n            530 => Some(OsCode::KEY_TOUCHPAD_TOGGLE),\n            531 => Some(OsCode::KEY_TOUCHPAD_ON),\n            532 => Some(OsCode::KEY_TOUCHPAD_OFF),\n            533 => Some(OsCode::KEY_CAMERA_ZOOMIN),\n            534 => Some(OsCode::KEY_CAMERA_ZOOMOUT),\n            535 => Some(OsCode::KEY_CAMERA_UP),\n            536 => Some(OsCode::KEY_CAMERA_DOWN),\n            537 => Some(OsCode::KEY_CAMERA_LEFT),\n            538 => Some(OsCode::KEY_CAMERA_RIGHT),\n            539 => Some(OsCode::KEY_ATTENDANT_ON),\n            540 => Some(OsCode::KEY_ATTENDANT_OFF),\n            541 => Some(OsCode::KEY_ATTENDANT_TOGGLE),\n            542 => Some(OsCode::KEY_LIGHTS_TOGGLE),\n            543 => Some(OsCode::KEY_543),\n            544 => Some(OsCode::BTN_DPAD_UP),\n            545 => Some(OsCode::BTN_DPAD_DOWN),\n            546 => Some(OsCode::BTN_DPAD_LEFT),\n            547 => Some(OsCode::BTN_DPAD_RIGHT),\n            548 => Some(OsCode::KEY_548),\n            549 => Some(OsCode::KEY_549),\n            550 => Some(OsCode::KEY_550),\n            551 => Some(OsCode::KEY_551),\n            552 => Some(OsCode::KEY_552),\n            553 => Some(OsCode::KEY_553),\n            554 => Some(OsCode::KEY_554),\n            555 => Some(OsCode::KEY_555),\n            556 => Some(OsCode::KEY_556),\n            557 => Some(OsCode::KEY_557),\n            558 => Some(OsCode::KEY_558),\n            559 => Some(OsCode::KEY_559),\n            560 => Some(OsCode::KEY_ALS_TOGGLE),\n            561 => Some(OsCode::KEY_ROTATE_LOCK_TOGGLE),\n            562 => Some(OsCode::KEY_562),\n            563 => Some(OsCode::KEY_563),\n            564 => Some(OsCode::KEY_564),\n            565 => Some(OsCode::KEY_565),\n            566 => Some(OsCode::KEY_566),\n            567 => Some(OsCode::KEY_567),\n            568 => Some(OsCode::KEY_568),\n            569 => Some(OsCode::KEY_569),\n            570 => Some(OsCode::KEY_570),\n            571 => Some(OsCode::KEY_571),\n            572 => Some(OsCode::KEY_572),\n            573 => Some(OsCode::KEY_573),\n            574 => Some(OsCode::KEY_574),\n            575 => Some(OsCode::KEY_575),\n            576 => Some(OsCode::KEY_BUTTONCONFIG),\n            577 => Some(OsCode::KEY_TASKMANAGER),\n            578 => Some(OsCode::KEY_JOURNAL),\n            579 => Some(OsCode::KEY_CONTROLPANEL),\n            580 => Some(OsCode::KEY_APPSELECT),\n            581 => Some(OsCode::KEY_SCREENSAVER),\n            582 => Some(OsCode::KEY_VOICECOMMAND),\n            583 => Some(OsCode::KEY_ASSISTANT),\n            584 => Some(OsCode::KEY_KBD_LAYOUT_NEXT),\n            585 => Some(OsCode::KEY_585),\n            586 => Some(OsCode::KEY_586),\n            587 => Some(OsCode::KEY_587),\n            588 => Some(OsCode::KEY_588),\n            589 => Some(OsCode::KEY_589),\n            590 => Some(OsCode::KEY_590),\n            591 => Some(OsCode::KEY_591),\n            592 => Some(OsCode::KEY_BRIGHTNESS_MIN),\n            593 => Some(OsCode::KEY_BRIGHTNESS_MAX),\n            594 => Some(OsCode::KEY_594),\n            595 => Some(OsCode::KEY_595),\n            596 => Some(OsCode::KEY_596),\n            597 => Some(OsCode::KEY_597),\n            598 => Some(OsCode::KEY_598),\n            599 => Some(OsCode::KEY_599),\n            600 => Some(OsCode::KEY_600),\n            601 => Some(OsCode::KEY_601),\n            602 => Some(OsCode::KEY_602),\n            603 => Some(OsCode::KEY_603),\n            604 => Some(OsCode::KEY_604),\n            605 => Some(OsCode::KEY_605),\n            606 => Some(OsCode::KEY_606),\n            607 => Some(OsCode::KEY_607),\n            608 => Some(OsCode::KEY_KBDINPUTASSIST_PREV),\n            609 => Some(OsCode::KEY_KBDINPUTASSIST_NEXT),\n            610 => Some(OsCode::KEY_KBDINPUTASSIST_PREVGROUP),\n            611 => Some(OsCode::KEY_KBDINPUTASSIST_NEXTGROUP),\n            612 => Some(OsCode::KEY_KBDINPUTASSIST_ACCEPT),\n            613 => Some(OsCode::KEY_KBDINPUTASSIST_CANCEL),\n            614 => Some(OsCode::KEY_RIGHT_UP),\n            615 => Some(OsCode::KEY_RIGHT_DOWN),\n            616 => Some(OsCode::KEY_LEFT_UP),\n            617 => Some(OsCode::KEY_LEFT_DOWN),\n            618 => Some(OsCode::KEY_ROOT_MENU),\n            619 => Some(OsCode::KEY_MEDIA_TOP_MENU),\n            620 => Some(OsCode::KEY_NUMERIC_11),\n            621 => Some(OsCode::KEY_NUMERIC_12),\n            622 => Some(OsCode::KEY_AUDIO_DESC),\n            623 => Some(OsCode::KEY_3D_MODE),\n            624 => Some(OsCode::KEY_NEXT_FAVORITE),\n            625 => Some(OsCode::KEY_STOP_RECORD),\n            626 => Some(OsCode::KEY_PAUSE_RECORD),\n            627 => Some(OsCode::KEY_VOD),\n            628 => Some(OsCode::KEY_UNMUTE),\n            629 => Some(OsCode::KEY_FASTREVERSE),\n            630 => Some(OsCode::KEY_SLOWREVERSE),\n            631 => Some(OsCode::KEY_DATA),\n            632 => Some(OsCode::KEY_ONSCREEN_KEYBOARD),\n            633 => Some(OsCode::KEY_633),\n            634 => Some(OsCode::KEY_634),\n            635 => Some(OsCode::KEY_635),\n            636 => Some(OsCode::KEY_636),\n            637 => Some(OsCode::KEY_637),\n            638 => Some(OsCode::KEY_638),\n            639 => Some(OsCode::KEY_639),\n            640 => Some(OsCode::KEY_640),\n            641 => Some(OsCode::KEY_641),\n            642 => Some(OsCode::KEY_642),\n            643 => Some(OsCode::KEY_643),\n            644 => Some(OsCode::KEY_644),\n            645 => Some(OsCode::KEY_645),\n            646 => Some(OsCode::KEY_646),\n            647 => Some(OsCode::KEY_647),\n            648 => Some(OsCode::KEY_648),\n            649 => Some(OsCode::KEY_649),\n            650 => Some(OsCode::KEY_650),\n            651 => Some(OsCode::KEY_651),\n            652 => Some(OsCode::KEY_652),\n            653 => Some(OsCode::KEY_653),\n            654 => Some(OsCode::KEY_654),\n            655 => Some(OsCode::KEY_655),\n            656 => Some(OsCode::KEY_656),\n            657 => Some(OsCode::KEY_657),\n            658 => Some(OsCode::KEY_658),\n            659 => Some(OsCode::KEY_659),\n            660 => Some(OsCode::KEY_660),\n            661 => Some(OsCode::KEY_661),\n            662 => Some(OsCode::KEY_662),\n            663 => Some(OsCode::KEY_663),\n            664 => Some(OsCode::KEY_664),\n            665 => Some(OsCode::KEY_665),\n            666 => Some(OsCode::KEY_666),\n            667 => Some(OsCode::KEY_667),\n            668 => Some(OsCode::KEY_668),\n            669 => Some(OsCode::KEY_669),\n            670 => Some(OsCode::KEY_670),\n            671 => Some(OsCode::KEY_671),\n            672 => Some(OsCode::KEY_672),\n            673 => Some(OsCode::KEY_673),\n            674 => Some(OsCode::KEY_674),\n            675 => Some(OsCode::KEY_675),\n            676 => Some(OsCode::KEY_676),\n            677 => Some(OsCode::KEY_677),\n            678 => Some(OsCode::KEY_678),\n            679 => Some(OsCode::KEY_679),\n            680 => Some(OsCode::KEY_680),\n            681 => Some(OsCode::KEY_681),\n            682 => Some(OsCode::KEY_682),\n            683 => Some(OsCode::KEY_683),\n            684 => Some(OsCode::KEY_684),\n            685 => Some(OsCode::KEY_685),\n            686 => Some(OsCode::KEY_686),\n            687 => Some(OsCode::KEY_687),\n            688 => Some(OsCode::KEY_688),\n            689 => Some(OsCode::KEY_689),\n            690 => Some(OsCode::KEY_690),\n            691 => Some(OsCode::KEY_691),\n            692 => Some(OsCode::KEY_692),\n            693 => Some(OsCode::KEY_693),\n            694 => Some(OsCode::KEY_694),\n            695 => Some(OsCode::KEY_695),\n            696 => Some(OsCode::KEY_696),\n            697 => Some(OsCode::KEY_697),\n            698 => Some(OsCode::KEY_698),\n            699 => Some(OsCode::KEY_699),\n            700 => Some(OsCode::KEY_700),\n            701 => Some(OsCode::KEY_701),\n            702 => Some(OsCode::KEY_702),\n            703 => Some(OsCode::KEY_703),\n            704 => Some(OsCode::BTN_TRIGGER_HAPPY1),\n            705 => Some(OsCode::BTN_TRIGGER_HAPPY2),\n            706 => Some(OsCode::BTN_TRIGGER_HAPPY3),\n            707 => Some(OsCode::BTN_TRIGGER_HAPPY4),\n            708 => Some(OsCode::BTN_TRIGGER_HAPPY5),\n            709 => Some(OsCode::BTN_TRIGGER_HAPPY6),\n            710 => Some(OsCode::BTN_TRIGGER_HAPPY7),\n            711 => Some(OsCode::BTN_TRIGGER_HAPPY8),\n            712 => Some(OsCode::BTN_TRIGGER_HAPPY9),\n            713 => Some(OsCode::BTN_TRIGGER_HAPPY10),\n            714 => Some(OsCode::BTN_TRIGGER_HAPPY11),\n            715 => Some(OsCode::BTN_TRIGGER_HAPPY12),\n            716 => Some(OsCode::BTN_TRIGGER_HAPPY13),\n            717 => Some(OsCode::BTN_TRIGGER_HAPPY14),\n            718 => Some(OsCode::BTN_TRIGGER_HAPPY15),\n            719 => Some(OsCode::BTN_TRIGGER_HAPPY16),\n            720 => Some(OsCode::BTN_TRIGGER_HAPPY17),\n            721 => Some(OsCode::BTN_TRIGGER_HAPPY18),\n            722 => Some(OsCode::BTN_TRIGGER_HAPPY19),\n            723 => Some(OsCode::BTN_TRIGGER_HAPPY20),\n            724 => Some(OsCode::BTN_TRIGGER_HAPPY21),\n            725 => Some(OsCode::BTN_TRIGGER_HAPPY22),\n            726 => Some(OsCode::BTN_TRIGGER_HAPPY23),\n            727 => Some(OsCode::BTN_TRIGGER_HAPPY24),\n            728 => Some(OsCode::BTN_TRIGGER_HAPPY25),\n            729 => Some(OsCode::BTN_TRIGGER_HAPPY26),\n            730 => Some(OsCode::BTN_TRIGGER_HAPPY27),\n            731 => Some(OsCode::BTN_TRIGGER_HAPPY28),\n            732 => Some(OsCode::BTN_TRIGGER_HAPPY29),\n            733 => Some(OsCode::BTN_TRIGGER_HAPPY30),\n            734 => Some(OsCode::BTN_TRIGGER_HAPPY31),\n            735 => Some(OsCode::BTN_TRIGGER_HAPPY32),\n            736 => Some(OsCode::BTN_TRIGGER_HAPPY33),\n            737 => Some(OsCode::BTN_TRIGGER_HAPPY34),\n            738 => Some(OsCode::BTN_TRIGGER_HAPPY35),\n            739 => Some(OsCode::BTN_TRIGGER_HAPPY36),\n            740 => Some(OsCode::BTN_TRIGGER_HAPPY37),\n            741 => Some(OsCode::BTN_TRIGGER_HAPPY38),\n            742 => Some(OsCode::BTN_TRIGGER_HAPPY39),\n            743 => Some(OsCode::BTN_TRIGGER_HAPPY40),\n            744 => Some(OsCode::BTN_MAX),\n            745 => Some(OsCode::MouseWheelUp),\n            746 => Some(OsCode::MouseWheelDown),\n            747 => Some(OsCode::MouseWheelLeft),\n            748 => Some(OsCode::MouseWheelRight),\n            767 => Some(OsCode::KEY_MAX),\n            _ => None,\n        }\n    }\n}\n\nuse crate::custom_action::Btn;\nimpl From<Btn> for OsCode {\n    fn from(btn: Btn) -> Self {\n        match btn {\n            Btn::Left => OsCode::BTN_LEFT,\n            Btn::Right => OsCode::BTN_RIGHT,\n            Btn::Mid => OsCode::BTN_MIDDLE,\n            Btn::Forward => OsCode::BTN_EXTRA,\n            Btn::Backward => OsCode::BTN_SIDE,\n        }\n    }\n}\n"
  },
  {
    "path": "parser/src/keys/macos.rs",
    "content": "use super::OsCode;\n\n// because the parser can't handle oscode u16 values > 767\n// and macos has fucked up key coding (page and code)\npub struct PageCode {\n    pub page: u32,\n    pub code: u32,\n}\n\nimpl TryFrom<OsCode> for PageCode {\n    type Error = &'static str;\n    fn try_from(item: OsCode) -> Result<Self, Self::Error> {\n        match item {\n            OsCode::KEY_RESERVED => Ok(PageCode {\n                page: 0xFF,\n                code: 0xFF,\n            }),\n            OsCode::KEY_A => Ok(PageCode {\n                page: 0x07,\n                code: 0x04,\n            }),\n            OsCode::KEY_B => Ok(PageCode {\n                page: 0x07,\n                code: 0x05,\n            }),\n            OsCode::KEY_C => Ok(PageCode {\n                page: 0x07,\n                code: 0x06,\n            }),\n            OsCode::KEY_D => Ok(PageCode {\n                page: 0x07,\n                code: 0x07,\n            }),\n            OsCode::KEY_E => Ok(PageCode {\n                page: 0x07,\n                code: 0x08,\n            }),\n            OsCode::KEY_F => Ok(PageCode {\n                page: 0x07,\n                code: 0x09,\n            }),\n            OsCode::KEY_G => Ok(PageCode {\n                page: 0x07,\n                code: 0x0A,\n            }),\n            OsCode::KEY_H => Ok(PageCode {\n                page: 0x07,\n                code: 0x0B,\n            }),\n            OsCode::KEY_I => Ok(PageCode {\n                page: 0x07,\n                code: 0x0C,\n            }),\n            OsCode::KEY_J => Ok(PageCode {\n                page: 0x07,\n                code: 0x0D,\n            }),\n            OsCode::KEY_K => Ok(PageCode {\n                page: 0x07,\n                code: 0x0E,\n            }),\n            OsCode::KEY_L => Ok(PageCode {\n                page: 0x07,\n                code: 0x0F,\n            }),\n            OsCode::KEY_M => Ok(PageCode {\n                page: 0x07,\n                code: 0x10,\n            }),\n            OsCode::KEY_N => Ok(PageCode {\n                page: 0x07,\n                code: 0x11,\n            }),\n            OsCode::KEY_O => Ok(PageCode {\n                page: 0x07,\n                code: 0x12,\n            }),\n            OsCode::KEY_P => Ok(PageCode {\n                page: 0x07,\n                code: 0x13,\n            }),\n            OsCode::KEY_Q => Ok(PageCode {\n                page: 0x07,\n                code: 0x14,\n            }),\n            OsCode::KEY_R => Ok(PageCode {\n                page: 0x07,\n                code: 0x15,\n            }),\n            OsCode::KEY_S => Ok(PageCode {\n                page: 0x07,\n                code: 0x16,\n            }),\n            OsCode::KEY_T => Ok(PageCode {\n                page: 0x07,\n                code: 0x17,\n            }),\n            OsCode::KEY_U => Ok(PageCode {\n                page: 0x07,\n                code: 0x18,\n            }),\n            OsCode::KEY_V => Ok(PageCode {\n                page: 0x07,\n                code: 0x19,\n            }),\n            OsCode::KEY_W => Ok(PageCode {\n                page: 0x07,\n                code: 0x1A,\n            }),\n            OsCode::KEY_X => Ok(PageCode {\n                page: 0x07,\n                code: 0x1B,\n            }),\n            OsCode::KEY_Y => Ok(PageCode {\n                page: 0x07,\n                code: 0x1C,\n            }),\n            OsCode::KEY_Z => Ok(PageCode {\n                page: 0x07,\n                code: 0x1D,\n            }),\n            OsCode::KEY_1 => Ok(PageCode {\n                page: 0x07,\n                code: 0x1E,\n            }),\n            OsCode::KEY_2 => Ok(PageCode {\n                page: 0x07,\n                code: 0x1F,\n            }),\n            OsCode::KEY_3 => Ok(PageCode {\n                page: 0x07,\n                code: 0x20,\n            }),\n            OsCode::KEY_4 => Ok(PageCode {\n                page: 0x07,\n                code: 0x21,\n            }),\n            OsCode::KEY_5 => Ok(PageCode {\n                page: 0x07,\n                code: 0x22,\n            }),\n            OsCode::KEY_6 => Ok(PageCode {\n                page: 0x07,\n                code: 0x23,\n            }),\n            OsCode::KEY_7 => Ok(PageCode {\n                page: 0x07,\n                code: 0x24,\n            }),\n            OsCode::KEY_8 => Ok(PageCode {\n                page: 0x07,\n                code: 0x25,\n            }),\n            OsCode::KEY_9 => Ok(PageCode {\n                page: 0x07,\n                code: 0x26,\n            }),\n            OsCode::KEY_0 => Ok(PageCode {\n                page: 0x07,\n                code: 0x27,\n            }),\n            OsCode::KEY_ENTER => Ok(PageCode {\n                page: 0x07,\n                code: 0x28,\n            }),\n            OsCode::KEY_ESC => Ok(PageCode {\n                page: 0x07,\n                code: 0x29,\n            }),\n            OsCode::KEY_BACKSPACE => Ok(PageCode {\n                page: 0x07,\n                code: 0x2A,\n            }),\n            OsCode::KEY_TAB => Ok(PageCode {\n                page: 0x07,\n                code: 0x2B,\n            }),\n            OsCode::KEY_SPACE => Ok(PageCode {\n                page: 0x07,\n                code: 0x2C,\n            }),\n            OsCode::KEY_MINUS => Ok(PageCode {\n                page: 0x07,\n                code: 0x2D,\n            }),\n            OsCode::KEY_EQUAL => Ok(PageCode {\n                page: 0x07,\n                code: 0x2E,\n            }),\n            OsCode::KEY_LEFTBRACE => Ok(PageCode {\n                page: 0x07,\n                code: 0x2F,\n            }),\n            OsCode::KEY_RIGHTBRACE => Ok(PageCode {\n                page: 0x07,\n                code: 0x30,\n            }),\n            OsCode::KEY_BACKSLASH => Ok(PageCode {\n                page: 0x07,\n                code: 0x31,\n            }),\n            // KeyboardNonUSPound                => 0x0732, todo\n            OsCode::KEY_SEMICOLON => Ok(PageCode {\n                page: 0x07,\n                code: 0x33,\n            }),\n            OsCode::KEY_APOSTROPHE => Ok(PageCode {\n                page: 0x07,\n                code: 0x34,\n            }),\n            OsCode::KEY_GRAVE => Ok(PageCode {\n                page: 0x07,\n                code: 0x35,\n            }),\n            OsCode::KEY_COMMA => Ok(PageCode {\n                page: 0x07,\n                code: 0x36,\n            }),\n            OsCode::KEY_DOT => Ok(PageCode {\n                page: 0x07,\n                code: 0x37,\n            }),\n            OsCode::KEY_SLASH => Ok(PageCode {\n                page: 0x07,\n                code: 0x38,\n            }),\n            OsCode::KEY_CAPSLOCK => Ok(PageCode {\n                page: 0x07,\n                code: 0x39,\n            }),\n            OsCode::KEY_F1 => Ok(PageCode {\n                page: 0x07,\n                code: 0x3A,\n            }),\n            OsCode::KEY_F2 => Ok(PageCode {\n                page: 0x07,\n                code: 0x3B,\n            }),\n            OsCode::KEY_F3 => Ok(PageCode {\n                page: 0x07,\n                code: 0x3C,\n            }),\n            OsCode::KEY_F4 => Ok(PageCode {\n                page: 0x07,\n                code: 0x3D,\n            }),\n            OsCode::KEY_F5 => Ok(PageCode {\n                page: 0x07,\n                code: 0x3E,\n            }),\n            OsCode::KEY_F6 => Ok(PageCode {\n                page: 0x07,\n                code: 0x3F,\n            }),\n            OsCode::KEY_F7 => Ok(PageCode {\n                page: 0x07,\n                code: 0x40,\n            }),\n            OsCode::KEY_F8 => Ok(PageCode {\n                page: 0x07,\n                code: 0x41,\n            }),\n            OsCode::KEY_F9 => Ok(PageCode {\n                page: 0x07,\n                code: 0x42,\n            }),\n            OsCode::KEY_F10 => Ok(PageCode {\n                page: 0x07,\n                code: 0x43,\n            }),\n            OsCode::KEY_F11 => Ok(PageCode {\n                page: 0x07,\n                code: 0x44,\n            }),\n            OsCode::KEY_F12 => Ok(PageCode {\n                page: 0x07,\n                code: 0x45,\n            }),\n            OsCode::KEY_PRINT => Ok(PageCode {\n                page: 0x07,\n                code: 0x46,\n            }),\n            OsCode::KEY_SCROLLLOCK => Ok(PageCode {\n                page: 0x07,\n                code: 0x47,\n            }),\n            OsCode::KEY_PAUSE => Ok(PageCode {\n                page: 0x07,\n                code: 0x48,\n            }),\n            OsCode::KEY_INSERT => Ok(PageCode {\n                page: 0x07,\n                code: 0x49,\n            }),\n            OsCode::KEY_HOME => Ok(PageCode {\n                page: 0x07,\n                code: 0x4A,\n            }),\n            OsCode::KEY_PAGEUP => Ok(PageCode {\n                page: 0x07,\n                code: 0x4B,\n            }),\n            OsCode::KEY_DELETE => Ok(PageCode {\n                page: 0x07,\n                code: 0x4C,\n            }),\n            OsCode::KEY_END => Ok(PageCode {\n                page: 0x07,\n                code: 0x4D,\n            }),\n            OsCode::KEY_PAGEDOWN => Ok(PageCode {\n                page: 0x07,\n                code: 0x4E,\n            }),\n            OsCode::KEY_RIGHT => Ok(PageCode {\n                page: 0x07,\n                code: 0x4F,\n            }),\n            OsCode::KEY_LEFT => Ok(PageCode {\n                page: 0x07,\n                code: 0x50,\n            }),\n            OsCode::KEY_DOWN => Ok(PageCode {\n                page: 0x07,\n                code: 0x51,\n            }),\n            OsCode::KEY_UP => Ok(PageCode {\n                page: 0x07,\n                code: 0x52,\n            }),\n            OsCode::KEY_NUMLOCK => Ok(PageCode {\n                page: 0x07,\n                code: 0x53,\n            }),\n            OsCode::KEY_KPSLASH => Ok(PageCode {\n                page: 0x07,\n                code: 0x54,\n            }),\n            OsCode::KEY_KPASTERISK => Ok(PageCode {\n                page: 0x07,\n                code: 0x55,\n            }),\n            OsCode::KEY_KPMINUS => Ok(PageCode {\n                page: 0x07,\n                code: 0x56,\n            }),\n            OsCode::KEY_KPPLUS => Ok(PageCode {\n                page: 0x07,\n                code: 0x57,\n            }),\n            OsCode::KEY_KPENTER => Ok(PageCode {\n                page: 0x07,\n                code: 0x58,\n            }),\n            OsCode::KEY_KP1 => Ok(PageCode {\n                page: 0x07,\n                code: 0x59,\n            }),\n            OsCode::KEY_KP2 => Ok(PageCode {\n                page: 0x07,\n                code: 0x5A,\n            }),\n            OsCode::KEY_KP3 => Ok(PageCode {\n                page: 0x07,\n                code: 0x5B,\n            }),\n            OsCode::KEY_KP4 => Ok(PageCode {\n                page: 0x07,\n                code: 0x5C,\n            }),\n            OsCode::KEY_KP5 => Ok(PageCode {\n                page: 0x07,\n                code: 0x5D,\n            }),\n            OsCode::KEY_KP6 => Ok(PageCode {\n                page: 0x07,\n                code: 0x5E,\n            }),\n            OsCode::KEY_KP7 => Ok(PageCode {\n                page: 0x07,\n                code: 0x5F,\n            }),\n            OsCode::KEY_KP8 => Ok(PageCode {\n                page: 0x07,\n                code: 0x60,\n            }),\n            OsCode::KEY_KP9 => Ok(PageCode {\n                page: 0x07,\n                code: 0x61,\n            }),\n            OsCode::KEY_KP0 => Ok(PageCode {\n                page: 0x07,\n                code: 0x62,\n            }),\n            OsCode::KEY_KPDOT => Ok(PageCode {\n                page: 0x07,\n                code: 0x63,\n            }),\n            OsCode::KEY_102ND => Ok(PageCode {\n                page: 0x07,\n                code: 0x64,\n            }), //KeyboardNonUSBackslash\n            OsCode::KEY_COMPOSE => Ok(PageCode {\n                page: 0x07,\n                code: 0x65,\n            }),\n            OsCode::KEY_POWER => Ok(PageCode {\n                page: 0x07,\n                code: 0x66,\n            }),\n            OsCode::KEY_KPEQUAL => Ok(PageCode {\n                page: 0x07,\n                code: 0x67,\n            }),\n            OsCode::KEY_F13 => Ok(PageCode {\n                page: 0x07,\n                code: 0x68,\n            }),\n            OsCode::KEY_F14 => Ok(PageCode {\n                page: 0x07,\n                code: 0x69,\n            }),\n            OsCode::KEY_F15 => Ok(PageCode {\n                page: 0x07,\n                code: 0x6A,\n            }),\n            OsCode::KEY_F16 => Ok(PageCode {\n                page: 0x07,\n                code: 0x6B,\n            }),\n            OsCode::KEY_F17 => Ok(PageCode {\n                page: 0x07,\n                code: 0x6C,\n            }),\n            OsCode::KEY_F18 => Ok(PageCode {\n                page: 0x07,\n                code: 0x6D,\n            }),\n            OsCode::KEY_F19 => Ok(PageCode {\n                page: 0x07,\n                code: 0x6E,\n            }),\n            OsCode::KEY_F20 => Ok(PageCode {\n                page: 0x07,\n                code: 0x6F,\n            }),\n            OsCode::KEY_F21 => Ok(PageCode {\n                page: 0x07,\n                code: 0x70,\n            }),\n            OsCode::KEY_F22 => Ok(PageCode {\n                page: 0x07,\n                code: 0x71,\n            }),\n            OsCode::KEY_F23 => Ok(PageCode {\n                page: 0x07,\n                code: 0x72,\n            }),\n            OsCode::KEY_F24 => Ok(PageCode {\n                page: 0x07,\n                code: 0x73,\n            }),\n            // KeyboardExecute                   => 0x0774,  todo\n            OsCode::KEY_HELP => Ok(PageCode {\n                page: 0x07,\n                code: 0x75,\n            }),\n            OsCode::KEY_MENU => Ok(PageCode {\n                page: 0x07,\n                code: 0x76,\n            }),\n            OsCode::KEY_SELECT => Ok(PageCode {\n                page: 0x07,\n                code: 0x77,\n            }),\n            OsCode::KEY_STOP => Ok(PageCode {\n                page: 0x07,\n                code: 0x78,\n            }),\n            OsCode::KEY_AGAIN => Ok(PageCode {\n                page: 0x07,\n                code: 0x79,\n            }),\n            OsCode::KEY_UNDO => Ok(PageCode {\n                page: 0x07,\n                code: 0x7A,\n            }),\n            OsCode::KEY_CUT => Ok(PageCode {\n                page: 0x07,\n                code: 0x7B,\n            }),\n            OsCode::KEY_COPY => Ok(PageCode {\n                page: 0x07,\n                code: 0x7C,\n            }),\n            OsCode::KEY_PASTE => Ok(PageCode {\n                page: 0x07,\n                code: 0x7D,\n            }),\n            OsCode::KEY_FIND => Ok(PageCode {\n                page: 0x07,\n                code: 0x7E,\n            }),\n            OsCode::KEY_MUTE => Ok(PageCode {\n                page: 0x0C,\n                code: 0xE2,\n            }), // 0x077f\n            OsCode::KEY_VOLUMEUP => Ok(PageCode {\n                page: 0x0C,\n                code: 0xE9,\n            }), // 0x0780\n            OsCode::KEY_VOLUMEDOWN => Ok(PageCode {\n                page: 0x0C,\n                code: 0xEA,\n            }), // 0x0781\n            OsCode::KEY_EJECTCD => Ok(PageCode {\n                page: 0x0C,\n                code: 0xB8,\n            }), // 0x0781\n            //KeyboardLockingCapsLock   => 82, todo\n            //KeyboardLockingNumLock    => 83, todo\n            //KeyboardLockingScrollLock => 84, todo\n            OsCode::KEY_KPCOMMA => Ok(PageCode {\n                page: 0x07,\n                code: 0x85,\n            }),\n            OsCode::KEY_RO => Ok(PageCode {\n                page: 0x07,\n                code: 0x87,\n            }),\n            OsCode::KEY_HANGEUL => Ok(PageCode {\n                page: 0x07,\n                code: 0x90,\n            }),\n            OsCode::KEY_HANJA => Ok(PageCode {\n                page: 0x07,\n                code: 0x91,\n            }),\n            OsCode::KEY_ALTERASE => Ok(PageCode {\n                page: 0x07,\n                code: 0x99,\n            }),\n            OsCode::KEY_CANCEL => Ok(PageCode {\n                page: 0x07,\n                code: 0x9B,\n            }),\n            OsCode::KEY_CLEAR => Ok(PageCode {\n                page: 0x07,\n                code: 0x9C,\n            }),\n            OsCode::KEY_LEFTCTRL => Ok(PageCode {\n                page: 0x07,\n                code: 0xE0,\n            }),\n            OsCode::KEY_LEFTSHIFT => Ok(PageCode {\n                page: 0x07,\n                code: 0xE1,\n            }),\n            OsCode::KEY_LEFTALT => Ok(PageCode {\n                page: 0x07,\n                code: 0xE2,\n            }),\n            OsCode::KEY_LEFTMETA => Ok(PageCode {\n                page: 0x07,\n                code: 0xE3,\n            }),\n            OsCode::KEY_RIGHTCTRL => Ok(PageCode {\n                page: 0x07,\n                code: 0xE4,\n            }),\n            OsCode::KEY_RIGHTSHIFT => Ok(PageCode {\n                page: 0x07,\n                code: 0xE5,\n            }),\n            OsCode::KEY_RIGHTALT => Ok(PageCode {\n                page: 0x07,\n                code: 0xE6,\n            }),\n            OsCode::KEY_RIGHTMETA => Ok(PageCode {\n                page: 0x07,\n                code: 0xE7,\n            }),\n            // ??\n            //OsCode::KEY_POWER           => Ok( PageCode { page: 0x0C, code: 0x30 } ),\n            OsCode::KEY_SLEEP => Ok(PageCode {\n                page: 0x0C,\n                code: 0x32,\n            }),\n            OsCode::KEY_FORWARD => Ok(PageCode {\n                page: 0x0C,\n                code: 0xB3,\n            }),\n            OsCode::KEY_REWIND => Ok(PageCode {\n                page: 0x0C,\n                code: 0xB4,\n            }),\n            OsCode::KEY_NEXTSONG => Ok(PageCode {\n                page: 0x0C,\n                code: 0xB5,\n            }),\n            OsCode::KEY_PREVIOUSSONG => Ok(PageCode {\n                page: 0x0C,\n                code: 0xB6,\n            }),\n            OsCode::KEY_PLAYPAUSE => Ok(PageCode {\n                page: 0x0C,\n                code: 0xCD,\n            }),\n            OsCode::KEY_FN => Ok(PageCode {\n                page: 0xFF,\n                code: 0x03,\n            }),\n            OsCode::KEY_BRIGHTNESSUP => Ok(PageCode {\n                page: 0x0C,\n                code: 0x6F,\n            }),\n            OsCode::KEY_BRIGHTNESSDOWN => Ok(PageCode {\n                page: 0x0C,\n                code: 0x70,\n            }),\n            // OsCode::KEY_BRIGHTNESSUP    => Ok( PageCode { page: 0x0C, code: 0x04 } ),\n            // OsCode::KEY_BRIGHTNESSDOWN  => Ok( PageCode { page: 0x0C, code: 0x05 } ),\n            // OsCode::KEY_KBDILLUMTOGGLE  => Ok( PageCode { page: 0x0C, code: 0x07 } ),\n            // OsCode::KEY_KBDILLUMUP      => Ok( PageCode { page: 0x0C, code: 0x08 } ),\n            // OsCode::KEY_KBDILLUMDOWN    => Ok( PageCode { page: 0x0C, code: 0x09 } ),\n            OsCode::KEY_KBDILLUMTOGGLE => Ok(PageCode {\n                page: 0xFF,\n                code: 0x07,\n            }),\n            OsCode::KEY_KBDILLUMUP => Ok(PageCode {\n                page: 0xFF,\n                code: 0x08,\n            }),\n            OsCode::KEY_KBDILLUMDOWN => Ok(PageCode {\n                page: 0xFF,\n                code: 0x09,\n            }),\n            // ??\n            OsCode::KEY_DASHBOARD => Ok(PageCode {\n                page: 0xFF,\n                code: 0x02,\n            }),\n            OsCode::KEY_SEARCH => Ok(PageCode {\n                page: 0xFF,\n                code: 0x01,\n            }),\n            OsCode::KEY_249 => Ok(PageCode {\n                page: 0x0C,\n                code: 0x221,\n            }),\n            OsCode::KEY_250 => Ok(PageCode {\n                page: 0x0C,\n                code: 0xCF,\n            }),\n            OsCode::KEY_251 => Ok(PageCode {\n                page: 0x01,\n                code: 0x9B,\n            }),\n            OsCode::KEY_252 => Ok(PageCode {\n                page: 0x0C,\n                code: 0x29F,\n            }),\n            OsCode::KEY_253 => Ok(PageCode {\n                page: 0x0C,\n                code: 0x2A0,\n            }),\n            // OsCode::KEY_FN_ESC          => 0x07,\n            // OsCode::KEY_FN_F1           => 0x07,\n            // OsCode::KEY_FN_F2           => 0x07,\n            // OsCode::KEY_FN_F3           => 0x07,\n            // OsCode::KEY_FN_F4           => 0x07,\n            // OsCode::KEY_FN_F5           => 0x07,\n            // OsCode::KEY_FN_F6           => 0x07,\n            // OsCode::KEY_FN_F7           => 0x07,\n            // OsCode::KEY_FN_F8           => 0x07,\n            // OsCode::KEY_FN_F9           => 0x07,\n            // OsCode::KEY_FN_F10          => 0x07,\n            // OsCode::KEY_FN_F11          => 0x07,\n            // OsCode::KEY_FN_F12          => 0x07,\n            // OsCode::KEY_FN_1            => 0x07,\n            // OsCode::KEY_FN_2            => 0x07,\n            // OsCode::KEY_FN_D            => 0x07,\n            // OsCode::KEY_FN_E            => 0x07,\n            // OsCode::KEY_FN_F            => 0x07,\n            // OsCode::KEY_FN_S            => 0x07,\n            // OsCode::KEY_FN_B            => 0x07,\n            // osc => Err(&format!(\"OsCode {} not mapped!\", osc.as_u16())),\n            _ => Err(\"OsCode unrecognized!\"),\n        }\n    }\n}\n\nimpl TryFrom<PageCode> for OsCode {\n    type Error = &'static str;\n    fn try_from(item: PageCode) -> Result<Self, Self::Error> {\n        match item {\n            PageCode {\n                page: 0xFF,\n                code: 0xFF,\n            } => Ok(OsCode::KEY_RESERVED),\n            PageCode {\n                page: 0x07,\n                code: 0x04,\n            } => Ok(OsCode::KEY_A),\n            PageCode {\n                page: 0x07,\n                code: 0x05,\n            } => Ok(OsCode::KEY_B),\n            PageCode {\n                page: 0x07,\n                code: 0x06,\n            } => Ok(OsCode::KEY_C),\n            PageCode {\n                page: 0x07,\n                code: 0x07,\n            } => Ok(OsCode::KEY_D),\n            PageCode {\n                page: 0x07,\n                code: 0x08,\n            } => Ok(OsCode::KEY_E),\n            PageCode {\n                page: 0x07,\n                code: 0x09,\n            } => Ok(OsCode::KEY_F),\n            PageCode {\n                page: 0x07,\n                code: 0x0A,\n            } => Ok(OsCode::KEY_G),\n            PageCode {\n                page: 0x07,\n                code: 0x0B,\n            } => Ok(OsCode::KEY_H),\n            PageCode {\n                page: 0x07,\n                code: 0x0C,\n            } => Ok(OsCode::KEY_I),\n            PageCode {\n                page: 0x07,\n                code: 0x0D,\n            } => Ok(OsCode::KEY_J),\n            PageCode {\n                page: 0x07,\n                code: 0x0E,\n            } => Ok(OsCode::KEY_K),\n            PageCode {\n                page: 0x07,\n                code: 0x0F,\n            } => Ok(OsCode::KEY_L),\n            PageCode {\n                page: 0x07,\n                code: 0x10,\n            } => Ok(OsCode::KEY_M),\n            PageCode {\n                page: 0x07,\n                code: 0x11,\n            } => Ok(OsCode::KEY_N),\n            PageCode {\n                page: 0x07,\n                code: 0x12,\n            } => Ok(OsCode::KEY_O),\n            PageCode {\n                page: 0x07,\n                code: 0x13,\n            } => Ok(OsCode::KEY_P),\n            PageCode {\n                page: 0x07,\n                code: 0x14,\n            } => Ok(OsCode::KEY_Q),\n            PageCode {\n                page: 0x07,\n                code: 0x15,\n            } => Ok(OsCode::KEY_R),\n            PageCode {\n                page: 0x07,\n                code: 0x16,\n            } => Ok(OsCode::KEY_S),\n            PageCode {\n                page: 0x07,\n                code: 0x17,\n            } => Ok(OsCode::KEY_T),\n            PageCode {\n                page: 0x07,\n                code: 0x18,\n            } => Ok(OsCode::KEY_U),\n            PageCode {\n                page: 0x07,\n                code: 0x19,\n            } => Ok(OsCode::KEY_V),\n            PageCode {\n                page: 0x07,\n                code: 0x1A,\n            } => Ok(OsCode::KEY_W),\n            PageCode {\n                page: 0x07,\n                code: 0x1B,\n            } => Ok(OsCode::KEY_X),\n            PageCode {\n                page: 0x07,\n                code: 0x1C,\n            } => Ok(OsCode::KEY_Y),\n            PageCode {\n                page: 0x07,\n                code: 0x1D,\n            } => Ok(OsCode::KEY_Z),\n            PageCode {\n                page: 0x07,\n                code: 0x1E,\n            } => Ok(OsCode::KEY_1),\n            PageCode {\n                page: 0x07,\n                code: 0x1F,\n            } => Ok(OsCode::KEY_2),\n            PageCode {\n                page: 0x07,\n                code: 0x20,\n            } => Ok(OsCode::KEY_3),\n            PageCode {\n                page: 0x07,\n                code: 0x21,\n            } => Ok(OsCode::KEY_4),\n            PageCode {\n                page: 0x07,\n                code: 0x22,\n            } => Ok(OsCode::KEY_5),\n            PageCode {\n                page: 0x07,\n                code: 0x23,\n            } => Ok(OsCode::KEY_6),\n            PageCode {\n                page: 0x07,\n                code: 0x24,\n            } => Ok(OsCode::KEY_7),\n            PageCode {\n                page: 0x07,\n                code: 0x25,\n            } => Ok(OsCode::KEY_8),\n            PageCode {\n                page: 0x07,\n                code: 0x26,\n            } => Ok(OsCode::KEY_9),\n            PageCode {\n                page: 0x07,\n                code: 0x27,\n            } => Ok(OsCode::KEY_0),\n            PageCode {\n                page: 0x07,\n                code: 0x28,\n            } => Ok(OsCode::KEY_ENTER),\n            PageCode {\n                page: 0x07,\n                code: 0x29,\n            } => Ok(OsCode::KEY_ESC),\n            PageCode {\n                page: 0x07,\n                code: 0x2A,\n            } => Ok(OsCode::KEY_BACKSPACE),\n            PageCode {\n                page: 0x07,\n                code: 0x2B,\n            } => Ok(OsCode::KEY_TAB),\n            PageCode {\n                page: 0x07,\n                code: 0x2C,\n            } => Ok(OsCode::KEY_SPACE),\n            PageCode {\n                page: 0x07,\n                code: 0x2D,\n            } => Ok(OsCode::KEY_MINUS),\n            PageCode {\n                page: 0x07,\n                code: 0x2E,\n            } => Ok(OsCode::KEY_EQUAL),\n            PageCode {\n                page: 0x07,\n                code: 0x2F,\n            } => Ok(OsCode::KEY_LEFTBRACE),\n            PageCode {\n                page: 0x07,\n                code: 0x30,\n            } => Ok(OsCode::KEY_RIGHTBRACE),\n            PageCode {\n                page: 0x07,\n                code: 0x31,\n            } => Ok(OsCode::KEY_BACKSLASH),\n            PageCode {\n                page: 0x07,\n                code: 0x33,\n            } => Ok(OsCode::KEY_SEMICOLON),\n            PageCode {\n                page: 0x07,\n                code: 0x34,\n            } => Ok(OsCode::KEY_APOSTROPHE),\n            PageCode {\n                page: 0x07,\n                code: 0x35,\n            } => Ok(OsCode::KEY_GRAVE),\n            PageCode {\n                page: 0x07,\n                code: 0x36,\n            } => Ok(OsCode::KEY_COMMA),\n            PageCode {\n                page: 0x07,\n                code: 0x37,\n            } => Ok(OsCode::KEY_DOT),\n            PageCode {\n                page: 0x07,\n                code: 0x38,\n            } => Ok(OsCode::KEY_SLASH),\n            PageCode {\n                page: 0x07,\n                code: 0x39,\n            } => Ok(OsCode::KEY_CAPSLOCK),\n            PageCode {\n                page: 0x07,\n                code: 0x3A,\n            } => Ok(OsCode::KEY_F1),\n            PageCode {\n                page: 0x07,\n                code: 0x3B,\n            } => Ok(OsCode::KEY_F2),\n            PageCode {\n                page: 0x07,\n                code: 0x3C,\n            } => Ok(OsCode::KEY_F3),\n            PageCode {\n                page: 0x07,\n                code: 0x3D,\n            } => Ok(OsCode::KEY_F4),\n            PageCode {\n                page: 0x07,\n                code: 0x3E,\n            } => Ok(OsCode::KEY_F5),\n            PageCode {\n                page: 0x07,\n                code: 0x3F,\n            } => Ok(OsCode::KEY_F6),\n            PageCode {\n                page: 0x07,\n                code: 0x40,\n            } => Ok(OsCode::KEY_F7),\n            PageCode {\n                page: 0x07,\n                code: 0x41,\n            } => Ok(OsCode::KEY_F8),\n            PageCode {\n                page: 0x07,\n                code: 0x42,\n            } => Ok(OsCode::KEY_F9),\n            PageCode {\n                page: 0x07,\n                code: 0x43,\n            } => Ok(OsCode::KEY_F10),\n            PageCode {\n                page: 0x07,\n                code: 0x44,\n            } => Ok(OsCode::KEY_F11),\n            PageCode {\n                page: 0x07,\n                code: 0x45,\n            } => Ok(OsCode::KEY_F12),\n            PageCode {\n                page: 0x07,\n                code: 0x46,\n            } => Ok(OsCode::KEY_PRINT),\n            PageCode {\n                page: 0x07,\n                code: 0x47,\n            } => Ok(OsCode::KEY_SCROLLLOCK),\n            PageCode {\n                page: 0x07,\n                code: 0x48,\n            } => Ok(OsCode::KEY_PAUSE),\n            PageCode {\n                page: 0x07,\n                code: 0x49,\n            } => Ok(OsCode::KEY_INSERT),\n            PageCode {\n                page: 0x07,\n                code: 0x4A,\n            } => Ok(OsCode::KEY_HOME),\n            PageCode {\n                page: 0x07,\n                code: 0x4B,\n            } => Ok(OsCode::KEY_PAGEUP),\n            PageCode {\n                page: 0x07,\n                code: 0x4C,\n            } => Ok(OsCode::KEY_DELETE),\n            PageCode {\n                page: 0x07,\n                code: 0x4D,\n            } => Ok(OsCode::KEY_END),\n            PageCode {\n                page: 0x07,\n                code: 0x4E,\n            } => Ok(OsCode::KEY_PAGEDOWN),\n            PageCode {\n                page: 0x07,\n                code: 0x4F,\n            } => Ok(OsCode::KEY_RIGHT),\n            PageCode {\n                page: 0x07,\n                code: 0x50,\n            } => Ok(OsCode::KEY_LEFT),\n            PageCode {\n                page: 0x07,\n                code: 0x51,\n            } => Ok(OsCode::KEY_DOWN),\n            PageCode {\n                page: 0x07,\n                code: 0x52,\n            } => Ok(OsCode::KEY_UP),\n            PageCode {\n                page: 0x07,\n                code: 0x53,\n            } => Ok(OsCode::KEY_NUMLOCK),\n            PageCode {\n                page: 0x07,\n                code: 0x54,\n            } => Ok(OsCode::KEY_KPSLASH),\n            PageCode {\n                page: 0x07,\n                code: 0x55,\n            } => Ok(OsCode::KEY_KPASTERISK),\n            PageCode {\n                page: 0x07,\n                code: 0x56,\n            } => Ok(OsCode::KEY_KPMINUS),\n            PageCode {\n                page: 0x07,\n                code: 0x57,\n            } => Ok(OsCode::KEY_KPPLUS),\n            PageCode {\n                page: 0x07,\n                code: 0x58,\n            } => Ok(OsCode::KEY_KPENTER),\n            PageCode {\n                page: 0x07,\n                code: 0x59,\n            } => Ok(OsCode::KEY_KP1),\n            PageCode {\n                page: 0x07,\n                code: 0x5A,\n            } => Ok(OsCode::KEY_KP2),\n            PageCode {\n                page: 0x07,\n                code: 0x5B,\n            } => Ok(OsCode::KEY_KP3),\n            PageCode {\n                page: 0x07,\n                code: 0x5C,\n            } => Ok(OsCode::KEY_KP4),\n            PageCode {\n                page: 0x07,\n                code: 0x5D,\n            } => Ok(OsCode::KEY_KP5),\n            PageCode {\n                page: 0x07,\n                code: 0x5E,\n            } => Ok(OsCode::KEY_KP6),\n            PageCode {\n                page: 0x07,\n                code: 0x5F,\n            } => Ok(OsCode::KEY_KP7),\n            PageCode {\n                page: 0x07,\n                code: 0x60,\n            } => Ok(OsCode::KEY_KP8),\n            PageCode {\n                page: 0x07,\n                code: 0x61,\n            } => Ok(OsCode::KEY_KP9),\n            PageCode {\n                page: 0x07,\n                code: 0x62,\n            } => Ok(OsCode::KEY_KP0),\n            PageCode {\n                page: 0x07,\n                code: 0x63,\n            } => Ok(OsCode::KEY_KPDOT),\n            PageCode {\n                page: 0x07,\n                code: 0x64,\n            } => Ok(OsCode::KEY_102ND),\n            PageCode {\n                page: 0x07,\n                code: 0x66,\n            } => Ok(OsCode::KEY_POWER),\n            PageCode {\n                page: 0x07,\n                code: 0x67,\n            } => Ok(OsCode::KEY_KPEQUAL),\n            PageCode {\n                page: 0x07,\n                code: 0x68,\n            } => Ok(OsCode::KEY_F13),\n            PageCode {\n                page: 0x07,\n                code: 0x69,\n            } => Ok(OsCode::KEY_F14),\n            PageCode {\n                page: 0x07,\n                code: 0x6A,\n            } => Ok(OsCode::KEY_F15),\n            PageCode {\n                page: 0x07,\n                code: 0x6B,\n            } => Ok(OsCode::KEY_F16),\n            PageCode {\n                page: 0x07,\n                code: 0x6C,\n            } => Ok(OsCode::KEY_F17),\n            PageCode {\n                page: 0x07,\n                code: 0x6D,\n            } => Ok(OsCode::KEY_F18),\n            PageCode {\n                page: 0x07,\n                code: 0x6E,\n            } => Ok(OsCode::KEY_F19),\n            PageCode {\n                page: 0x07,\n                code: 0x6F,\n            } => Ok(OsCode::KEY_F20),\n            PageCode {\n                page: 0x07,\n                code: 0x70,\n            } => Ok(OsCode::KEY_F21),\n            PageCode {\n                page: 0x07,\n                code: 0x71,\n            } => Ok(OsCode::KEY_F22),\n            PageCode {\n                page: 0x07,\n                code: 0x72,\n            } => Ok(OsCode::KEY_F23),\n            PageCode {\n                page: 0x07,\n                code: 0x73,\n            } => Ok(OsCode::KEY_F24),\n            PageCode {\n                page: 0x07,\n                code: 0x75,\n            } => Ok(OsCode::KEY_HELP),\n            PageCode {\n                page: 0x07,\n                code: 0x76,\n            } => Ok(OsCode::KEY_MENU),\n            PageCode {\n                page: 0x07,\n                code: 0x77,\n            } => Ok(OsCode::KEY_SELECT),\n            PageCode {\n                page: 0x07,\n                code: 0x78,\n            } => Ok(OsCode::KEY_STOP),\n            PageCode {\n                page: 0x07,\n                code: 0x79,\n            } => Ok(OsCode::KEY_AGAIN),\n            PageCode {\n                page: 0x07,\n                code: 0x7A,\n            } => Ok(OsCode::KEY_UNDO),\n            PageCode {\n                page: 0x07,\n                code: 0x7B,\n            } => Ok(OsCode::KEY_CUT),\n            PageCode {\n                page: 0x07,\n                code: 0x7C,\n            } => Ok(OsCode::KEY_COPY),\n            PageCode {\n                page: 0x07,\n                code: 0x7D,\n            } => Ok(OsCode::KEY_PASTE),\n            PageCode {\n                page: 0x07,\n                code: 0x7E,\n            } => Ok(OsCode::KEY_FIND),\n            PageCode {\n                page: 0x0C,\n                code: 0xE2,\n            } => Ok(OsCode::KEY_MUTE),\n            PageCode {\n                page: 0x0C,\n                code: 0xE9,\n            } => Ok(OsCode::KEY_VOLUMEUP),\n            PageCode {\n                page: 0x0C,\n                code: 0xEA,\n            } => Ok(OsCode::KEY_VOLUMEDOWN),\n            PageCode {\n                page: 0x0C,\n                code: 0xB8,\n            } => Ok(OsCode::KEY_EJECTCD),\n            PageCode {\n                page: 0x07,\n                code: 0x85,\n            } => Ok(OsCode::KEY_KPCOMMA),\n            PageCode {\n                page: 0x07,\n                code: 0x87,\n            } => Ok(OsCode::KEY_RO),\n            PageCode {\n                page: 0x07,\n                code: 0x90,\n            } => Ok(OsCode::KEY_HANGEUL),\n            PageCode {\n                page: 0x07,\n                code: 0x91,\n            } => Ok(OsCode::KEY_HANJA),\n            PageCode {\n                page: 0x07,\n                code: 0x99,\n            } => Ok(OsCode::KEY_ALTERASE),\n            PageCode {\n                page: 0x07,\n                code: 0x9B,\n            } => Ok(OsCode::KEY_CANCEL),\n            PageCode {\n                page: 0x07,\n                code: 0x9C,\n            } => Ok(OsCode::KEY_CLEAR),\n            PageCode {\n                page: 0x07,\n                code: 0xE0,\n            } => Ok(OsCode::KEY_LEFTCTRL),\n            PageCode {\n                page: 0x07,\n                code: 0xE1,\n            } => Ok(OsCode::KEY_LEFTSHIFT),\n            PageCode {\n                page: 0x07,\n                code: 0xE2,\n            } => Ok(OsCode::KEY_LEFTALT),\n            PageCode {\n                page: 0x07,\n                code: 0xE3,\n            } => Ok(OsCode::KEY_LEFTMETA),\n            PageCode {\n                page: 0x07,\n                code: 0xE4,\n            } => Ok(OsCode::KEY_RIGHTCTRL),\n            PageCode {\n                page: 0x07,\n                code: 0xE5,\n            } => Ok(OsCode::KEY_RIGHTSHIFT),\n            PageCode {\n                page: 0x07,\n                code: 0xE6,\n            } => Ok(OsCode::KEY_RIGHTALT),\n            PageCode {\n                page: 0x07,\n                code: 0xE7,\n            } => Ok(OsCode::KEY_RIGHTMETA),\n            PageCode {\n                page: 0x0C,\n                code: 0x32,\n            } => Ok(OsCode::KEY_SLEEP),\n            PageCode {\n                page: 0x0C,\n                code: 0xB3,\n            } => Ok(OsCode::KEY_FORWARD),\n            PageCode {\n                page: 0x0C,\n                code: 0xB4,\n            } => Ok(OsCode::KEY_REWIND),\n            PageCode {\n                page: 0x0C,\n                code: 0xB5,\n            } => Ok(OsCode::KEY_NEXTSONG),\n            PageCode {\n                page: 0x0C,\n                code: 0xB6,\n            } => Ok(OsCode::KEY_PREVIOUSSONG),\n            PageCode {\n                page: 0x0C,\n                code: 0xCD,\n            } => Ok(OsCode::KEY_PLAYPAUSE),\n            PageCode {\n                page: 0xFF,\n                code: 0x03,\n            } => Ok(OsCode::KEY_FN),\n            PageCode {\n                page: 0x0C,\n                code: 0x6F,\n            } => Ok(OsCode::KEY_BRIGHTNESSUP),\n            PageCode {\n                page: 0x0C,\n                code: 0x70,\n            } => Ok(OsCode::KEY_BRIGHTNESSDOWN),\n            PageCode {\n                page: 0xFF,\n                code: 0x07,\n            } => Ok(OsCode::KEY_KBDILLUMTOGGLE),\n            PageCode {\n                page: 0xFF,\n                code: 0x08,\n            } => Ok(OsCode::KEY_KBDILLUMUP),\n            PageCode {\n                page: 0xFF,\n                code: 0x09,\n            } => Ok(OsCode::KEY_KBDILLUMDOWN),\n            PageCode {\n                page: 0xFF,\n                code: 0x02,\n            } => Ok(OsCode::KEY_DASHBOARD),\n            PageCode {\n                page: 0xFF,\n                code: 0x01,\n            } => Ok(OsCode::KEY_SEARCH),\n            PageCode {\n                page: 0x0C,\n                code: 0x221,\n            } => Ok(OsCode::KEY_249),\n            PageCode {\n                page: 0x0C,\n                code: 0xCF,\n            } => Ok(OsCode::KEY_250),\n            PageCode {\n                page: 0x01,\n                code: 0x9B,\n            } => Ok(OsCode::KEY_251),\n            PageCode {\n                page: 0x0C,\n                code: 0x29F,\n            } => Ok(OsCode::KEY_252),\n            PageCode {\n                page: 0x0C,\n                code: 0x2A0,\n            } => Ok(OsCode::KEY_253),\n            _ => Err(\"PageCode unrecognized!\"),\n        }\n    }\n}\n\nimpl OsCode {\n    pub(super) const fn as_u16_macos(self) -> u16 {\n        self as u16\n    }\n    pub(super) const fn from_u16_macos(code: u16) -> Option<Self> {\n        match code {\n            0 => Some(OsCode::KEY_RESERVED),\n            1 => Some(OsCode::KEY_ESC),\n            2 => Some(OsCode::KEY_1),\n            3 => Some(OsCode::KEY_2),\n            4 => Some(OsCode::KEY_3),\n            5 => Some(OsCode::KEY_4),\n            6 => Some(OsCode::KEY_5),\n            7 => Some(OsCode::KEY_6),\n            8 => Some(OsCode::KEY_7),\n            9 => Some(OsCode::KEY_8),\n            10 => Some(OsCode::KEY_9),\n            11 => Some(OsCode::KEY_0),\n            12 => Some(OsCode::KEY_MINUS),\n            13 => Some(OsCode::KEY_EQUAL),\n            14 => Some(OsCode::KEY_BACKSPACE),\n            15 => Some(OsCode::KEY_TAB),\n            16 => Some(OsCode::KEY_Q),\n            17 => Some(OsCode::KEY_W),\n            18 => Some(OsCode::KEY_E),\n            19 => Some(OsCode::KEY_R),\n            20 => Some(OsCode::KEY_T),\n            21 => Some(OsCode::KEY_Y),\n            22 => Some(OsCode::KEY_U),\n            23 => Some(OsCode::KEY_I),\n            24 => Some(OsCode::KEY_O),\n            25 => Some(OsCode::KEY_P),\n            26 => Some(OsCode::KEY_LEFTBRACE),\n            27 => Some(OsCode::KEY_RIGHTBRACE),\n            28 => Some(OsCode::KEY_ENTER),\n            29 => Some(OsCode::KEY_LEFTCTRL),\n            30 => Some(OsCode::KEY_A),\n            31 => Some(OsCode::KEY_S),\n            32 => Some(OsCode::KEY_D),\n            33 => Some(OsCode::KEY_F),\n            34 => Some(OsCode::KEY_G),\n            35 => Some(OsCode::KEY_H),\n            36 => Some(OsCode::KEY_J),\n            37 => Some(OsCode::KEY_K),\n            38 => Some(OsCode::KEY_L),\n            39 => Some(OsCode::KEY_SEMICOLON),\n            40 => Some(OsCode::KEY_APOSTROPHE),\n            41 => Some(OsCode::KEY_GRAVE),\n            42 => Some(OsCode::KEY_LEFTSHIFT),\n            43 => Some(OsCode::KEY_BACKSLASH),\n            44 => Some(OsCode::KEY_Z),\n            45 => Some(OsCode::KEY_X),\n            46 => Some(OsCode::KEY_C),\n            47 => Some(OsCode::KEY_V),\n            48 => Some(OsCode::KEY_B),\n            49 => Some(OsCode::KEY_N),\n            50 => Some(OsCode::KEY_M),\n            51 => Some(OsCode::KEY_COMMA),\n            52 => Some(OsCode::KEY_DOT),\n            53 => Some(OsCode::KEY_SLASH),\n            54 => Some(OsCode::KEY_RIGHTSHIFT),\n            55 => Some(OsCode::KEY_KPASTERISK),\n            56 => Some(OsCode::KEY_LEFTALT),\n            57 => Some(OsCode::KEY_SPACE),\n            58 => Some(OsCode::KEY_CAPSLOCK),\n            59 => Some(OsCode::KEY_F1),\n            60 => Some(OsCode::KEY_F2),\n            61 => Some(OsCode::KEY_F3),\n            62 => Some(OsCode::KEY_F4),\n            63 => Some(OsCode::KEY_F5),\n            64 => Some(OsCode::KEY_F6),\n            65 => Some(OsCode::KEY_F7),\n            66 => Some(OsCode::KEY_F8),\n            67 => Some(OsCode::KEY_F9),\n            68 => Some(OsCode::KEY_F10),\n            69 => Some(OsCode::KEY_NUMLOCK),\n            70 => Some(OsCode::KEY_SCROLLLOCK),\n            71 => Some(OsCode::KEY_KP7),\n            72 => Some(OsCode::KEY_KP8),\n            73 => Some(OsCode::KEY_KP9),\n            74 => Some(OsCode::KEY_KPMINUS),\n            75 => Some(OsCode::KEY_KP4),\n            76 => Some(OsCode::KEY_KP5),\n            77 => Some(OsCode::KEY_KP6),\n            78 => Some(OsCode::KEY_KPPLUS),\n            79 => Some(OsCode::KEY_KP1),\n            80 => Some(OsCode::KEY_KP2),\n            81 => Some(OsCode::KEY_KP3),\n            82 => Some(OsCode::KEY_KP0),\n            83 => Some(OsCode::KEY_KPDOT),\n            84 => Some(OsCode::KEY_84),\n            85 => Some(OsCode::KEY_ZENKAKUHANKAKU),\n            86 => Some(OsCode::KEY_102ND),\n            87 => Some(OsCode::KEY_F11),\n            88 => Some(OsCode::KEY_F12),\n            89 => Some(OsCode::KEY_RO),\n            90 => Some(OsCode::KEY_KATAKANA),\n            91 => Some(OsCode::KEY_HIRAGANA),\n            92 => Some(OsCode::KEY_HENKAN),\n            93 => Some(OsCode::KEY_KATAKANAHIRAGANA),\n            94 => Some(OsCode::KEY_MUHENKAN),\n            95 => Some(OsCode::KEY_KPJPCOMMA),\n            96 => Some(OsCode::KEY_KPENTER),\n            97 => Some(OsCode::KEY_RIGHTCTRL),\n            98 => Some(OsCode::KEY_KPSLASH),\n            99 => Some(OsCode::KEY_SYSRQ),\n            100 => Some(OsCode::KEY_RIGHTALT),\n            101 => Some(OsCode::KEY_LINEFEED),\n            102 => Some(OsCode::KEY_HOME),\n            103 => Some(OsCode::KEY_UP),\n            104 => Some(OsCode::KEY_PAGEUP),\n            105 => Some(OsCode::KEY_LEFT),\n            106 => Some(OsCode::KEY_RIGHT),\n            107 => Some(OsCode::KEY_END),\n            108 => Some(OsCode::KEY_DOWN),\n            109 => Some(OsCode::KEY_PAGEDOWN),\n            110 => Some(OsCode::KEY_INSERT),\n            111 => Some(OsCode::KEY_DELETE),\n            112 => Some(OsCode::KEY_MACRO),\n            113 => Some(OsCode::KEY_MUTE),\n            114 => Some(OsCode::KEY_VOLUMEDOWN),\n            115 => Some(OsCode::KEY_VOLUMEUP),\n            116 => Some(OsCode::KEY_POWER),\n            117 => Some(OsCode::KEY_KPEQUAL),\n            118 => Some(OsCode::KEY_KPPLUSMINUS),\n            119 => Some(OsCode::KEY_PAUSE),\n            120 => Some(OsCode::KEY_SCALE),\n            121 => Some(OsCode::KEY_KPCOMMA),\n            122 => Some(OsCode::KEY_HANGEUL),\n            123 => Some(OsCode::KEY_HANJA),\n            124 => Some(OsCode::KEY_YEN),\n            125 => Some(OsCode::KEY_LEFTMETA),\n            126 => Some(OsCode::KEY_RIGHTMETA),\n            127 => Some(OsCode::KEY_COMPOSE),\n            128 => Some(OsCode::KEY_STOP),\n            129 => Some(OsCode::KEY_AGAIN),\n            130 => Some(OsCode::KEY_PROPS),\n            131 => Some(OsCode::KEY_UNDO),\n            132 => Some(OsCode::KEY_FRONT),\n            133 => Some(OsCode::KEY_COPY),\n            134 => Some(OsCode::KEY_OPEN),\n            135 => Some(OsCode::KEY_PASTE),\n            136 => Some(OsCode::KEY_FIND),\n            137 => Some(OsCode::KEY_CUT),\n            138 => Some(OsCode::KEY_HELP),\n            139 => Some(OsCode::KEY_MENU),\n            140 => Some(OsCode::KEY_CALC),\n            141 => Some(OsCode::KEY_SETUP),\n            142 => Some(OsCode::KEY_SLEEP),\n            143 => Some(OsCode::KEY_WAKEUP),\n            144 => Some(OsCode::KEY_FILE),\n            145 => Some(OsCode::KEY_SENDFILE),\n            146 => Some(OsCode::KEY_DELETEFILE),\n            147 => Some(OsCode::KEY_XFER),\n            148 => Some(OsCode::KEY_PROG1),\n            149 => Some(OsCode::KEY_PROG2),\n            150 => Some(OsCode::KEY_WWW),\n            151 => Some(OsCode::KEY_MSDOS),\n            152 => Some(OsCode::KEY_COFFEE),\n            153 => Some(OsCode::KEY_ROTATE_DISPLAY),\n            154 => Some(OsCode::KEY_CYCLEWINDOWS),\n            155 => Some(OsCode::KEY_MAIL),\n            156 => Some(OsCode::KEY_BOOKMARKS),\n            157 => Some(OsCode::KEY_COMPUTER),\n            158 => Some(OsCode::KEY_BACK),\n            159 => Some(OsCode::KEY_FORWARD),\n            160 => Some(OsCode::KEY_CLOSECD),\n            161 => Some(OsCode::KEY_EJECTCD),\n            162 => Some(OsCode::KEY_EJECTCLOSECD),\n            163 => Some(OsCode::KEY_NEXTSONG),\n            164 => Some(OsCode::KEY_PLAYPAUSE),\n            165 => Some(OsCode::KEY_PREVIOUSSONG),\n            166 => Some(OsCode::KEY_STOPCD),\n            167 => Some(OsCode::KEY_RECORD),\n            168 => Some(OsCode::KEY_REWIND),\n            169 => Some(OsCode::KEY_PHONE),\n            170 => Some(OsCode::KEY_ISO),\n            171 => Some(OsCode::KEY_CONFIG),\n            172 => Some(OsCode::KEY_HOMEPAGE),\n            173 => Some(OsCode::KEY_REFRESH),\n            174 => Some(OsCode::KEY_EXIT),\n            175 => Some(OsCode::KEY_MOVE),\n            176 => Some(OsCode::KEY_EDIT),\n            177 => Some(OsCode::KEY_SCROLLUP),\n            178 => Some(OsCode::KEY_SCROLLDOWN),\n            179 => Some(OsCode::KEY_KPLEFTPAREN),\n            180 => Some(OsCode::KEY_KPRIGHTPAREN),\n            181 => Some(OsCode::KEY_NEW),\n            182 => Some(OsCode::KEY_REDO),\n            183 => Some(OsCode::KEY_F13),\n            184 => Some(OsCode::KEY_F14),\n            185 => Some(OsCode::KEY_F15),\n            186 => Some(OsCode::KEY_F16),\n            187 => Some(OsCode::KEY_F17),\n            188 => Some(OsCode::KEY_F18),\n            189 => Some(OsCode::KEY_F19),\n            190 => Some(OsCode::KEY_F20),\n            191 => Some(OsCode::KEY_F21),\n            192 => Some(OsCode::KEY_F22),\n            193 => Some(OsCode::KEY_F23),\n            194 => Some(OsCode::KEY_F24),\n            195 => Some(OsCode::KEY_195),\n            196 => Some(OsCode::KEY_196),\n            197 => Some(OsCode::KEY_197),\n            198 => Some(OsCode::KEY_198),\n            199 => Some(OsCode::KEY_199),\n            200 => Some(OsCode::KEY_PLAYCD),\n            201 => Some(OsCode::KEY_PAUSECD),\n            202 => Some(OsCode::KEY_PROG3),\n            203 => Some(OsCode::KEY_PROG4),\n            204 => Some(OsCode::KEY_DASHBOARD),\n            205 => Some(OsCode::KEY_SUSPEND),\n            206 => Some(OsCode::KEY_CLOSE),\n            207 => Some(OsCode::KEY_PLAY),\n            208 => Some(OsCode::KEY_FASTFORWARD),\n            209 => Some(OsCode::KEY_BASSBOOST),\n            210 => Some(OsCode::KEY_PRINT),\n            211 => Some(OsCode::KEY_HP),\n            212 => Some(OsCode::KEY_CAMERA),\n            213 => Some(OsCode::KEY_SOUND),\n            214 => Some(OsCode::KEY_QUESTION),\n            215 => Some(OsCode::KEY_EMAIL),\n            216 => Some(OsCode::KEY_CHAT),\n            217 => Some(OsCode::KEY_SEARCH),\n            218 => Some(OsCode::KEY_CONNECT),\n            219 => Some(OsCode::KEY_FINANCE),\n            220 => Some(OsCode::KEY_SPORT),\n            221 => Some(OsCode::KEY_SHOP),\n            222 => Some(OsCode::KEY_ALTERASE),\n            223 => Some(OsCode::KEY_CANCEL),\n            224 => Some(OsCode::KEY_BRIGHTNESSDOWN),\n            225 => Some(OsCode::KEY_BRIGHTNESSUP),\n            226 => Some(OsCode::KEY_MEDIA),\n            227 => Some(OsCode::KEY_SWITCHVIDEOMODE),\n            228 => Some(OsCode::KEY_KBDILLUMTOGGLE),\n            229 => Some(OsCode::KEY_KBDILLUMDOWN),\n            230 => Some(OsCode::KEY_KBDILLUMUP),\n            231 => Some(OsCode::KEY_SEND),\n            232 => Some(OsCode::KEY_REPLY),\n            233 => Some(OsCode::KEY_FORWARDMAIL),\n            234 => Some(OsCode::KEY_SAVE),\n            235 => Some(OsCode::KEY_DOCUMENTS),\n            236 => Some(OsCode::KEY_BATTERY),\n            237 => Some(OsCode::KEY_BLUETOOTH),\n            238 => Some(OsCode::KEY_WLAN),\n            239 => Some(OsCode::KEY_UWB),\n            240 => Some(OsCode::KEY_UNKNOWN),\n            241 => Some(OsCode::KEY_VIDEO_NEXT),\n            242 => Some(OsCode::KEY_VIDEO_PREV),\n            243 => Some(OsCode::KEY_BRIGHTNESS_CYCLE),\n            244 => Some(OsCode::KEY_BRIGHTNESS_AUTO),\n            245 => Some(OsCode::KEY_DISPLAY_OFF),\n            246 => Some(OsCode::KEY_WWAN),\n            247 => Some(OsCode::KEY_RFKILL),\n            248 => Some(OsCode::KEY_MICMUTE),\n            249 => Some(OsCode::KEY_249),\n            250 => Some(OsCode::KEY_250),\n            251 => Some(OsCode::KEY_251),\n            252 => Some(OsCode::KEY_252),\n            253 => Some(OsCode::KEY_253),\n            254 => Some(OsCode::KEY_254),\n            255 => Some(OsCode::KEY_255),\n            256 => Some(OsCode::BTN_0),\n            257 => Some(OsCode::BTN_1),\n            258 => Some(OsCode::BTN_2),\n            259 => Some(OsCode::BTN_3),\n            260 => Some(OsCode::BTN_4),\n            261 => Some(OsCode::BTN_5),\n            262 => Some(OsCode::BTN_6),\n            263 => Some(OsCode::BTN_7),\n            264 => Some(OsCode::BTN_8),\n            265 => Some(OsCode::BTN_9),\n            266 => Some(OsCode::KEY_266),\n            267 => Some(OsCode::KEY_267),\n            268 => Some(OsCode::KEY_268),\n            269 => Some(OsCode::KEY_269),\n            270 => Some(OsCode::KEY_270),\n            271 => Some(OsCode::KEY_271),\n            272 => Some(OsCode::BTN_LEFT),\n            273 => Some(OsCode::BTN_RIGHT),\n            274 => Some(OsCode::BTN_MIDDLE),\n            275 => Some(OsCode::BTN_SIDE),\n            276 => Some(OsCode::BTN_EXTRA),\n            277 => Some(OsCode::BTN_FORWARD),\n            278 => Some(OsCode::BTN_BACK),\n            279 => Some(OsCode::BTN_TASK),\n            280 => Some(OsCode::KEY_280),\n            281 => Some(OsCode::KEY_281),\n            282 => Some(OsCode::KEY_282),\n            283 => Some(OsCode::KEY_283),\n            284 => Some(OsCode::KEY_284),\n            285 => Some(OsCode::KEY_285),\n            286 => Some(OsCode::KEY_286),\n            287 => Some(OsCode::KEY_287),\n            288 => Some(OsCode::BTN_TRIGGER),\n            289 => Some(OsCode::BTN_THUMB),\n            290 => Some(OsCode::BTN_THUMB2),\n            291 => Some(OsCode::BTN_TOP),\n            292 => Some(OsCode::BTN_TOP2),\n            293 => Some(OsCode::BTN_PINKIE),\n            294 => Some(OsCode::BTN_BASE),\n            295 => Some(OsCode::BTN_BASE2),\n            296 => Some(OsCode::BTN_BASE3),\n            297 => Some(OsCode::BTN_BASE4),\n            298 => Some(OsCode::BTN_BASE5),\n            299 => Some(OsCode::BTN_BASE6),\n            300 => Some(OsCode::KEY_300),\n            301 => Some(OsCode::KEY_301),\n            302 => Some(OsCode::KEY_302),\n            303 => Some(OsCode::BTN_DEAD),\n            304 => Some(OsCode::BTN_SOUTH),\n            305 => Some(OsCode::BTN_EAST),\n            306 => Some(OsCode::BTN_C),\n            307 => Some(OsCode::BTN_NORTH),\n            308 => Some(OsCode::BTN_WEST),\n            309 => Some(OsCode::BTN_Z),\n            310 => Some(OsCode::BTN_TL),\n            311 => Some(OsCode::BTN_TR),\n            312 => Some(OsCode::BTN_TL2),\n            313 => Some(OsCode::BTN_TR2),\n            314 => Some(OsCode::BTN_SELECT),\n            315 => Some(OsCode::BTN_START),\n            316 => Some(OsCode::BTN_MODE),\n            317 => Some(OsCode::BTN_THUMBL),\n            318 => Some(OsCode::BTN_THUMBR),\n            319 => Some(OsCode::KEY_319),\n            320 => Some(OsCode::BTN_TOOL_PEN),\n            321 => Some(OsCode::BTN_TOOL_RUBBER),\n            322 => Some(OsCode::BTN_TOOL_BRUSH),\n            323 => Some(OsCode::BTN_TOOL_PENCIL),\n            324 => Some(OsCode::BTN_TOOL_AIRBRUSH),\n            325 => Some(OsCode::BTN_TOOL_FINGER),\n            326 => Some(OsCode::BTN_TOOL_MOUSE),\n            327 => Some(OsCode::BTN_TOOL_LENS),\n            328 => Some(OsCode::BTN_TOOL_QUINTTAP),\n            329 => Some(OsCode::BTN_STYLUS3),\n            330 => Some(OsCode::BTN_TOUCH),\n            331 => Some(OsCode::BTN_STYLUS),\n            332 => Some(OsCode::BTN_STYLUS2),\n            333 => Some(OsCode::BTN_TOOL_DOUBLETAP),\n            334 => Some(OsCode::BTN_TOOL_TRIPLETAP),\n            335 => Some(OsCode::BTN_TOOL_QUADTAP),\n            336 => Some(OsCode::BTN_GEAR_DOWN),\n            337 => Some(OsCode::BTN_GEAR_UP),\n            338 => Some(OsCode::KEY_338),\n            339 => Some(OsCode::KEY_339),\n            340 => Some(OsCode::KEY_340),\n            341 => Some(OsCode::KEY_341),\n            342 => Some(OsCode::KEY_342),\n            343 => Some(OsCode::KEY_343),\n            344 => Some(OsCode::KEY_344),\n            345 => Some(OsCode::KEY_345),\n            346 => Some(OsCode::KEY_346),\n            347 => Some(OsCode::KEY_347),\n            348 => Some(OsCode::KEY_348),\n            349 => Some(OsCode::KEY_349),\n            350 => Some(OsCode::KEY_350),\n            351 => Some(OsCode::KEY_351),\n            352 => Some(OsCode::KEY_OK),\n            353 => Some(OsCode::KEY_SELECT),\n            354 => Some(OsCode::KEY_GOTO),\n            355 => Some(OsCode::KEY_CLEAR),\n            356 => Some(OsCode::KEY_POWER2),\n            357 => Some(OsCode::KEY_OPTION),\n            358 => Some(OsCode::KEY_INFO),\n            359 => Some(OsCode::KEY_TIME),\n            360 => Some(OsCode::KEY_VENDOR),\n            361 => Some(OsCode::KEY_ARCHIVE),\n            362 => Some(OsCode::KEY_PROGRAM),\n            363 => Some(OsCode::KEY_CHANNEL),\n            364 => Some(OsCode::KEY_FAVORITES),\n            365 => Some(OsCode::KEY_EPG),\n            366 => Some(OsCode::KEY_PVR),\n            367 => Some(OsCode::KEY_MHP),\n            368 => Some(OsCode::KEY_LANGUAGE),\n            369 => Some(OsCode::KEY_TITLE),\n            370 => Some(OsCode::KEY_SUBTITLE),\n            371 => Some(OsCode::KEY_ANGLE),\n            372 => Some(OsCode::KEY_FULL_SCREEN),\n            373 => Some(OsCode::KEY_MODE),\n            374 => Some(OsCode::KEY_KEYBOARD),\n            375 => Some(OsCode::KEY_ASPECT_RATIO),\n            376 => Some(OsCode::KEY_PC),\n            377 => Some(OsCode::KEY_TV),\n            378 => Some(OsCode::KEY_TV2),\n            379 => Some(OsCode::KEY_VCR),\n            380 => Some(OsCode::KEY_VCR2),\n            381 => Some(OsCode::KEY_SAT),\n            382 => Some(OsCode::KEY_SAT2),\n            383 => Some(OsCode::KEY_CD),\n            384 => Some(OsCode::KEY_TAPE),\n            385 => Some(OsCode::KEY_RADIO),\n            386 => Some(OsCode::KEY_TUNER),\n            387 => Some(OsCode::KEY_PLAYER),\n            388 => Some(OsCode::KEY_TEXT),\n            389 => Some(OsCode::KEY_DVD),\n            390 => Some(OsCode::KEY_AUX),\n            391 => Some(OsCode::KEY_MP3),\n            392 => Some(OsCode::KEY_AUDIO),\n            393 => Some(OsCode::KEY_VIDEO),\n            394 => Some(OsCode::KEY_DIRECTORY),\n            395 => Some(OsCode::KEY_LIST),\n            396 => Some(OsCode::KEY_MEMO),\n            397 => Some(OsCode::KEY_CALENDAR),\n            398 => Some(OsCode::KEY_RED),\n            399 => Some(OsCode::KEY_GREEN),\n            400 => Some(OsCode::KEY_YELLOW),\n            401 => Some(OsCode::KEY_BLUE),\n            402 => Some(OsCode::KEY_CHANNELUP),\n            403 => Some(OsCode::KEY_CHANNELDOWN),\n            404 => Some(OsCode::KEY_FIRST),\n            405 => Some(OsCode::KEY_LAST),\n            406 => Some(OsCode::KEY_AB),\n            407 => Some(OsCode::KEY_NEXT),\n            408 => Some(OsCode::KEY_RESTART),\n            409 => Some(OsCode::KEY_SLOW),\n            410 => Some(OsCode::KEY_SHUFFLE),\n            411 => Some(OsCode::KEY_BREAK),\n            412 => Some(OsCode::KEY_PREVIOUS),\n            413 => Some(OsCode::KEY_DIGITS),\n            414 => Some(OsCode::KEY_TEEN),\n            415 => Some(OsCode::KEY_TWEN),\n            416 => Some(OsCode::KEY_VIDEOPHONE),\n            417 => Some(OsCode::KEY_GAMES),\n            418 => Some(OsCode::KEY_ZOOMIN),\n            419 => Some(OsCode::KEY_ZOOMOUT),\n            420 => Some(OsCode::KEY_ZOOMRESET),\n            421 => Some(OsCode::KEY_WORDPROCESSOR),\n            422 => Some(OsCode::KEY_EDITOR),\n            423 => Some(OsCode::KEY_SPREADSHEET),\n            424 => Some(OsCode::KEY_GRAPHICSEDITOR),\n            425 => Some(OsCode::KEY_PRESENTATION),\n            426 => Some(OsCode::KEY_DATABASE),\n            427 => Some(OsCode::KEY_NEWS),\n            428 => Some(OsCode::KEY_VOICEMAIL),\n            429 => Some(OsCode::KEY_ADDRESSBOOK),\n            430 => Some(OsCode::KEY_MESSENGER),\n            431 => Some(OsCode::KEY_DISPLAYTOGGLE),\n            432 => Some(OsCode::KEY_SPELLCHECK),\n            433 => Some(OsCode::KEY_LOGOFF),\n            434 => Some(OsCode::KEY_DOLLAR),\n            435 => Some(OsCode::KEY_EURO),\n            436 => Some(OsCode::KEY_FRAMEBACK),\n            437 => Some(OsCode::KEY_FRAMEFORWARD),\n            438 => Some(OsCode::KEY_CONTEXT_MENU),\n            439 => Some(OsCode::KEY_MEDIA_REPEAT),\n            440 => Some(OsCode::KEY_10CHANNELSUP),\n            441 => Some(OsCode::KEY_10CHANNELSDOWN),\n            442 => Some(OsCode::KEY_IMAGES),\n            443 => Some(OsCode::KEY_443),\n            444 => Some(OsCode::KEY_444),\n            445 => Some(OsCode::KEY_445),\n            446 => Some(OsCode::KEY_446),\n            447 => Some(OsCode::KEY_447),\n            448 => Some(OsCode::KEY_DEL_EOL),\n            449 => Some(OsCode::KEY_DEL_EOS),\n            450 => Some(OsCode::KEY_INS_LINE),\n            451 => Some(OsCode::KEY_DEL_LINE),\n            452 => Some(OsCode::KEY_452),\n            453 => Some(OsCode::KEY_453),\n            454 => Some(OsCode::KEY_454),\n            455 => Some(OsCode::KEY_455),\n            456 => Some(OsCode::KEY_456),\n            457 => Some(OsCode::KEY_457),\n            458 => Some(OsCode::KEY_458),\n            459 => Some(OsCode::KEY_459),\n            460 => Some(OsCode::KEY_460),\n            461 => Some(OsCode::KEY_461),\n            462 => Some(OsCode::KEY_462),\n            463 => Some(OsCode::KEY_463),\n            464 => Some(OsCode::KEY_FN),\n            465 => Some(OsCode::KEY_FN_ESC),\n            466 => Some(OsCode::KEY_FN_F1),\n            467 => Some(OsCode::KEY_FN_F2),\n            468 => Some(OsCode::KEY_FN_F3),\n            469 => Some(OsCode::KEY_FN_F4),\n            470 => Some(OsCode::KEY_FN_F5),\n            471 => Some(OsCode::KEY_FN_F6),\n            472 => Some(OsCode::KEY_FN_F7),\n            473 => Some(OsCode::KEY_FN_F8),\n            474 => Some(OsCode::KEY_FN_F9),\n            475 => Some(OsCode::KEY_FN_F10),\n            476 => Some(OsCode::KEY_FN_F11),\n            477 => Some(OsCode::KEY_FN_F12),\n            478 => Some(OsCode::KEY_FN_1),\n            479 => Some(OsCode::KEY_FN_2),\n            480 => Some(OsCode::KEY_FN_D),\n            481 => Some(OsCode::KEY_FN_E),\n            482 => Some(OsCode::KEY_FN_F),\n            483 => Some(OsCode::KEY_FN_S),\n            484 => Some(OsCode::KEY_FN_B),\n            485 => Some(OsCode::KEY_485),\n            486 => Some(OsCode::KEY_486),\n            487 => Some(OsCode::KEY_487),\n            488 => Some(OsCode::KEY_488),\n            489 => Some(OsCode::KEY_489),\n            490 => Some(OsCode::KEY_490),\n            491 => Some(OsCode::KEY_491),\n            492 => Some(OsCode::KEY_492),\n            493 => Some(OsCode::KEY_493),\n            494 => Some(OsCode::KEY_494),\n            495 => Some(OsCode::KEY_495),\n            496 => Some(OsCode::KEY_496),\n            497 => Some(OsCode::KEY_BRL_DOT1),\n            498 => Some(OsCode::KEY_BRL_DOT2),\n            499 => Some(OsCode::KEY_BRL_DOT3),\n            500 => Some(OsCode::KEY_BRL_DOT4),\n            501 => Some(OsCode::KEY_BRL_DOT5),\n            502 => Some(OsCode::KEY_BRL_DOT6),\n            503 => Some(OsCode::KEY_BRL_DOT7),\n            504 => Some(OsCode::KEY_BRL_DOT8),\n            505 => Some(OsCode::KEY_BRL_DOT9),\n            506 => Some(OsCode::KEY_BRL_DOT10),\n            507 => Some(OsCode::KEY_507),\n            508 => Some(OsCode::KEY_508),\n            509 => Some(OsCode::KEY_509),\n            510 => Some(OsCode::KEY_510),\n            511 => Some(OsCode::KEY_511),\n            512 => Some(OsCode::KEY_NUMERIC_0),\n            513 => Some(OsCode::KEY_NUMERIC_1),\n            514 => Some(OsCode::KEY_NUMERIC_2),\n            515 => Some(OsCode::KEY_NUMERIC_3),\n            516 => Some(OsCode::KEY_NUMERIC_4),\n            517 => Some(OsCode::KEY_NUMERIC_5),\n            518 => Some(OsCode::KEY_NUMERIC_6),\n            519 => Some(OsCode::KEY_NUMERIC_7),\n            520 => Some(OsCode::KEY_NUMERIC_8),\n            521 => Some(OsCode::KEY_NUMERIC_9),\n            522 => Some(OsCode::KEY_NUMERIC_STAR),\n            523 => Some(OsCode::KEY_NUMERIC_POUND),\n            524 => Some(OsCode::KEY_NUMERIC_A),\n            525 => Some(OsCode::KEY_NUMERIC_B),\n            526 => Some(OsCode::KEY_NUMERIC_C),\n            527 => Some(OsCode::KEY_NUMERIC_D),\n            528 => Some(OsCode::KEY_CAMERA_FOCUS),\n            529 => Some(OsCode::KEY_WPS_BUTTON),\n            530 => Some(OsCode::KEY_TOUCHPAD_TOGGLE),\n            531 => Some(OsCode::KEY_TOUCHPAD_ON),\n            532 => Some(OsCode::KEY_TOUCHPAD_OFF),\n            533 => Some(OsCode::KEY_CAMERA_ZOOMIN),\n            534 => Some(OsCode::KEY_CAMERA_ZOOMOUT),\n            535 => Some(OsCode::KEY_CAMERA_UP),\n            536 => Some(OsCode::KEY_CAMERA_DOWN),\n            537 => Some(OsCode::KEY_CAMERA_LEFT),\n            538 => Some(OsCode::KEY_CAMERA_RIGHT),\n            539 => Some(OsCode::KEY_ATTENDANT_ON),\n            540 => Some(OsCode::KEY_ATTENDANT_OFF),\n            541 => Some(OsCode::KEY_ATTENDANT_TOGGLE),\n            542 => Some(OsCode::KEY_LIGHTS_TOGGLE),\n            543 => Some(OsCode::KEY_543),\n            544 => Some(OsCode::BTN_DPAD_UP),\n            545 => Some(OsCode::BTN_DPAD_DOWN),\n            546 => Some(OsCode::BTN_DPAD_LEFT),\n            547 => Some(OsCode::BTN_DPAD_RIGHT),\n            548 => Some(OsCode::KEY_548),\n            549 => Some(OsCode::KEY_549),\n            550 => Some(OsCode::KEY_550),\n            551 => Some(OsCode::KEY_551),\n            552 => Some(OsCode::KEY_552),\n            553 => Some(OsCode::KEY_553),\n            554 => Some(OsCode::KEY_554),\n            555 => Some(OsCode::KEY_555),\n            556 => Some(OsCode::KEY_556),\n            557 => Some(OsCode::KEY_557),\n            558 => Some(OsCode::KEY_558),\n            559 => Some(OsCode::KEY_559),\n            560 => Some(OsCode::KEY_ALS_TOGGLE),\n            561 => Some(OsCode::KEY_ROTATE_LOCK_TOGGLE),\n            562 => Some(OsCode::KEY_562),\n            563 => Some(OsCode::KEY_563),\n            564 => Some(OsCode::KEY_564),\n            565 => Some(OsCode::KEY_565),\n            566 => Some(OsCode::KEY_566),\n            567 => Some(OsCode::KEY_567),\n            568 => Some(OsCode::KEY_568),\n            569 => Some(OsCode::KEY_569),\n            570 => Some(OsCode::KEY_570),\n            571 => Some(OsCode::KEY_571),\n            572 => Some(OsCode::KEY_572),\n            573 => Some(OsCode::KEY_573),\n            574 => Some(OsCode::KEY_574),\n            575 => Some(OsCode::KEY_575),\n            576 => Some(OsCode::KEY_BUTTONCONFIG),\n            577 => Some(OsCode::KEY_TASKMANAGER),\n            578 => Some(OsCode::KEY_JOURNAL),\n            579 => Some(OsCode::KEY_CONTROLPANEL),\n            580 => Some(OsCode::KEY_APPSELECT),\n            581 => Some(OsCode::KEY_SCREENSAVER),\n            582 => Some(OsCode::KEY_VOICECOMMAND),\n            583 => Some(OsCode::KEY_ASSISTANT),\n            584 => Some(OsCode::KEY_KBD_LAYOUT_NEXT),\n            585 => Some(OsCode::KEY_585),\n            586 => Some(OsCode::KEY_586),\n            587 => Some(OsCode::KEY_587),\n            588 => Some(OsCode::KEY_588),\n            589 => Some(OsCode::KEY_589),\n            590 => Some(OsCode::KEY_590),\n            591 => Some(OsCode::KEY_591),\n            592 => Some(OsCode::KEY_BRIGHTNESS_MIN),\n            593 => Some(OsCode::KEY_BRIGHTNESS_MAX),\n            594 => Some(OsCode::KEY_594),\n            595 => Some(OsCode::KEY_595),\n            596 => Some(OsCode::KEY_596),\n            597 => Some(OsCode::KEY_597),\n            598 => Some(OsCode::KEY_598),\n            599 => Some(OsCode::KEY_599),\n            600 => Some(OsCode::KEY_600),\n            601 => Some(OsCode::KEY_601),\n            602 => Some(OsCode::KEY_602),\n            603 => Some(OsCode::KEY_603),\n            604 => Some(OsCode::KEY_604),\n            605 => Some(OsCode::KEY_605),\n            606 => Some(OsCode::KEY_606),\n            607 => Some(OsCode::KEY_607),\n            608 => Some(OsCode::KEY_KBDINPUTASSIST_PREV),\n            609 => Some(OsCode::KEY_KBDINPUTASSIST_NEXT),\n            610 => Some(OsCode::KEY_KBDINPUTASSIST_PREVGROUP),\n            611 => Some(OsCode::KEY_KBDINPUTASSIST_NEXTGROUP),\n            612 => Some(OsCode::KEY_KBDINPUTASSIST_ACCEPT),\n            613 => Some(OsCode::KEY_KBDINPUTASSIST_CANCEL),\n            614 => Some(OsCode::KEY_RIGHT_UP),\n            615 => Some(OsCode::KEY_RIGHT_DOWN),\n            616 => Some(OsCode::KEY_LEFT_UP),\n            617 => Some(OsCode::KEY_LEFT_DOWN),\n            618 => Some(OsCode::KEY_ROOT_MENU),\n            619 => Some(OsCode::KEY_MEDIA_TOP_MENU),\n            620 => Some(OsCode::KEY_NUMERIC_11),\n            621 => Some(OsCode::KEY_NUMERIC_12),\n            622 => Some(OsCode::KEY_AUDIO_DESC),\n            623 => Some(OsCode::KEY_3D_MODE),\n            624 => Some(OsCode::KEY_NEXT_FAVORITE),\n            625 => Some(OsCode::KEY_STOP_RECORD),\n            626 => Some(OsCode::KEY_PAUSE_RECORD),\n            627 => Some(OsCode::KEY_VOD),\n            628 => Some(OsCode::KEY_UNMUTE),\n            629 => Some(OsCode::KEY_FASTREVERSE),\n            630 => Some(OsCode::KEY_SLOWREVERSE),\n            631 => Some(OsCode::KEY_DATA),\n            632 => Some(OsCode::KEY_ONSCREEN_KEYBOARD),\n            633 => Some(OsCode::KEY_633),\n            634 => Some(OsCode::KEY_634),\n            635 => Some(OsCode::KEY_635),\n            636 => Some(OsCode::KEY_636),\n            637 => Some(OsCode::KEY_637),\n            638 => Some(OsCode::KEY_638),\n            639 => Some(OsCode::KEY_639),\n            640 => Some(OsCode::KEY_640),\n            641 => Some(OsCode::KEY_641),\n            642 => Some(OsCode::KEY_642),\n            643 => Some(OsCode::KEY_643),\n            644 => Some(OsCode::KEY_644),\n            645 => Some(OsCode::KEY_645),\n            646 => Some(OsCode::KEY_646),\n            647 => Some(OsCode::KEY_647),\n            648 => Some(OsCode::KEY_648),\n            649 => Some(OsCode::KEY_649),\n            650 => Some(OsCode::KEY_650),\n            651 => Some(OsCode::KEY_651),\n            652 => Some(OsCode::KEY_652),\n            653 => Some(OsCode::KEY_653),\n            654 => Some(OsCode::KEY_654),\n            655 => Some(OsCode::KEY_655),\n            656 => Some(OsCode::KEY_656),\n            657 => Some(OsCode::KEY_657),\n            658 => Some(OsCode::KEY_658),\n            659 => Some(OsCode::KEY_659),\n            660 => Some(OsCode::KEY_660),\n            661 => Some(OsCode::KEY_661),\n            662 => Some(OsCode::KEY_662),\n            663 => Some(OsCode::KEY_663),\n            664 => Some(OsCode::KEY_664),\n            665 => Some(OsCode::KEY_665),\n            666 => Some(OsCode::KEY_666),\n            667 => Some(OsCode::KEY_667),\n            668 => Some(OsCode::KEY_668),\n            669 => Some(OsCode::KEY_669),\n            670 => Some(OsCode::KEY_670),\n            671 => Some(OsCode::KEY_671),\n            672 => Some(OsCode::KEY_672),\n            673 => Some(OsCode::KEY_673),\n            674 => Some(OsCode::KEY_674),\n            675 => Some(OsCode::KEY_675),\n            676 => Some(OsCode::KEY_676),\n            677 => Some(OsCode::KEY_677),\n            678 => Some(OsCode::KEY_678),\n            679 => Some(OsCode::KEY_679),\n            680 => Some(OsCode::KEY_680),\n            681 => Some(OsCode::KEY_681),\n            682 => Some(OsCode::KEY_682),\n            683 => Some(OsCode::KEY_683),\n            684 => Some(OsCode::KEY_684),\n            685 => Some(OsCode::KEY_685),\n            686 => Some(OsCode::KEY_686),\n            687 => Some(OsCode::KEY_687),\n            688 => Some(OsCode::KEY_688),\n            689 => Some(OsCode::KEY_689),\n            690 => Some(OsCode::KEY_690),\n            691 => Some(OsCode::KEY_691),\n            692 => Some(OsCode::KEY_692),\n            693 => Some(OsCode::KEY_693),\n            694 => Some(OsCode::KEY_694),\n            695 => Some(OsCode::KEY_695),\n            696 => Some(OsCode::KEY_696),\n            697 => Some(OsCode::KEY_697),\n            698 => Some(OsCode::KEY_698),\n            699 => Some(OsCode::KEY_699),\n            700 => Some(OsCode::KEY_700),\n            701 => Some(OsCode::KEY_701),\n            702 => Some(OsCode::KEY_702),\n            703 => Some(OsCode::KEY_703),\n            704 => Some(OsCode::BTN_TRIGGER_HAPPY1),\n            705 => Some(OsCode::BTN_TRIGGER_HAPPY2),\n            706 => Some(OsCode::BTN_TRIGGER_HAPPY3),\n            707 => Some(OsCode::BTN_TRIGGER_HAPPY4),\n            708 => Some(OsCode::BTN_TRIGGER_HAPPY5),\n            709 => Some(OsCode::BTN_TRIGGER_HAPPY6),\n            710 => Some(OsCode::BTN_TRIGGER_HAPPY7),\n            711 => Some(OsCode::BTN_TRIGGER_HAPPY8),\n            712 => Some(OsCode::BTN_TRIGGER_HAPPY9),\n            713 => Some(OsCode::BTN_TRIGGER_HAPPY10),\n            714 => Some(OsCode::BTN_TRIGGER_HAPPY11),\n            715 => Some(OsCode::BTN_TRIGGER_HAPPY12),\n            716 => Some(OsCode::BTN_TRIGGER_HAPPY13),\n            717 => Some(OsCode::BTN_TRIGGER_HAPPY14),\n            718 => Some(OsCode::BTN_TRIGGER_HAPPY15),\n            719 => Some(OsCode::BTN_TRIGGER_HAPPY16),\n            720 => Some(OsCode::BTN_TRIGGER_HAPPY17),\n            721 => Some(OsCode::BTN_TRIGGER_HAPPY18),\n            722 => Some(OsCode::BTN_TRIGGER_HAPPY19),\n            723 => Some(OsCode::BTN_TRIGGER_HAPPY20),\n            724 => Some(OsCode::BTN_TRIGGER_HAPPY21),\n            725 => Some(OsCode::BTN_TRIGGER_HAPPY22),\n            726 => Some(OsCode::BTN_TRIGGER_HAPPY23),\n            727 => Some(OsCode::BTN_TRIGGER_HAPPY24),\n            728 => Some(OsCode::BTN_TRIGGER_HAPPY25),\n            729 => Some(OsCode::BTN_TRIGGER_HAPPY26),\n            730 => Some(OsCode::BTN_TRIGGER_HAPPY27),\n            731 => Some(OsCode::BTN_TRIGGER_HAPPY28),\n            732 => Some(OsCode::BTN_TRIGGER_HAPPY29),\n            733 => Some(OsCode::BTN_TRIGGER_HAPPY30),\n            734 => Some(OsCode::BTN_TRIGGER_HAPPY31),\n            735 => Some(OsCode::BTN_TRIGGER_HAPPY32),\n            736 => Some(OsCode::BTN_TRIGGER_HAPPY33),\n            737 => Some(OsCode::BTN_TRIGGER_HAPPY34),\n            738 => Some(OsCode::BTN_TRIGGER_HAPPY35),\n            739 => Some(OsCode::BTN_TRIGGER_HAPPY36),\n            740 => Some(OsCode::BTN_TRIGGER_HAPPY37),\n            741 => Some(OsCode::BTN_TRIGGER_HAPPY38),\n            742 => Some(OsCode::BTN_TRIGGER_HAPPY39),\n            743 => Some(OsCode::BTN_TRIGGER_HAPPY40),\n            744 => Some(OsCode::BTN_MAX),\n            745 => Some(OsCode::MouseWheelUp),\n            746 => Some(OsCode::MouseWheelDown),\n            747 => Some(OsCode::MouseWheelLeft),\n            748 => Some(OsCode::MouseWheelRight),\n            767 => Some(OsCode::KEY_MAX),\n            _ => None,\n        }\n    }\n}\n"
  },
  {
    "path": "parser/src/keys/mappings.rs",
    "content": "use super::KeyCode;\nuse super::OsCode;\n\nimpl From<KeyCode> for OsCode {\n    fn from(item: KeyCode) -> Self {\n        unsafe { std::mem::transmute(item) }\n    }\n}\n\nimpl From<OsCode> for KeyCode {\n    fn from(item: OsCode) -> KeyCode {\n        unsafe { std::mem::transmute(item) }\n    }\n}\n\n#[test]\nfn roundtrip_oscode_keycode() {\n    use super::OsCode::*;\n    let oscs = &[\n        KEY_RESERVED,\n        KEY_ESC,\n        KEY_1,\n        KEY_2,\n        KEY_3,\n        KEY_4,\n        KEY_5,\n        KEY_6,\n        KEY_7,\n        KEY_8,\n        KEY_9,\n        KEY_0,\n        KEY_MINUS,\n        KEY_EQUAL,\n        KEY_BACKSPACE,\n        KEY_TAB,\n        KEY_Q,\n        KEY_W,\n        KEY_E,\n        KEY_R,\n        KEY_T,\n        KEY_Y,\n        KEY_U,\n        KEY_I,\n        KEY_O,\n        KEY_P,\n        KEY_LEFTBRACE,\n        KEY_RIGHTBRACE,\n        KEY_ENTER,\n        KEY_LEFTCTRL,\n        KEY_A,\n        KEY_S,\n        KEY_D,\n        KEY_F,\n        KEY_G,\n        KEY_H,\n        KEY_J,\n        KEY_K,\n        KEY_L,\n        KEY_SEMICOLON,\n        KEY_APOSTROPHE,\n        KEY_GRAVE,\n        KEY_LEFTSHIFT,\n        KEY_BACKSLASH,\n        KEY_Z,\n        KEY_X,\n        KEY_C,\n        KEY_V,\n        KEY_B,\n        KEY_N,\n        KEY_M,\n        KEY_COMMA,\n        KEY_DOT,\n        KEY_SLASH,\n        KEY_RIGHTSHIFT,\n        KEY_KPASTERISK,\n        KEY_LEFTALT,\n        KEY_SPACE,\n        KEY_CAPSLOCK,\n        KEY_F1,\n        KEY_F2,\n        KEY_F3,\n        KEY_F4,\n        KEY_F5,\n        KEY_F6,\n        KEY_F7,\n        KEY_F8,\n        KEY_F9,\n        KEY_F10,\n        KEY_NUMLOCK,\n        KEY_SCROLLLOCK,\n        KEY_KP7,\n        KEY_KP8,\n        KEY_KP9,\n        KEY_KPMINUS,\n        KEY_KP4,\n        KEY_KP5,\n        KEY_KP6,\n        KEY_KPPLUS,\n        KEY_KP1,\n        KEY_KP2,\n        KEY_KP3,\n        KEY_KP0,\n        KEY_KPDOT,\n        KEY_84,\n        KEY_ZENKAKUHANKAKU,\n        KEY_102ND,\n        KEY_F11,\n        KEY_F12,\n        KEY_RO,\n        KEY_KATAKANA,\n        KEY_HIRAGANA,\n        KEY_HENKAN,\n        KEY_KATAKANAHIRAGANA,\n        KEY_MUHENKAN,\n        KEY_KPJPCOMMA,\n        KEY_KPENTER,\n        KEY_RIGHTCTRL,\n        KEY_KPSLASH,\n        KEY_SYSRQ,\n        KEY_RIGHTALT,\n        KEY_LINEFEED,\n        KEY_HOME,\n        KEY_UP,\n        KEY_PAGEUP,\n        KEY_LEFT,\n        KEY_RIGHT,\n        KEY_END,\n        KEY_DOWN,\n        KEY_PAGEDOWN,\n        KEY_INSERT,\n        KEY_DELETE,\n        KEY_MACRO,\n        KEY_MUTE,\n        KEY_VOLUMEDOWN,\n        KEY_VOLUMEUP,\n        KEY_POWER,\n        KEY_KPEQUAL,\n        KEY_KPPLUSMINUS,\n        KEY_PAUSE,\n        KEY_SCALE,\n        KEY_KPCOMMA,\n        KEY_HANGEUL,\n        KEY_HANJA,\n        KEY_YEN,\n        KEY_LEFTMETA,\n        KEY_RIGHTMETA,\n        KEY_COMPOSE,\n        KEY_STOP,\n        KEY_AGAIN,\n        KEY_PROPS,\n        KEY_UNDO,\n        KEY_FRONT,\n        KEY_COPY,\n        KEY_OPEN,\n        KEY_PASTE,\n        KEY_FIND,\n        KEY_CUT,\n        KEY_HELP,\n        KEY_MENU,\n        KEY_CALC,\n        KEY_SETUP,\n        KEY_SLEEP,\n        KEY_WAKEUP,\n        KEY_FILE,\n        KEY_SENDFILE,\n        KEY_DELETEFILE,\n        KEY_XFER,\n        KEY_PROG1,\n        KEY_PROG2,\n        KEY_WWW,\n        KEY_MSDOS,\n        KEY_COFFEE,\n        KEY_ROTATE_DISPLAY,\n        KEY_CYCLEWINDOWS,\n        KEY_MAIL,\n        KEY_BOOKMARKS,\n        KEY_COMPUTER,\n        KEY_BACK,\n        KEY_FORWARD,\n        KEY_CLOSECD,\n        KEY_EJECTCD,\n        KEY_EJECTCLOSECD,\n        KEY_NEXTSONG,\n        KEY_PLAYPAUSE,\n        KEY_PREVIOUSSONG,\n        KEY_STOPCD,\n        KEY_RECORD,\n        KEY_REWIND,\n        KEY_PHONE,\n        KEY_ISO,\n        KEY_CONFIG,\n        KEY_HOMEPAGE,\n        KEY_REFRESH,\n        KEY_EXIT,\n        KEY_MOVE,\n        KEY_EDIT,\n        KEY_SCROLLUP,\n        KEY_SCROLLDOWN,\n        KEY_KPLEFTPAREN,\n        KEY_KPRIGHTPAREN,\n        KEY_NEW,\n        KEY_REDO,\n        KEY_F13,\n        KEY_F14,\n        KEY_F15,\n        KEY_F16,\n        KEY_F17,\n        KEY_F18,\n        KEY_F19,\n        KEY_F20,\n        KEY_F21,\n        KEY_F22,\n        KEY_F23,\n        KEY_F24,\n        KEY_195,\n        KEY_196,\n        KEY_197,\n        KEY_198,\n        KEY_199,\n        KEY_PLAYCD,\n        KEY_PAUSECD,\n        KEY_PROG3,\n        KEY_PROG4,\n        KEY_DASHBOARD,\n        KEY_SUSPEND,\n        KEY_CLOSE,\n        KEY_PLAY,\n        KEY_FASTFORWARD,\n        KEY_BASSBOOST,\n        KEY_PRINT,\n        KEY_HP,\n        KEY_CAMERA,\n        KEY_SOUND,\n        KEY_QUESTION,\n        KEY_EMAIL,\n        KEY_CHAT,\n        KEY_SEARCH,\n        KEY_CONNECT,\n        KEY_FINANCE,\n        KEY_SPORT,\n        KEY_SHOP,\n        KEY_ALTERASE,\n        KEY_CANCEL,\n        KEY_BRIGHTNESSDOWN,\n        KEY_BRIGHTNESSUP,\n        KEY_MEDIA,\n        KEY_SWITCHVIDEOMODE,\n        KEY_KBDILLUMTOGGLE,\n        KEY_KBDILLUMDOWN,\n        KEY_KBDILLUMUP,\n        KEY_SEND,\n        KEY_REPLY,\n        KEY_FORWARDMAIL,\n        KEY_SAVE,\n        KEY_DOCUMENTS,\n        KEY_BATTERY,\n        KEY_BLUETOOTH,\n        KEY_WLAN,\n        KEY_UWB,\n        KEY_UNKNOWN,\n        KEY_VIDEO_NEXT,\n        KEY_VIDEO_PREV,\n        KEY_BRIGHTNESS_CYCLE,\n        KEY_BRIGHTNESS_AUTO,\n        KEY_DISPLAY_OFF,\n        KEY_WWAN,\n        KEY_RFKILL,\n        KEY_MICMUTE,\n        KEY_249,\n        KEY_250,\n        KEY_251,\n        KEY_252,\n        KEY_253,\n        KEY_254,\n        KEY_255,\n        BTN_0,\n        BTN_1,\n        BTN_2,\n        BTN_3,\n        BTN_4,\n        BTN_5,\n        BTN_6,\n        BTN_7,\n        BTN_8,\n        BTN_9,\n        KEY_266,\n        KEY_267,\n        KEY_268,\n        KEY_269,\n        KEY_270,\n        KEY_271,\n        BTN_LEFT,\n        BTN_RIGHT,\n        BTN_MIDDLE,\n        BTN_SIDE,\n        BTN_EXTRA,\n        BTN_FORWARD,\n        BTN_BACK,\n        BTN_TASK,\n        KEY_280,\n        KEY_281,\n        KEY_282,\n        KEY_283,\n        KEY_284,\n        KEY_285,\n        KEY_286,\n        KEY_287,\n        BTN_TRIGGER,\n        BTN_THUMB,\n        BTN_THUMB2,\n        BTN_TOP,\n        BTN_TOP2,\n        BTN_PINKIE,\n        BTN_BASE,\n        BTN_BASE2,\n        BTN_BASE3,\n        BTN_BASE4,\n        BTN_BASE5,\n        BTN_BASE6,\n        KEY_300,\n        KEY_301,\n        KEY_302,\n        BTN_DEAD,\n        BTN_SOUTH,\n        BTN_EAST,\n        BTN_C,\n        BTN_NORTH,\n        BTN_WEST,\n        BTN_Z,\n        BTN_TL,\n        BTN_TR,\n        BTN_TL2,\n        BTN_TR2,\n        BTN_SELECT,\n        BTN_START,\n        BTN_MODE,\n        BTN_THUMBL,\n        BTN_THUMBR,\n        KEY_319,\n        BTN_TOOL_PEN,\n        BTN_TOOL_RUBBER,\n        BTN_TOOL_BRUSH,\n        BTN_TOOL_PENCIL,\n        BTN_TOOL_AIRBRUSH,\n        BTN_TOOL_FINGER,\n        BTN_TOOL_MOUSE,\n        BTN_TOOL_LENS,\n        BTN_TOOL_QUINTTAP,\n        BTN_STYLUS3,\n        BTN_TOUCH,\n        BTN_STYLUS,\n        BTN_STYLUS2,\n        BTN_TOOL_DOUBLETAP,\n        BTN_TOOL_TRIPLETAP,\n        BTN_TOOL_QUADTAP,\n        BTN_GEAR_DOWN,\n        BTN_GEAR_UP,\n        KEY_338,\n        KEY_339,\n        KEY_340,\n        KEY_341,\n        KEY_342,\n        KEY_343,\n        KEY_344,\n        KEY_345,\n        KEY_346,\n        KEY_347,\n        KEY_348,\n        KEY_349,\n        KEY_350,\n        KEY_351,\n        KEY_OK,\n        KEY_SELECT,\n        KEY_GOTO,\n        KEY_CLEAR,\n        KEY_POWER2,\n        KEY_OPTION,\n        KEY_INFO,\n        KEY_TIME,\n        KEY_VENDOR,\n        KEY_ARCHIVE,\n        KEY_PROGRAM,\n        KEY_CHANNEL,\n        KEY_FAVORITES,\n        KEY_EPG,\n        KEY_PVR,\n        KEY_MHP,\n        KEY_LANGUAGE,\n        KEY_TITLE,\n        KEY_SUBTITLE,\n        KEY_ANGLE,\n        KEY_FULL_SCREEN,\n        KEY_MODE,\n        KEY_KEYBOARD,\n        KEY_ASPECT_RATIO,\n        KEY_PC,\n        KEY_TV,\n        KEY_TV2,\n        KEY_VCR,\n        KEY_VCR2,\n        KEY_SAT,\n        KEY_SAT2,\n        KEY_CD,\n        KEY_TAPE,\n        KEY_RADIO,\n        KEY_TUNER,\n        KEY_PLAYER,\n        KEY_TEXT,\n        KEY_DVD,\n        KEY_AUX,\n        KEY_MP3,\n        KEY_AUDIO,\n        KEY_VIDEO,\n        KEY_DIRECTORY,\n        KEY_LIST,\n        KEY_MEMO,\n        KEY_CALENDAR,\n        KEY_RED,\n        KEY_GREEN,\n        KEY_YELLOW,\n        KEY_BLUE,\n        KEY_CHANNELUP,\n        KEY_CHANNELDOWN,\n        KEY_FIRST,\n        KEY_LAST,\n        KEY_AB,\n        KEY_NEXT,\n        KEY_RESTART,\n        KEY_SLOW,\n        KEY_SHUFFLE,\n        KEY_BREAK,\n        KEY_PREVIOUS,\n        KEY_DIGITS,\n        KEY_TEEN,\n        KEY_TWEN,\n        KEY_VIDEOPHONE,\n        KEY_GAMES,\n        KEY_ZOOMIN,\n        KEY_ZOOMOUT,\n        KEY_ZOOMRESET,\n        KEY_WORDPROCESSOR,\n        KEY_EDITOR,\n        KEY_SPREADSHEET,\n        KEY_GRAPHICSEDITOR,\n        KEY_PRESENTATION,\n        KEY_DATABASE,\n        KEY_NEWS,\n        KEY_VOICEMAIL,\n        KEY_ADDRESSBOOK,\n        KEY_MESSENGER,\n        KEY_DISPLAYTOGGLE,\n        KEY_SPELLCHECK,\n        KEY_LOGOFF,\n        KEY_DOLLAR,\n        KEY_EURO,\n        KEY_FRAMEBACK,\n        KEY_FRAMEFORWARD,\n        KEY_CONTEXT_MENU,\n        KEY_MEDIA_REPEAT,\n        KEY_10CHANNELSUP,\n        KEY_10CHANNELSDOWN,\n        KEY_IMAGES,\n        KEY_443,\n        KEY_444,\n        KEY_445,\n        KEY_446,\n        KEY_447,\n        KEY_DEL_EOL,\n        KEY_DEL_EOS,\n        KEY_INS_LINE,\n        KEY_DEL_LINE,\n        KEY_452,\n        KEY_453,\n        KEY_454,\n        KEY_455,\n        KEY_456,\n        KEY_457,\n        KEY_458,\n        KEY_459,\n        KEY_460,\n        KEY_461,\n        KEY_462,\n        KEY_463,\n        KEY_FN,\n        KEY_FN_ESC,\n        KEY_FN_F1,\n        KEY_FN_F2,\n        KEY_FN_F3,\n        KEY_FN_F4,\n        KEY_FN_F5,\n        KEY_FN_F6,\n        KEY_FN_F7,\n        KEY_FN_F8,\n        KEY_FN_F9,\n        KEY_FN_F10,\n        KEY_FN_F11,\n        KEY_FN_F12,\n        KEY_FN_1,\n        KEY_FN_2,\n        KEY_FN_D,\n        KEY_FN_E,\n        KEY_FN_F,\n        KEY_FN_S,\n        KEY_FN_B,\n        KEY_485,\n        KEY_486,\n        KEY_487,\n        KEY_488,\n        KEY_489,\n        KEY_490,\n        KEY_491,\n        KEY_492,\n        KEY_493,\n        KEY_494,\n        KEY_495,\n        KEY_496,\n        KEY_BRL_DOT1,\n        KEY_BRL_DOT2,\n        KEY_BRL_DOT3,\n        KEY_BRL_DOT4,\n        KEY_BRL_DOT5,\n        KEY_BRL_DOT6,\n        KEY_BRL_DOT7,\n        KEY_BRL_DOT8,\n        KEY_BRL_DOT9,\n        KEY_BRL_DOT10,\n        KEY_507,\n        KEY_508,\n        KEY_509,\n        KEY_510,\n        KEY_511,\n        KEY_NUMERIC_0,\n        KEY_NUMERIC_1,\n        KEY_NUMERIC_2,\n        KEY_NUMERIC_3,\n        KEY_NUMERIC_4,\n        KEY_NUMERIC_5,\n        KEY_NUMERIC_6,\n        KEY_NUMERIC_7,\n        KEY_NUMERIC_8,\n        KEY_NUMERIC_9,\n        KEY_NUMERIC_STAR,\n        KEY_NUMERIC_POUND,\n        KEY_NUMERIC_A,\n        KEY_NUMERIC_B,\n        KEY_NUMERIC_C,\n        KEY_NUMERIC_D,\n        KEY_CAMERA_FOCUS,\n        KEY_WPS_BUTTON,\n        KEY_TOUCHPAD_TOGGLE,\n        KEY_TOUCHPAD_ON,\n        KEY_TOUCHPAD_OFF,\n        KEY_CAMERA_ZOOMIN,\n        KEY_CAMERA_ZOOMOUT,\n        KEY_CAMERA_UP,\n        KEY_CAMERA_DOWN,\n        KEY_CAMERA_LEFT,\n        KEY_CAMERA_RIGHT,\n        KEY_ATTENDANT_ON,\n        KEY_ATTENDANT_OFF,\n        KEY_ATTENDANT_TOGGLE,\n        KEY_LIGHTS_TOGGLE,\n        KEY_543,\n        BTN_DPAD_UP,\n        BTN_DPAD_DOWN,\n        BTN_DPAD_LEFT,\n        BTN_DPAD_RIGHT,\n        KEY_548,\n        KEY_549,\n        KEY_550,\n        KEY_551,\n        KEY_552,\n        KEY_553,\n        KEY_554,\n        KEY_555,\n        KEY_556,\n        KEY_557,\n        KEY_558,\n        KEY_559,\n        KEY_ALS_TOGGLE,\n        KEY_ROTATE_LOCK_TOGGLE,\n        KEY_562,\n        KEY_563,\n        KEY_564,\n        KEY_565,\n        KEY_566,\n        KEY_567,\n        KEY_568,\n        KEY_569,\n        KEY_570,\n        KEY_571,\n        KEY_572,\n        KEY_573,\n        KEY_574,\n        KEY_575,\n        KEY_BUTTONCONFIG,\n        KEY_TASKMANAGER,\n        KEY_JOURNAL,\n        KEY_CONTROLPANEL,\n        KEY_APPSELECT,\n        KEY_SCREENSAVER,\n        KEY_VOICECOMMAND,\n        KEY_ASSISTANT,\n        KEY_KBD_LAYOUT_NEXT,\n        KEY_585,\n        KEY_586,\n        KEY_587,\n        KEY_588,\n        KEY_589,\n        KEY_590,\n        KEY_591,\n        KEY_BRIGHTNESS_MIN,\n        KEY_BRIGHTNESS_MAX,\n        KEY_594,\n        KEY_595,\n        KEY_596,\n        KEY_597,\n        KEY_598,\n        KEY_599,\n        KEY_600,\n        KEY_601,\n        KEY_602,\n        KEY_603,\n        KEY_604,\n        KEY_605,\n        KEY_606,\n        KEY_607,\n        KEY_KBDINPUTASSIST_PREV,\n        KEY_KBDINPUTASSIST_NEXT,\n        KEY_KBDINPUTASSIST_PREVGROUP,\n        KEY_KBDINPUTASSIST_NEXTGROUP,\n        KEY_KBDINPUTASSIST_ACCEPT,\n        KEY_KBDINPUTASSIST_CANCEL,\n        KEY_RIGHT_UP,\n        KEY_RIGHT_DOWN,\n        KEY_LEFT_UP,\n        KEY_LEFT_DOWN,\n        KEY_ROOT_MENU,\n        KEY_MEDIA_TOP_MENU,\n        KEY_NUMERIC_11,\n        KEY_NUMERIC_12,\n        KEY_AUDIO_DESC,\n        KEY_3D_MODE,\n        KEY_NEXT_FAVORITE,\n        KEY_STOP_RECORD,\n        KEY_PAUSE_RECORD,\n        KEY_VOD,\n        KEY_UNMUTE,\n        KEY_FASTREVERSE,\n        KEY_SLOWREVERSE,\n        KEY_DATA,\n        KEY_ONSCREEN_KEYBOARD,\n        KEY_633,\n        KEY_634,\n        KEY_635,\n        KEY_636,\n        KEY_637,\n        KEY_638,\n        KEY_639,\n        KEY_640,\n        KEY_641,\n        KEY_642,\n        KEY_643,\n        KEY_644,\n        KEY_645,\n        KEY_646,\n        KEY_647,\n        KEY_648,\n        KEY_649,\n        KEY_650,\n        KEY_651,\n        KEY_652,\n        KEY_653,\n        KEY_654,\n        KEY_655,\n        KEY_656,\n        KEY_657,\n        KEY_658,\n        KEY_659,\n        KEY_660,\n        KEY_661,\n        KEY_662,\n        KEY_663,\n        KEY_664,\n        KEY_665,\n        KEY_666,\n        KEY_667,\n        KEY_668,\n        KEY_669,\n        KEY_670,\n        KEY_671,\n        KEY_672,\n        KEY_673,\n        KEY_674,\n        KEY_675,\n        KEY_676,\n        KEY_677,\n        KEY_678,\n        KEY_679,\n        KEY_680,\n        KEY_681,\n        KEY_682,\n        KEY_683,\n        KEY_684,\n        KEY_685,\n        KEY_686,\n        KEY_687,\n        KEY_688,\n        KEY_689,\n        KEY_690,\n        KEY_691,\n        KEY_692,\n        KEY_693,\n        KEY_694,\n        KEY_695,\n        KEY_696,\n        KEY_697,\n        KEY_698,\n        KEY_699,\n        KEY_700,\n        KEY_701,\n        KEY_702,\n        KEY_703,\n        BTN_TRIGGER_HAPPY1,\n        BTN_TRIGGER_HAPPY2,\n        BTN_TRIGGER_HAPPY3,\n        BTN_TRIGGER_HAPPY4,\n        BTN_TRIGGER_HAPPY5,\n        BTN_TRIGGER_HAPPY6,\n        BTN_TRIGGER_HAPPY7,\n        BTN_TRIGGER_HAPPY8,\n        BTN_TRIGGER_HAPPY9,\n        BTN_TRIGGER_HAPPY10,\n        BTN_TRIGGER_HAPPY11,\n        BTN_TRIGGER_HAPPY12,\n        BTN_TRIGGER_HAPPY13,\n        BTN_TRIGGER_HAPPY14,\n        BTN_TRIGGER_HAPPY15,\n        BTN_TRIGGER_HAPPY16,\n        BTN_TRIGGER_HAPPY17,\n        BTN_TRIGGER_HAPPY18,\n        BTN_TRIGGER_HAPPY19,\n        BTN_TRIGGER_HAPPY20,\n        BTN_TRIGGER_HAPPY21,\n        BTN_TRIGGER_HAPPY22,\n        BTN_TRIGGER_HAPPY23,\n        BTN_TRIGGER_HAPPY24,\n        BTN_TRIGGER_HAPPY25,\n        BTN_TRIGGER_HAPPY26,\n        BTN_TRIGGER_HAPPY27,\n        BTN_TRIGGER_HAPPY28,\n        BTN_TRIGGER_HAPPY29,\n        BTN_TRIGGER_HAPPY30,\n        BTN_TRIGGER_HAPPY31,\n        BTN_TRIGGER_HAPPY32,\n        BTN_TRIGGER_HAPPY33,\n        BTN_TRIGGER_HAPPY34,\n        BTN_TRIGGER_HAPPY35,\n        BTN_TRIGGER_HAPPY36,\n        BTN_TRIGGER_HAPPY37,\n        BTN_TRIGGER_HAPPY38,\n        BTN_TRIGGER_HAPPY39,\n        BTN_TRIGGER_HAPPY40,\n        BTN_MAX,\n        MouseWheelUp,\n        MouseWheelDown,\n        MouseWheelLeft,\n        MouseWheelRight,\n        KEY_749,\n        KEY_750,\n        KEY_751,\n        KEY_752,\n        KEY_753,\n        KEY_754,\n        KEY_755,\n        KEY_756,\n        KEY_757,\n        KEY_758,\n        KEY_759,\n        KEY_760,\n        KEY_761,\n        KEY_762,\n        KEY_763,\n        KEY_764,\n        KEY_765,\n        KEY_766,\n        KEY_MAX,\n    ];\n\n    for osc in oscs.iter().copied() {\n        let roundtrip = OsCode::from(KeyCode::from(osc));\n        assert_eq!(osc, roundtrip);\n    }\n}\n"
  },
  {
    "path": "parser/src/keys/mod.rs",
    "content": "//! Platform specific code for OS key code mappings.\n\nuse kanata_keyberon::key_code::*;\nuse once_cell::sync::Lazy;\nuse parking_lot::Mutex;\nuse rustc_hash::FxHashMap as HashMap;\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\nmod linux;\n#[cfg(any(target_os = \"macos\", target_os = \"unknown\"))]\nmod macos;\n#[cfg(any(target_os = \"windows\", target_os = \"unknown\"))]\nmod windows;\n#[cfg(any(target_os = \"macos\", target_os = \"unknown\"))]\npub use macos::PageCode;\n\n#[cfg(target_os = \"windows\")]\npub use windows::VK_KPENTER_FAKE;\n\nmod mappings;\n\n#[cfg(target_os = \"unknown\")]\n#[derive(Clone, Copy)]\npub enum Platform {\n    Win,\n    Linux,\n    Macos,\n}\n\n#[cfg(target_os = \"unknown\")]\npub static OSCODE_MAPPING_VARIANT: Mutex<Platform> = Mutex::new(Platform::Linux);\n\nimpl OsCode {\n    pub fn as_u16(self) -> u16 {\n        #[cfg(target_os = \"unknown\")]\n        return match *OSCODE_MAPPING_VARIANT.lock() {\n            Platform::Win => self.as_u16_windows(),\n            Platform::Linux => self.as_u16_linux(),\n            Platform::Macos => self.as_u16_macos(),\n        };\n\n        #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n        return self.as_u16_linux();\n\n        #[cfg(target_os = \"windows\")]\n        return self.as_u16_windows();\n\n        #[cfg(target_os = \"macos\")]\n        return self.as_u16_macos();\n    }\n\n    pub fn from_u16(code: u16) -> Option<Self> {\n        #[cfg(target_os = \"unknown\")]\n        return match *OSCODE_MAPPING_VARIANT.lock() {\n            Platform::Win => OsCode::from_u16_windows(code),\n            Platform::Linux => OsCode::from_u16_linux(code),\n            Platform::Macos => OsCode::from_u16_macos(code),\n        };\n\n        #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n        return OsCode::from_u16_linux(code);\n\n        #[cfg(target_os = \"windows\")]\n        return OsCode::from_u16_windows(code);\n\n        #[cfg(target_os = \"macos\")]\n        return OsCode::from_u16_macos(code);\n    }\n\n    pub fn is_modifier(self) -> bool {\n        matches!(\n            self,\n            OsCode::KEY_LEFTSHIFT\n                | OsCode::KEY_RIGHTSHIFT\n                | OsCode::KEY_LEFTMETA\n                | OsCode::KEY_RIGHTMETA\n                | OsCode::KEY_LEFTCTRL\n                | OsCode::KEY_RIGHTCTRL\n                | OsCode::KEY_LEFTALT\n                | OsCode::KEY_RIGHTALT\n        )\n    }\n\n    #[cfg(feature = \"zippychord\")]\n    pub fn is_zippy_ignored(self) -> bool {\n        matches!(\n            self,\n            OsCode::KEY_LEFTSHIFT\n                | OsCode::KEY_RIGHTSHIFT\n                | OsCode::KEY_LEFTMETA\n                | OsCode::KEY_RIGHTMETA\n                | OsCode::KEY_LEFTCTRL\n                | OsCode::KEY_RIGHTCTRL\n                | OsCode::KEY_LEFTALT\n                | OsCode::KEY_RIGHTALT\n                | OsCode::KEY_ESC\n                | OsCode::KEY_BACKSPACE\n                | OsCode::KEY_DELETE\n        )\n    }\n}\n\nstatic CUSTOM_STRS_TO_OSCODES: Lazy<Mutex<HashMap<String, OsCode>>> = Lazy::new(|| {\n    let mut mappings = HashMap::default();\n    add_default_str_osc_mappings(&mut mappings);\n    mappings.shrink_to_fit();\n    Mutex::new(mappings)\n});\n\n/// Replaces the stateful custom `String` to `OsCode` mapping in this module with the input\n/// mapping.\n///\n/// This will change how `str_to_oscode` behaves. One could imagine that a new `struct` could be\n/// created and `str_to_oscode` would become a method on that struct instead of a standalone\n/// function. I'm too lazy to do that right now and based on how `keys` is used right now, it\n/// should not be a problem. A potential immediate issue that comes to mind is concurrent tests\n/// that have `defcustomkeys`.\npub fn replace_custom_str_oscode_mapping(mapping: &HashMap<String, OsCode>) {\n    let mut local_mapping = CUSTOM_STRS_TO_OSCODES.lock();\n    local_mapping.clear();\n    local_mapping.extend(mapping.iter().map(|kv| (kv.0.clone(), *kv.1)));\n    add_default_str_osc_mappings(&mut local_mapping);\n    local_mapping.shrink_to_fit();\n}\n\n/// Clears the stateful custom `String` to `OsCode` mapping in this module.\npub fn clear_custom_str_oscode_mapping() {\n    let mut local_mapping = CUSTOM_STRS_TO_OSCODES.lock();\n    local_mapping.clear();\n    local_mapping.shrink_to_fit();\n}\n\n/// Used for backwards compatibility. If there is hardcoded key name in `str_to_oscode` that would\n/// be useful to remap via `defcustomkeys`, then it should be moved into here. This is so that the\n/// key name can be remapped while also working for older configurations that already use it.\nfn add_default_str_osc_mappings(mapping: &mut HashMap<String, OsCode>) {\n    const DEFAULT_MAPPINGS: &[(&str, OsCode)] = &[\n        (\"+\", OsCode::KEY_KPPLUS),\n        (\"[\", OsCode::KEY_LEFTBRACE),\n        (\"]\", OsCode::KEY_RIGHTBRACE),\n        (\"{\", OsCode::KEY_LEFTBRACE),\n        (\"}\", OsCode::KEY_RIGHTBRACE),\n        (\"/\", OsCode::KEY_SLASH),\n        (\";\", OsCode::KEY_SEMICOLON),\n        (\"`\", OsCode::KEY_GRAVE),\n        (\"=\", OsCode::KEY_EQUAL),\n        (\"-\", OsCode::KEY_MINUS),\n        (\"'\", OsCode::KEY_APOSTROPHE),\n        (\",\", OsCode::KEY_COMMA),\n        (\".\", OsCode::KEY_DOT),\n        (\"\\\\\", OsCode::KEY_BACKSLASH),\n        // Mapped as backslash because in some locales/fonts, yen=backslash\n        (\"yen\", OsCode::KEY_BACKSLASH),\n        // Unicode yen is probably the yen key, so map this to a separate oscode by default.\n        (\"¥\", OsCode::KEY_YEN),\n        (\"right\", OsCode::KEY_RIGHT),\n        (\"grave\", OsCode::KEY_GRAVE),\n    ];\n    for dm in DEFAULT_MAPPINGS {\n        mapping.entry(dm.0.into()).or_insert(dm.1);\n    }\n}\n\n/// Convert a `&str` to an `OsCode`.\n///\n/// kmonad's str to key mapping is found here as a reference:\n/// https://github.com/kmonad/kmonad/blob/master/src/KMonad/Keyboard/Keycode.hs\n///\n/// Do your best to keep the str side a maximum character length of 4 so that configuration file\n/// can stay clean.\n#[rustfmt::skip]\npub fn str_to_oscode(s: &str) -> Option<OsCode> {\n    if let Some(osc) = CUSTOM_STRS_TO_OSCODES.lock().get(s) {\n        return Some(*osc);\n    }\n    Some(match s {\n        \"Backquote\" | \"grv\" | \"ˋ\" | \"˜\" => OsCode::KEY_GRAVE,\n        \"Digit1\" | \"1\" => OsCode::KEY_1,\n        \"Digit2\" | \"2\" => OsCode::KEY_2,\n        \"Digit3\" | \"3\" => OsCode::KEY_3,\n        \"Digit4\" | \"4\" => OsCode::KEY_4,\n        \"Digit5\" | \"5\" => OsCode::KEY_5,\n        \"Digit6\" | \"6\" => OsCode::KEY_6,\n        \"Digit7\" | \"7\" => OsCode::KEY_7,\n        \"Digit8\" | \"8\" => OsCode::KEY_8,\n        \"Digit9\" | \"9\" => OsCode::KEY_9,\n        \"Digit0\" | \"0\" => OsCode::KEY_0,\n        \"Minus\" | \"min\" | \"‐\" => OsCode::KEY_MINUS,\n        \"Equal\" | \"eql\" | \"₌\" => OsCode::KEY_EQUAL,\n        \"Backspace\" | \"bspc\" | \"bks\" | \"␈\" | \"⌫\"  => OsCode::KEY_BACKSPACE,\n        \"Tab\" | \"tab\" | \"⭾\" | \"↹\" => OsCode::KEY_TAB,\n        \"KeyQ\" | \"q\" => OsCode::KEY_Q,\n        \"KeyW\" | \"w\" => OsCode::KEY_W,\n        \"KeyE\" | \"e\" => OsCode::KEY_E,\n        \"KeyR\" | \"r\" => OsCode::KEY_R,\n        \"KeyT\" | \"t\" => OsCode::KEY_T,\n        \"KeyY\" | \"y\" => OsCode::KEY_Y,\n        \"KeyU\" | \"u\" => OsCode::KEY_U,\n        \"KeyI\" | \"i\" => OsCode::KEY_I,\n        \"KeyO\" | \"o\" => OsCode::KEY_O,\n        \"KeyP\" | \"p\" => OsCode::KEY_P,\n        \"BracketLeft\" | \"lbrc\" | \"【\" | \"「\" | \"〔\" | \"⎡\" => OsCode::KEY_LEFTBRACE,\n        \"BracketRight\" | \"rbrc\" | \"】\" | \"」\" | \"〕\" | \"⎣\" => OsCode::KEY_RIGHTBRACE,\n        \"CapsLock\" | \"caps\" | \"⇪\" => OsCode::KEY_CAPSLOCK,\n        \"KeyA\" | \"a\" => OsCode::KEY_A,\n        \"KeyS\" | \"s\" => OsCode::KEY_S,\n        \"KeyD\" | \"d\" => OsCode::KEY_D,\n        \"KeyF\" | \"f\" => OsCode::KEY_F,\n        \"KeyG\" | \"g\" => OsCode::KEY_G,\n        \"KeyH\" | \"h\" => OsCode::KEY_H,\n        \"KeyJ\" | \"j\" => OsCode::KEY_J,\n        \"KeyK\" | \"k\" => OsCode::KEY_K,\n        \"KeyL\" | \"l\" => OsCode::KEY_L,\n        \"Semicolon\" | \"scln\" | \"︔\" => OsCode::KEY_SEMICOLON,\n        \"Quote\" | \"apo\" | \"apos\" => OsCode::KEY_APOSTROPHE,\n        \"Enter\" | \"ret\" | \"return\" | \"ent\" | \"enter\" | \"⏎\" | \"↩\" | \"↵\" | \"↲\" | \"⤶\" | \"⎆\" | \"⌤\" | \"␤\" => OsCode::KEY_ENTER,\n        \"ShiftLeft\" | \"lshift\" | \"lshft\" | \"lsft\" | \"shft\" | \"sft\" | \"‹⇧\" => OsCode::KEY_LEFTSHIFT,\n        \"KeyZ\" | \"z\" => OsCode::KEY_Z,\n        \"KeyX\" | \"x\" => OsCode::KEY_X,\n        \"KeyC\" | \"c\" => OsCode::KEY_C,\n        \"KeyV\" | \"v\" => OsCode::KEY_V,\n        \"KeyB\" | \"b\" => OsCode::KEY_B,\n        \"KeyN\" | \"n\" => OsCode::KEY_N,\n        \"KeyM\" | \"m\" => OsCode::KEY_M,\n        \"Comma\" | \"comm\" | \"⸴\" => OsCode::KEY_COMMA,\n        \"Period\" | \"．\" => OsCode::KEY_DOT,\n        \"Slash\" | \"⁄\" => OsCode::KEY_SLASH,\n        \"Backslash\" | \"bksl\" | \"⧵\" | \"＼\" =>  OsCode::KEY_BACKSLASH,\n        \"kp=\" | \"clr\" => OsCode::KEY_CLEAR,\n        // The kp<etc> keys are also known as the numpad keys. E.g. below is numpad enter.\n        \"Numpad0\" | \"kp0\" | \"🔢₀\" => OsCode::KEY_KP0,\n        \"Numpad1\" | \"kp1\" | \"🔢₁\" => OsCode::KEY_KP1,\n        \"Numpad2\" | \"kp2\" | \"🔢₂\" => OsCode::KEY_KP2,\n        \"Numpad3\" | \"kp3\" | \"🔢₃\" => OsCode::KEY_KP3,\n        \"Numpad4\" | \"kp4\" | \"🔢₄\" => OsCode::KEY_KP4,\n        \"Numpad5\" | \"kp5\" | \"🔢₅\" => OsCode::KEY_KP5,\n        \"Numpad6\" | \"kp6\" | \"🔢₆\" => OsCode::KEY_KP6,\n        \"Numpad7\" | \"kp7\" | \"🔢₇\" => OsCode::KEY_KP7,\n        \"Numpad8\" | \"kp8\" | \"🔢₈\" => OsCode::KEY_KP8,\n        \"Numpad9\" | \"kp9\" | \"🔢₉\" => OsCode::KEY_KP9,\n        \"NumpadEnter\" | \"kprt\" | \"🔢⏎\" | \"🔢↩\" | \"🔢↵\" | \"🔢↲\" | \"🔢⤶\" | \"🔢⎆\" | \"🔢⌤\" | \"🔢␤\" => OsCode::KEY_KPENTER,\n        \"NumpadDivide\" | \"kp/\" | \"🔢⁄\" => OsCode::KEY_KPSLASH,\n        \"NumpadAdd\" | \"kp+\" | \"🔢₊\" => OsCode::KEY_KPPLUS,\n        \"NumpadMultiply\" | \"kp*\" | \"🔢∗\" => OsCode::KEY_KPASTERISK,\n        \"NumpadEqual\" | \"🔢₌\" => OsCode::KEY_KPEQUAL,\n        \"NumpadSubtract\" | \"kp-\" | \"🔢₋\" => OsCode::KEY_KPMINUS,\n        \"NumpadDecimal\" | \"kp.\" | \"🔢．\" => OsCode::KEY_KPDOT,\n        \"NumpadComma\" | \"kp,\" | \"🔢⸴\" =>OsCode::KEY_KPCOMMA,\n        \"NumpadLeftParen\" | \"leftparen\" | \"lpar\" | \"kp(\" | \"🔢₍\" => OsCode::KEY_KPLEFTPAREN,\n        \"NumpadRightParen\" | \"rightparen\" | \"rpar\" | \"kp)\" | \"🔢₎\" => OsCode::KEY_KPRIGHTPAREN,\n        \"ssrq\" | \"sys\" => OsCode::KEY_SYSRQ,\n        // Typically the Non-US backslash, near the left shift key\n        \"IntlBackslash\" | \"102d\" | \"lsgt\" | \"nubs\" | \"nonusbslash\" | \"﹨\" | \"<\" => OsCode::KEY_102ND,\n        \"ScrollLock\" | \"scrlck\" | \"slck\" | \"⇳🔒\" => OsCode::KEY_SCROLLLOCK,\n        \"Pause\" | \"pause\" | \"break\" | \"brk\" => OsCode::KEY_PAUSE,\n        \"WakeUp\" | \"wkup\" => OsCode::KEY_WAKEUP,\n        \"Escape\" | \"esc\" | \"⎋\" => OsCode::KEY_ESC,\n        \"ShiftRight\" | \"RightShift\" | \"rshift\" | \"rshft\" | \"rsft\" | \"⇧›\" => OsCode::KEY_RIGHTSHIFT,\n        \"ControlLeft\" | \"lctrl\" | \"lctl\" | \"ctl\" | \"‹⎈\" | \"‹⌃\" => OsCode::KEY_LEFTCTRL,\n        \"AltLeft\" | \"lalt\" | \"alt\" | \"‹⎇\" | \"‹⌥\" => OsCode::KEY_LEFTALT,\n        \"Space\" | \"spc\" | \"␠\" | \"␣\" => OsCode::KEY_SPACE,\n        \"AltRight\" | \"ralt\" | \"altgr\" | \"⎇›\" | \"⌥›\" | \"⇮\" => OsCode::KEY_RIGHTALT,\n        \"ContextMenu\" | \"comp\" | \"cmps\" | \"cmp\" | \"menu\" | \"apps\" | \"▤\" | \"☰\" | \"𝌆\" => OsCode::KEY_COMPOSE,\n        \"🎛\" => OsCode::KEY_DASHBOARD,\n        // Also known as Windows, GUI, Command, Super\n        \"MetaLeft\" | \"lmeta\" | \"lmet\" | \"met\" | \"‹◆\" | \"‹⌘\" | \"‹❖\" | \"‹⊞\" => OsCode::KEY_LEFTMETA,\n        \"MetaRight\" | \"rmeta\" | \"rmet\" | \"◆›\" | \"⌘›\" | \"❖›\" | \"⊞›\" => OsCode::KEY_RIGHTMETA,\n        \"ControlRight\" | \"rctrl\" | \"rctl\" | \"⎈›\" | \"⌃›\" => OsCode::KEY_RIGHTCTRL,\n        \"Delete\" | \"del\" | \"␡\" | \"⌦\" => OsCode::KEY_DELETE,\n        \"Insert\" | \"ins\" | \"⎀\" => OsCode::KEY_INSERT,\n        \"BrowserBack\" | \"bck\" => OsCode::KEY_BACK,\n        \"BrowserForward\" | \"fwd\" => OsCode::KEY_FORWARD,\n        \"PageUp\" | \"pgup\" | \"⇞\" | \"⎗\" => OsCode::KEY_PAGEUP,\n        \"PageDown\" | \"pgdn\" | \"⇟\" | \"⎘\" => OsCode::KEY_PAGEDOWN,\n        \"ArrowUp\" | \"up\" | \"▲\" | \"↑\" => OsCode::KEY_UP,\n        \"ArrowDown\" | \"down\" | \"▼\" | \"↓\" => OsCode::KEY_DOWN,\n        \"ArrowLeft\" | \"lft\" | \"left\" | \"◀\" | \"←\" => OsCode::KEY_LEFT,\n        \"ArrowRight\" | \"rght\" | \"▶\" | \"→\" => OsCode::KEY_RIGHT,\n        \"Home\" | \"home\" | \"⇤\" | \"⤒\" | \"↖\" | \"⇱\" => OsCode::KEY_HOME,\n        \"End\" | \"end\" | \"⇥\" | \"⤓\" | \"↘\" | \"⇲\" => OsCode::KEY_END,\n        \"NumLock\" | \"nlck\" | \"nlk\" | \"⇭\"=> OsCode::KEY_NUMLOCK,\n        \"VolumeMute\" | \"mute\"  | \"🔇\" | \"🔈⓪\" | \"🔈⓿\" | \"🔈₀\" => OsCode::KEY_MUTE,\n        \"VolumeUp\" | \"volu\" | \"🔊\" | \"🔈+\" | \"🔈➕\" | \"🔈₊\" | \"🔈⊕\" => OsCode::KEY_VOLUMEUP,\n        \"VolumeDown\" | \"voldwn\" | \"vold\" | \"🔉\" | \"🔈−\" | \"🔈➖\" | \"🔈₋\" | \"🔈⊖\" => OsCode::KEY_VOLUMEDOWN,\n        \"EjectCD\" | \"eject\" => OsCode::KEY_EJECTCD,\n        \"brup\" | \"bru\" | \"🔆\" => OsCode::KEY_BRIGHTNESSUP,\n        \"brdown\" | \"brdwn\" | \"brdn\" | \"🔅\" => OsCode::KEY_BRIGHTNESSDOWN,\n        \"blup\" | \"⌨💡+\" | \"⌨💡➕\" | \"⌨💡₊\" | \"⌨💡⊕\" => OsCode::KEY_KBDILLUMUP,\n        \"bldn\" | \"⌨💡−\" | \"⌨💡➖\" | \"⌨💡₋\" | \"⌨💡⊖\" => OsCode::KEY_KBDILLUMDOWN,\n        \"MediaTrackNext\" | \"next\" | \"▶▶\" => OsCode::KEY_NEXTSONG,\n        \"MediaPlayPause\" | \"pp\" | \"▶⏸\" => OsCode::KEY_PLAYPAUSE,\n        \"MediaTrackPrevious\" | \"prev\" | \"◀◀\" => OsCode::KEY_PREVIOUSSONG,\n        \"F1\" | \"f1\" => OsCode::KEY_F1,\n        \"F2\" | \"f2\" => OsCode::KEY_F2,\n        \"F3\" | \"f3\" => OsCode::KEY_F3,\n        \"F4\" | \"f4\" => OsCode::KEY_F4,\n        \"F5\" | \"f5\" => OsCode::KEY_F5,\n        \"F6\" | \"f6\" => OsCode::KEY_F6,\n        \"F7\" | \"f7\" => OsCode::KEY_F7,\n        \"F8\" | \"f8\" => OsCode::KEY_F8,\n        \"F9\" | \"f9\" => OsCode::KEY_F9,\n        \"F10\" | \"f10\" => OsCode::KEY_F10,\n        \"F11\" | \"f11\" => OsCode::KEY_F11,\n        \"F12\" | \"f12\" => OsCode::KEY_F12,\n        \"F13\" | \"f13\" => OsCode::KEY_F13,\n        \"F14\" | \"f14\" => OsCode::KEY_F14,\n        \"F15\" | \"f15\" => OsCode::KEY_F15,\n        \"F16\" | \"f16\" => OsCode::KEY_F16,\n        \"F17\" | \"f17\" => OsCode::KEY_F17,\n        \"F18\" | \"f18\" => OsCode::KEY_F18,\n        \"F19\" | \"f19\" => OsCode::KEY_F19,\n        \"F20\" | \"f20\" => OsCode::KEY_F20,\n        \"F21\" | \"f21\" => OsCode::KEY_F21,\n        \"F22\" | \"f22\" => OsCode::KEY_F22,\n        \"F23\" | \"f23\" => OsCode::KEY_F23,\n        \"F24\" | \"f24\" => OsCode::KEY_F24,\n        #[cfg(any(target_os = \"macos\", target_os = \"unknown\", target_os = \"linux\"))]\n        \"fn\" | \"🌐\" | \"ƒ\" | \"ⓕ\" | \"Ⓕ\" | \"🄵\" | \"🅕\" | \"🅵\" => OsCode::KEY_FN,\n        #[cfg(target_os = \"windows\")]\n        \"kana\" | \"katakana\" | \"katakanahiragana\" => OsCode::KEY_HANGEUL,\n        #[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\n        \"kana\" | \"katakanahiragana\" => OsCode::KEY_KATAKANAHIRAGANA,\n        #[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\n        \"hiragana\" => OsCode::KEY_HIRAGANA,\n        #[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\n        \"katakana\" => OsCode::KEY_KATAKANA,\n        \"cnv\" | \"conv\" | \"henk\" | \"hnk\" | \"henkan\" => OsCode::KEY_HENKAN,\n        \"ncnv\" | \"mhnk\" | \"muhenkan\" => OsCode::KEY_MUHENKAN,\n        #[cfg(target_os = \"macos\")]\n        \"Lang1\" | \"kana\" => OsCode::KEY_HANGEUL,\n        #[cfg(any(target_os = \"macos\", target_os = \"unknown\"))]\n        \"Lang2\" | \"eisu\" => OsCode::KEY_HANJA,\n\n        \"IntlRo\" | \"ro\" => OsCode::KEY_RO,\n\n        #[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\n        \"PrintScreen\" | \"prtsc\" | \"prnt\" | \"⎙\" => OsCode::KEY_SYSRQ,\n        #[cfg(target_os = \"windows\")]\n        \"PrintScreen\" | \"prtsc\" | \"prnt\" | \"⎙\" => OsCode::KEY_PRINT,\n\n        \"mlft\" | \"mouseleft\" | \"🖰1\" | \"‹🖰\" => OsCode::BTN_LEFT,\n        \"mrgt\" | \"mouseright\" | \"🖰2\" | \"🖰›\" => OsCode::BTN_RIGHT,\n        \"mmid\" | \"mousemid\" | \"🖰3\" => OsCode::BTN_MIDDLE,\n        \"mbck\" | \"mousebackward\" | \"🖰4\" => OsCode::BTN_SIDE,\n        \"mfwd\" | \"mouseforward\" | \"🖰5\" => OsCode::BTN_EXTRA,\n        \"mwu\" | \"mousewheelup\" => OsCode::MouseWheelUp,\n        \"mwd\" | \"mousewheeldown\" => OsCode::MouseWheelDown,\n        \"mwl\" | \"mousewheelleft\" => OsCode::MouseWheelLeft,\n        \"mwr\" | \"mousewheelright\" => OsCode::MouseWheelRight,\n\n        \"hmpg\" | \"homepage\" => OsCode::KEY_HOMEPAGE,\n        \"mdia\" | \"media\" => OsCode::KEY_MEDIA,\n        \"LaunchMail\" | \"mail\" => OsCode::KEY_MAIL,\n        \"email\" => OsCode::KEY_EMAIL,\n        \"calc\" => OsCode::KEY_CALC,\n\n        // NOTE: these are linux-only right now due to missing the mappings in windows.rs\n        #[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\n        \"plyr\" | \"player\" => OsCode::KEY_PLAYER,\n        #[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\n        \"powr\" | \"power\" => OsCode::KEY_POWER,\n        #[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"unknown\"))]\n        \"zzz\" | \"sleep\" => OsCode::KEY_SLEEP,\n\n        \"sls\" | \"SpotLightSearch\" => OsCode::KEY_249,\n        \"dtn\" | \"Dictation\" => OsCode::KEY_250,\n        \"dnd\" | \"DoNotDisturb\" => OsCode::KEY_251,\n        \"mctl\" | \"MissionControl\" => OsCode::KEY_252,\n        \"lpad\" | \"LaunchPad\" => OsCode::KEY_253,\n\n        // Keys that behave as no-ops but can be used in sequences.\n        // Also see: POTENTIAL PROBLEM - G-keys\n        \"nop0\" => OsCode::KEY_676,\n        \"nop1\" => OsCode::KEY_677,\n        \"nop2\" => OsCode::KEY_678,\n        \"nop3\" => OsCode::KEY_679,\n        \"nop4\" => OsCode::KEY_680,\n        \"nop5\" => OsCode::KEY_681,\n        \"nop6\" => OsCode::KEY_682,\n        \"nop7\" => OsCode::KEY_683,\n        \"nop8\" => OsCode::KEY_684,\n        \"nop9\" => OsCode::KEY_685,\n\n        // has no output mapping. only intended to be used in the input\n        // position, in conjunction with `mouse-movement-key mvmt`\n        \"mvmt\" | \"mousemovement\" | \"🖰mv\" => OsCode::KEY_766,\n\n        _ => return None,\n    })\n}\n\n/// This is a shameless copy of evdev_rs::enums::EV_KEY.\n/// I've added the Copy trait and I'll be able\n/// to added my own Impl(s) to it\n#[repr(u16)]\n#[allow(non_camel_case_types)]\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]\npub enum OsCode {\n    KEY_RESERVED = 0,\n    KEY_ESC = 1,\n    KEY_1 = 2,\n    KEY_2 = 3,\n    KEY_3 = 4,\n    KEY_4 = 5,\n    KEY_5 = 6,\n    KEY_6 = 7,\n    KEY_7 = 8,\n    KEY_8 = 9,\n    KEY_9 = 10,\n    KEY_0 = 11,\n    KEY_MINUS = 12,\n    KEY_EQUAL = 13,\n    KEY_BACKSPACE = 14,\n    KEY_TAB = 15,\n    KEY_Q = 16,\n    KEY_W = 17,\n    KEY_E = 18,\n    KEY_R = 19,\n    KEY_T = 20,\n    KEY_Y = 21,\n    KEY_U = 22,\n    KEY_I = 23,\n    KEY_O = 24,\n    KEY_P = 25,\n    KEY_LEFTBRACE = 26,\n    KEY_RIGHTBRACE = 27,\n    KEY_ENTER = 28,\n    KEY_LEFTCTRL = 29,\n    KEY_A = 30,\n    KEY_S = 31,\n    KEY_D = 32,\n    KEY_F = 33,\n    KEY_G = 34,\n    KEY_H = 35,\n    KEY_J = 36,\n    KEY_K = 37,\n    KEY_L = 38,\n    KEY_SEMICOLON = 39,\n    KEY_APOSTROPHE = 40,\n    KEY_GRAVE = 41,\n    KEY_LEFTSHIFT = 42,\n    KEY_BACKSLASH = 43,\n    KEY_Z = 44,\n    KEY_X = 45,\n    KEY_C = 46,\n    KEY_V = 47,\n    KEY_B = 48,\n    KEY_N = 49,\n    KEY_M = 50,\n    KEY_COMMA = 51,\n    KEY_DOT = 52,\n    KEY_SLASH = 53,\n    KEY_RIGHTSHIFT = 54,\n    KEY_KPASTERISK = 55,\n    KEY_LEFTALT = 56,\n    KEY_SPACE = 57,\n    KEY_CAPSLOCK = 58,\n    KEY_F1 = 59,\n    KEY_F2 = 60,\n    KEY_F3 = 61,\n    KEY_F4 = 62,\n    KEY_F5 = 63,\n    KEY_F6 = 64,\n    KEY_F7 = 65,\n    KEY_F8 = 66,\n    KEY_F9 = 67,\n    KEY_F10 = 68,\n    KEY_NUMLOCK = 69,\n    KEY_SCROLLLOCK = 70,\n    KEY_KP7 = 71,\n    KEY_KP8 = 72,\n    KEY_KP9 = 73,\n    KEY_KPMINUS = 74,\n    KEY_KP4 = 75,\n    KEY_KP5 = 76,\n    KEY_KP6 = 77,\n    KEY_KPPLUS = 78,\n    KEY_KP1 = 79,\n    KEY_KP2 = 80,\n    KEY_KP3 = 81,\n    KEY_KP0 = 82,\n    KEY_KPDOT = 83,\n    KEY_84 = 84,\n    KEY_ZENKAKUHANKAKU = 85,\n    KEY_102ND = 86,\n    KEY_F11 = 87,\n    KEY_F12 = 88,\n    KEY_RO = 89,\n    KEY_KATAKANA = 90,\n    KEY_HIRAGANA = 91,\n    KEY_HENKAN = 92,\n    KEY_KATAKANAHIRAGANA = 93,\n    KEY_MUHENKAN = 94,\n    KEY_KPJPCOMMA = 95,\n    KEY_KPENTER = 96,\n    KEY_RIGHTCTRL = 97,\n    KEY_KPSLASH = 98,\n    KEY_SYSRQ = 99,\n    KEY_RIGHTALT = 100,\n    KEY_LINEFEED = 101,\n    KEY_HOME = 102,\n    KEY_UP = 103,\n    KEY_PAGEUP = 104,\n    KEY_LEFT = 105,\n    KEY_RIGHT = 106,\n    KEY_END = 107,\n    KEY_DOWN = 108,\n    KEY_PAGEDOWN = 109,\n    KEY_INSERT = 110,\n    KEY_DELETE = 111,\n    KEY_MACRO = 112,\n    KEY_MUTE = 113,\n    KEY_VOLUMEDOWN = 114,\n    KEY_VOLUMEUP = 115,\n    KEY_POWER = 116,\n    KEY_KPEQUAL = 117,\n    KEY_KPPLUSMINUS = 118,\n    KEY_PAUSE = 119,\n    KEY_SCALE = 120,\n    KEY_KPCOMMA = 121,\n    KEY_HANGEUL = 122,\n    KEY_HANJA = 123,\n    KEY_YEN = 124,\n    KEY_LEFTMETA = 125,\n    KEY_RIGHTMETA = 126,\n    KEY_COMPOSE = 127,\n    KEY_STOP = 128,\n    KEY_AGAIN = 129,\n    KEY_PROPS = 130,\n    KEY_UNDO = 131,\n    KEY_FRONT = 132,\n    KEY_COPY = 133,\n    KEY_OPEN = 134,\n    KEY_PASTE = 135,\n    KEY_FIND = 136,\n    KEY_CUT = 137,\n    KEY_HELP = 138,\n    KEY_MENU = 139,\n    KEY_CALC = 140,\n    KEY_SETUP = 141,\n    KEY_SLEEP = 142,\n    KEY_WAKEUP = 143,\n    KEY_FILE = 144,\n    KEY_SENDFILE = 145,\n    KEY_DELETEFILE = 146,\n    KEY_XFER = 147,\n    KEY_PROG1 = 148,\n    KEY_PROG2 = 149,\n    KEY_WWW = 150,\n    KEY_MSDOS = 151,\n    KEY_COFFEE = 152,\n    KEY_ROTATE_DISPLAY = 153,\n    KEY_CYCLEWINDOWS = 154,\n    KEY_MAIL = 155,\n    KEY_BOOKMARKS = 156,\n    KEY_COMPUTER = 157,\n    KEY_BACK = 158,\n    KEY_FORWARD = 159,\n    KEY_CLOSECD = 160,\n    KEY_EJECTCD = 161,\n    KEY_EJECTCLOSECD = 162,\n    KEY_NEXTSONG = 163,\n    KEY_PLAYPAUSE = 164,\n    KEY_PREVIOUSSONG = 165,\n    KEY_STOPCD = 166,\n    KEY_RECORD = 167,\n    KEY_REWIND = 168,\n    KEY_PHONE = 169,\n    KEY_ISO = 170,\n    KEY_CONFIG = 171,\n    KEY_HOMEPAGE = 172,\n    KEY_REFRESH = 173,\n    KEY_EXIT = 174,\n    KEY_MOVE = 175,\n    KEY_EDIT = 176,\n    KEY_SCROLLUP = 177,\n    KEY_SCROLLDOWN = 178,\n    KEY_KPLEFTPAREN = 179,\n    KEY_KPRIGHTPAREN = 180,\n    KEY_NEW = 181,\n    KEY_REDO = 182,\n    KEY_F13 = 183,\n    KEY_F14 = 184,\n    KEY_F15 = 185,\n    KEY_F16 = 186,\n    KEY_F17 = 187,\n    KEY_F18 = 188,\n    KEY_F19 = 189,\n    KEY_F20 = 190,\n    KEY_F21 = 191,\n    KEY_F22 = 192,\n    KEY_F23 = 193,\n    KEY_F24 = 194,\n    KEY_195 = 195,\n    KEY_196 = 196,\n    KEY_197 = 197,\n    KEY_198 = 198,\n    KEY_199 = 199,\n    KEY_PLAYCD = 200,\n    KEY_PAUSECD = 201,\n    KEY_PROG3 = 202,\n    KEY_PROG4 = 203,\n    KEY_DASHBOARD = 204,\n    KEY_SUSPEND = 205,\n    KEY_CLOSE = 206,\n    KEY_PLAY = 207,\n    KEY_FASTFORWARD = 208,\n    KEY_BASSBOOST = 209,\n    KEY_PRINT = 210,\n    KEY_HP = 211,\n    KEY_CAMERA = 212,\n    KEY_SOUND = 213,\n    KEY_QUESTION = 214,\n    KEY_EMAIL = 215,\n    KEY_CHAT = 216,\n    KEY_SEARCH = 217,\n    KEY_CONNECT = 218,\n    KEY_FINANCE = 219,\n    KEY_SPORT = 220,\n    KEY_SHOP = 221,\n    KEY_ALTERASE = 222,\n    KEY_CANCEL = 223,\n    KEY_BRIGHTNESSDOWN = 224,\n    KEY_BRIGHTNESSUP = 225,\n    KEY_MEDIA = 226,\n    KEY_SWITCHVIDEOMODE = 227,\n    KEY_KBDILLUMTOGGLE = 228,\n    KEY_KBDILLUMDOWN = 229,\n    KEY_KBDILLUMUP = 230,\n    KEY_SEND = 231,\n    KEY_REPLY = 232,\n    KEY_FORWARDMAIL = 233,\n    KEY_SAVE = 234,\n    KEY_DOCUMENTS = 235,\n    KEY_BATTERY = 236,\n    KEY_BLUETOOTH = 237,\n    KEY_WLAN = 238,\n    KEY_UWB = 239,\n    KEY_UNKNOWN = 240,\n    KEY_VIDEO_NEXT = 241,\n    KEY_VIDEO_PREV = 242,\n    KEY_BRIGHTNESS_CYCLE = 243,\n    KEY_BRIGHTNESS_AUTO = 244,\n    KEY_DISPLAY_OFF = 245,\n    KEY_WWAN = 246,\n    KEY_RFKILL = 247,\n    KEY_MICMUTE = 248,\n    KEY_249 = 249,\n    KEY_250 = 250,\n    KEY_251 = 251,\n    KEY_252 = 252,\n    KEY_253 = 253,\n    KEY_254 = 254,\n    KEY_255 = 255,\n    BTN_0 = 256,\n    BTN_1 = 257,\n    BTN_2 = 258,\n    BTN_3 = 259,\n    BTN_4 = 260,\n    BTN_5 = 261,\n    BTN_6 = 262,\n    BTN_7 = 263,\n    BTN_8 = 264,\n    BTN_9 = 265,\n    KEY_266 = 266,\n    KEY_267 = 267,\n    KEY_268 = 268,\n    KEY_269 = 269,\n    KEY_270 = 270,\n    KEY_271 = 271,\n    BTN_LEFT = 272,\n    BTN_RIGHT = 273,\n    BTN_MIDDLE = 274,\n    BTN_SIDE = 275,\n    BTN_EXTRA = 276,\n    BTN_FORWARD = 277,\n    BTN_BACK = 278,\n    BTN_TASK = 279,\n    KEY_280 = 280,\n    KEY_281 = 281,\n    KEY_282 = 282,\n    KEY_283 = 283,\n    KEY_284 = 284,\n    KEY_285 = 285,\n    KEY_286 = 286,\n    KEY_287 = 287,\n    BTN_TRIGGER = 288,\n    BTN_THUMB = 289,\n    BTN_THUMB2 = 290,\n    BTN_TOP = 291,\n    BTN_TOP2 = 292,\n    BTN_PINKIE = 293,\n    BTN_BASE = 294,\n    BTN_BASE2 = 295,\n    BTN_BASE3 = 296,\n    BTN_BASE4 = 297,\n    BTN_BASE5 = 298,\n    BTN_BASE6 = 299,\n    KEY_300 = 300,\n    KEY_301 = 301,\n    KEY_302 = 302,\n    BTN_DEAD = 303,\n    BTN_SOUTH = 304,\n    BTN_EAST = 305,\n    BTN_C = 306,\n    BTN_NORTH = 307,\n    BTN_WEST = 308,\n    BTN_Z = 309,\n    BTN_TL = 310,\n    BTN_TR = 311,\n    BTN_TL2 = 312,\n    BTN_TR2 = 313,\n    BTN_SELECT = 314,\n    BTN_START = 315,\n    BTN_MODE = 316,\n    BTN_THUMBL = 317,\n    BTN_THUMBR = 318,\n    KEY_319 = 319,\n    BTN_TOOL_PEN = 320,\n    BTN_TOOL_RUBBER = 321,\n    BTN_TOOL_BRUSH = 322,\n    BTN_TOOL_PENCIL = 323,\n    BTN_TOOL_AIRBRUSH = 324,\n    BTN_TOOL_FINGER = 325,\n    BTN_TOOL_MOUSE = 326,\n    BTN_TOOL_LENS = 327,\n    BTN_TOOL_QUINTTAP = 328,\n    BTN_STYLUS3 = 329,\n    BTN_TOUCH = 330,\n    BTN_STYLUS = 331,\n    BTN_STYLUS2 = 332,\n    BTN_TOOL_DOUBLETAP = 333,\n    BTN_TOOL_TRIPLETAP = 334,\n    BTN_TOOL_QUADTAP = 335,\n    BTN_GEAR_DOWN = 336,\n    BTN_GEAR_UP = 337,\n    KEY_338 = 338,\n    KEY_339 = 339,\n    KEY_340 = 340,\n    KEY_341 = 341,\n    KEY_342 = 342,\n    KEY_343 = 343,\n    KEY_344 = 344,\n    KEY_345 = 345,\n    KEY_346 = 346,\n    KEY_347 = 347,\n    KEY_348 = 348,\n    KEY_349 = 349,\n    KEY_350 = 350,\n    KEY_351 = 351,\n    KEY_OK = 352,\n    KEY_SELECT = 353,\n    KEY_GOTO = 354,\n    KEY_CLEAR = 355,\n    KEY_POWER2 = 356,\n    KEY_OPTION = 357,\n    KEY_INFO = 358,\n    KEY_TIME = 359,\n    KEY_VENDOR = 360,\n    KEY_ARCHIVE = 361,\n    KEY_PROGRAM = 362,\n    KEY_CHANNEL = 363,\n    KEY_FAVORITES = 364,\n    KEY_EPG = 365,\n    KEY_PVR = 366,\n    KEY_MHP = 367,\n    KEY_LANGUAGE = 368,\n    KEY_TITLE = 369,\n    KEY_SUBTITLE = 370,\n    KEY_ANGLE = 371,\n    KEY_FULL_SCREEN = 372,\n    KEY_MODE = 373,\n    KEY_KEYBOARD = 374,\n    KEY_ASPECT_RATIO = 375,\n    KEY_PC = 376,\n    KEY_TV = 377,\n    KEY_TV2 = 378,\n    KEY_VCR = 379,\n    KEY_VCR2 = 380,\n    KEY_SAT = 381,\n    KEY_SAT2 = 382,\n    KEY_CD = 383,\n    KEY_TAPE = 384,\n    KEY_RADIO = 385,\n    KEY_TUNER = 386,\n    KEY_PLAYER = 387,\n    KEY_TEXT = 388,\n    KEY_DVD = 389,\n    KEY_AUX = 390,\n    KEY_MP3 = 391,\n    KEY_AUDIO = 392,\n    KEY_VIDEO = 393,\n    KEY_DIRECTORY = 394,\n    KEY_LIST = 395,\n    KEY_MEMO = 396,\n    KEY_CALENDAR = 397,\n    KEY_RED = 398,\n    KEY_GREEN = 399,\n    KEY_YELLOW = 400,\n    KEY_BLUE = 401,\n    KEY_CHANNELUP = 402,\n    KEY_CHANNELDOWN = 403,\n    KEY_FIRST = 404,\n    KEY_LAST = 405,\n    KEY_AB = 406,\n    KEY_NEXT = 407,\n    KEY_RESTART = 408,\n    KEY_SLOW = 409,\n    KEY_SHUFFLE = 410,\n    KEY_BREAK = 411,\n    KEY_PREVIOUS = 412,\n    KEY_DIGITS = 413,\n    KEY_TEEN = 414,\n    KEY_TWEN = 415,\n    KEY_VIDEOPHONE = 416,\n    KEY_GAMES = 417,\n    KEY_ZOOMIN = 418,\n    KEY_ZOOMOUT = 419,\n    KEY_ZOOMRESET = 420,\n    KEY_WORDPROCESSOR = 421,\n    KEY_EDITOR = 422,\n    KEY_SPREADSHEET = 423,\n    KEY_GRAPHICSEDITOR = 424,\n    KEY_PRESENTATION = 425,\n    KEY_DATABASE = 426,\n    KEY_NEWS = 427,\n    KEY_VOICEMAIL = 428,\n    KEY_ADDRESSBOOK = 429,\n    KEY_MESSENGER = 430,\n    KEY_DISPLAYTOGGLE = 431,\n    KEY_SPELLCHECK = 432,\n    KEY_LOGOFF = 433,\n    KEY_DOLLAR = 434,\n    KEY_EURO = 435,\n    KEY_FRAMEBACK = 436,\n    KEY_FRAMEFORWARD = 437,\n    KEY_CONTEXT_MENU = 438,\n    KEY_MEDIA_REPEAT = 439,\n    KEY_10CHANNELSUP = 440,\n    KEY_10CHANNELSDOWN = 441,\n    KEY_IMAGES = 442,\n    KEY_443 = 443,\n    KEY_444 = 444,\n    KEY_445 = 445,\n    KEY_446 = 446,\n    KEY_447 = 447,\n    KEY_DEL_EOL = 448,\n    KEY_DEL_EOS = 449,\n    KEY_INS_LINE = 450,\n    KEY_DEL_LINE = 451,\n    KEY_452 = 452,\n    KEY_453 = 453,\n    KEY_454 = 454,\n    KEY_455 = 455,\n    KEY_456 = 456,\n    KEY_457 = 457,\n    KEY_458 = 458,\n    KEY_459 = 459,\n    KEY_460 = 460,\n    KEY_461 = 461,\n    KEY_462 = 462,\n    KEY_463 = 463,\n    KEY_FN = 464,\n    KEY_FN_ESC = 465,\n    KEY_FN_F1 = 466,\n    KEY_FN_F2 = 467,\n    KEY_FN_F3 = 468,\n    KEY_FN_F4 = 469,\n    KEY_FN_F5 = 470,\n    KEY_FN_F6 = 471,\n    KEY_FN_F7 = 472,\n    KEY_FN_F8 = 473,\n    KEY_FN_F9 = 474,\n    KEY_FN_F10 = 475,\n    KEY_FN_F11 = 476,\n    KEY_FN_F12 = 477,\n    KEY_FN_1 = 478,\n    KEY_FN_2 = 479,\n    KEY_FN_D = 480,\n    KEY_FN_E = 481,\n    KEY_FN_F = 482,\n    KEY_FN_S = 483,\n    KEY_FN_B = 484,\n    KEY_485 = 485,\n    KEY_486 = 486,\n    KEY_487 = 487,\n    KEY_488 = 488,\n    KEY_489 = 489,\n    KEY_490 = 490,\n    KEY_491 = 491,\n    KEY_492 = 492,\n    KEY_493 = 493,\n    KEY_494 = 494,\n    KEY_495 = 495,\n    KEY_496 = 496,\n    KEY_BRL_DOT1 = 497,\n    KEY_BRL_DOT2 = 498,\n    KEY_BRL_DOT3 = 499,\n    KEY_BRL_DOT4 = 500,\n    KEY_BRL_DOT5 = 501,\n    KEY_BRL_DOT6 = 502,\n    KEY_BRL_DOT7 = 503,\n    KEY_BRL_DOT8 = 504,\n    KEY_BRL_DOT9 = 505,\n    KEY_BRL_DOT10 = 506,\n    KEY_507 = 507,\n    KEY_508 = 508,\n    KEY_509 = 509,\n    KEY_510 = 510,\n    KEY_511 = 511,\n    KEY_NUMERIC_0 = 512,\n    KEY_NUMERIC_1 = 513,\n    KEY_NUMERIC_2 = 514,\n    KEY_NUMERIC_3 = 515,\n    KEY_NUMERIC_4 = 516,\n    KEY_NUMERIC_5 = 517,\n    KEY_NUMERIC_6 = 518,\n    KEY_NUMERIC_7 = 519,\n    KEY_NUMERIC_8 = 520,\n    KEY_NUMERIC_9 = 521,\n    KEY_NUMERIC_STAR = 522,\n    KEY_NUMERIC_POUND = 523,\n    KEY_NUMERIC_A = 524,\n    KEY_NUMERIC_B = 525,\n    KEY_NUMERIC_C = 526,\n    KEY_NUMERIC_D = 527,\n    KEY_CAMERA_FOCUS = 528,\n    KEY_WPS_BUTTON = 529,\n    KEY_TOUCHPAD_TOGGLE = 530,\n    KEY_TOUCHPAD_ON = 531,\n    KEY_TOUCHPAD_OFF = 532,\n    KEY_CAMERA_ZOOMIN = 533,\n    KEY_CAMERA_ZOOMOUT = 534,\n    KEY_CAMERA_UP = 535,\n    KEY_CAMERA_DOWN = 536,\n    KEY_CAMERA_LEFT = 537,\n    KEY_CAMERA_RIGHT = 538,\n    KEY_ATTENDANT_ON = 539,\n    KEY_ATTENDANT_OFF = 540,\n    KEY_ATTENDANT_TOGGLE = 541,\n    KEY_LIGHTS_TOGGLE = 542,\n    KEY_543 = 543,\n    BTN_DPAD_UP = 544,\n    BTN_DPAD_DOWN = 545,\n    BTN_DPAD_LEFT = 546,\n    BTN_DPAD_RIGHT = 547,\n    KEY_548 = 548,\n    KEY_549 = 549,\n    KEY_550 = 550,\n    KEY_551 = 551,\n    KEY_552 = 552,\n    KEY_553 = 553,\n    KEY_554 = 554,\n    KEY_555 = 555,\n    KEY_556 = 556,\n    KEY_557 = 557,\n    KEY_558 = 558,\n    KEY_559 = 559,\n    KEY_ALS_TOGGLE = 560,\n    KEY_ROTATE_LOCK_TOGGLE = 561,\n    KEY_562 = 562,\n    KEY_563 = 563,\n    KEY_564 = 564,\n    KEY_565 = 565,\n    KEY_566 = 566,\n    KEY_567 = 567,\n    KEY_568 = 568,\n    KEY_569 = 569,\n    KEY_570 = 570,\n    KEY_571 = 571,\n    KEY_572 = 572,\n    KEY_573 = 573,\n    KEY_574 = 574,\n    KEY_575 = 575,\n    KEY_BUTTONCONFIG = 576,\n    KEY_TASKMANAGER = 577,\n    KEY_JOURNAL = 578,\n    KEY_CONTROLPANEL = 579,\n    KEY_APPSELECT = 580,\n    KEY_SCREENSAVER = 581,\n    KEY_VOICECOMMAND = 582,\n    KEY_ASSISTANT = 583,\n    KEY_KBD_LAYOUT_NEXT = 584,\n    KEY_585 = 585,\n    KEY_586 = 586,\n    KEY_587 = 587,\n    KEY_588 = 588,\n    KEY_589 = 589,\n    KEY_590 = 590,\n    KEY_591 = 591,\n    KEY_BRIGHTNESS_MIN = 592,\n    KEY_BRIGHTNESS_MAX = 593,\n    KEY_594 = 594,\n    KEY_595 = 595,\n    KEY_596 = 596,\n    KEY_597 = 597,\n    KEY_598 = 598,\n    KEY_599 = 599,\n    KEY_600 = 600,\n    KEY_601 = 601,\n    KEY_602 = 602,\n    KEY_603 = 603,\n    KEY_604 = 604,\n    KEY_605 = 605,\n    KEY_606 = 606,\n    KEY_607 = 607,\n    KEY_KBDINPUTASSIST_PREV = 608,\n    KEY_KBDINPUTASSIST_NEXT = 609,\n    KEY_KBDINPUTASSIST_PREVGROUP = 610,\n    KEY_KBDINPUTASSIST_NEXTGROUP = 611,\n    KEY_KBDINPUTASSIST_ACCEPT = 612,\n    KEY_KBDINPUTASSIST_CANCEL = 613,\n    KEY_RIGHT_UP = 614,\n    KEY_RIGHT_DOWN = 615,\n    KEY_LEFT_UP = 616,\n    KEY_LEFT_DOWN = 617,\n    KEY_ROOT_MENU = 618,\n    KEY_MEDIA_TOP_MENU = 619,\n    KEY_NUMERIC_11 = 620,\n    KEY_NUMERIC_12 = 621,\n    KEY_AUDIO_DESC = 622,\n    KEY_3D_MODE = 623,\n    KEY_NEXT_FAVORITE = 624,\n    KEY_STOP_RECORD = 625,\n    KEY_PAUSE_RECORD = 626,\n    KEY_VOD = 627,\n    KEY_UNMUTE = 628,\n    KEY_FASTREVERSE = 629,\n    KEY_SLOWREVERSE = 630,\n    KEY_DATA = 631,\n    KEY_ONSCREEN_KEYBOARD = 632,\n    KEY_633 = 633,\n    KEY_634 = 634,\n    KEY_635 = 635,\n    KEY_636 = 636,\n    KEY_637 = 637,\n    KEY_638 = 638,\n    KEY_639 = 639,\n    KEY_640 = 640,\n    KEY_641 = 641,\n    KEY_642 = 642,\n    KEY_643 = 643,\n    KEY_644 = 644,\n    KEY_645 = 645,\n    KEY_646 = 646,\n    KEY_647 = 647,\n    KEY_648 = 648,\n    KEY_649 = 649,\n    KEY_650 = 650,\n    KEY_651 = 651,\n    KEY_652 = 652,\n    KEY_653 = 653,\n    KEY_654 = 654,\n    KEY_655 = 655,\n    KEY_656 = 656, // 0x290 : KEY_MACRO1:\n    KEY_657 = 657, // https://github.com/torvalds/linux/commit/b5625db9d23e58a573eb10a7f6d0c2ae060bc0e8\n    KEY_658 = 658, // ...\n    KEY_659 = 659,\n    KEY_660 = 660,\n    KEY_661 = 661,\n    KEY_662 = 662,\n    KEY_663 = 663,\n    KEY_664 = 664,\n    KEY_665 = 665,\n    KEY_666 = 666,\n    KEY_667 = 667,\n    KEY_668 = 668,\n    KEY_669 = 669,\n    KEY_670 = 670,\n    KEY_671 = 671,\n    KEY_672 = 672,\n    KEY_673 = 673,\n    KEY_674 = 674,\n    KEY_675 = 675,\n    KEY_676 = 676, // 0x2a4: KEY_MACRO21\n    KEY_677 = 677,\n    KEY_678 = 678,\n    KEY_679 = 679,\n    KEY_680 = 680,\n    KEY_681 = 681,\n    KEY_682 = 682,\n    KEY_683 = 683,\n    KEY_684 = 684, // ...\n    KEY_685 = 685, // 0x2ad: KEY_MACRO30\n    KEY_686 = 686,\n    KEY_687 = 687,\n    KEY_688 = 688,\n    KEY_689 = 689,\n    KEY_690 = 690,\n    KEY_691 = 691,\n    KEY_692 = 692,\n    KEY_693 = 693,\n    KEY_694 = 694,\n    KEY_695 = 695,\n    KEY_696 = 696,\n    KEY_697 = 697,\n    KEY_698 = 698,\n    KEY_699 = 699,\n    KEY_700 = 700,\n    KEY_701 = 701,\n    KEY_702 = 702,\n    KEY_703 = 703,\n    BTN_TRIGGER_HAPPY1 = 704,\n    BTN_TRIGGER_HAPPY2 = 705,\n    BTN_TRIGGER_HAPPY3 = 706,\n    BTN_TRIGGER_HAPPY4 = 707,\n    BTN_TRIGGER_HAPPY5 = 708,\n    BTN_TRIGGER_HAPPY6 = 709,\n    BTN_TRIGGER_HAPPY7 = 710,\n    BTN_TRIGGER_HAPPY8 = 711,\n    BTN_TRIGGER_HAPPY9 = 712,\n    BTN_TRIGGER_HAPPY10 = 713,\n    BTN_TRIGGER_HAPPY11 = 714,\n    BTN_TRIGGER_HAPPY12 = 715,\n    BTN_TRIGGER_HAPPY13 = 716,\n    BTN_TRIGGER_HAPPY14 = 717,\n    BTN_TRIGGER_HAPPY15 = 718,\n    BTN_TRIGGER_HAPPY16 = 719,\n    BTN_TRIGGER_HAPPY17 = 720,\n    BTN_TRIGGER_HAPPY18 = 721,\n    BTN_TRIGGER_HAPPY19 = 722,\n    BTN_TRIGGER_HAPPY20 = 723,\n    BTN_TRIGGER_HAPPY21 = 724,\n    BTN_TRIGGER_HAPPY22 = 725,\n    BTN_TRIGGER_HAPPY23 = 726,\n    BTN_TRIGGER_HAPPY24 = 727,\n    BTN_TRIGGER_HAPPY25 = 728,\n    BTN_TRIGGER_HAPPY26 = 729,\n    BTN_TRIGGER_HAPPY27 = 730,\n    BTN_TRIGGER_HAPPY28 = 731,\n    BTN_TRIGGER_HAPPY29 = 732,\n    BTN_TRIGGER_HAPPY30 = 733,\n    BTN_TRIGGER_HAPPY31 = 734,\n    BTN_TRIGGER_HAPPY32 = 735,\n    BTN_TRIGGER_HAPPY33 = 736,\n    BTN_TRIGGER_HAPPY34 = 737,\n    BTN_TRIGGER_HAPPY35 = 738,\n    BTN_TRIGGER_HAPPY36 = 739,\n    BTN_TRIGGER_HAPPY37 = 740,\n    BTN_TRIGGER_HAPPY38 = 741,\n    BTN_TRIGGER_HAPPY39 = 742,\n    BTN_TRIGGER_HAPPY40 = 743,\n    BTN_MAX = 744,\n\n    // Mouse wheel events are not a part of EV_KEY, so they technically\n    // shouldn't be there, but they're still there, because this way\n    // it's easier to implement allowing to add them to defsrc without\n    // making tons of changes all over the codebase.\n    MouseWheelUp = 745,\n    MouseWheelDown = 746,\n    MouseWheelLeft = 747,\n    MouseWheelRight = 748,\n\n    KEY_749 = 749,\n    KEY_750 = 750,\n    KEY_751 = 751,\n    KEY_752 = 752,\n    KEY_753 = 753,\n    KEY_754 = 754,\n    KEY_755 = 755,\n    KEY_756 = 756,\n    KEY_757 = 757,\n    KEY_758 = 758,\n    KEY_759 = 759,\n    KEY_760 = 760,\n    KEY_761 = 761,\n    KEY_762 = 762,\n    KEY_763 = 763,\n    KEY_764 = 764,\n    KEY_765 = 765,\n    KEY_766 = 766, // aliased to mvmt as a dummy input for use with mouse-movement-key\n\n    KEY_MAX = 767,\n}\n\nimpl OsCode {\n    pub fn is_mouse_code(&self) -> bool {\n        use OsCode::*;\n        matches!(\n            self,\n            BTN_LEFT\n                | BTN_RIGHT\n                | BTN_MIDDLE\n                | BTN_SIDE\n                | BTN_EXTRA\n                | MouseWheelUp\n                | MouseWheelDown\n                | MouseWheelLeft\n                | MouseWheelRight\n        )\n    }\n}\n\nuse core::fmt;\nimpl fmt::Display for OsCode {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        let self_dbg = format!(\"{self:?}\");\n        if let Some(key) = self_dbg.strip_prefix(\"KEY_\") {\n            write!(f, \"{key}\")\n        } else {\n            write!(f, \"{self:?}\")\n        }\n    }\n}\n\n#[test]\nfn parser_key_max_lt_keyberon_key_max() {\n    assert!(u16::from(OsCode::KEY_MAX) < KEY_MAX);\n}\n\nimpl TryFrom<usize> for OsCode {\n    type Error = ();\n    fn try_from(item: usize) -> Result<Self, Self::Error> {\n        match Self::from_u16(item as u16) {\n            Some(kc) => Ok(kc),\n            _ => Err(()),\n        }\n    }\n}\n\nimpl From<u32> for OsCode {\n    fn from(item: u32) -> Self {\n        Self::from_u16(item as u16).unwrap_or_else(|| panic!(\"Invalid KeyCode: {item}\"))\n    }\n}\n\nimpl From<u16> for OsCode {\n    fn from(item: u16) -> Self {\n        Self::from_u16(item).unwrap_or_else(|| panic!(\"Invalid KeyCode: {item}\"))\n    }\n}\n\nimpl From<OsCode> for usize {\n    fn from(item: OsCode) -> Self {\n        item.as_u16() as usize\n    }\n}\n\nimpl From<OsCode> for u32 {\n    fn from(item: OsCode) -> Self {\n        item.as_u16() as u32\n    }\n}\n\nimpl From<OsCode> for i32 {\n    fn from(item: OsCode) -> Self {\n        item.as_u16() as i32\n    }\n}\n\nimpl From<OsCode> for u16 {\n    fn from(item: OsCode) -> Self {\n        item.as_u16()\n    }\n}\n\nimpl From<&OsCode> for KeyCode {\n    fn from(item: &OsCode) -> KeyCode {\n        (*item).into()\n    }\n}\nimpl From<&KeyCode> for OsCode {\n    fn from(item: &KeyCode) -> Self {\n        (*item).into()\n    }\n}\n"
  },
  {
    "path": "parser/src/keys/windows.rs",
    "content": "// This file is adapted from the original ktrl's `keys.rs` file for Windows.\n\nuse super::OsCode;\n\n#[allow(unused)]\nmod keys {\n    // Taken from:\n    // https://github.com/microsoft/windows-rs/blob/0.55.0/crates/libs/windows/src/Windows/Win32/UI/Input/KeyboardAndMouse/mod.rs\n    // Category             Count  List\n    // Reserved             #  6   7 10 11                            94                                     184 185\n    // Unassigned/undefined # 19           14 15 58 59 60 61 62 63 64    151 152 153 154 155 156 157 158 159         232\n    // Assigned             #230\n    // Total                #255\n    // 232 unassigned used as a `VK_KPENTER_FAKE`\n    pub type VirtualKey = u16;\n    pub const VK_KPENTER_FAKE: VirtualKey = 232u16; // unassigned VK used to enable Interception use\n    pub const VK_0: VirtualKey = 48u16;\n    pub const VK_1: VirtualKey = 49u16;\n    pub const VK_2: VirtualKey = 50u16;\n    pub const VK_3: VirtualKey = 51u16;\n    pub const VK_4: VirtualKey = 52u16;\n    pub const VK_5: VirtualKey = 53u16;\n    pub const VK_6: VirtualKey = 54u16;\n    pub const VK_7: VirtualKey = 55u16;\n    pub const VK_8: VirtualKey = 56u16;\n    pub const VK_9: VirtualKey = 57u16;\n    pub const VK_A: VirtualKey = 65u16;\n    pub const VK_ABNT_C1: VirtualKey = 193u16;\n    pub const VK_ABNT_C2: VirtualKey = 194u16;\n    pub const VK_ACCEPT: VirtualKey = 30u16;\n    pub const VK_ADD: VirtualKey = 107u16;\n    pub const VK_APPS: VirtualKey = 93u16;\n    pub const VK_ATTN: VirtualKey = 246u16;\n    pub const VK_B: VirtualKey = 66u16;\n    pub const VK_BACK: VirtualKey = 8u16;\n    pub const VK_BROWSER_BACK: VirtualKey = 166u16;\n    pub const VK_BROWSER_FAVORITES: VirtualKey = 171u16;\n    pub const VK_BROWSER_FORWARD: VirtualKey = 167u16;\n    pub const VK_BROWSER_HOME: VirtualKey = 172u16;\n    pub const VK_BROWSER_REFRESH: VirtualKey = 168u16;\n    pub const VK_BROWSER_SEARCH: VirtualKey = 170u16;\n    pub const VK_BROWSER_STOP: VirtualKey = 169u16;\n    pub const VK_C: VirtualKey = 67u16;\n    pub const VK_CANCEL: VirtualKey = 3u16;\n    pub const VK_CAPITAL: VirtualKey = 20u16;\n    pub const VK_CLEAR: VirtualKey = 12u16;\n    pub const VK_CONTROL: VirtualKey = 17u16;\n    pub const VK_CONVERT: VirtualKey = 28u16;\n    pub const VK_CRSEL: VirtualKey = 247u16;\n    pub const VK_D: VirtualKey = 68u16;\n    pub const VK_DBE_ALPHANUMERIC: VirtualKey = 240u16;\n    pub const VK_DBE_CODEINPUT: VirtualKey = 250u16;\n    pub const VK_DBE_DBCSCHAR: VirtualKey = 244u16;\n    pub const VK_DBE_DETERMINESTRING: VirtualKey = 252u16;\n    pub const VK_DBE_ENTERDLGCONVERSIONMODE: VirtualKey = 253u16;\n    pub const VK_DBE_ENTERIMECONFIGMODE: VirtualKey = 248u16;\n    pub const VK_DBE_ENTERWORDREGISTERMODE: VirtualKey = 247u16;\n    pub const VK_DBE_FLUSHSTRING: VirtualKey = 249u16;\n    pub const VK_DBE_HIRAGANA: VirtualKey = 242u16;\n    pub const VK_DBE_KATAKANA: VirtualKey = 241u16;\n    pub const VK_DBE_NOCODEINPUT: VirtualKey = 251u16;\n    pub const VK_DBE_NOROMAN: VirtualKey = 246u16;\n    pub const VK_DBE_ROMAN: VirtualKey = 245u16;\n    pub const VK_DBE_SBCSCHAR: VirtualKey = 243u16;\n    pub const VK_DECIMAL: VirtualKey = 110u16;\n    pub const VK_DELETE: VirtualKey = 46u16;\n    pub const VK_DIVIDE: VirtualKey = 111u16;\n    pub const VK_DOWN: VirtualKey = 40u16;\n    pub const VK_E: VirtualKey = 69u16;\n    pub const VK_END: VirtualKey = 35u16;\n    pub const VK_EREOF: VirtualKey = 249u16;\n    pub const VK_ESCAPE: VirtualKey = 27u16;\n    pub const VK_EXECUTE: VirtualKey = 43u16;\n    pub const VK_EXSEL: VirtualKey = 248u16;\n    pub const VK_F: VirtualKey = 70u16;\n    pub const VK_F1: VirtualKey = 112u16;\n    pub const VK_F10: VirtualKey = 121u16;\n    pub const VK_F11: VirtualKey = 122u16;\n    pub const VK_F12: VirtualKey = 123u16;\n    pub const VK_F13: VirtualKey = 124u16;\n    pub const VK_F14: VirtualKey = 125u16;\n    pub const VK_F15: VirtualKey = 126u16;\n    pub const VK_F16: VirtualKey = 127u16;\n    pub const VK_F17: VirtualKey = 128u16;\n    pub const VK_F18: VirtualKey = 129u16;\n    pub const VK_F19: VirtualKey = 130u16;\n    pub const VK_F2: VirtualKey = 113u16;\n    pub const VK_F20: VirtualKey = 131u16;\n    pub const VK_F21: VirtualKey = 132u16;\n    pub const VK_F22: VirtualKey = 133u16;\n    pub const VK_F23: VirtualKey = 134u16;\n    pub const VK_F24: VirtualKey = 135u16;\n    pub const VK_F3: VirtualKey = 114u16;\n    pub const VK_F4: VirtualKey = 115u16;\n    pub const VK_F5: VirtualKey = 116u16;\n    pub const VK_F6: VirtualKey = 117u16;\n    pub const VK_F7: VirtualKey = 118u16;\n    pub const VK_F8: VirtualKey = 119u16;\n    pub const VK_F9: VirtualKey = 120u16;\n    pub const VK_FINAL: VirtualKey = 24u16;\n    pub const VK_G: VirtualKey = 71u16;\n    pub const VK_GAMEPAD_A: VirtualKey = 195u16;\n    pub const VK_GAMEPAD_B: VirtualKey = 196u16;\n    pub const VK_GAMEPAD_DPAD_DOWN: VirtualKey = 204u16;\n    pub const VK_GAMEPAD_DPAD_LEFT: VirtualKey = 205u16;\n    pub const VK_GAMEPAD_DPAD_RIGHT: VirtualKey = 206u16;\n    pub const VK_GAMEPAD_DPAD_UP: VirtualKey = 203u16;\n    pub const VK_GAMEPAD_LEFT_SHOULDER: VirtualKey = 200u16;\n    pub const VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON: VirtualKey = 209u16;\n    pub const VK_GAMEPAD_LEFT_THUMBSTICK_DOWN: VirtualKey = 212u16;\n    pub const VK_GAMEPAD_LEFT_THUMBSTICK_LEFT: VirtualKey = 214u16;\n    pub const VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT: VirtualKey = 213u16;\n    pub const VK_GAMEPAD_LEFT_THUMBSTICK_UP: VirtualKey = 211u16;\n    pub const VK_GAMEPAD_LEFT_TRIGGER: VirtualKey = 201u16;\n    pub const VK_GAMEPAD_MENU: VirtualKey = 207u16;\n    pub const VK_GAMEPAD_RIGHT_SHOULDER: VirtualKey = 199u16;\n    pub const VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON: VirtualKey = 210u16;\n    pub const VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN: VirtualKey = 216u16;\n    pub const VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT: VirtualKey = 218u16;\n    pub const VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT: VirtualKey = 217u16;\n    pub const VK_GAMEPAD_RIGHT_THUMBSTICK_UP: VirtualKey = 215u16;\n    pub const VK_GAMEPAD_RIGHT_TRIGGER: VirtualKey = 202u16;\n    pub const VK_GAMEPAD_VIEW: VirtualKey = 208u16;\n    pub const VK_GAMEPAD_X: VirtualKey = 197u16;\n    pub const VK_GAMEPAD_Y: VirtualKey = 198u16;\n    pub const VK_H: VirtualKey = 72u16;\n    pub const VK_HANGEUL: VirtualKey = 21u16;\n    pub const VK_HANGUL: VirtualKey = 21u16;\n    pub const VK_HANJA: VirtualKey = 25u16;\n    pub const VK_HELP: VirtualKey = 47u16;\n    pub const VK_HOME: VirtualKey = 36u16;\n    pub const VK_I: VirtualKey = 73u16;\n    pub const VK_ICO_00: VirtualKey = 228u16;\n    pub const VK_ICO_CLEAR: VirtualKey = 230u16;\n    pub const VK_ICO_HELP: VirtualKey = 227u16;\n    pub const VK_IME_OFF: VirtualKey = 26u16;\n    pub const VK_IME_ON: VirtualKey = 22u16;\n    pub const VK_INSERT: VirtualKey = 45u16;\n    pub const VK_J: VirtualKey = 74u16;\n    pub const VK_JUNJA: VirtualKey = 23u16;\n    pub const VK_K: VirtualKey = 75u16;\n    pub const VK_KANA: VirtualKey = 21u16;\n    pub const VK_KANJI: VirtualKey = 25u16;\n    pub const VK_L: VirtualKey = 76u16;\n    pub const VK_LAUNCH_APP1: VirtualKey = 182u16;\n    pub const VK_LAUNCH_APP2: VirtualKey = 183u16;\n    pub const VK_LAUNCH_MAIL: VirtualKey = 180u16;\n    pub const VK_LAUNCH_MEDIA_SELECT: VirtualKey = 181u16;\n    pub const VK_LBUTTON: VirtualKey = 1u16;\n    pub const VK_LCONTROL: VirtualKey = 162u16;\n    pub const VK_LEFT: VirtualKey = 37u16;\n    pub const VK_LMENU: VirtualKey = 164u16;\n    pub const VK_LSHIFT: VirtualKey = 160u16;\n    pub const VK_LWIN: VirtualKey = 91u16;\n    pub const VK_M: VirtualKey = 77u16;\n    pub const VK_MBUTTON: VirtualKey = 4u16;\n    pub const VK_MEDIA_NEXT_TRACK: VirtualKey = 176u16;\n    pub const VK_MEDIA_PLAY_PAUSE: VirtualKey = 179u16;\n    pub const VK_MEDIA_PREV_TRACK: VirtualKey = 177u16;\n    pub const VK_MEDIA_STOP: VirtualKey = 178u16;\n    pub const VK_MENU: VirtualKey = 18u16;\n    pub const VK_MODECHANGE: VirtualKey = 31u16;\n    pub const VK_MULTIPLY: VirtualKey = 106u16;\n    pub const VK_N: VirtualKey = 78u16;\n    pub const VK_NAVIGATION_ACCEPT: VirtualKey = 142u16;\n    pub const VK_NAVIGATION_CANCEL: VirtualKey = 143u16;\n    pub const VK_NAVIGATION_DOWN: VirtualKey = 139u16;\n    pub const VK_NAVIGATION_LEFT: VirtualKey = 140u16;\n    pub const VK_NAVIGATION_MENU: VirtualKey = 137u16;\n    pub const VK_NAVIGATION_RIGHT: VirtualKey = 141u16;\n    pub const VK_NAVIGATION_UP: VirtualKey = 138u16;\n    pub const VK_NAVIGATION_VIEW: VirtualKey = 136u16;\n    pub const VK_NEXT: VirtualKey = 34u16;\n    pub const VK_NONAME: VirtualKey = 252u16;\n    pub const VK_NONCONVERT: VirtualKey = 29u16;\n    pub const VK_NUMLOCK: VirtualKey = 144u16;\n    pub const VK_NUMPAD0: VirtualKey = 96u16;\n    pub const VK_NUMPAD1: VirtualKey = 97u16;\n    pub const VK_NUMPAD2: VirtualKey = 98u16;\n    pub const VK_NUMPAD3: VirtualKey = 99u16;\n    pub const VK_NUMPAD4: VirtualKey = 100u16;\n    pub const VK_NUMPAD5: VirtualKey = 101u16;\n    pub const VK_NUMPAD6: VirtualKey = 102u16;\n    pub const VK_NUMPAD7: VirtualKey = 103u16;\n    pub const VK_NUMPAD8: VirtualKey = 104u16;\n    pub const VK_NUMPAD9: VirtualKey = 105u16;\n    pub const VK_O: VirtualKey = 79u16;\n    pub const VK_OEM_1: VirtualKey = 186u16;\n    pub const VK_OEM_102: VirtualKey = 226u16;\n    pub const VK_OEM_2: VirtualKey = 191u16;\n    pub const VK_OEM_3: VirtualKey = 192u16;\n    pub const VK_OEM_4: VirtualKey = 219u16;\n    pub const VK_OEM_5: VirtualKey = 220u16;\n    pub const VK_OEM_6: VirtualKey = 221u16;\n    pub const VK_OEM_7: VirtualKey = 222u16;\n    pub const VK_OEM_8: VirtualKey = 223u16;\n    pub const VK_OEM_ATTN: VirtualKey = 240u16;\n    pub const VK_OEM_AUTO: VirtualKey = 243u16;\n    pub const VK_OEM_AX: VirtualKey = 225u16;\n    pub const VK_OEM_BACKTAB: VirtualKey = 245u16;\n    pub const VK_OEM_CLEAR: VirtualKey = 254u16;\n    pub const VK_OEM_COMMA: VirtualKey = 188u16;\n    pub const VK_OEM_COPY: VirtualKey = 242u16;\n    pub const VK_OEM_CUSEL: VirtualKey = 239u16;\n    pub const VK_OEM_ENLW: VirtualKey = 244u16;\n    pub const VK_OEM_FINISH: VirtualKey = 241u16;\n    pub const VK_OEM_FJ_JISHO: VirtualKey = 146u16;\n    pub const VK_OEM_FJ_LOYA: VirtualKey = 149u16;\n    pub const VK_OEM_FJ_MASSHOU: VirtualKey = 147u16;\n    pub const VK_OEM_FJ_ROYA: VirtualKey = 150u16;\n    pub const VK_OEM_FJ_TOUROKU: VirtualKey = 148u16;\n    pub const VK_OEM_JUMP: VirtualKey = 234u16;\n    pub const VK_OEM_MINUS: VirtualKey = 189u16;\n    pub const VK_OEM_NEC_EQUAL: VirtualKey = 146u16;\n    pub const VK_OEM_PA1: VirtualKey = 235u16;\n    pub const VK_OEM_PA2: VirtualKey = 236u16;\n    pub const VK_OEM_PA3: VirtualKey = 237u16;\n    pub const VK_OEM_PERIOD: VirtualKey = 190u16;\n    pub const VK_OEM_PLUS: VirtualKey = 187u16;\n    pub const VK_OEM_RESET: VirtualKey = 233u16;\n    pub const VK_OEM_WSCTRL: VirtualKey = 238u16;\n    pub const VK_P: VirtualKey = 80u16;\n    pub const VK_PA1: VirtualKey = 253u16;\n    pub const VK_PACKET: VirtualKey = 231u16;\n    pub const VK_PAUSE: VirtualKey = 19u16;\n    pub const VK_PLAY: VirtualKey = 250u16;\n    pub const VK_PRINT: VirtualKey = 42u16;\n    pub const VK_PRIOR: VirtualKey = 33u16;\n    pub const VK_PROCESSKEY: VirtualKey = 229u16;\n    pub const VK_Q: VirtualKey = 81u16;\n    pub const VK_R: VirtualKey = 82u16;\n    pub const VK_RBUTTON: VirtualKey = 2u16;\n    pub const VK_RCONTROL: VirtualKey = 163u16;\n    pub const VK_RETURN: VirtualKey = 13u16;\n    pub const VK_RIGHT: VirtualKey = 39u16;\n    pub const VK_RMENU: VirtualKey = 165u16;\n    pub const VK_RSHIFT: VirtualKey = 161u16;\n    pub const VK_RWIN: VirtualKey = 92u16;\n    pub const VK_S: VirtualKey = 83u16;\n    pub const VK_SCROLL: VirtualKey = 145u16;\n    pub const VK_SELECT: VirtualKey = 41u16;\n    pub const VK_SEPARATOR: VirtualKey = 108u16;\n    pub const VK_SHIFT: VirtualKey = 16u16;\n    pub const VK_SLEEP: VirtualKey = 95u16;\n    pub const VK_SNAPSHOT: VirtualKey = 44u16;\n    pub const VK_SPACE: VirtualKey = 32u16;\n    pub const VK_SUBTRACT: VirtualKey = 109u16;\n    pub const VK_T: VirtualKey = 84u16;\n    pub const VK_TAB: VirtualKey = 9u16;\n    pub const VK_U: VirtualKey = 85u16;\n    pub const VK_UP: VirtualKey = 38u16;\n    pub const VK_V: VirtualKey = 86u16;\n    pub const VK_VOLUME_DOWN: VirtualKey = 174u16;\n    pub const VK_VOLUME_MUTE: VirtualKey = 173u16;\n    pub const VK_VOLUME_UP: VirtualKey = 175u16;\n    pub const VK_W: VirtualKey = 87u16;\n    pub const VK_X: VirtualKey = 88u16;\n    pub const VK_XBUTTON1: VirtualKey = 5u16;\n    pub const VK_XBUTTON2: VirtualKey = 6u16;\n    pub const VK_Y: VirtualKey = 89u16;\n    pub const VK_Z: VirtualKey = 90u16;\n    pub const VK_ZOOM: VirtualKey = 251u16;\n    pub const VK_NONE: VirtualKey = 255u16;\n}\npub use keys::*;\n\nimpl OsCode {\n    pub(super) const fn from_u16_windows(code: u16) -> Option<Self> {\n        match code {\n            0x30 => Some(OsCode::KEY_0),\n            0x31 => Some(OsCode::KEY_1),\n            0x32 => Some(OsCode::KEY_2),\n            0x33 => Some(OsCode::KEY_3),\n            0x34 => Some(OsCode::KEY_4),\n            0x35 => Some(OsCode::KEY_5),\n            0x36 => Some(OsCode::KEY_6),\n            0x37 => Some(OsCode::KEY_7),\n            0x38 => Some(OsCode::KEY_8),\n            0x39 => Some(OsCode::KEY_9),\n            0x41 => Some(OsCode::KEY_A),\n            0x42 => Some(OsCode::KEY_B),\n            0x43 => Some(OsCode::KEY_C),\n            0x44 => Some(OsCode::KEY_D),\n            0x45 => Some(OsCode::KEY_E),\n            0x46 => Some(OsCode::KEY_F),\n            0x47 => Some(OsCode::KEY_G),\n            0x48 => Some(OsCode::KEY_H),\n            0x49 => Some(OsCode::KEY_I),\n            0x4A => Some(OsCode::KEY_J),\n            0x4B => Some(OsCode::KEY_K),\n            0x4C => Some(OsCode::KEY_L),\n            0x4D => Some(OsCode::KEY_M),\n            0x4E => Some(OsCode::KEY_N),\n            0x4F => Some(OsCode::KEY_O),\n            0x50 => Some(OsCode::KEY_P),\n            0x51 => Some(OsCode::KEY_Q),\n            0x52 => Some(OsCode::KEY_R),\n            0x53 => Some(OsCode::KEY_S),\n            0x54 => Some(OsCode::KEY_T),\n            0x55 => Some(OsCode::KEY_U),\n            0x56 => Some(OsCode::KEY_V),\n            0x57 => Some(OsCode::KEY_W),\n            0x58 => Some(OsCode::KEY_X),\n            0x59 => Some(OsCode::KEY_Y),\n            0x5A => Some(OsCode::KEY_Z),\n            VK_OEM_1 => Some(OsCode::KEY_SEMICOLON),\n            VK_OEM_2 => Some(OsCode::KEY_SLASH),\n            VK_OEM_3 => Some(OsCode::KEY_GRAVE),\n            VK_OEM_4 => Some(OsCode::KEY_LEFTBRACE),\n            VK_OEM_5 => Some(OsCode::KEY_BACKSLASH),\n            VK_OEM_6 => Some(OsCode::KEY_RIGHTBRACE),\n            VK_OEM_7 => Some(OsCode::KEY_APOSTROPHE),\n            VK_OEM_MINUS => Some(OsCode::KEY_MINUS),\n            VK_OEM_PERIOD => Some(OsCode::KEY_DOT),\n            VK_OEM_PLUS => Some(OsCode::KEY_EQUAL),\n            VK_BACK => Some(OsCode::KEY_BACKSPACE),\n            VK_ESCAPE => Some(OsCode::KEY_ESC),\n            VK_TAB => Some(OsCode::KEY_TAB),\n            VK_RETURN => Some(OsCode::KEY_ENTER),\n            VK_LCONTROL => Some(OsCode::KEY_LEFTCTRL),\n            VK_LSHIFT => Some(OsCode::KEY_LEFTSHIFT),\n            VK_OEM_COMMA => Some(OsCode::KEY_COMMA),\n            VK_RSHIFT => Some(OsCode::KEY_RIGHTSHIFT),\n            VK_MULTIPLY => Some(OsCode::KEY_KPASTERISK),\n            VK_LMENU => Some(OsCode::KEY_LEFTALT),\n            VK_SPACE => Some(OsCode::KEY_SPACE),\n            VK_CAPITAL => Some(OsCode::KEY_CAPSLOCK),\n            VK_F1 => Some(OsCode::KEY_F1),\n            VK_F2 => Some(OsCode::KEY_F2),\n            VK_F3 => Some(OsCode::KEY_F3),\n            VK_F4 => Some(OsCode::KEY_F4),\n            VK_F5 => Some(OsCode::KEY_F5),\n            VK_F6 => Some(OsCode::KEY_F6),\n            VK_F7 => Some(OsCode::KEY_F7),\n            VK_F8 => Some(OsCode::KEY_F8),\n            VK_F9 => Some(OsCode::KEY_F9),\n            VK_F10 => Some(OsCode::KEY_F10),\n            VK_F11 => Some(OsCode::KEY_F11),\n            VK_F12 => Some(OsCode::KEY_F12),\n            VK_NUMLOCK => Some(OsCode::KEY_NUMLOCK),\n            VK_CLEAR => Some(OsCode::KEY_CLEAR),\n            VK_SCROLL => Some(OsCode::KEY_SCROLLLOCK),\n            VK_NUMPAD0 => Some(OsCode::KEY_KP0),\n            VK_NUMPAD1 => Some(OsCode::KEY_KP1),\n            VK_NUMPAD2 => Some(OsCode::KEY_KP2),\n            VK_NUMPAD3 => Some(OsCode::KEY_KP3),\n            VK_NUMPAD4 => Some(OsCode::KEY_KP4),\n            VK_NUMPAD5 => Some(OsCode::KEY_KP5),\n            VK_NUMPAD6 => Some(OsCode::KEY_KP6),\n            VK_NUMPAD7 => Some(OsCode::KEY_KP7),\n            VK_NUMPAD8 => Some(OsCode::KEY_KP8),\n            VK_NUMPAD9 => Some(OsCode::KEY_KP9),\n            VK_SUBTRACT => Some(OsCode::KEY_KPMINUS),\n            VK_ADD => Some(OsCode::KEY_KPPLUS),\n            VK_DECIMAL => Some(OsCode::KEY_KPDOT),\n            VK_KPENTER_FAKE => Some(OsCode::KEY_KPENTER),\n            VK_RCONTROL => Some(OsCode::KEY_RIGHTCTRL),\n            VK_DIVIDE => Some(OsCode::KEY_KPSLASH),\n            VK_RMENU => Some(OsCode::KEY_RIGHTALT),\n            VK_HOME => Some(OsCode::KEY_HOME),\n            VK_UP => Some(OsCode::KEY_UP),\n            VK_PRIOR => Some(OsCode::KEY_PAGEUP),\n            VK_LEFT => Some(OsCode::KEY_LEFT),\n            VK_RIGHT => Some(OsCode::KEY_RIGHT),\n            VK_END => Some(OsCode::KEY_END),\n            VK_DOWN => Some(OsCode::KEY_DOWN),\n            VK_NEXT => Some(OsCode::KEY_PAGEDOWN),\n            VK_INSERT => Some(OsCode::KEY_INSERT),\n            VK_DELETE => Some(OsCode::KEY_DELETE),\n            VK_VOLUME_MUTE => Some(OsCode::KEY_MUTE),\n            VK_VOLUME_DOWN => Some(OsCode::KEY_VOLUMEDOWN),\n            VK_VOLUME_UP => Some(OsCode::KEY_VOLUMEUP),\n            VK_PAUSE => Some(OsCode::KEY_PAUSE),\n            VK_LWIN => Some(OsCode::KEY_LEFTMETA),\n            VK_RWIN => Some(OsCode::KEY_RIGHTMETA),\n            VK_APPS => Some(OsCode::KEY_COMPOSE),\n            VK_BROWSER_BACK => Some(OsCode::KEY_BACK),\n            VK_BROWSER_FORWARD => Some(OsCode::KEY_FORWARD),\n            VK_MEDIA_NEXT_TRACK => Some(OsCode::KEY_NEXTSONG),\n            VK_MEDIA_PLAY_PAUSE => Some(OsCode::KEY_PLAYPAUSE),\n            VK_MEDIA_PREV_TRACK => Some(OsCode::KEY_PREVIOUSSONG),\n            VK_MEDIA_STOP => Some(OsCode::KEY_STOP),\n            VK_BROWSER_HOME => Some(OsCode::KEY_HOMEPAGE),\n            VK_LAUNCH_MAIL => Some(OsCode::KEY_MAIL),\n            VK_LAUNCH_MEDIA_SELECT => Some(OsCode::KEY_MEDIA),\n            VK_BROWSER_REFRESH => Some(OsCode::KEY_REFRESH),\n            VK_F13 => Some(OsCode::KEY_F13),\n            VK_F14 => Some(OsCode::KEY_F14),\n            VK_F15 => Some(OsCode::KEY_F15),\n            VK_F16 => Some(OsCode::KEY_F16),\n            VK_F17 => Some(OsCode::KEY_F17),\n            VK_F18 => Some(OsCode::KEY_F18),\n            VK_F19 => Some(OsCode::KEY_F19),\n            VK_F20 => Some(OsCode::KEY_F20),\n            VK_F21 => Some(OsCode::KEY_F21),\n            VK_F22 => Some(OsCode::KEY_F22),\n            VK_F23 => Some(OsCode::KEY_F23),\n            VK_F24 => Some(OsCode::KEY_F24),\n            VK_HANGEUL => Some(OsCode::KEY_HANGEUL),\n            VK_HANJA => Some(OsCode::KEY_HANJA),\n            // KEY_252 is nonsensical, but just use it anyway. No idea what Linux OsCode this is.\n            // As long as it's not an existing key and the mapping round-trips, this works fine.\n            VK_OEM_8 => Some(OsCode::KEY_252),\n            VK_OEM_102 => Some(OsCode::KEY_102ND),\n            VK_PLAY => Some(OsCode::KEY_PLAY),\n            VK_SNAPSHOT => Some(OsCode::KEY_PRINT),\n            VK_BROWSER_SEARCH => Some(OsCode::KEY_SEARCH),\n            VK_BROWSER_FAVORITES => Some(OsCode::KEY_FAVORITES),\n            0xC1 => Some(OsCode::KEY_RO),\n            VK_CONVERT => Some(OsCode::KEY_HENKAN),\n            VK_NONCONVERT => Some(OsCode::KEY_MUHENKAN),\n            VK_DBE_KATAKANA => Some(OsCode::KEY_KATAKANA),\n            255 => Some(OsCode::KEY_YEN),\n            256 => Some(OsCode::BTN_0),\n            257 => Some(OsCode::BTN_1),\n            258 => Some(OsCode::BTN_2),\n            259 => Some(OsCode::BTN_3),\n            260 => Some(OsCode::BTN_4),\n            261 => Some(OsCode::BTN_5),\n            262 => Some(OsCode::BTN_6),\n            263 => Some(OsCode::BTN_7),\n            264 => Some(OsCode::BTN_8),\n            265 => Some(OsCode::BTN_9),\n            266 => Some(OsCode::KEY_266),\n            267 => Some(OsCode::KEY_267),\n            268 => Some(OsCode::KEY_268),\n            269 => Some(OsCode::KEY_269),\n            270 => Some(OsCode::KEY_270),\n            271 => Some(OsCode::KEY_271),\n            1 => Some(OsCode::BTN_LEFT),\n            2 => Some(OsCode::BTN_RIGHT),\n            4 => Some(OsCode::BTN_MIDDLE),\n            5 => Some(OsCode::BTN_SIDE),\n            6 => Some(OsCode::BTN_EXTRA),\n            277 => Some(OsCode::BTN_FORWARD),\n            278 => Some(OsCode::BTN_BACK),\n            279 => Some(OsCode::BTN_TASK),\n            280 => Some(OsCode::KEY_280),\n            281 => Some(OsCode::KEY_281),\n            282 => Some(OsCode::KEY_282),\n            283 => Some(OsCode::KEY_283),\n            284 => Some(OsCode::KEY_284),\n            285 => Some(OsCode::KEY_285),\n            286 => Some(OsCode::KEY_286),\n            287 => Some(OsCode::KEY_287),\n            288 => Some(OsCode::BTN_TRIGGER),\n            289 => Some(OsCode::BTN_THUMB),\n            290 => Some(OsCode::BTN_THUMB2),\n            291 => Some(OsCode::BTN_TOP),\n            292 => Some(OsCode::BTN_TOP2),\n            293 => Some(OsCode::BTN_PINKIE),\n            294 => Some(OsCode::BTN_BASE),\n            295 => Some(OsCode::BTN_BASE2),\n            296 => Some(OsCode::BTN_BASE3),\n            297 => Some(OsCode::BTN_BASE4),\n            298 => Some(OsCode::BTN_BASE5),\n            299 => Some(OsCode::BTN_BASE6),\n            300 => Some(OsCode::KEY_300),\n            301 => Some(OsCode::KEY_301),\n            302 => Some(OsCode::KEY_302),\n            303 => Some(OsCode::BTN_DEAD),\n            304 => Some(OsCode::BTN_SOUTH),\n            305 => Some(OsCode::BTN_EAST),\n            306 => Some(OsCode::BTN_C),\n            307 => Some(OsCode::BTN_NORTH),\n            308 => Some(OsCode::BTN_WEST),\n            309 => Some(OsCode::BTN_Z),\n            310 => Some(OsCode::BTN_TL),\n            311 => Some(OsCode::BTN_TR),\n            312 => Some(OsCode::BTN_TL2),\n            313 => Some(OsCode::BTN_TR2),\n            314 => Some(OsCode::BTN_SELECT),\n            315 => Some(OsCode::BTN_START),\n            316 => Some(OsCode::BTN_MODE),\n            317 => Some(OsCode::BTN_THUMBL),\n            318 => Some(OsCode::BTN_THUMBR),\n            319 => Some(OsCode::KEY_319),\n            320 => Some(OsCode::BTN_TOOL_PEN),\n            321 => Some(OsCode::BTN_TOOL_RUBBER),\n            322 => Some(OsCode::BTN_TOOL_BRUSH),\n            323 => Some(OsCode::BTN_TOOL_PENCIL),\n            324 => Some(OsCode::BTN_TOOL_AIRBRUSH),\n            325 => Some(OsCode::BTN_TOOL_FINGER),\n            326 => Some(OsCode::BTN_TOOL_MOUSE),\n            327 => Some(OsCode::BTN_TOOL_LENS),\n            328 => Some(OsCode::BTN_TOOL_QUINTTAP),\n            329 => Some(OsCode::BTN_STYLUS3),\n            330 => Some(OsCode::BTN_TOUCH),\n            331 => Some(OsCode::BTN_STYLUS),\n            332 => Some(OsCode::BTN_STYLUS2),\n            333 => Some(OsCode::BTN_TOOL_DOUBLETAP),\n            334 => Some(OsCode::BTN_TOOL_TRIPLETAP),\n            335 => Some(OsCode::BTN_TOOL_QUADTAP),\n            336 => Some(OsCode::BTN_GEAR_DOWN),\n            337 => Some(OsCode::BTN_GEAR_UP),\n            338 => Some(OsCode::KEY_338),\n            339 => Some(OsCode::KEY_339),\n            340 => Some(OsCode::KEY_340),\n            341 => Some(OsCode::KEY_341),\n            342 => Some(OsCode::KEY_342),\n            343 => Some(OsCode::KEY_343),\n            344 => Some(OsCode::KEY_344),\n            345 => Some(OsCode::KEY_345),\n            346 => Some(OsCode::KEY_346),\n            347 => Some(OsCode::KEY_347),\n            348 => Some(OsCode::KEY_348),\n            349 => Some(OsCode::KEY_349),\n            350 => Some(OsCode::KEY_350),\n            351 => Some(OsCode::KEY_351),\n            352 => Some(OsCode::KEY_OK),\n            353 => Some(OsCode::KEY_SELECT),\n            354 => Some(OsCode::KEY_GOTO),\n            355 => Some(OsCode::KEY_CLEAR),\n            356 => Some(OsCode::KEY_POWER2),\n            357 => Some(OsCode::KEY_OPTION),\n            358 => Some(OsCode::KEY_INFO),\n            359 => Some(OsCode::KEY_TIME),\n            360 => Some(OsCode::KEY_VENDOR),\n            361 => Some(OsCode::KEY_ARCHIVE),\n            362 => Some(OsCode::KEY_PROGRAM),\n            363 => Some(OsCode::KEY_CHANNEL),\n            364 => Some(OsCode::KEY_FAVORITES),\n            365 => Some(OsCode::KEY_EPG),\n            366 => Some(OsCode::KEY_PVR),\n            367 => Some(OsCode::KEY_MHP),\n            368 => Some(OsCode::KEY_LANGUAGE),\n            369 => Some(OsCode::KEY_TITLE),\n            370 => Some(OsCode::KEY_SUBTITLE),\n            371 => Some(OsCode::KEY_ANGLE),\n            372 => Some(OsCode::KEY_FULL_SCREEN),\n            373 => Some(OsCode::KEY_MODE),\n            374 => Some(OsCode::KEY_KEYBOARD),\n            375 => Some(OsCode::KEY_ASPECT_RATIO),\n            376 => Some(OsCode::KEY_PC),\n            377 => Some(OsCode::KEY_TV),\n            378 => Some(OsCode::KEY_TV2),\n            379 => Some(OsCode::KEY_VCR),\n            380 => Some(OsCode::KEY_VCR2),\n            381 => Some(OsCode::KEY_SAT),\n            382 => Some(OsCode::KEY_SAT2),\n            383 => Some(OsCode::KEY_CD),\n            384 => Some(OsCode::KEY_TAPE),\n            385 => Some(OsCode::KEY_RADIO),\n            386 => Some(OsCode::KEY_TUNER),\n            387 => Some(OsCode::KEY_PLAYER),\n            388 => Some(OsCode::KEY_TEXT),\n            389 => Some(OsCode::KEY_DVD),\n            390 => Some(OsCode::KEY_AUX),\n            391 => Some(OsCode::KEY_MP3),\n            392 => Some(OsCode::KEY_AUDIO),\n            393 => Some(OsCode::KEY_VIDEO),\n            394 => Some(OsCode::KEY_DIRECTORY),\n            395 => Some(OsCode::KEY_LIST),\n            396 => Some(OsCode::KEY_MEMO),\n            397 => Some(OsCode::KEY_CALENDAR),\n            398 => Some(OsCode::KEY_RED),\n            399 => Some(OsCode::KEY_GREEN),\n            400 => Some(OsCode::KEY_YELLOW),\n            401 => Some(OsCode::KEY_BLUE),\n            402 => Some(OsCode::KEY_CHANNELUP),\n            403 => Some(OsCode::KEY_CHANNELDOWN),\n            404 => Some(OsCode::KEY_FIRST),\n            405 => Some(OsCode::KEY_LAST),\n            406 => Some(OsCode::KEY_AB),\n            407 => Some(OsCode::KEY_NEXT),\n            408 => Some(OsCode::KEY_RESTART),\n            409 => Some(OsCode::KEY_SLOW),\n            410 => Some(OsCode::KEY_SHUFFLE),\n            411 => Some(OsCode::KEY_BREAK),\n            412 => Some(OsCode::KEY_PREVIOUS),\n            413 => Some(OsCode::KEY_DIGITS),\n            414 => Some(OsCode::KEY_TEEN),\n            415 => Some(OsCode::KEY_TWEN),\n            416 => Some(OsCode::KEY_VIDEOPHONE),\n            417 => Some(OsCode::KEY_GAMES),\n            418 => Some(OsCode::KEY_ZOOMIN),\n            419 => Some(OsCode::KEY_ZOOMOUT),\n            420 => Some(OsCode::KEY_ZOOMRESET),\n            421 => Some(OsCode::KEY_WORDPROCESSOR),\n            422 => Some(OsCode::KEY_EDITOR),\n            423 => Some(OsCode::KEY_SPREADSHEET),\n            424 => Some(OsCode::KEY_GRAPHICSEDITOR),\n            425 => Some(OsCode::KEY_PRESENTATION),\n            426 => Some(OsCode::KEY_DATABASE),\n            427 => Some(OsCode::KEY_NEWS),\n            428 => Some(OsCode::KEY_VOICEMAIL),\n            429 => Some(OsCode::KEY_ADDRESSBOOK),\n            430 => Some(OsCode::KEY_MESSENGER),\n            431 => Some(OsCode::KEY_DISPLAYTOGGLE),\n            432 => Some(OsCode::KEY_SPELLCHECK),\n            433 => Some(OsCode::KEY_LOGOFF),\n            434 => Some(OsCode::KEY_DOLLAR),\n            435 => Some(OsCode::KEY_EURO),\n            436 => Some(OsCode::KEY_FRAMEBACK),\n            437 => Some(OsCode::KEY_FRAMEFORWARD),\n            438 => Some(OsCode::KEY_CONTEXT_MENU),\n            439 => Some(OsCode::KEY_MEDIA_REPEAT),\n            440 => Some(OsCode::KEY_10CHANNELSUP),\n            441 => Some(OsCode::KEY_10CHANNELSDOWN),\n            442 => Some(OsCode::KEY_IMAGES),\n            443 => Some(OsCode::KEY_443),\n            444 => Some(OsCode::KEY_444),\n            445 => Some(OsCode::KEY_445),\n            446 => Some(OsCode::KEY_446),\n            447 => Some(OsCode::KEY_447),\n            448 => Some(OsCode::KEY_DEL_EOL),\n            449 => Some(OsCode::KEY_DEL_EOS),\n            450 => Some(OsCode::KEY_INS_LINE),\n            451 => Some(OsCode::KEY_DEL_LINE),\n            452 => Some(OsCode::KEY_452),\n            453 => Some(OsCode::KEY_453),\n            454 => Some(OsCode::KEY_454),\n            455 => Some(OsCode::KEY_455),\n            456 => Some(OsCode::KEY_456),\n            457 => Some(OsCode::KEY_457),\n            458 => Some(OsCode::KEY_458),\n            459 => Some(OsCode::KEY_459),\n            460 => Some(OsCode::KEY_460),\n            461 => Some(OsCode::KEY_461),\n            462 => Some(OsCode::KEY_462),\n            463 => Some(OsCode::KEY_463),\n            464 => Some(OsCode::KEY_FN),\n            465 => Some(OsCode::KEY_FN_ESC),\n            466 => Some(OsCode::KEY_FN_F1),\n            467 => Some(OsCode::KEY_FN_F2),\n            468 => Some(OsCode::KEY_FN_F3),\n            469 => Some(OsCode::KEY_FN_F4),\n            470 => Some(OsCode::KEY_FN_F5),\n            471 => Some(OsCode::KEY_FN_F6),\n            472 => Some(OsCode::KEY_FN_F7),\n            473 => Some(OsCode::KEY_FN_F8),\n            474 => Some(OsCode::KEY_FN_F9),\n            475 => Some(OsCode::KEY_FN_F10),\n            476 => Some(OsCode::KEY_FN_F11),\n            477 => Some(OsCode::KEY_FN_F12),\n            478 => Some(OsCode::KEY_FN_1),\n            479 => Some(OsCode::KEY_FN_2),\n            480 => Some(OsCode::KEY_FN_D),\n            481 => Some(OsCode::KEY_FN_E),\n            482 => Some(OsCode::KEY_FN_F),\n            483 => Some(OsCode::KEY_FN_S),\n            484 => Some(OsCode::KEY_FN_B),\n            485 => Some(OsCode::KEY_485),\n            486 => Some(OsCode::KEY_486),\n            487 => Some(OsCode::KEY_487),\n            488 => Some(OsCode::KEY_488),\n            489 => Some(OsCode::KEY_489),\n            490 => Some(OsCode::KEY_490),\n            491 => Some(OsCode::KEY_491),\n            492 => Some(OsCode::KEY_492),\n            493 => Some(OsCode::KEY_493),\n            494 => Some(OsCode::KEY_494),\n            495 => Some(OsCode::KEY_495),\n            496 => Some(OsCode::KEY_496),\n            497 => Some(OsCode::KEY_BRL_DOT1),\n            498 => Some(OsCode::KEY_BRL_DOT2),\n            499 => Some(OsCode::KEY_BRL_DOT3),\n            500 => Some(OsCode::KEY_BRL_DOT4),\n            501 => Some(OsCode::KEY_BRL_DOT5),\n            502 => Some(OsCode::KEY_BRL_DOT6),\n            503 => Some(OsCode::KEY_BRL_DOT7),\n            504 => Some(OsCode::KEY_BRL_DOT8),\n            505 => Some(OsCode::KEY_BRL_DOT9),\n            506 => Some(OsCode::KEY_BRL_DOT10),\n            507 => Some(OsCode::KEY_507),\n            508 => Some(OsCode::KEY_508),\n            509 => Some(OsCode::KEY_509),\n            510 => Some(OsCode::KEY_510),\n            511 => Some(OsCode::KEY_511),\n            512 => Some(OsCode::KEY_NUMERIC_0),\n            513 => Some(OsCode::KEY_NUMERIC_1),\n            514 => Some(OsCode::KEY_NUMERIC_2),\n            515 => Some(OsCode::KEY_NUMERIC_3),\n            516 => Some(OsCode::KEY_NUMERIC_4),\n            517 => Some(OsCode::KEY_NUMERIC_5),\n            518 => Some(OsCode::KEY_NUMERIC_6),\n            519 => Some(OsCode::KEY_NUMERIC_7),\n            520 => Some(OsCode::KEY_NUMERIC_8),\n            521 => Some(OsCode::KEY_NUMERIC_9),\n            522 => Some(OsCode::KEY_NUMERIC_STAR),\n            523 => Some(OsCode::KEY_NUMERIC_POUND),\n            524 => Some(OsCode::KEY_NUMERIC_A),\n            525 => Some(OsCode::KEY_NUMERIC_B),\n            526 => Some(OsCode::KEY_NUMERIC_C),\n            527 => Some(OsCode::KEY_NUMERIC_D),\n            528 => Some(OsCode::KEY_CAMERA_FOCUS),\n            529 => Some(OsCode::KEY_WPS_BUTTON),\n            530 => Some(OsCode::KEY_TOUCHPAD_TOGGLE),\n            531 => Some(OsCode::KEY_TOUCHPAD_ON),\n            532 => Some(OsCode::KEY_TOUCHPAD_OFF),\n            533 => Some(OsCode::KEY_CAMERA_ZOOMIN),\n            534 => Some(OsCode::KEY_CAMERA_ZOOMOUT),\n            535 => Some(OsCode::KEY_CAMERA_UP),\n            536 => Some(OsCode::KEY_CAMERA_DOWN),\n            537 => Some(OsCode::KEY_CAMERA_LEFT),\n            538 => Some(OsCode::KEY_CAMERA_RIGHT),\n            539 => Some(OsCode::KEY_ATTENDANT_ON),\n            540 => Some(OsCode::KEY_ATTENDANT_OFF),\n            541 => Some(OsCode::KEY_ATTENDANT_TOGGLE),\n            542 => Some(OsCode::KEY_LIGHTS_TOGGLE),\n            543 => Some(OsCode::KEY_543),\n            544 => Some(OsCode::BTN_DPAD_UP),\n            545 => Some(OsCode::BTN_DPAD_DOWN),\n            546 => Some(OsCode::BTN_DPAD_LEFT),\n            547 => Some(OsCode::BTN_DPAD_RIGHT),\n            548 => Some(OsCode::KEY_548),\n            549 => Some(OsCode::KEY_549),\n            550 => Some(OsCode::KEY_550),\n            551 => Some(OsCode::KEY_551),\n            552 => Some(OsCode::KEY_552),\n            553 => Some(OsCode::KEY_553),\n            554 => Some(OsCode::KEY_554),\n            555 => Some(OsCode::KEY_555),\n            556 => Some(OsCode::KEY_556),\n            557 => Some(OsCode::KEY_557),\n            558 => Some(OsCode::KEY_558),\n            559 => Some(OsCode::KEY_559),\n            560 => Some(OsCode::KEY_ALS_TOGGLE),\n            561 => Some(OsCode::KEY_ROTATE_LOCK_TOGGLE),\n            562 => Some(OsCode::KEY_562),\n            563 => Some(OsCode::KEY_563),\n            564 => Some(OsCode::KEY_564),\n            565 => Some(OsCode::KEY_565),\n            566 => Some(OsCode::KEY_566),\n            567 => Some(OsCode::KEY_567),\n            568 => Some(OsCode::KEY_568),\n            569 => Some(OsCode::KEY_569),\n            570 => Some(OsCode::KEY_570),\n            571 => Some(OsCode::KEY_571),\n            572 => Some(OsCode::KEY_572),\n            573 => Some(OsCode::KEY_573),\n            574 => Some(OsCode::KEY_574),\n            575 => Some(OsCode::KEY_575),\n            576 => Some(OsCode::KEY_BUTTONCONFIG),\n            577 => Some(OsCode::KEY_TASKMANAGER),\n            578 => Some(OsCode::KEY_JOURNAL),\n            579 => Some(OsCode::KEY_CONTROLPANEL),\n            580 => Some(OsCode::KEY_APPSELECT),\n            581 => Some(OsCode::KEY_SCREENSAVER),\n            582 => Some(OsCode::KEY_VOICECOMMAND),\n            583 => Some(OsCode::KEY_ASSISTANT),\n            584 => Some(OsCode::KEY_KBD_LAYOUT_NEXT),\n            585 => Some(OsCode::KEY_585),\n            586 => Some(OsCode::KEY_586),\n            587 => Some(OsCode::KEY_587),\n            588 => Some(OsCode::KEY_588),\n            589 => Some(OsCode::KEY_589),\n            590 => Some(OsCode::KEY_590),\n            591 => Some(OsCode::KEY_591),\n            592 => Some(OsCode::KEY_BRIGHTNESS_MIN),\n            593 => Some(OsCode::KEY_BRIGHTNESS_MAX),\n            594 => Some(OsCode::KEY_594),\n            595 => Some(OsCode::KEY_595),\n            596 => Some(OsCode::KEY_596),\n            597 => Some(OsCode::KEY_597),\n            598 => Some(OsCode::KEY_598),\n            599 => Some(OsCode::KEY_599),\n            600 => Some(OsCode::KEY_600),\n            601 => Some(OsCode::KEY_601),\n            602 => Some(OsCode::KEY_602),\n            603 => Some(OsCode::KEY_603),\n            604 => Some(OsCode::KEY_604),\n            605 => Some(OsCode::KEY_605),\n            606 => Some(OsCode::KEY_606),\n            607 => Some(OsCode::KEY_607),\n            608 => Some(OsCode::KEY_KBDINPUTASSIST_PREV),\n            609 => Some(OsCode::KEY_KBDINPUTASSIST_NEXT),\n            610 => Some(OsCode::KEY_KBDINPUTASSIST_PREVGROUP),\n            611 => Some(OsCode::KEY_KBDINPUTASSIST_NEXTGROUP),\n            612 => Some(OsCode::KEY_KBDINPUTASSIST_ACCEPT),\n            613 => Some(OsCode::KEY_KBDINPUTASSIST_CANCEL),\n            614 => Some(OsCode::KEY_RIGHT_UP),\n            615 => Some(OsCode::KEY_RIGHT_DOWN),\n            616 => Some(OsCode::KEY_LEFT_UP),\n            617 => Some(OsCode::KEY_LEFT_DOWN),\n            618 => Some(OsCode::KEY_ROOT_MENU),\n            619 => Some(OsCode::KEY_MEDIA_TOP_MENU),\n            620 => Some(OsCode::KEY_NUMERIC_11),\n            621 => Some(OsCode::KEY_NUMERIC_12),\n            622 => Some(OsCode::KEY_AUDIO_DESC),\n            623 => Some(OsCode::KEY_3D_MODE),\n            624 => Some(OsCode::KEY_NEXT_FAVORITE),\n            625 => Some(OsCode::KEY_STOP_RECORD),\n            626 => Some(OsCode::KEY_PAUSE_RECORD),\n            627 => Some(OsCode::KEY_VOD),\n            628 => Some(OsCode::KEY_UNMUTE),\n            629 => Some(OsCode::KEY_FASTREVERSE),\n            630 => Some(OsCode::KEY_SLOWREVERSE),\n            631 => Some(OsCode::KEY_DATA),\n            632 => Some(OsCode::KEY_ONSCREEN_KEYBOARD),\n            633 => Some(OsCode::KEY_633),\n            634 => Some(OsCode::KEY_634),\n            635 => Some(OsCode::KEY_635),\n            636 => Some(OsCode::KEY_636),\n            637 => Some(OsCode::KEY_637),\n            638 => Some(OsCode::KEY_638),\n            639 => Some(OsCode::KEY_639),\n            640 => Some(OsCode::KEY_640),\n            641 => Some(OsCode::KEY_641),\n            642 => Some(OsCode::KEY_642),\n            643 => Some(OsCode::KEY_643),\n            644 => Some(OsCode::KEY_644),\n            645 => Some(OsCode::KEY_645),\n            646 => Some(OsCode::KEY_646),\n            647 => Some(OsCode::KEY_647),\n            648 => Some(OsCode::KEY_648),\n            649 => Some(OsCode::KEY_649),\n            650 => Some(OsCode::KEY_650),\n            651 => Some(OsCode::KEY_651),\n            652 => Some(OsCode::KEY_652),\n            653 => Some(OsCode::KEY_653),\n            654 => Some(OsCode::KEY_654),\n            655 => Some(OsCode::KEY_655),\n            656 => Some(OsCode::KEY_656),\n            657 => Some(OsCode::KEY_657),\n            658 => Some(OsCode::KEY_658),\n            659 => Some(OsCode::KEY_659),\n            660 => Some(OsCode::KEY_660),\n            661 => Some(OsCode::KEY_661),\n            662 => Some(OsCode::KEY_662),\n            663 => Some(OsCode::KEY_663),\n            664 => Some(OsCode::KEY_664),\n            665 => Some(OsCode::KEY_665),\n            666 => Some(OsCode::KEY_666),\n            667 => Some(OsCode::KEY_667),\n            668 => Some(OsCode::KEY_668),\n            669 => Some(OsCode::KEY_669),\n            670 => Some(OsCode::KEY_670),\n            671 => Some(OsCode::KEY_671),\n            672 => Some(OsCode::KEY_672),\n            673 => Some(OsCode::KEY_673),\n            674 => Some(OsCode::KEY_674),\n            675 => Some(OsCode::KEY_675),\n            676 => Some(OsCode::KEY_676),\n            677 => Some(OsCode::KEY_677),\n            678 => Some(OsCode::KEY_678),\n            679 => Some(OsCode::KEY_679),\n            680 => Some(OsCode::KEY_680),\n            681 => Some(OsCode::KEY_681),\n            682 => Some(OsCode::KEY_682),\n            683 => Some(OsCode::KEY_683),\n            684 => Some(OsCode::KEY_684),\n            685 => Some(OsCode::KEY_685),\n            686 => Some(OsCode::KEY_686),\n            687 => Some(OsCode::KEY_687),\n            688 => Some(OsCode::KEY_688),\n            689 => Some(OsCode::KEY_689),\n            690 => Some(OsCode::KEY_690),\n            691 => Some(OsCode::KEY_691),\n            692 => Some(OsCode::KEY_692),\n            693 => Some(OsCode::KEY_693),\n            694 => Some(OsCode::KEY_694),\n            695 => Some(OsCode::KEY_695),\n            696 => Some(OsCode::KEY_696),\n            697 => Some(OsCode::KEY_697),\n            698 => Some(OsCode::KEY_698),\n            699 => Some(OsCode::KEY_699),\n            700 => Some(OsCode::KEY_700),\n            701 => Some(OsCode::KEY_701),\n            702 => Some(OsCode::KEY_702),\n            703 => Some(OsCode::KEY_703),\n            704 => Some(OsCode::BTN_TRIGGER_HAPPY1),\n            705 => Some(OsCode::BTN_TRIGGER_HAPPY2),\n            706 => Some(OsCode::BTN_TRIGGER_HAPPY3),\n            707 => Some(OsCode::BTN_TRIGGER_HAPPY4),\n            708 => Some(OsCode::BTN_TRIGGER_HAPPY5),\n            709 => Some(OsCode::BTN_TRIGGER_HAPPY6),\n            710 => Some(OsCode::BTN_TRIGGER_HAPPY7),\n            711 => Some(OsCode::BTN_TRIGGER_HAPPY8),\n            712 => Some(OsCode::BTN_TRIGGER_HAPPY9),\n            713 => Some(OsCode::BTN_TRIGGER_HAPPY10),\n            714 => Some(OsCode::BTN_TRIGGER_HAPPY11),\n            715 => Some(OsCode::BTN_TRIGGER_HAPPY12),\n            716 => Some(OsCode::BTN_TRIGGER_HAPPY13),\n            717 => Some(OsCode::BTN_TRIGGER_HAPPY14),\n            718 => Some(OsCode::BTN_TRIGGER_HAPPY15),\n            719 => Some(OsCode::BTN_TRIGGER_HAPPY16),\n            720 => Some(OsCode::BTN_TRIGGER_HAPPY17),\n            721 => Some(OsCode::BTN_TRIGGER_HAPPY18),\n            722 => Some(OsCode::BTN_TRIGGER_HAPPY19),\n            723 => Some(OsCode::BTN_TRIGGER_HAPPY20),\n            724 => Some(OsCode::BTN_TRIGGER_HAPPY21),\n            725 => Some(OsCode::BTN_TRIGGER_HAPPY22),\n            726 => Some(OsCode::BTN_TRIGGER_HAPPY23),\n            727 => Some(OsCode::BTN_TRIGGER_HAPPY24),\n            728 => Some(OsCode::BTN_TRIGGER_HAPPY25),\n            729 => Some(OsCode::BTN_TRIGGER_HAPPY26),\n            730 => Some(OsCode::BTN_TRIGGER_HAPPY27),\n            731 => Some(OsCode::BTN_TRIGGER_HAPPY28),\n            732 => Some(OsCode::BTN_TRIGGER_HAPPY29),\n            733 => Some(OsCode::BTN_TRIGGER_HAPPY30),\n            734 => Some(OsCode::BTN_TRIGGER_HAPPY31),\n            735 => Some(OsCode::BTN_TRIGGER_HAPPY32),\n            736 => Some(OsCode::BTN_TRIGGER_HAPPY33),\n            737 => Some(OsCode::BTN_TRIGGER_HAPPY34),\n            738 => Some(OsCode::BTN_TRIGGER_HAPPY35),\n            739 => Some(OsCode::BTN_TRIGGER_HAPPY36),\n            740 => Some(OsCode::BTN_TRIGGER_HAPPY37),\n            741 => Some(OsCode::BTN_TRIGGER_HAPPY38),\n            742 => Some(OsCode::BTN_TRIGGER_HAPPY39),\n            743 => Some(OsCode::BTN_TRIGGER_HAPPY40),\n            744 => Some(OsCode::BTN_MAX),\n            745 => Some(OsCode::MouseWheelUp),\n            746 => Some(OsCode::MouseWheelDown),\n            747 => Some(OsCode::MouseWheelLeft),\n            748 => Some(OsCode::MouseWheelRight),\n            767 => Some(OsCode::KEY_MAX),\n            _ => None,\n        }\n    }\n\n    pub(super) const fn as_u16_windows(self) -> u16 {\n        match self {\n            OsCode::KEY_0 => 0x30,\n            OsCode::KEY_1 => 0x31,\n            OsCode::KEY_2 => 0x32,\n            OsCode::KEY_3 => 0x33,\n            OsCode::KEY_4 => 0x34,\n            OsCode::KEY_5 => 0x35,\n            OsCode::KEY_6 => 0x36,\n            OsCode::KEY_7 => 0x37,\n            OsCode::KEY_8 => 0x38,\n            OsCode::KEY_9 => 0x39,\n            OsCode::KEY_A => 0x41,\n            OsCode::KEY_B => 0x42,\n            OsCode::KEY_C => 0x43,\n            OsCode::KEY_D => 0x44,\n            OsCode::KEY_E => 0x45,\n            OsCode::KEY_F => 0x46,\n            OsCode::KEY_G => 0x47,\n            OsCode::KEY_H => 0x48,\n            OsCode::KEY_I => 0x49,\n            OsCode::KEY_J => 0x4A,\n            OsCode::KEY_K => 0x4B,\n            OsCode::KEY_L => 0x4C,\n            OsCode::KEY_M => 0x4D,\n            OsCode::KEY_N => 0x4E,\n            OsCode::KEY_O => 0x4F,\n            OsCode::KEY_P => 0x50,\n            OsCode::KEY_Q => 0x51,\n            OsCode::KEY_R => 0x52,\n            OsCode::KEY_S => 0x53,\n            OsCode::KEY_T => 0x54,\n            OsCode::KEY_U => 0x55,\n            OsCode::KEY_V => 0x56,\n            OsCode::KEY_W => 0x57,\n            OsCode::KEY_X => 0x58,\n            OsCode::KEY_Y => 0x59,\n            OsCode::KEY_Z => 0x5A,\n            OsCode::KEY_SEMICOLON => VK_OEM_1,\n            OsCode::KEY_SLASH => VK_OEM_2,\n            OsCode::KEY_GRAVE => VK_OEM_3,\n            OsCode::KEY_LEFTBRACE => VK_OEM_4,\n            OsCode::KEY_BACKSLASH => VK_OEM_5,\n            OsCode::KEY_RIGHTBRACE => VK_OEM_6,\n            OsCode::KEY_APOSTROPHE => VK_OEM_7,\n            OsCode::KEY_MINUS => VK_OEM_MINUS,\n            OsCode::KEY_DOT => VK_OEM_PERIOD,\n            OsCode::KEY_EQUAL => VK_OEM_PLUS,\n            OsCode::KEY_BACKSPACE => VK_BACK,\n            OsCode::KEY_ESC => VK_ESCAPE,\n            OsCode::KEY_TAB => VK_TAB,\n            OsCode::KEY_ENTER => VK_RETURN,\n            OsCode::KEY_LEFTCTRL => VK_LCONTROL,\n            OsCode::KEY_LEFTSHIFT => VK_LSHIFT,\n            OsCode::KEY_COMMA => VK_OEM_COMMA,\n            OsCode::KEY_RIGHTSHIFT => VK_RSHIFT,\n            OsCode::KEY_KPASTERISK => VK_MULTIPLY,\n            OsCode::KEY_LEFTALT => VK_LMENU,\n            OsCode::KEY_SPACE => VK_SPACE,\n            OsCode::KEY_CAPSLOCK => VK_CAPITAL,\n            OsCode::KEY_F1 => VK_F1,\n            OsCode::KEY_F2 => VK_F2,\n            OsCode::KEY_F3 => VK_F3,\n            OsCode::KEY_F4 => VK_F4,\n            OsCode::KEY_F5 => VK_F5,\n            OsCode::KEY_F6 => VK_F6,\n            OsCode::KEY_F7 => VK_F7,\n            OsCode::KEY_F8 => VK_F8,\n            OsCode::KEY_F9 => VK_F9,\n            OsCode::KEY_F10 => VK_F10,\n            OsCode::KEY_F11 => VK_F11,\n            OsCode::KEY_F12 => VK_F12,\n            OsCode::KEY_NUMLOCK => VK_NUMLOCK,\n            OsCode::KEY_CLEAR => VK_CLEAR,\n            OsCode::KEY_SCROLLLOCK => VK_SCROLL,\n            OsCode::KEY_KP0 => VK_NUMPAD0,\n            OsCode::KEY_KP1 => VK_NUMPAD1,\n            OsCode::KEY_KP2 => VK_NUMPAD2,\n            OsCode::KEY_KP3 => VK_NUMPAD3,\n            OsCode::KEY_KP4 => VK_NUMPAD4,\n            OsCode::KEY_KP5 => VK_NUMPAD5,\n            OsCode::KEY_KP6 => VK_NUMPAD6,\n            OsCode::KEY_KP7 => VK_NUMPAD7,\n            OsCode::KEY_KP8 => VK_NUMPAD8,\n            OsCode::KEY_KP9 => VK_NUMPAD9,\n            OsCode::KEY_KPMINUS => VK_SUBTRACT,\n            OsCode::KEY_KPPLUS => VK_ADD,\n            OsCode::KEY_KPDOT => VK_DECIMAL,\n            OsCode::KEY_KPENTER => VK_KPENTER_FAKE,\n            OsCode::KEY_RIGHTCTRL => VK_RCONTROL,\n            OsCode::KEY_KPSLASH => VK_DIVIDE,\n            OsCode::KEY_RIGHTALT => VK_RMENU,\n            OsCode::KEY_HOME => VK_HOME,\n            OsCode::KEY_UP => VK_UP,\n            OsCode::KEY_PAGEUP => VK_PRIOR,\n            OsCode::KEY_LEFT => VK_LEFT,\n            OsCode::KEY_RIGHT => VK_RIGHT,\n            OsCode::KEY_END => VK_END,\n            OsCode::KEY_DOWN => VK_DOWN,\n            OsCode::KEY_PAGEDOWN => VK_NEXT,\n            OsCode::KEY_INSERT => VK_INSERT,\n            OsCode::KEY_DELETE => VK_DELETE,\n            OsCode::KEY_MUTE => VK_VOLUME_MUTE,\n            OsCode::KEY_VOLUMEDOWN => VK_VOLUME_DOWN,\n            OsCode::KEY_VOLUMEUP => VK_VOLUME_UP,\n            OsCode::KEY_PAUSE => VK_PAUSE,\n            OsCode::KEY_LEFTMETA => VK_LWIN,\n            OsCode::KEY_RIGHTMETA => VK_RWIN,\n            OsCode::KEY_COMPOSE => VK_APPS,\n            OsCode::KEY_BACK => VK_BROWSER_BACK,\n            OsCode::KEY_FORWARD => VK_BROWSER_FORWARD,\n            OsCode::KEY_NEXTSONG => VK_MEDIA_NEXT_TRACK,\n            OsCode::KEY_PLAYPAUSE => VK_MEDIA_PLAY_PAUSE,\n            OsCode::KEY_PREVIOUSSONG => VK_MEDIA_PREV_TRACK,\n            OsCode::KEY_STOP => VK_MEDIA_STOP,\n            OsCode::KEY_HOMEPAGE => VK_BROWSER_HOME,\n            OsCode::KEY_MAIL => VK_LAUNCH_MAIL,\n            OsCode::KEY_MEDIA => VK_LAUNCH_MEDIA_SELECT,\n            OsCode::KEY_REFRESH => VK_BROWSER_REFRESH,\n            OsCode::KEY_F13 => VK_F13,\n            OsCode::KEY_F14 => VK_F14,\n            OsCode::KEY_F15 => VK_F15,\n            OsCode::KEY_F16 => VK_F16,\n            OsCode::KEY_F17 => VK_F17,\n            OsCode::KEY_F18 => VK_F18,\n            OsCode::KEY_F19 => VK_F19,\n            OsCode::KEY_F20 => VK_F20,\n            OsCode::KEY_F21 => VK_F21,\n            OsCode::KEY_F22 => VK_F22,\n            OsCode::KEY_F23 => VK_F23,\n            OsCode::KEY_F24 => VK_F24,\n            OsCode::KEY_HANGEUL => VK_HANGEUL,\n            OsCode::KEY_HANJA => VK_HANJA,\n            OsCode::KEY_252 => VK_OEM_8,\n            OsCode::KEY_102ND => VK_OEM_102,\n            OsCode::KEY_PLAY => VK_PLAY,\n            OsCode::KEY_PRINT => VK_SNAPSHOT,\n            OsCode::KEY_SEARCH => VK_BROWSER_SEARCH,\n            OsCode::KEY_FAVORITES => VK_BROWSER_FAVORITES,\n            OsCode::KEY_RO => 0xC1,\n            OsCode::KEY_HENKAN => VK_CONVERT,\n            OsCode::KEY_MUHENKAN => VK_NONCONVERT,\n            OsCode::BTN_LEFT => VK_LBUTTON,\n            OsCode::BTN_RIGHT => VK_RBUTTON,\n            OsCode::BTN_MIDDLE => VK_MBUTTON,\n            OsCode::BTN_SIDE => VK_XBUTTON1,\n            OsCode::BTN_EXTRA => VK_XBUTTON2,\n            OsCode::KEY_YEN => 255,\n            OsCode::KEY_KATAKANA => VK_DBE_KATAKANA,\n            osc => osc as u16,\n        }\n    }\n}\n"
  },
  {
    "path": "parser/src/layers.rs",
    "content": "use kanata_keyberon::key_code::KeyCode;\nuse kanata_keyberon::layout::*;\n\nuse crate::cfg::KanataAction;\nuse crate::cfg::alloc::*;\nuse crate::custom_action::*;\nuse crate::keys::OsCode;\n\nuse std::sync::Arc;\n\n// OsCode::KEY_MAX is the biggest OsCode\npub const KEYS_IN_ROW: usize = OsCode::KEY_MAX as usize;\npub const LAYER_ROWS: usize = 2;\npub const DEFAULT_ACTION: KanataAction = KanataAction::KeyCode(KeyCode::ErrorUndefined);\n\npub type IntermediateLayers = Box<[[Row; LAYER_ROWS]]>;\n\npub type KLayers =\n    Layers<'static, KEYS_IN_ROW, LAYER_ROWS, &'static &'static [&'static CustomAction]>;\n\npub struct KanataLayers {\n    pub(crate) layers:\n        Layers<'static, KEYS_IN_ROW, LAYER_ROWS, &'static &'static [&'static CustomAction]>,\n    _allocations: Arc<Allocations>,\n}\n\nimpl std::fmt::Debug for KanataLayers {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"KanataLayers\").finish()\n    }\n}\n\npub type Row = [kanata_keyberon::action::Action<'static, &'static &'static [&'static CustomAction]>;\n    KEYS_IN_ROW];\n\npub fn new_layers(layers: usize) -> IntermediateLayers {\n    let actual_num_layers = layers;\n    // Note: why construct it like this?\n    // Because don't want to construct KanataLayers on the stack.\n    // The stack will overflow because of lack of placement new.\n    let mut layers = Vec::with_capacity(actual_num_layers);\n    for _ in 0..actual_num_layers {\n        layers.push([[DEFAULT_ACTION; KEYS_IN_ROW], [DEFAULT_ACTION; KEYS_IN_ROW]]);\n    }\n    layers.into_boxed_slice()\n}\n\nimpl KanataLayers {\n    /// # Safety\n    ///\n    /// The allocations must hold all of the &'static pointers found in layers.\n    pub(crate) unsafe fn new(layers: KLayers, allocations: Arc<Allocations>) -> Self {\n        Self {\n            layers,\n            _allocations: allocations,\n        }\n    }\n\n    pub(crate) fn get(&self) -> (KLayers, Arc<Allocations>) {\n        (self.layers, self._allocations.clone())\n    }\n}\n"
  },
  {
    "path": "parser/src/lib.rs",
    "content": "//! A parser for configuration language of [kanata](https://github.com/jtroo/kanata), a keyboard remapper.\n\npub mod cfg;\npub mod custom_action;\npub mod keys;\npub mod layers;\npub mod lsp_hints;\npub mod sequences;\npub mod subset;\npub mod trie;\n"
  },
  {
    "path": "parser/src/lsp_hints.rs",
    "content": "use crate::cfg::sexpr::SExpr;\n\npub use inner::*;\n\n#[cfg(not(feature = \"lsp\"))]\nmod inner {\n    #[derive(Debug, Default)]\n    pub struct LspHints {}\n}\n\n#[cfg(feature = \"lsp\")]\nmod inner {\n    use crate::cfg::sexpr::{Span, Spanned};\n    type HashMap<K, V> = rustc_hash::FxHashMap<K, V>;\n\n    #[derive(Debug, Default)]\n    pub struct LspHints {\n        pub inactive_code: Vec<InactiveCode>,\n        pub definition_locations: DefinitionLocations,\n        pub reference_locations: ReferenceLocations,\n    }\n\n    #[derive(Debug, Clone)]\n    pub struct InactiveCode {\n        pub span: Span,\n        pub reason: String,\n    }\n\n    #[derive(Debug, Default, Clone)]\n    pub struct DefinitionLocations {\n        pub alias: HashMap<String, Span>,\n        pub variable: HashMap<String, Span>,\n        pub virtual_key: HashMap<String, Span>,\n        pub layer: HashMap<String, Span>,\n        pub template: HashMap<String, Span>,\n    }\n\n    #[derive(Debug, Default, Clone)]\n    pub struct ReferenceLocations {\n        pub alias: ReferencesMap,\n        pub variable: ReferencesMap,\n        pub virtual_key: ReferencesMap,\n        pub layer: ReferencesMap,\n        pub template: ReferencesMap,\n        pub include: ReferencesMap,\n    }\n\n    #[derive(Debug, Default, Clone)]\n    pub struct ReferencesMap(pub HashMap<String, Vec<Span>>);\n\n    #[allow(unused)]\n    impl ReferencesMap {\n        pub(crate) fn push_from_atom(&mut self, atom: &Spanned<String>) {\n            match self.0.get_mut(&atom.t) {\n                Some(refs) => refs.push(atom.span.clone()),\n                None => {\n                    self.0.insert(atom.t.clone(), vec![atom.span.clone()]);\n                }\n            };\n        }\n\n        pub(crate) fn push(&mut self, name: &str, span: Span) {\n            match self.0.get_mut(name) {\n                Some(refs) => refs.push(span),\n                None => {\n                    self.0.insert(name.to_owned(), vec![span]);\n                }\n            };\n        }\n    }\n}\n\n#[allow(unused)]\npub(crate) fn set_layer_change_lsp_hint(layer_name_expr: &SExpr, lsp_hints: &mut LspHints) {\n    #[cfg(feature = \"lsp\")]\n    {\n        let layer_name_atom = match layer_name_expr {\n            SExpr::Atom(x) => x,\n            SExpr::List(_) => unreachable!(\"checked in layer_idx\"),\n        };\n        lsp_hints\n            .reference_locations\n            .layer\n            .push_from_atom(layer_name_atom);\n    }\n}\n"
  },
  {
    "path": "parser/src/sequences.rs",
    "content": "use kanata_keyberon::key_code::KeyCode;\n\npub const MASK_KEYCODES: u16 = 0x03FF;\npub const MASK_MODDED: u16 = 0xFC00;\npub const KEY_OVERLAP: KeyCode = KeyCode::ErrorRollOver;\npub const KEY_OVERLAP_MARKER: u16 = 0x0400;\n\npub fn mod_mask_for_keycode(kc: KeyCode) -> u16 {\n    use KeyCode::*;\n    match kc {\n        LShift | RShift => 0x8000,\n        LCtrl | RCtrl => 0x4000,\n        LAlt => 0x2000,\n        RAlt => 0x1000,\n        LGui | RGui => 0x0800,\n        // This is not real... this is a marker to help signify that key presses should be\n        // overlapping. The way this will look in the chord sequence is as such:\n        //\n        //   [ (0x0400 | X), (0x0400 | Y), (0x0400) ]\n        ErrorRollOver => KEY_OVERLAP_MARKER,\n        _ => 0,\n    }\n}\n\n#[test]\nfn keys_fit_within_mask() {\n    use crate::keys::OsCode;\n    assert!(MASK_KEYCODES >= u16::from(OsCode::KEY_MAX));\n}\n"
  },
  {
    "path": "parser/src/subset.rs",
    "content": "//! Collection where keys are slices of a type, and supports a get operation to check if the key\n//! exists, is a subset of any existing key, or is neither of the aforementioned cases.\n//!\n//! In the underlying structure the value is cloned for each participating member of slice key, so\n//! you should ensure values are cheaply clonable. If the value is not, consider putting it inside\n//! `Rc` or `Arc`.\n\nuse rustc_hash::FxHashMap;\nuse std::hash::Hash;\n\n// # Design considerations:\n//\n// It was considered whether `key` should be in an `Arc` instead of a `Box` or whether\n// `SsmKeyValue` should be wrapped in `Arc`.\n//\n// ## No usage of `Arc`\n//\n// With no reference counting, the key slice `&[K]` will be allocated in a separate box for every\n// instance of `SsmKeyValue`. The number of instances of the key is equal to the length of the key.\n// This is obviously the best choice if key lengths are mostly expected to be 1. For key lengths\n// larger than 1, the point at which an `Arc` would be better would need to be measured.\n//\n// ## `key: Arc<[K]>`\n//\n// The benefit of using an `Arc` for the key instead of `Box` is that clones don't create a new\n// allocation. The downside is that the allocations use more space, namely there is an extra\n// `2 * usize` in the allocation for the strong and weak pointers, so 16 extra bytes.\n//\n// Kanata uses `K=u16` only today (August 2025). This means perfectly sized allocations, it would\n// take a 3 length key for `Box` to begin to reach `Arc`'s size:\n//   - Arc: 16 + (3*2) = 22 bytes\n//   - Box:  3 x (3*2) = 18 bytes\n//\n// A 4-length key is much worse:\n//   - Arc: 16 + (4*2) = 22 bytes\n//   - Box:  4 x (4*2) = 32 bytes\n//\n// In practice, allocators have allocation space overhead and/or minimum allocation sizes. With the\n// effects of these overheads and CPU caching, the estimate of when `Arc` outperforms `Box` for\n// read-only usage is likely a key length of 3 or even 2. Read-only is notable because Kanata\n// doesn't care about write performance; write only happens at parse time and only reads are done\n// for standard runtime.\n//\n// ## Vec<Arc<SsmKeyValue<...>>\n//\n// This has the downside of needing to follow two pointers to dereference `key`. For `Box`-only,\n// or `key: Arc<[K]>`, this is not the case. Having two indirections is not desirable.\n\n#[derive(Debug, Clone)]\npub struct SubsetMap<K, V> {\n    map: FxHashMap<K, Vec<SsmKeyValue<K, V>>>,\n}\n\n#[derive(Debug, Clone)]\nstruct SsmKeyValue<K, V> {\n    key: Box<[K]>,\n    value: V,\n}\n\nimpl<K, V> SsmKeyValue<K, V>\nwhere\n    K: Clone,\n{\n    fn ssmkv_new(key: impl AsRef<[K]>, value: V) -> Self {\n        Self {\n            key: key.as_ref().to_vec().into_boxed_slice(),\n            value,\n        }\n    }\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\npub enum GetOrIsSubsetOfKnownKey<T> {\n    HasValue(T),\n    IsSubset,\n    Neither,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\npub enum SsmKeyExistedBeforeInsert {\n    Existed,\n    NotThere,\n}\n\nuse GetOrIsSubsetOfKnownKey::*;\n\nimpl<K, V> Default for SubsetMap<K, V>\nwhere\n    K: Clone + PartialEq + Ord + Hash,\n    V: Clone,\n{\n    fn default() -> Self {\n        Self::ssm_new()\n    }\n}\n\nimpl<K, V> SubsetMap<K, V>\nwhere\n    K: Clone + PartialEq + Ord + Hash,\n    V: Clone,\n{\n    pub fn ssm_new() -> Self {\n        Self {\n            map: FxHashMap::default(),\n        }\n    }\n\n    /// Inserts a potentially unsorted key. Sorts the key and then calls ssm_insert_ksorted.\n    pub fn ssm_insert(&mut self, mut key: impl AsMut<[K]>, val: V) -> SsmKeyExistedBeforeInsert {\n        key.as_mut().sort();\n        self.ssm_insert_ksorted(key.as_mut(), val)\n    }\n\n    /// Inserts a sorted key. Failure to enforce that the key is sorted results in defined but\n    /// unspecified behaviour.\n    pub fn ssm_insert_ksorted(\n        &mut self,\n        key: impl AsRef<[K]>,\n        val: V,\n    ) -> SsmKeyExistedBeforeInsert {\n        let mut key_existed = SsmKeyExistedBeforeInsert::NotThere;\n        for k in key.as_ref().iter().cloned() {\n            let keyvals_for_key_item = self.map.entry(k).or_default();\n            match keyvals_for_key_item\n                .binary_search_by(|probe| probe.key.as_ref().cmp(key.as_ref()))\n            {\n                Ok(pos) => {\n                    key_existed = SsmKeyExistedBeforeInsert::Existed;\n                    keyvals_for_key_item[pos] = SsmKeyValue::ssmkv_new(key.as_ref(), val.clone());\n                }\n                Err(pos) => {\n                    keyvals_for_key_item\n                        .insert(pos, SsmKeyValue::ssmkv_new(key.as_ref(), val.clone()));\n                }\n            }\n        }\n        key_existed\n    }\n\n    /// Gets using a potentially unsorted key. Sorts the key then calls\n    /// ssm_get_or_is_subset_ksorted.\n    pub fn ssm_get_or_is_subset(&self, mut key: impl AsMut<[K]>) -> GetOrIsSubsetOfKnownKey<V> {\n        key.as_mut().sort();\n        self.ssm_get_or_is_subset_ksorted(key.as_mut())\n    }\n\n    /// Gets using a sorted key. Failure to enforce a sorted key results in defined but unspecified\n    /// behaviour.\n    pub fn ssm_get_or_is_subset_ksorted(\n        &self,\n        get_key: impl AsRef<[K]>,\n    ) -> GetOrIsSubsetOfKnownKey<V> {\n        let get_key = get_key.as_ref();\n        if get_key.is_empty() {\n            return match self.is_empty() {\n                true => Neither,\n                false => IsSubset,\n            };\n        }\n        match self.map.get(&get_key[0]) {\n            None => Neither,\n            Some(keyvals_for_key_item) => {\n                match keyvals_for_key_item\n                    .binary_search_by(|probe| probe.key.as_ref().cmp(get_key.as_ref()))\n                {\n                    Ok(pos) => HasValue(keyvals_for_key_item[pos].value.clone()),\n                    Err(_) => {\n                        for kv in keyvals_for_key_item.iter() {\n                            if get_key.iter().all(|kitem| kv.key.contains(kitem)) {\n                                return IsSubset;\n                            }\n                        }\n                        Neither\n                    }\n                }\n            }\n        }\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.map.is_empty()\n    }\n}\n"
  },
  {
    "path": "parser/src/trie.rs",
    "content": "//! Wrapper around a trie type for (hopefully) easier swapping of libraries if desired.\n\nuse bytemuck::cast_slice;\nuse patricia_tree::map::PatriciaMap;\n\npub type TrieKeyElement = u16;\n\n#[derive(Debug, Clone)]\npub struct Trie<T> {\n    inner: patricia_tree::map::PatriciaMap<T>,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\npub enum GetOrDescendentExistsResult<T> {\n    NotInTrie,\n    InTrie,\n    HasValue(T),\n}\n\nuse GetOrDescendentExistsResult::*;\n\nimpl<T> Default for Trie<T> {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nfn key_len(k: impl AsRef<[u16]>) -> usize {\n    debug_assert!(std::mem::size_of::<TrieKeyElement>() == 2 * std::mem::size_of::<u8>());\n    k.as_ref().len() * 2\n}\n\nimpl<T> Trie<T> {\n    pub fn new() -> Self {\n        Self {\n            inner: PatriciaMap::new(),\n        }\n    }\n\n    pub fn ancestor_exists(&self, key: impl AsRef<[u16]>) -> bool {\n        self.inner\n            .get_longest_common_prefix(cast_slice(key.as_ref()))\n            .is_some()\n    }\n\n    pub fn descendant_exists(&self, key: impl AsRef<[u16]>) -> bool {\n        // Length of the [u8] interpretation of the [u16] key is doubled.\n        self.inner\n            .longest_common_prefix_len(cast_slice(key.as_ref()))\n            == key_len(key)\n    }\n\n    pub fn insert(&mut self, key: impl AsRef<[u16]>, val: T) {\n        self.inner.insert(cast_slice(key.as_ref()), val);\n    }\n\n    pub fn get_or_descendant_exists(&self, key: impl AsRef<[u16]>) -> GetOrDescendentExistsResult<T>\n    where\n        T: Clone,\n    {\n        let mut descendants = self.inner.iter_prefix(cast_slice(key.as_ref()));\n        match descendants.next() {\n            None => NotInTrie,\n            Some(descendant) => {\n                if descendant.0.len() == key_len(key.as_ref()) {\n                    HasValue(descendant.1.clone())\n                } else {\n                    InTrie\n                }\n            }\n        }\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.inner.is_empty()\n    }\n}\n"
  },
  {
    "path": "parser/test_cfgs/all_keys_in_defsrc.kbd",
    "content": "(defcfg)\n\n(defsrc\n  grv\n  1\n  2\n  3\n  4\n  5\n  6\n  7\n  8\n  9\n  0\n  min\n  eql\n  bspc\n  tab\n  q\n  w\n  e\n  r\n  t\n  y\n  u\n  i\n  o\n  p\n  {\n  }\n  \\\n  caps\n  a\n  s\n  d\n  f\n  g\n  h\n  j\n  k\n  l\n  scln\n  '\n  ret\n  lshift\n  z\n  x\n  c\n  v\n  b\n  n\n  m\n  comm\n  .\n  /\n  kp=\n  kp0\n  kp1\n  kp2\n  kp3\n  kp4\n  kp5\n  kp6\n  kp7\n  kp8\n  kp9\n  kprt\n  kp/\n  kp+\n  kp*\n  kp-\n  kp.\n  102d\n  scrlck\n  pause\n  wkup\n  esc\n  rshift\n  lctrl\n  lalt\n  spc\n  ralt\n  comp\n  lmeta\n  rmeta\n  rctrl\n  del\n  ins\n  bck\n  fwd\n  pgup\n  pgdn\n  up\n  down\n  lft\n  rght\n  home\n  end\n  nlck\n  mute\n  volu\n  voldwn\n  brup\n  brdown\n  blup\n  bldn\n  next\n  pp\n  prev\n  f1\n  f2\n  f3\n  f4\n  f5\n  f6\n  f7\n  f8\n  f9\n  f10\n  f11\n  f12\n  f13\n  f14\n  f15\n  f16\n  f17\n  f18\n  f19\n  f20\n  f21\n  f22\n  f23\n  f24\n)\n\n(deflayer base\n  grv\n  1\n  2\n  3\n  4\n  5\n  6\n  7\n  8\n  9\n  0\n  min\n  eql\n  bspc\n  tab\n  q\n  w\n  e\n  r\n  t\n  y\n  u\n  i\n  o\n  p\n  {\n  }\n  \\\n  caps\n  a\n  s\n  d\n  f\n  g\n  h\n  j\n  k\n  l\n  scln\n  '\n  ret\n  lshift\n  z\n  x\n  c\n  v\n  b\n  n\n  m\n  comm\n  .\n  /\n  kp=\n  kp0\n  kp1\n  kp2\n  kp3\n  kp4\n  kp5\n  kp6\n  kp7\n  kp8\n  kp9\n  kprt\n  kp/\n  kp+\n  kp*\n  kp-\n  kp.\n  102d\n  scrlck\n  pause\n  wkup\n  esc\n  rshift\n  lctrl\n  lalt\n  spc\n  ralt\n  comp\n  lmeta\n  rmeta\n  rctrl\n  del\n  ins\n  bck\n  fwd\n  pgup\n  pgdn\n  up\n  down\n  lft\n  rght\n  home\n  end\n  nlck\n  mute\n  volu\n  voldwn\n  brup\n  brdown\n  blup\n  bldn\n  next\n  pp\n  prev\n  f1\n  f2\n  f3\n  f4\n  f5\n  f6\n  f7\n  f8\n  f9\n  f10\n  f11\n  f12\n  f13\n  f14\n  f15\n  f16\n  f17\n  f18\n  f19\n  f20\n  f21\n  f22\n  f23\n  f24\n)\n"
  },
  {
    "path": "parser/test_cfgs/ancestor_seq.kbd",
    "content": "(defcfg)\n\n(defsrc a b c)\n\n(deflayer base _ _ _)\n\n(deffakekeys a a)\n\n(defseq a (a b c))\n(defseq a (a b))\n"
  },
  {
    "path": "parser/test_cfgs/bad_multi.kbd",
    "content": "(defcfg)\n(defsrc 1)\n(deflayer base (multi (tap-hold 1 1 a b) (tap-dance 1 (a b c))))\n"
  },
  {
    "path": "parser/test_cfgs/descendant_seq.kbd",
    "content": "(defcfg)\n\n(defsrc a b c)\n\n(deflayer base _ _ _)\n\n(deffakekeys a a)\n\n(defseq a (a b))\n(defseq a (a b c))\n"
  },
  {
    "path": "parser/test_cfgs/icon_bad_dupe.kbd",
    "content": ";; This config file is invalid and should be rejected\n(defcfg)\n(defsrc 1)\n(deflayer\t(base\ticon base.png 🖻 n.ico\t) 1)\n"
  },
  {
    "path": "parser/test_cfgs/icon_good.kbd",
    "content": "(defcfg)\n(defsrc 1)\n(deflayer\t(base       \ticon base.png   \t) 1)\n(deflayer\t(1emoji     \t🖻 1symbols.png  \t) 1)\n(deflayer\t(2icon-quote\t🖻 \"2Nav Num.png\"\t) 1)\n(deflayer\t(3emoji_alt \t🖼 3trans.parent \t) 1)\n(deflayermap\t(4layermap \t🖼 3trans.parent \t) 0 0)\n"
  },
  {
    "path": "parser/test_cfgs/include-bad.kbd",
    "content": "(defsrc a)\n(include included-bad.kbd)\n"
  },
  {
    "path": "parser/test_cfgs/include-bad2.kbd",
    "content": "(defsrc a)\n(include included-bad2.kbd)\n(defalias no-action-uh-oh)\n"
  },
  {
    "path": "parser/test_cfgs/include-good-optional-absent.kbd",
    "content": "(defsrc a)\n(include included-non-existing-file.kbd)\n(include included-good.kbd)\n"
  },
  {
    "path": "parser/test_cfgs/include-good.kbd",
    "content": "(defsrc a)\n(include included-good.kbd)\n"
  },
  {
    "path": "parser/test_cfgs/included-bad.kbd",
    "content": "(deflayer not-enough-elements)\n"
  },
  {
    "path": "parser/test_cfgs/included-bad2.kbd",
    "content": "(deflayer base a)\n"
  },
  {
    "path": "parser/test_cfgs/included-good.kbd",
    "content": "(deflayer base a)\n"
  },
  {
    "path": "parser/test_cfgs/macro-chord-dont-panic.kbd",
    "content": "(defsrc a)\n(deflayer test (macro @ch|bas))\n"
  },
  {
    "path": "parser/test_cfgs/multiline_comment.kbd",
    "content": "(defcfg)\n\n#|\n\nTop level multi-line comment\nHello world\n\n|#\n\n(defsrc #||# a #| |# b #|  |# c #|   |#)\n\n(deflayer\nbase _\n\n#| --------------------------------------------------------------------------\nQuick reference: https://github.com/jtroo/kanata/blob/main/cfg_samples/kanata.kbd\nList of keycodes: https://github.com/kmonad/kmonad/blob/master/src/KMonad/Keyboard/Keycode.hs\n-------------------------------------------------------------------------- |#\n_\n#| inner multi-line comment block\n1\n2\n3|#\n#|  |#\n#| |#\n#||#\n#||##||##| |##|  |#\n_\n)\n\n#|\n#| || # #|\n|#\n\n"
  },
  {
    "path": "parser/test_cfgs/nested_tap_hold.kbd",
    "content": "(defcfg\n  linux-dev /dev/input/by-path/platform-i8042-serio-0-event-kbd\n)\n\n(defsrc\n       a\n)\n\n;; Note: this config file is invalid and should be rejected\n(deflayer test\n       (tap-hold 200 200 (tap-hold 200 200 a b) c)\n)\n"
  },
  {
    "path": "parser/test_cfgs/test.zch",
    "content": "dy\tday\ndy 1\tMonday\n abc\tAlphabet\nr df\trecipient\n w  a\tWashington\n"
  },
  {
    "path": "parser/test_cfgs/testzch.kbd",
    "content": "(defsrc)\n(deflayer base)\n(defzippy test.zch)\n"
  },
  {
    "path": "parser/test_cfgs/unknown_defcfg_opt.kbd",
    "content": "(defcfg\n  this-should-error yes\n)\n\n(defsrc)\n(deflayer base)\n"
  },
  {
    "path": "parser/test_cfgs/utf8bom-included.kbd",
    "content": "﻿#|\n\nBOM should have been added via:\n\n  $f = Get-Content .\\utf8bom-included.kbd\n  $f | Out-File -Encoding UTF8 .\\utf8bom-included.kbd\n\n|#\n\n(deflayermap (layer-included)\n  y (unicode 🚀)\n)\n"
  },
  {
    "path": "parser/test_cfgs/utf8bom.kbd",
    "content": "﻿#|\n\nBOM should have been added via:\n\n  $f = Get-Content .\\utf8bom.kbd\n  $f | Out-File -Encoding UTF8 .\\utf8bom.kbd\n\n|#\n\n(defsrc)\n(deflayermap (layer-name)\n  x (unicode 🙂)\n)\n\n(include utf8bom-included.kbd)\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "edition = \"2024\"\nstyle_edition = \"2024\"\n"
  },
  {
    "path": "scripts/test_linux_list_devices.sh",
    "content": "#!/bin/bash\n\n# Linux Testing Script for kanata --list functionality\n# Run this on your Ubuntu machine after cloning the repo\n\nset -e  # Exit on any error\n\necho \"🐧 Linux Testing Script for kanata --list\"\necho \"========================================\"\necho\n\n# Colors for output\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nBLUE='\\033[0;34m'\nNC='\\033[0m' # No Color\n\nprint_step() {\n    echo -e \"${BLUE}📋 $1${NC}\"\n}\n\nprint_success() {\n    echo -e \"${GREEN}✅ $1${NC}\"\n}\n\nprint_warning() {\n    echo -e \"${YELLOW}⚠️  $1${NC}\"\n}\n\nprint_error() {\n    echo -e \"${RED}❌ $1${NC}\"\n}\n\n# Test 1: Basic Build Test\nprint_step \"Test 1: Building kanata on Linux\"\necho \"Building with default features...\"\ncargo build --release\nif [ $? -eq 0 ]; then\n    print_success \"Build successful\"\nelse\n    print_error \"Build failed\"\n    exit 1\nfi\necho\n\n# Test 2: Help Output Test\nprint_step \"Test 2: Checking --list availability in help\"\necho \"Running: cargo run --release -- --help\"\nHELP_OUTPUT=$(cargo run --release -- --help 2>&1)\nif echo \"$HELP_OUTPUT\" | grep -q \"\\-l, \\-\\-list\"; then\n    print_success \"--list option found in help output\"\nelse\n    print_error \"--list option NOT found in help output\"\n    echo \"Help output:\"\n    echo \"$HELP_OUTPUT\"\n    exit 1\nfi\necho\n\n# Test 3: List Devices Functionality\nprint_step \"Test 3: Testing --list functionality\"\necho \"Running: cargo run --release -- --list\"\necho \"Note: This may require permissions or show permission errors\"\necho\n\nLIST_OUTPUT=$(cargo run --release -- --list 2>&1)\nEXIT_CODE=$?\n\necho \"Exit code: $EXIT_CODE\"\necho \"Output:\"\necho \"$LIST_OUTPUT\"\necho\n\nif [ $EXIT_CODE -eq 0 ]; then\n    print_success \"--list executed successfully\"\n    \n    # Check for expected output format\n    if echo \"$LIST_OUTPUT\" | grep -q \"Available keyboard devices:\"; then\n        print_success \"Found expected header\"\n    else\n        print_warning \"Expected header not found\"\n    fi\n    \n    if echo \"$LIST_OUTPUT\" | grep -q \"Configuration example:\"; then\n        print_success \"Found configuration example\"\n    else\n        print_warning \"Configuration example not found\"\n    fi\n    \nelse\n    print_warning \"--list execution returned non-zero exit code\"\n    \n    # Check if it's a permission issue\n    if echo \"$LIST_OUTPUT\" | grep -q -i \"permission\"; then\n        print_warning \"Permission issue detected - this is expected on some systems\"\n        echo\n        echo \"💡 Try running with sudo or adding user to input group:\"\n        echo \"   sudo usermod -a -G input \\$USER\"\n        echo \"   (then log out and back in)\"\n    else\n        print_error \"Unexpected error\"\n    fi\nfi\necho\n\n# Test 4: System Information\nprint_step \"Test 4: System Information\"\necho \"OS Information:\"\nlsb_release -a 2>/dev/null || cat /etc/os-release\necho\necho \"Available input devices in /dev/input/:\"\nls -la /dev/input/ | head -10\necho\necho \"Current user groups:\"\ngroups\necho\necho \"Input group membership:\"\nif groups | grep -q input; then\n    print_success \"User is in input group\"\nelse\n    print_warning \"User is NOT in input group\"\n    echo \"💡 Add user to input group: sudo usermod -a -G input \\$USER\"\nfi\necho\n\n# Test 5: Device Files Test\nprint_step \"Test 5: Input Device Files\"\necho \"Checking for keyboard-like devices...\"\nDEVICE_COUNT=$(ls /dev/input/event* 2>/dev/null | wc -l)\necho \"Found $DEVICE_COUNT event devices\"\n\nif [ $DEVICE_COUNT -gt 0 ]; then\n    print_success \"Input devices found\"\n    echo \"Device files:\"\n    ls -la /dev/input/event* 2>/dev/null | head -5\nelse\n    print_warning \"No input devices found\"\nfi\necho\n\n# Test 6: Dependencies Check\nprint_step \"Test 6: Dependencies Check\"\necho \"Rust version:\"\nrustc --version\necho \"Cargo version:\"\ncargo --version\necho\n\nprint_step \"Test 7: Feature Build Test\"  \necho \"Testing build without explicit features...\"\ncargo clean\ncargo build --release --no-default-features\nif [ $? -eq 0 ]; then\n    print_success \"No-default-features build successful\"\nelse\n    print_warning \"No-default-features build failed\"\nfi\necho\n\n# Summary\necho \"🏁 Linux Testing Summary\"\necho \"=======================\"\necho \"✅ Build test\"\necho \"✅ Help output test\"\necho \"✅ --list functionality test\"\necho \"✅ System information gathering\"\necho \"✅ Dependencies check\"\necho\necho \"📋 Next Steps:\"\necho \"1. If permission errors: Add user to input group and re-test\"\necho \"2. If successful: Test with actual USB keyboard plugged in\"\necho \"3. Test edge cases (no keyboards, multiple keyboards, etc.)\"\necho\necho \"🎯 Linux testing complete!\"\n"
  },
  {
    "path": "simulated_input/.gitignore",
    "content": "Cargo.lock\r\n"
  },
  {
    "path": "simulated_input/Cargo.toml",
    "content": "[package]\nname = \"kanata-sim\"\nversion = \"0.1.0\"\nauthors = [\"jtroo <j.andreitabs@gmail.com>\"]\ndescription = \"Simulated input using kanata\"\nkeywords = [\"kanata\", \"input\", \"simulated\"]\nhomepage = \"https://github.com/jtroo/kanata\"\nrepository = \"https://github.com/jtroo/kanata\"\nreadme = \"README.md\"\nlicense = \"LGPL-3.0\"\nedition = \"2021\"\n\n[[bin]]\nname = \"kanata_simulated_input\"\npath = \"src/sim.rs\"\n\n[dependencies]\nanyhow = \"1\"\nclap = { version = \"4\", features = [ \"std\", \"derive\", \"help\", \"suggestions\" ], default-features = false }\ndirs = \"5.0.1\"\nlog = { version = \"0.4.8\", default-features = false }\nsimplelog = \"0.12.0\"\ntime = \"0.3.36\"\n\nkanata = { path = \"..\" , default-features = false }\n\n[features]\ndefault = [\"simulated_output\", \"tcp_server\"]\nsimulated_output = [\"kanata/simulated_output\"]\nsimulated_input = [\"kanata/simulated_input\"]\npassthru_ahk = [\"simulated_input\",\"simulated_output\"]\ntcp_server = [\"kanata/tcp_server\"]\n"
  },
  {
    "path": "simulated_input/README.md",
    "content": "# Kanata simulated input\n\nA CLI tool that lets you run simulated kanata input.\n\nUse the `-c` flag to specify a kanata configuration file\nand the `-s` flag to specify an input simulation file.\nYou can pass the `--help` flag for more details.\n\nThe input file format is described in the\n[guide](https://github.com/jtroo/kanata/blob/main/docs/config.adoc#test-your-config).\n\n"
  },
  {
    "path": "simulated_input/src/sim.rs",
    "content": "use anyhow::Result;\nuse anyhow::{anyhow, bail};\nuse clap::Parser;\nuse kanata_state_machine::kanata::handle_fakekey_action;\nuse kanata_state_machine::{FAKE_KEY_ROW, FakeKeyAction, oskbd::*, *};\nuse simplelog::{format_description, *};\nuse std::path::PathBuf;\n\npub fn default_sim() -> Vec<PathBuf> {\n    let mut cfgs = Vec::new();\n\n    let default = PathBuf::from(\"test/sim.txt\");\n    if default.is_file() {\n        cfgs.push(default);\n    }\n\n    if let Some(config_dir) = dirs::config_dir() {\n        let fallback = config_dir.join(\"kanata\").join(\"test\").join(\"sim.txt\");\n        if fallback.is_file() {\n            cfgs.push(fallback);\n        }\n    }\n\n    cfgs\n}\n\n#[derive(Parser, Debug)]\n#[command(author, version, verbatim_doc_comment)]\n/// kanata_simulated_input: a cli tool that helps debug kanata's user configuration by:\n/// - reading a text file with a sequence of key events, including key delays\n/// - interpreting them with kanata\n/// - printing out which actions or key/mouse events kanata would execute if the keys were\n///   pressed by a user\n/// - (optionally) saving the result to a file for reference\nstruct Args {\n    // Display different platform specific paths based on the target OS\n    #[cfg_attr(\n        target_os = \"windows\",\n        doc = r\"Configuration file(s) to use with kanata. If not specified, defaults to\nkanata.kbd in the current working directory and\n'C:\\Users\\user\\AppData\\Roaming\\kanata\\kanata.kbd'\"\n    )]\n    #[cfg_attr(\n        target_os = \"macos\",\n        doc = \"Configuration file(s) to use with kanata. If not specified, defaults to\nkanata.kbd in the current working directory and\n'$HOME/Library/Application Support/kanata/kanata.kbd.'\"\n    )]\n    #[cfg_attr(\n        not(any(target_os = \"macos\", target_os = \"windows\")),\n        doc = \"Configuration file(s) to use with kanata. If not specified, defaults to\nkanata.kbd in the current working directory and\n'$XDG_CONFIG_HOME/kanata/kanata.kbd'\"\n    )]\n    #[arg(short, long, verbatim_doc_comment)]\n    cfg: Option<Vec<PathBuf>>,\n\n    // Display different platform specific paths based on the target OS\n    #[cfg_attr(\n        target_os = \"windows\",\n        doc = r\"Simulation file(s) to use with kanata_simulated_input. If not specified, defaults to\ntest\\sim.txt in the current working directory and\n'C:\\Users\\user\\AppData\\Roaming\\kanata\\test\\sim.txt'\"\n    )]\n    #[cfg_attr(\n        target_os = \"macos\",\n        doc = \"Simulation file(s) to use with kanata_simulated_input. If not specified, defaults to\ntest/sim.txt in the current working directory and\n'$HOME/Library/Application Support/kanata/test/sim.txt.'\"\n    )]\n    #[cfg_attr(\n        not(any(target_os = \"macos\", target_os = \"windows\")),\n        doc = \"Simulation file(s) to use with kanata_simulated_input. If not specified, defaults to\ntest/sim.txt in the current working directory and\n'$XDG_CONFIG_HOME/kanata/test/sim.txt'\"\n    )]\n    #[arg(short = 's', long, verbatim_doc_comment)]\n    sim: Option<Vec<PathBuf>>,\n    /// Save output to the simulation file's path with its name appended by the value of this argument.\n    /// This flag generates an error if the binary is compiled without simulated output.\n    #[arg(short = 'o', long, verbatim_doc_comment)]\n    out: Option<String>,\n}\n\nfn log_init() {\n    let mut log_cfg = ConfigBuilder::new();\n    if let Err(e) = log_cfg.set_time_offset_to_local() {\n        eprintln!(\"WARNING: could not set log TZ to local: {e:?}\");\n    };\n    log_cfg.set_time_format_custom(format_description!(\n        version = 2,\n        \"[hour]:[minute]:[second].[subsecond digits:4]\"\n    ));\n    CombinedLogger::init(vec![TermLogger::new(\n        LevelFilter::Info,\n        log_cfg.build(),\n        TerminalMode::Stderr,\n        ColorChoice::AlwaysAnsi,\n    )])\n    .expect(\"logger can init\");\n}\n\n/// Parse CLI arguments\nfn cli_init_fsim() -> Result<(ValidatedArgs, Vec<PathBuf>, Option<String>)> {\n    let args = Args::parse();\n    let cfg_paths = args.cfg.unwrap_or_else(default_cfg);\n    let sim_paths = args.sim.unwrap_or_else(default_sim);\n    let sim_appendix = args.out;\n\n    log::info!(\n        \"kanata_simulated_input v{} starting\",\n        env!(\"CARGO_PKG_VERSION\")\n    );\n\n    if let Some(config_file) = cfg_paths.first() {\n        if !config_file.exists() {\n            bail!(\n                \"Could not find the config file ({})\\nFor more info, pass the `-h` or `--help` flags.\",\n                cfg_paths[0].to_str().unwrap_or(\"?\")\n            )\n        }\n    } else {\n        bail!(\"No config files provided\\nFor more info, pass the `-h` or `--help` flags.\");\n    }\n    if let Some(config_sim_file) = sim_paths.first() {\n        if !config_sim_file.exists() {\n            bail!(\n                \"Could not find the simulation file ({})\\nFor more info, pass the `-h` or `--help` flags.\",\n                sim_paths[0].to_str().unwrap_or(\"?\")\n            )\n        }\n    } else {\n        bail!(\"No simulation files provided\\nFor more info, pass the `-h` or `--help` flags.\");\n    }\n\n    Ok((\n        ValidatedArgs {\n            paths: cfg_paths,\n            #[cfg(feature = \"tcp_server\")]\n            tcp_server_address: None::<SocketAddrWrapper>,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            symlink_path: None,\n            nodelay: true,\n        },\n        sim_paths,\n        sim_appendix,\n    ))\n}\n\nfn split_at_1(s: &str) -> (&str, &str) {\n    match s.chars().next() {\n        Some(c) => s.split_at(c.len_utf8()),\n        None => s.split_at(0),\n    }\n}\n\nfn parse_fakekey_spec(spec: &str) -> Result<(&str, FakeKeyAction)> {\n    let (name, action) = match spec.split_once(':') {\n        Some((name, action_str)) => {\n            let action = match action_str {\n                \"press\" | \"p\" => FakeKeyAction::Press,\n                \"release\" => FakeKeyAction::Release,\n                \"tap\" | \"t\" => FakeKeyAction::Tap,\n                \"toggle\" | \"g\" => FakeKeyAction::Toggle,\n                _ => bail!(\n                    \"unknown fakekey action: {action_str}. Expected: press, release, tap, or toggle\"\n                ),\n            };\n            (name, action)\n        }\n        None => (spec, FakeKeyAction::Press),\n    };\n\n    if name.is_empty() {\n        bail!(\"fakekey name cannot be empty\");\n    }\n\n    Ok((name, action))\n}\n\nfn apply_fakekey_action(k: &mut Kanata, name: &str, action: FakeKeyAction) -> Result<()> {\n    let index = k\n        .virtual_keys\n        .get(name)\n        .ok_or_else(|| anyhow!(\"unknown virtual key: {name}\"))?;\n    log::info!(\"fakekey action: {name} {action:?}\");\n    handle_fakekey_action(action, k.layout.bm(), FAKE_KEY_ROW, *index as u16);\n    Ok(())\n}\n\nfn apply_layer_switch(k: &mut Kanata, layer_name: &str) -> Result<()> {\n    let layer_idx = k\n        .layer_info\n        .iter()\n        .position(|l| l.name == layer_name)\n        .ok_or_else(|| anyhow!(\"unknown layer: {layer_name}\"))?;\n    log::info!(\"layer-switch: {layer_name} (index {layer_idx})\");\n    k.layout.bm().set_default_layer(layer_idx);\n    Ok(())\n}\n#[derive(Debug, Copy, Clone, PartialEq)]\npub enum LogFmtT {\n    // partial dupe of @simulated since otherwise no-features clippy fails when features are disabled since even though the function body is conditional and doesn't use the enum, the ↓ function signature still uses it, so will warn\n    InKeyUp,\n    InKeyDown,\n    InKeyRep,\n    InTick,\n}\nfn kbd_out_log(\n    _kbd_out: &mut KbdOut,\n    _log_type: LogFmtT,\n    _key_code: Option<OsCode>,\n    _tick: Option<u128>,\n) {\n    let a = LogFmtT::InTick;\n    if a == LogFmtT::InTick {\n        println!(\"{}\", 42)\n    };\n    #[cfg(all(\n        not(feature = \"simulated_input\"),\n        not(feature = \"passthru_ahk\"),\n        feature = \"simulated_output\"\n    ))]\n    {\n        match _log_type {\n            LogFmtT::InTick => {\n                if let Some(tick) = _tick {\n                    _kbd_out.log.in_tick(tick);\n                }\n            }\n            LogFmtT::InKeyUp => {\n                if let Some(key_code) = _key_code {\n                    _kbd_out.log.in_release_key(key_code);\n                }\n            }\n            LogFmtT::InKeyDown => {\n                if let Some(key_code) = _key_code {\n                    _kbd_out.log.in_press_key(key_code);\n                }\n            }\n            LogFmtT::InKeyRep => {\n                if let Some(key_code) = _key_code {\n                    _kbd_out.log.in_repeat_key(key_code);\n                }\n            }\n        }\n    }\n}\nfn main_impl() -> Result<()> {\n    log_init();\n    let (args, sim_paths, _sim_appendix) = cli_init_fsim()?;\n    #[cfg(not(feature = \"simulated_output\"))]\n    {\n        if _sim_appendix.is_some() {\n            bail!(\n                \"The program was compiled without simulated output. The -o|--out flag is unsupported\"\n            );\n        }\n    }\n\n    for config_sim_file in &sim_paths {\n        let mut k = Kanata::new(&args)?;\n        log::info!(\"Evaluating simulation file = {:?}\", config_sim_file);\n        let s = std::fs::read_to_string(config_sim_file)?;\n        for l in s.lines() {\n            for pair in l.split_whitespace() {\n                match pair.split_once(':') {\n                    Some((kind, val)) => match kind {\n                        \"tick\" | \"🕐\" | \"t\" => {\n                            let tick = str::parse::<u128>(val)?;\n                            kbd_out_log(&mut k.kbd_out, LogFmtT::InTick, None, Some(tick));\n                            k.tick_ms(tick, &None)?;\n                        }\n                        \"press\" | \"↓\" | \"d\" | \"down\" => {\n                            let key_code =\n                                str_to_oscode(val).ok_or_else(|| anyhow!(\"unknown key: {val}\"))?;\n                            kbd_out_log(&mut k.kbd_out, LogFmtT::InKeyDown, Some(key_code), None);\n                            k.handle_input_event(&KeyEvent {\n                                code: key_code,\n                                value: KeyValue::Press,\n                            })?;\n                        }\n                        \"release\" | \"↑\" | \"u\" | \"up\" => {\n                            let key_code =\n                                str_to_oscode(val).ok_or_else(|| anyhow!(\"unknown key: {val}\"))?;\n                            kbd_out_log(&mut k.kbd_out, LogFmtT::InKeyUp, Some(key_code), None);\n                            k.handle_input_event(&KeyEvent {\n                                code: key_code,\n                                value: KeyValue::Release,\n                            })?;\n                        }\n                        \"repeat\" | \"⟳\" | \"r\" => {\n                            let key_code =\n                                str_to_oscode(val).ok_or_else(|| anyhow!(\"unknown key: {val}\"))?;\n                            kbd_out_log(&mut k.kbd_out, LogFmtT::InKeyRep, Some(key_code), None);\n                            k.handle_input_event(&KeyEvent {\n                                code: key_code,\n                                value: KeyValue::Repeat,\n                            })?;\n                        }\n                        // Virtual/fake key activation: fakekey:name[:action] or vk:name[:action]\n                        // Supported actions: press, release, tap, toggle\n                        // Examples: fakekey:vk_bear:press, vk:vk_bear, virtualkey:vk_bear:tap\n                        \"fakekey\" | \"vk\" | \"virtualkey\" | \"🎭\" => {\n                            let (vk_name, action) = parse_fakekey_spec(val)?;\n                            apply_fakekey_action(&mut k, vk_name, action)?;\n                        }\n                        // Layer switch: ls:layer_name\n                        // Switches to the specified layer as the new default layer.\n                        // Example: ls:nav, ls:symbols\n                        \"ls\" | \"layer-switch\" | \"🔀\" => {\n                            apply_layer_switch(&mut k, val)?;\n                        }\n                        _ => bail!(\"invalid pair prefix: {kind}\"),\n                    },\n                    None => {\n                        let (kind, val) = split_at_1(pair);\n                        match kind {\n                            //allow skipping : separator for unique non-key symbols\n                            \"🕐\" => {\n                                let tick = str::parse::<u128>(val)?;\n                                kbd_out_log(&mut k.kbd_out, LogFmtT::InTick, None, Some(tick));\n                                k.tick_ms(tick, &None)?;\n                            }\n                            \"↓\" => {\n                                let key_code = str_to_oscode(val)\n                                    .ok_or_else(|| anyhow!(\"unknown key: {val}\"))?;\n                                kbd_out_log(\n                                    &mut k.kbd_out,\n                                    LogFmtT::InKeyDown,\n                                    Some(key_code),\n                                    None,\n                                );\n                                k.handle_input_event(&KeyEvent {\n                                    code: key_code,\n                                    value: KeyValue::Press,\n                                })?;\n                            }\n                            \"↑\" => {\n                                let key_code = str_to_oscode(val)\n                                    .ok_or_else(|| anyhow!(\"unknown key: {val}\"))?;\n                                kbd_out_log(&mut k.kbd_out, LogFmtT::InKeyUp, Some(key_code), None);\n                                k.handle_input_event(&KeyEvent {\n                                    code: key_code,\n                                    value: KeyValue::Release,\n                                })?;\n                            }\n                            \"⟳\" => {\n                                let key_code = str_to_oscode(val)\n                                    .ok_or_else(|| anyhow!(\"unknown key: {val}\"))?;\n                                kbd_out_log(\n                                    &mut k.kbd_out,\n                                    LogFmtT::InKeyRep,\n                                    Some(key_code),\n                                    None,\n                                );\n                                k.handle_input_event(&KeyEvent {\n                                    code: key_code,\n                                    value: KeyValue::Repeat,\n                                })?;\n                            }\n                            \"🎭\" => {\n                                // Virtual key activation with emoji prefix (defaults to press)\n                                // Format: 🎭vk_name or 🎭vk_name:action\n                                let (vk_name, action) = parse_fakekey_spec(val)?;\n                                apply_fakekey_action(&mut k, vk_name, action)?;\n                            }\n                            \"🔀\" => {\n                                // Layer switch with emoji prefix\n                                // Format: 🔀layer_name\n                                apply_layer_switch(&mut k, val)?;\n                            }\n                            _ => bail!(\"invalid pair: {l}\"),\n                        }\n                    }\n                }\n            }\n        }\n        #[cfg(all(\n            not(feature = \"simulated_input\"),\n            not(feature = \"passthru_ahk\"),\n            feature = \"simulated_output\"\n        ))]\n        println!(\"{}\", k.kbd_out.outputs.events.join(\"\\n\"));\n        #[cfg(all(\n            not(feature = \"simulated_input\"),\n            not(feature = \"passthru_ahk\"),\n            feature = \"simulated_output\"\n        ))]\n        k.kbd_out.log.end(config_sim_file, _sim_appendix.clone());\n    }\n\n    Ok(())\n}\n\nfn main() -> Result<()> {\n    let ret = main_impl();\n    if let Err(ref e) = ret {\n        log::error!(\"{e}\\n\");\n    }\n    ret\n}\n"
  },
  {
    "path": "simulated_passthru/.gitignore",
    "content": "Cargo.lock\r\n"
  },
  {
    "path": "simulated_passthru/Cargo.toml",
    "content": "[package]\nname   \t= \"simulated_passthru\"\nversion\t= \"0.0.1\"\nedition\t= \"2021\"\n\n[lib]\nname      \t= \"kanata_passthru\"\npath      \t= \"src/lib_passthru.rs\"\ncrate-type\t= ['lib','cdylib']\n\n[dependencies]\nanyhow = \"1\"\nlog = { version = \"0.4.8\", default-features = false }\nparking_lot = \"0.12\"\nregex = \"1.10.3\"\n\nkanata = {path=\"..\" , default-features=false}\n\nlazy_static = \"1.4.0\"\n\n[target.'cfg(target_os = \"windows\")'.dependencies]\nencode_unicode = \"0.3.6\"\nwinapi = { version = \"0.3.9\", features = [\n    \"wincon\",\n    \"timeapi\",\n    \"mmsystem\",\n] }\nnative-windows-gui = { version = \"1.0.12\", default-features = false }\nkanata-interception = { version = \"0.3.0\", optional = true }\nwin_dbg_logger = \"0.1.0\"\nwidestring = \"1.1.0\"\n\n[features]\ndefault         \t= [\"simulated_output\",\"tcp_server\"]\ntcp_server      \t= [\"kanata/tcp_server\"]\nsimulated_output\t= [\"kanata/simulated_output\"]\nsimulated_input \t= [\"kanata/simulated_input\"]\npassthru_ahk    \t= [\"simulated_input\",\"simulated_output\",\"kanata/passthru_ahk\"]\nperf_logging    \t= []\n"
  },
  {
    "path": "simulated_passthru/ReadMe.md",
    "content": "# Kanata passthru (simulated input and output)\n\nA Windows dynamic library (DLL) that lets you run simulated kanata input and get simulated kanata output.\n\nSee 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\n"
  },
  {
    "path": "simulated_passthru/src/key_in.rs",
    "content": "use kanata_state_machine::oskbd::*;\nuse log::*;\n\nuse winapi::ctypes::*;\nuse winapi::shared::minwindef::*;\n\nuse crate::oskbd::HOOK_CB;\n\n/// Exported function: receives key input and uses event_loop's input event handler\n/// callback (which will in turn communicate via the internal kanata's channels to\n/// keyberon state machine etc.)\n#[no_mangle]\npub extern \"win64\" fn input_ev_listener(vk: c_uint, sc: c_uint, up: c_int) -> LRESULT {\n    #[cfg(feature = \"perf_logging\")]\n    let start = std::time::Instant::now();\n    let key_event = InputEvent::from_vk_sc(vk, sc, up); //{code:KEY_0,value:Press}\n    let mut h_cbl = HOOK_CB.lock(); // to access the closure we move its box out of the mutex\n    // and put it back after it returned\n    if let Some(mut fnhook) = h_cbl.take() {\n        // move our opt+boxed closure, replacing it with None, can't just .unwrap since Copy\n        // trait not implemented for dyn fnMut\n        let handled = fnhook(key_event); // box(closure)() = closure()\n        *h_cbl = Some(fnhook); // put our closure back\n        if handled {\n            // now try to get the out key events that another thread should've sent via\n            #[cfg(feature = \"perf_logging\")]\n            debug!(\n                \" 🕐{}μs   →→→✓ {key_event} from {vk} sc={sc} up={up}\",\n                (start.elapsed()).as_micros()\n            );\n            #[cfg(not(feature = \"perf_logging\"))]\n            debug!(\"   →→→✓ {key_event} from {vk} sc={sc} up={up}\");\n            1\n        } else {\n            0\n        }\n    } else {\n        error!(\n            \"fnHook processing key events isn't available yet {key_event} from {vk} sc={sc} up={up}\"\n        );\n        0\n    }\n}\n"
  },
  {
    "path": "simulated_passthru/src/key_out.rs",
    "content": "use anyhow::Result;\n\nuse kanata_state_machine::oskbd::*;\nuse log::*;\n\nuse winapi::ctypes::*;\nuse winapi::shared::minwindef::*;\n\nuse std::cell::Cell;\n\ntype CbOutEvFn = dyn Fn(i64, i64, i64) -> i64 + 'static;\nthread_local! {static CBOUTEV_WRAP:Cell<Option<Box<CbOutEvFn>>> = Cell::default();}\n// Stores the hook callback for the current thread\n\n/// - Get the address of AutoHotkey's callback function that accepts simulated output\n/// events (and sends them to the OS)\n///   - `cbKanataOut(vk,sc,up) {return 1}` All args are i64 (AHK doesn't support u64)\n/// - Store it in a static thread-local Cell (AHK is single-threaded, so we can only\n///  use this callback from the main thread). KbdOut will use a channel to send a\n///  message key event that will use call the fn from this Cell\n/// address: pointer-sized integer, equivalent to Int64 on ahk64 (c_longlong=i64).\n/// Will be `as`-cast to a raw pointer before `transmute`ing to a function pointer to avoid\n///  an integer-to-pointer `transmute`, which can be problematic. Transmuting between raw pointers\n///  and function pointers (i.e., two pointer types) is fine.\n/// AHK uses x64 calling convention: TODO: is this the same as win64? extern \"C\" also seems to work?\n#[cfg(feature = \"passthru_ahk\")]\npub fn set_cb_out_ev(cb_addr: c_longlong) -> Result<()> {\n    trace!(\"got func address {}\", cb_addr);\n    let ptr_fn = cb_addr as *const ();\n    let cb_out_ev =\n        unsafe { std::mem::transmute::<*const (), fn(vk: i64, sc: i64, up: i64) -> i64>(ptr_fn) };\n    CBOUTEV_WRAP.with(|state| {\n        assert!(\n            state.take().is_none(),\n            \"Only 1 callback can be registered per thread\"\n        );\n        state.set(Some(Box::new(cb_out_ev)));\n    });\n    Ok(())\n}\n#[cfg(not(feature = \"passthru_ahk\"))]\nfn set_cb_out_ev(cb_addr: c_longlong) -> Result<()> {\n    debug!(\"✗✗✗✗ unimplemented!\");\n    unimplemented!();\n    Ok(())\n}\n\npub fn send_out_ev(in_ev: InputEvent) -> Result<()> {\n    // ext callback accepts vk:i64,sc:i64,up:i64\n    #[cfg(feature = \"perf_logging\")]\n    let start = std::time::Instant::now();\n    let key_event = KeyEvent::try_from(in_ev);\n    debug!(\"@send_out_ev key_event={key_event:?}\");\n    let vk: i64 = in_ev.code.into();\n    let sc: i64 = 0;\n    let up: i64 = in_ev.up.into();\n\n    let mut handled = 0i64;\n    CBOUTEV_WRAP.with(|state| {\n        if let Some(hook) = state.take() {\n            handled = hook(vk, sc, up);\n            state.set(Some(hook));\n        }\n    });\n    #[cfg(feature = \"perf_logging\")]\n    debug!(\n        \"🕐{}μs ←←←{} fnHookCC {key_event:?} {vk} {sc} {up}\",\n        (start.elapsed()).as_micros(),\n        if handled == 1 { \"✓\" } else { \"✗\" }\n    );\n    #[cfg(not(feature = \"perf_logging\"))]\n    debug!(\n        \"←←←{} fnHookCC {key_event:?} {vk} {sc} {up}\",\n        if handled == 1 { \"✓\" } else { \"✗\" }\n    );\n    Ok(())\n}\n\nuse crate::RX_KEY_EV_OUT;\nuse std::sync::mpsc::TryRecvError; // thread_local Cell<Option<Receiver<InputEvent>>>\n// Stores receiver for key data to be sent out for\n// the current thread\n/// Exported function: checks if processing thread has sent key output and sends it\n/// back to an external callback\n#[no_mangle]\npub extern \"win64\" fn output_ev_check() -> LRESULT {\n    let mut res: isize = 0;\n    RX_KEY_EV_OUT.with(|state| {\n        if let Some(rx) = state.take() {\n            match rx.try_recv() {\n                Ok(in_ev) => {\n                    debug!(\"✓ rx_kout@key_out(dll) ‘{in_ev}’\");\n                    if send_out_ev(in_ev).is_ok() {\n                        res = 0;\n                    } else {\n                        res = -1;\n                    };\n                }\n                Err(TryRecvError::Empty) => {\n                    debug!(\"✗ rx_kout@key_out(dll) no data yet\");\n                    res = -2\n                }\n                Err(TryRecvError::Disconnected) => {\n                    debug!(\"✗ rx_kout@key_out(dll) Disconnected\");\n                    res = -3\n                }\n            }\n            state.set(Some(rx));\n        } else {\n            debug!(\"✗ RX_KEY_EV_OUT@key_out(dll) empty\");\n            state.set(None);\n            res = -4\n        }\n    });\n    res\n}\n"
  },
  {
    "path": "simulated_passthru/src/lib_passthru.rs",
    "content": "#![cfg(all(feature = \"passthru_ahk\", target_os = \"windows\"))]\nuse anyhow::Result;\nuse anyhow::bail;\n\nuse kanata_state_machine::{oskbd::*, *};\nuse log::*;\n\nfn log_init(max_lvl: &c_longlong) {\n    log_win::init();\n    let _a = log_win::set_thread_state(true);\n    let log_lvl = match max_lvl {\n        1 => log::LevelFilter::Error,\n        2 => log::LevelFilter::Warn,\n        3 => log::LevelFilter::Info,\n        4 => log::LevelFilter::Debug,\n        5 => log::LevelFilter::Trace,\n        _ => log::LevelFilter::Info,\n    };\n    log::set_max_level(log_lvl);\n}\n\nuse parking_lot::Mutex;\nuse std::cell::Cell;\nuse std::sync::{Arc, OnceLock};\nstatic CFG: OnceLock<Arc<Mutex<Kanata>>> = OnceLock::new();\n\nuse winapi::ctypes::*;\nuse winapi::shared::minwindef::*;\n#[no_mangle]\npub extern \"win64\" fn reset_kanata_state(tick: c_longlong) -> LRESULT {\n    debug!(\"                               ext →→→ reset_kanata_state\");\n    if let Some(cfg) = CFG.get() {\n        if kanata::clean_state(cfg, tick.try_into().unwrap()).is_err() {\n            debug!(\"✗ @ reset_kanata_state\");\n            return 1;\n        };\n    } else {\n        debug!(\"✗ @ reset_kanata_state, no CFG\");\n        return 2;\n    };\n    0\n}\n\nuse std::path::PathBuf;\n/// Parse CLI arguments\nfn cli_init(cfg_path: &str) -> Result<ValidatedArgs> {\n    let cfg_file = PathBuf::from(&cfg_path);\n    if !cfg_file.exists() {\n        bail!(\"Could not find the config file ({:?})\", cfg_file)\n    }\n    Ok(ValidatedArgs {\n        paths: vec![cfg_file],\n        #[cfg(feature = \"tcp_server\")]\n        tcp_server_address: None, //todo: any need in a dll?\n        nodelay: true,\n    })\n}\n\nuse std::sync::mpsc::Receiver;\nthread_local! {pub static RX_KEY_EV_OUT:Cell<Option<Receiver<InputEvent>>> = Cell::default();}\n// Stores receiver for key data to be sent out for the current thread\n\nfn lib_impl(cfg_path: &str) -> Result<()> {\n    let args = cli_init(cfg_path)?;\n    let (tx_kout, rx_kout) = std::sync::mpsc::channel(); //async channel\n    let cfg_arc = Kanata::new_with_output_channel(&args, Some(tx_kout))?;\n    // new configuration from a file\n    debug!(\"loaded {:?}\", args.paths[0]);\n    if CFG.set(cfg_arc.clone()).is_err() {\n        warn!(\"Someone else set our ‘CFG’\");\n    }; // store a clone of cfg so that we can ask it to reset itself\n\n    RX_KEY_EV_OUT.with(|state| {\n        assert!(\n            state.take().is_none(),\n            \"Only one channel to send keys out can be registered per thread.\"\n        );\n        state.set(Some(rx_kout));\n    });\n\n    // Start a processing loop in another thread and run the event loop in this thread\n    // The reason for two different event loops is that the \"event loop\" only listens for keyboard\n    // events, which it sends to the \"processing loop\". The processing loop handles keyboard events\n    // while also maintaining `tick()` calls to keyberon.\n    let (tx, rx) = std::sync::mpsc::sync_channel(100);\n    let ntx = None;\n    Kanata::start_processing_loop(cfg_arc.clone(), rx, ntx, args.nodelay); // 2 handles keyboard\n    // events while also\n    // maintaining `tick()`\n    // calls to keyberon\n\n    Kanata::event_loop(cfg_arc, tx)?; // 1 only listens for keyboard events\n    // (not a real loop, just registers callback closures\n    // for external function to call at will)\n\n    Ok(())\n}\n\nmod key_in;\nmod key_out;\n\nuse crate::key_out::*;\nuse widestring::{\n    U16CStr, //   0 U16/U32-CString wide version of the standard CString type\n    U16Str,\n    Utf16Str, // no0 UTF-16 encoded, growable owned string\n    WideChar,\n};\n\npub mod log_win;\n#[no_mangle]\npub extern \"win64\" fn lib_kanata_passthru(\n    cb_addr: c_longlong,\n    cfg_path: &WideChar,\n    max_lvl: c_longlong,\n) -> LRESULT {\n    log_init(&max_lvl);\n    let ret = set_cb_out_ev(cb_addr);\n    if let Err(ref _e) = ret {\n        error!(\"couldn't register external key out event callback\");\n        return 1;\n    };\n    let cfg_path_wc = unsafe { U16CStr::from_ptr_str(cfg_path) }; // Constructs a wide C string\n    // slice from a nul-terminated\n    // string pointer\n    let cfg_path_wx: &U16Str = cfg_path_wc.as_ustr(); // 16b wide string slice with undefined\n    // encoding reject invalid UTF16 (skip check\n    // with from_ustr_unchecked if certain input\n    // is valid UTF16)\n    let cfg_path_w: &Utf16Str = match Utf16Str::from_ustr(cfg_path_wx) {\n        Ok(s) => s,\n        Err(_e) => {\n            error!(\"{_e}\\n\");\n            return -1;\n        }\n    };\n    let cfg_path_s: String = cfg_path_w.to_string(); // valid UTF16→conv is lossless&non-fallible\n\n    let ret = lib_impl(&cfg_path_s);\n    if let Err(ref e) = ret {\n        error!(\"{e}\\n\");\n        return -2;\n    }\n    0\n}\n"
  },
  {
    "path": "simulated_passthru/src/log_win.rs",
    "content": "//! A logger that prints to OutputDebugString (Windows only)\nuse log::{Level, Metadata, Record};\n\n/// Implements `log::Log`, so can be used as a logging provider to\n/// forward log messages to the Windows `OutputDebugString` API\npub struct WinDebugLogger;\n\n/// Static instance of `WinDebugLogger`, can be directly registered using `log::set_logger`<br>\n/// ```\n/// use kanata_passthru::log_win;\n/// let _ = log_win::init(); // Init\n/// log::set_max_level(log::LevelFilter::Debug);\n/// use log::debug; // Use\n/// debug!(\"Debug log\");\n/// ```\npub static WINDBG_LOGGER: WinDebugLogger = WinDebugLogger;\n\n/// Convert logging levels to shorter and more visible icons\npub fn iconify(lvl: log::Level) -> char {\n    match lvl {\n        Level::Error => '❗',\n        Level::Warn => '⚠',\n        Level::Info => 'ⓘ',\n        Level::Debug => 'ⓓ',\n        Level::Trace => 'ⓣ',\n    }\n}\n\nuse std::sync::OnceLock;\npub fn is_thread_state() -> &'static bool {\n    set_thread_state(false)\n}\npub fn set_thread_state(is: bool) -> &'static bool {\n    // accessor function to avoid get_or_init on every call (lazycell\n    // allows doing that without an extra function)\n    static CELL: OnceLock<bool> = OnceLock::new();\n    CELL.get_or_init(|| is)\n}\n\nuse lazy_static::lazy_static;\nuse regex::Regex;\nlazy_static! { // shorten source file name, no src/ no .rs ext\n  static ref RE_EXT:Regex = Regex::new(r\"\\..*$\"   ).unwrap();\n  static ref RE_SRC:Regex = Regex::new(r\"src[\\\\/]\").unwrap();\n}\nfn clean_name(path: Option<&str>) -> String {\n    if let Some(p) = path {\n        RE_SRC.replace(&RE_EXT.replace(p, \"\"), \"\").to_string()\n    } else {\n        \"?\".to_string()\n    }\n}\n\n#[cfg(target_os = \"windows\")]\nuse winapi::um::processthreadsapi::GetCurrentThreadId;\nimpl log::Log for WinDebugLogger {\n    #[cfg(windows)]\n    fn enabled(&self, _metadata: &Metadata) -> bool {\n        true\n    }\n    #[cfg(not(windows))]\n    fn enabled(&self, metadata: &Metadata) -> bool {\n        false\n    }\n    fn log(&self, record: &Record) {\n        #[cfg(not(target_os = \"windows\"))]\n        let thread_id = \"\";\n        #[cfg(target_os = \"windows\")]\n        let thread_id = if *is_thread_state() {\n            format!(\"¦{}¦\", unsafe { GetCurrentThreadId() })\n        } else {\n            \"\".to_string()\n        };\n        if self.enabled(record.metadata()) {\n            let s = format!(\n                \"{}{}{}:{} {}\",\n                thread_id,\n                iconify(record.level()),\n                clean_name(record.file()),\n                record.line().unwrap_or(0),\n                record.args()\n            );\n            dbg_win(&s);\n        }\n    }\n    fn flush(&self) {}\n}\n\npub fn dbg_win(s: &str) {\n    //! Calls the `OutputDebugString` API to log a string (on Windows only)<br>\n    //! See [`OutputDebugStringW`](https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-outputdebugstringw).\n    #[cfg(windows)]\n    {\n        let len = s.encode_utf16().count() + 1;\n        let mut s_utf16: Vec<u16> = Vec::with_capacity(len + 1);\n        s_utf16.extend(s.encode_utf16());\n        s_utf16.push(0);\n        unsafe {\n            OutputDebugStringW(&s_utf16[0]);\n        }\n    }\n}\n\n#[cfg(windows)]\nextern \"system\" {\n    fn OutputDebugStringW(chars: *const u16);\n}\n\npub fn init() {\n    //! Set `WinDebugLogger` as the active logger<br>\n    //! Doesn't panic on failure as it creates other problems for FFI etc.\n    match log::set_logger(&WINDBG_LOGGER) {\n        Ok(()) => {}\n        Err(_) => {\n            dbg_win(\"Warning: ✗ Failed to register WinDebugLogger\\n\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/gui/mod.rs",
    "content": "pub mod win;\npub use win::*;\npub mod win_dbg_logger;\npub mod win_nwg_ext;\npub use win_dbg_logger as log_win;\npub use win_dbg_logger::WINDBG_LOGGER;\npub use win_nwg_ext::*;\n\nuse crate::*;\nuse parking_lot::Mutex;\nuse std::sync::mpsc::Sender as ASender;\nuse std::sync::{Arc, OnceLock};\npub static CFG: OnceLock<Arc<Mutex<Kanata>>> = OnceLock::new();\npub static GUI_TX: OnceLock<native_windows_gui::NoticeSender> = OnceLock::new();\npub static GUI_CFG_TX: OnceLock<native_windows_gui::NoticeSender> = OnceLock::new();\npub static GUI_ERR_TX: OnceLock<native_windows_gui::NoticeSender> = OnceLock::new();\npub static GUI_ERR_MSG_TX: OnceLock<ASender<(String, String)>> = OnceLock::new();\npub static GUI_EXIT_TX: OnceLock<native_windows_gui::NoticeSender> = OnceLock::new();\n"
  },
  {
    "path": "src/gui/win.rs",
    "content": "use crate::Kanata;\nuse anyhow::{Result, bail};\nuse core::cell::RefCell;\nuse kanata_parser::cfg::CfgOptionsGui;\nuse log::Level::*;\nuse std::cell::Cell;\nuse winapi::shared::windef::HWND;\n\nuse core::ffi::c_void;\nuse native_windows_gui as nwg;\nuse parking_lot::Mutex;\nuse parking_lot::MutexGuard;\n\nuse std::collections::HashMap;\nuse std::env::{current_exe, var_os};\nuse std::ffi::OsStr;\nuse std::iter::once;\nuse std::os::windows::ffi::OsStrExt;\nuse std::path::{Path, PathBuf};\nuse std::sync::OnceLock;\nuse std::sync::mpsc::{Receiver, Sender as ASender, TryRecvError};\nuse std::time::Duration;\nuse winapi::shared::minwindef::{BYTE, DWORD};\nuse winapi::shared::windef::COLORREF;\nuse windows_sys::Wdk::System::SystemServices::RtlGetVersion;\nuse windows_sys::Win32::Foundation::{ERROR_FILE_NOT_FOUND, ERROR_SUCCESS, POINT, RECT, SIZE};\nuse windows_sys::Win32::System::Registry::{HKEY_CURRENT_USER, RRF_RT_REG_DWORD, RegGetValueW};\nuse windows_sys::Win32::System::SystemInformation::OSVERSIONINFOW;\nuse windows_sys::Win32::UI::HiDpi::GetSystemMetricsForDpi;\nuse windows_sys::Win32::UI::WindowsAndMessaging::{\n    CalculatePopupWindowPosition, SM_CXCURSOR, SM_CYCURSOR, TPM_WORKAREA,\n};\n\nuse crate::gui::win_nwg_ext::{BitmapEx, MenuEx, MenuItemEx};\nuse kanata_parser::cfg;\nuse nwg::{ControlHandle, NativeUi};\nuse std::sync::Arc;\n\ntrait PathExt {\n    fn add_ext(&mut self, ext_o: impl AsRef<std::path::Path>);\n}\nimpl PathExt for PathBuf {\n    fn add_ext(&mut self, ext_o: impl AsRef<std::path::Path>) {\n        match self.extension() {\n            Some(ext) => {\n                let mut ext = ext.to_os_string();\n                ext.push(\".\");\n                ext.push(ext_o.as_ref());\n                self.set_extension(ext)\n            }\n            None => self.set_extension(ext_o.as_ref()),\n        };\n    }\n}\n\n#[derive(Default, Debug, Clone)]\npub struct SystemTrayData {\n    pub tooltip: String,\n    pub cfg_p: Vec<PathBuf>,\n    pub cfg_icon: Option<String>,\n    pub layer0_name: String,\n    pub layer0_icon: Option<String>,\n    pub gui_opts: CfgOptionsGui,\n    pub tt_duration_pre: u16,\n    pub tt_size_pre: (u16, u16),\n}\n#[derive(Default)]\npub struct Icn {\n    pub tray: nwg::Bitmap, // uses an image of different size to fit the menu items\n    pub tooltip: nwg::Bitmap, // uses an image of different size to fit the tooltip\n    pub icon: nwg::Icon,\n}\n#[derive(Default)]\npub struct SystemTray {\n    pub app_data: RefCell<SystemTrayData>,\n    /// Store dynamically created tray menu items\n    pub tray_item_dyn: RefCell<Vec<nwg::MenuItem>>,\n    /// Store dynamically created tray menu items' handlers\n    pub handlers_dyn: RefCell<Vec<nwg::EventHandler>>,\n    /// Store dynamically created icons to not load them from a file every time\n    /// (icon format for tray icon, bitmap for tray MenuItem icons and tooltips)\n    pub img_dyn: RefCell<HashMap<PathBuf, Option<Icn>>>,\n    /// Store 'img_dyn' hashmap key for the currently active icon ('cfg_path:🗍layer_name' format)\n    pub icon_act_key: RefCell<Option<PathBuf>>,\n    /// Store 'img_dyn' hashmap key for the first deflayer to allow skipping it in tooltips\n    pub icon_0_key: RefCell<Option<PathBuf>>,\n    /// Store embedded-in-the-binary resources like icons not to load them from a file\n    pub embed: nwg::EmbedResource,\n    pub icon: nwg::Icon,\n    decoder: nwg::ImageDecoder,\n    pub window: nwg::MessageWindow,\n    /// A tooltip-like (no title/resize/focus/taskbar/clickthru) window to show notifications\n    /// (e.g., layer change messages)\n    pub win_tt: nwg::Window,\n    win_tt_ifr: nwg::ImageFrame,\n    win_tt_timer: nwg::AnimationTimer,\n    pub layer_notice: nwg::Notice,\n    pub cfg_notice: nwg::Notice,\n    pub err_notice: nwg::Notice,\n    pub exit_notice: nwg::Notice,\n    pub tt_notice: nwg::Notice,\n    /// Receiver of error message content sent from other threads\n    /// (e.g., from key event thread via WinDbgLogger that will also notify our GUI\n    /// (but not pass data) after sending data to this receiver)\n    pub err_recv: Option<Receiver<(String, String)>>,\n    pub tt2m_channel: Option<(ASender<bool>, Receiver<bool>)>,\n    // receiver will be created before a thread is spawned and moved there\n    pub m2tt_sender: RefCell<Option<ASender<bool>>>,\n    pub m_ptr_wh: RefCell<(u32, u32)>,\n    pub tray: nwg::TrayNotification,\n    pub tray_menu: nwg::Menu,\n    pub tray_1cfg_m: nwg::Menu,\n    pub tray_2reload: nwg::MenuItem,\n    pub tray_3exit: nwg::MenuItem,\n    pub img_reload: nwg::Bitmap,\n    pub img_exit: nwg::Bitmap,\n}\npub fn get_appdata() -> Option<PathBuf> {\n    var_os(\"APPDATA\").map(PathBuf::from)\n}\npub fn get_user_home() -> Option<PathBuf> {\n    var_os(\"USERPROFILE\").map(PathBuf::from)\n}\npub fn get_xdg_home() -> Option<PathBuf> {\n    var_os(\"XDG_CONFIG_HOME\").map(PathBuf::from)\n}\n\nconst CFG_FD: [&str; 3] = [\"\", \"kanata\", \"kanata-tray\"]; // blank \"\" allow checking directly for\n// user passed values\nconst ASSET_FD: [&str; 4] = [\"\", \"icon\", \"img\", \"icons\"];\nconst IMG_EXT: [&str; 7] = [\"ico\", \"jpg\", \"jpeg\", \"png\", \"bmp\", \"dds\", \"tiff\"];\nconst PRE_LAYER: &str = \"\\n🗍: \"; // : invalid path marker, so should be safe to use as a separator\nconst TTTIMER_L: u16 = 9; // lifetime delta to duration for a tooltip timer\nuse crate::gui::{CFG, GUI_CFG_TX, GUI_ERR_MSG_TX, GUI_ERR_TX, GUI_EXIT_TX, GUI_TX};\n\npub fn send_gui_notice() {\n    if let Some(gui_tx) = GUI_TX.get() {\n        gui_tx.notice();\n    } else {\n        error!(\"no GUI_TX to notify GUI thread of layer changes\");\n    }\n}\npub fn send_gui_cfg_notice() {\n    if let Some(gui_tx) = GUI_CFG_TX.get() {\n        gui_tx.notice();\n    } else {\n        error!(\"no GUI_CFG_TX to notify GUI thread of layer changes\");\n    }\n}\npub fn send_gui_err_notice() {\n    if let Some(gui_tx) = GUI_ERR_TX.get() {\n        gui_tx.notice();\n    } else {\n        error!(\"no GUI_ERR_TX to notify GUI thread of errors\");\n    }\n}\npub fn send_gui_exit_notice() {\n    if let Some(gui_tx) = GUI_EXIT_TX.get() {\n        gui_tx.notice();\n    } else {\n        error!(\"no GUI_EXIT_TX to ask GUI thread to exit\");\n    }\n}\npub fn show_err_msg_nofail(title: String, msg: String) {\n    // log gets insalized before gui, so some errors might have no target to log to, ignore them\n    if let Some(gui_msg_tx) = GUI_ERR_MSG_TX.get() {\n        if gui_msg_tx.send((title, msg)).is_err() {\n            warn!(\"send_gui_err_msg_notice failed to use OS notifications\")\n        } else {\n            // can't Error to avoid an ∞ error loop ↑\n            if let Some(gui_tx) = GUI_ERR_TX.get() {\n                gui_tx.notice();\n            }\n        }\n    }\n}\n\n/// Find an icon file that matches a given config icon name for a layer `lyr_icn` or a layer name\n/// `lyr_nm` (if `match_name` is `true`) or a given config icon name for the whole config `cfg_p`\n/// or a config file name at various locations (where config file is, where executable is,\n/// in user config folders)\nfn get_icon_p<S1, S2, S3, P>(\n    lyr_icn: S1,\n    lyr_nm: S2,\n    cfg_icn: S3,\n    cfg_p: P,\n    match_name: &bool,\n) -> Option<String>\nwhere\n    S1: AsRef<str>,\n    S2: AsRef<str>,\n    S3: AsRef<str>,\n    P: AsRef<Path>,\n{\n    get_icon_p_impl(\n        lyr_icn.as_ref(),\n        lyr_nm.as_ref(),\n        cfg_icn.as_ref(),\n        cfg_p.as_ref(),\n        match_name,\n    )\n}\n\nfn get_icon_p_impl(\n    lyr_icn: &str,\n    lyr_nm: &str,\n    cfg_icn: &str,\n    p: &Path,\n    match_name: &bool,\n) -> Option<String> {\n    trace!(\n        \"lyr_icn={lyr_icn} lyr_nm={lyr_nm} cfg_icn={cfg_icn} cfg_p={p:?} match_name={match_name}\"\n    );\n    let mut icon_file = PathBuf::new();\n    let blank_p = Path::new(\"\");\n    let lyr_icn_p = Path::new(&lyr_icn);\n    let lyr_nm_p = Path::new(&lyr_nm);\n    let cfg_icn_p = Path::new(&cfg_icn);\n    let cfg_stem = &p.file_stem().unwrap_or_else(|| OsStr::new(\"\"));\n    let cfg_name = &p.file_name().unwrap_or_else(|| OsStr::new(\"\"));\n    let f_name = [\n        lyr_icn_p.as_os_str(),\n        if *match_name {\n            lyr_nm_p.as_os_str()\n        } else {\n            OsStr::new(\"\")\n        },\n        cfg_icn_p.as_os_str(),\n        cfg_stem,\n        cfg_name,\n    ]\n    .into_iter();\n    let f_ext = [\n        lyr_icn_p.extension(),\n        if *match_name {\n            lyr_nm_p.extension()\n        } else {\n            None\n        },\n        cfg_icn_p.extension(),\n        None,\n        None,\n    ];\n    let pre_p = p.parent().unwrap_or_else(|| Path::new(\"\"));\n    let cur_exe = current_exe().unwrap_or_else(|_| PathBuf::new());\n    let xdg_cfg = get_xdg_home().unwrap_or_default();\n    let app_data = get_appdata().unwrap_or_default();\n    let mut user_cfg = get_user_home().unwrap_or_default();\n    user_cfg.push(\".config\");\n    let parents = [\n        Path::new(\"\"),\n        pre_p,\n        &cur_exe,\n        &xdg_cfg,\n        &app_data,\n        &user_cfg,\n    ]; // empty path to allow no prefixes when icon path is explictily set in case it's a full\n    // path already\n\n    for (i, nm) in f_name.enumerate() {\n        trace!(\"{}nm={:?}\", \"\", nm);\n        if nm.is_empty() {\n            trace!(\"no file name to test, skip\");\n            continue;\n        }\n        let mut is_full_p = false;\n        if nm == lyr_icn_p {\n            is_full_p = true\n        }; // user configs can have full paths, so test them even if all parent folders are emtpy\n        if nm == cfg_icn_p {\n            is_full_p = true\n        };\n        let icn_ext = &f_ext[i]\n            .unwrap_or_else(|| OsStr::new(\"\"))\n            .to_string_lossy()\n            .to_string();\n        let is_icn_ext_valid = if f_ext[i].is_some() {\n            if IMG_EXT.iter().any(|&i| i == icn_ext) {\n                trace!(\"icn_ext={:?}\", icn_ext);\n                true\n            } else {\n                warn!(\n                    \"user icon extension \\\"{}\\\" might be invalid (or just not an extension)!\",\n                    icn_ext\n                );\n                false\n            }\n        } else {\n            false\n        };\n        'p: for p_par in parents {\n            trace!(\"{}p_par={:?}\", \"  \", p_par);\n            if p_par == blank_p && !is_full_p {\n                trace!(\"blank parent for non-user, skip\");\n                continue;\n            }\n            for p_kan in CFG_FD {\n                trace!(\"{}p_kan={:?}\", \"    \", p_kan);\n                for p_icn in ASSET_FD {\n                    trace!(\"{}p_icn={:?}\", \"      \", p_icn);\n                    for ext in IMG_EXT {\n                        trace!(\"{}  ext={:?}\", \"        \", ext);\n                        if p_par != blank_p {\n                            icon_file.push(p_par);\n                        } // folders\n                        if !p_kan.is_empty() {\n                            icon_file.push(p_kan);\n                        }\n                        if !p_icn.is_empty() {\n                            icon_file.push(p_icn);\n                        }\n                        if !nm.is_empty() {\n                            icon_file.push(nm);\n                        }\n                        if !is_full_p {\n                            icon_file.set_extension(ext); // no icon name passed, iterate extensions\n                        } else if !is_icn_ext_valid {\n                            icon_file.add_ext(ext);\n                        } else {\n                            trace!(\"skip ext\");\n                        } // replace invalid icon extension\n                        trace!(\"testing icon file {:?}\", icon_file);\n                        if !icon_file.is_file() {\n                            icon_file.clear();\n                            if p_par == blank_p && p_kan.is_empty() && p_icn.is_empty() && is_full_p\n                            {\n                                trace!(\n                                    \"skipping further sub-iters on an empty parent with user config {:?}\",\n                                    nm\n                                );\n                                continue 'p;\n                            }\n                        } else {\n                            debug!(\"✓ found icon file: {}\", icon_file.display().to_string());\n                            return Some(icon_file.display().to_string());\n                        }\n                    }\n                }\n            }\n        }\n    }\n    debug!(\"✗ no icon file found\");\n    None\n}\n\npub const ICN_SZ_MENU: [u32; 2] = [24, 24]; // size for menu icons\npub const ICN_SZ_TT: [u32; 2] = [36, 36]; // size for tooltip icons\npub const ICN_SZ_MENU_I: [i32; 2] = [24, 24]; // for the builder, which needs i32\npub const ICN_SZ_TT_I: [i32; 2] = [36, 36]; // for the builder, which needs i32\n\nmacro_rules! win_ver {\n    () => {{\n        static WIN_VER: OnceLock<(u32, u32, u32)> = OnceLock::new();\n        *WIN_VER.get_or_init(|| {\n            let os_ver_i: *mut OSVERSIONINFOW = &mut OSVERSIONINFOW {\n                dwOSVersionInfoSize: 0, //u32\n                dwMajorVersion: 0,      //u32\n                dwMinorVersion: 0,      //u32\n                dwBuildNumber: 0,       //u32\n                dwPlatformId: 0,        //u32\n                szCSDVersion: [0; 128], //[u16; 128]\n            };\n            unsafe {\n                if 0 == RtlGetVersion(os_ver_i) {\n                    return (\n                        (*os_ver_i).dwMajorVersion,\n                        (*os_ver_i).dwMinorVersion,\n                        (*os_ver_i).dwBuildNumber,\n                    );\n                }\n            }\n            (0, 0, 0)\n        })\n    }};\n}\n/// Convert string to wide array and append null\npub fn to_wide_str(s: &str) -> Vec<u16> {\n    OsStr::new(s).encode_wide().chain(once(0)).collect()\n}\nmacro_rules! mouse_scale_factor {\n    // screen size = dpi⋅size⋅scaleF\n    () => {{\n        //TODO: track changes by subscribing via RegNotifyChangeKeyValue and reset value\n        static MOUSE_PTR_SCALE_F: OnceLock<u32> = OnceLock::new();\n        *MOUSE_PTR_SCALE_F.get_or_init(|| {\n            // 3. pointer scale factor @ Settings/Ease of Access/Mouse pointer\n            let key_root = HKEY_CURRENT_USER;\n            let key_path_s = r\"SOFTWARE\\Microsoft\\Accessibility\";\n            let key_name_s = \"CursorSize\";\n            let key_path = to_wide_str(key_path_s);\n            let key_name = to_wide_str(key_name_s);\n            let mut mouse_scale: DWORD = 0;\n            let mouse_scale_p: *mut c_void = &mut mouse_scale as *mut u32 as *mut std::ffi::c_void;\n            let mut mouse_scale_sz: DWORD = std::mem::size_of::<DWORD>() as DWORD;\n            let res = unsafe {\n                RegGetValueW(\n                    key_root,\n                    key_path.as_ptr(),\n                    key_name.as_ptr(),\n                    RRF_RT_REG_DWORD,     //restrict type to REG_DWORD\n                    std::ptr::null_mut(), //pdwType\n                    mouse_scale_p,\n                    &mut mouse_scale_sz,\n                )\n            };\n            match res as DWORD {\n                ERROR_SUCCESS => {}\n                ERROR_FILE_NOT_FOUND => {\n                    log::error!(r\"Registry '{}\\{}' not found\", key_path_s, key_name_s);\n                    mouse_scale = 1;\n                }\n                _ => {\n                    log::error!(\n                        r\"Registry '{}\\{}' couldn't be read as DWORD {}\",\n                        key_path_s,\n                        key_name_s,\n                        res\n                    );\n                    mouse_scale = 1;\n                }\n            }\n            mouse_scale\n        })\n    }};\n}\npub fn get_mouse_ptr_size(dpi_scale: bool) -> (u32, u32) {\n    // 1. get monitor DPI\n    let dpi = if dpi_scale { unsafe { nwg::dpi() } } else { 96 };\n    // 2. icon size @ dpi\n    let cur_w = SM_CXCURSOR;\n    let cur_h = SM_CYCURSOR;\n    let width = unsafe { GetSystemMetricsForDpi(cur_w, dpi as u32) } as u32;\n    let height = unsafe { GetSystemMetricsForDpi(cur_h, dpi as u32) } as u32;\n    let mouse_scale = mouse_scale_factor!();\n    (mouse_scale * width, mouse_scale * height)\n}\n\n// stores old mouse pointer position to avoid refreshing tooltips if mouse doesn't move\nthread_local! {static MXY:Cell<(i32,i32)> = Cell::default();}\nimpl SystemTray {\n    /// Read an image from a file, convert it to various formats: tray, tooltip, icon\n    fn get_icon_from_file<P>(&self, ico_p: P) -> Result<Icn>\n    where\n        P: AsRef<str>,\n    {\n        self.get_icon_from_file_impl(ico_p.as_ref())\n    }\n    fn get_icon_from_file_impl(&self, ico_p: &str) -> Result<Icn> {\n        let app_data = self.app_data.borrow();\n        let icn_sz_tt = [\n            app_data.gui_opts.tooltip_size.0 as u32,\n            app_data.gui_opts.tooltip_size.1 as u32,\n        ];\n        if let Ok(img_data) = self\n            .decoder\n            .from_filename(ico_p)\n            .and_then(|img_src| img_src.frame(0))\n        {\n            if let Ok(cfg_img_menu) = self.decoder.resize_image(&img_data, ICN_SZ_MENU) {\n                let cfg_icon_bmp_tray = cfg_img_menu.as_bitmap()?;\n                let cfg_icon_bmp_icon = cfg_icon_bmp_tray.copy_as_icon();\n                if let Ok(cfg_img_menu) = self.decoder.resize_image(&img_data, icn_sz_tt) {\n                    let cfg_icon_bmp_tt = cfg_img_menu.as_bitmap()?;\n                    return Ok(Icn {\n                        tray: cfg_icon_bmp_tray,\n                        tooltip: cfg_icon_bmp_tt,\n                        icon: cfg_icon_bmp_icon,\n                    });\n                } else {\n                    debug!(\"✓ main ✗ icon resize Tray for {:?}\", ico_p);\n                }\n            } else {\n                debug!(\"✓ main ✗ icon resize TTip for {:?}\", ico_p);\n            }\n        } else {\n            debug!(\"✗ main 0 icon ✓ icon path for {:?}\", ico_p);\n        }\n        bail!(\"✗ couldn't get a valid icon at {:?}\", ico_p)\n    }\n    /// Read an image from a file, convert it to a menu-sized icon,\n    /// assign to a menu and return the image in various formats (tray, tooltip, icon)\n    fn set_menu_item_cfg_icon(\n        &self,\n        menu_item: &mut nwg::MenuItem,\n        cfg_icon_s: &str,\n        cfg_p: &PathBuf,\n    ) -> Result<Icn> {\n        if let Some(ico_p) = get_icon_p(\"\", \"\", cfg_icon_s, cfg_p, &false)\n            && let Ok(icn) = self.get_icon_from_file(ico_p)\n        {\n            menu_item.set_bitmap(Some(&icn.tray));\n            return Ok(icn);\n        } else if get_icon_p(\"\", \"\", cfg_icon_s, cfg_p, &false).is_some() {\n            debug!(\n                \"✗ main 0 icon ✓ icon path, will be using DEFAULT icon for {:?}\",\n                cfg_p\n            );\n        }\n        menu_item.set_bitmap(None);\n        bail!(\"✗couldn't get a valid icon for {:?}\", cfg_p)\n    }\n    /// Move tooltip to the current mouse pointer position\n    fn update_tooltip_pos(&self) {\n        let app_data = self.app_data.borrow();\n        let mut x = 0;\n        let mut y = 0;\n        let mut is_same = false;\n        MXY.with(|mxy| {\n            (x, y) = nwg::GlobalCursor::position();\n            let (mx, my) = mxy.get();\n            if mx == x && my == y {\n                is_same = true;\n                return;\n            }\n            mxy.set((x, y));\n        });\n        if is_same {\n            return;\n        };\n        let win_ver = win_ver!();\n        // image width/height to take it into account when calculating overlaps\n        let w = app_data.gui_opts.tooltip_size.0 as i32;\n        let h = app_data.gui_opts.tooltip_size.1 as i32;\n        let flags = if (win_ver.0 >= 6 && win_ver.1 >= 1) || win_ver.0 > 6 {\n            TPM_WORKAREA\n        } else {\n            0\n        };\n        // 🖰 pointer size to make sure tooltip doesn't overlap,\n        // don't adjust for dpi in internal calculations\n        // tooltip offset vs. 🖰 pointer by 25% its size\n        let (mouse_ptr_w, mouse_ptr_h) = *self.m_ptr_wh.borrow();\n        let tt_off_x = (mouse_ptr_w as f64 * 0.25).round() as i32;\n        let tt_off_y = (mouse_ptr_h as f64 * 0.25).round() as i32; //\n        let (mouse_ptr_w, mouse_ptr_h) = (mouse_ptr_w as i32, mouse_ptr_h as i32);\n        let anchorpoint = &POINT {\n            x: x + tt_off_x,\n            y: y + tt_off_y,\n        };\n        let tt_win_sz = &SIZE { cx: w, cy: h };\n        let excluderect = &RECT {\n            left: x.saturating_sub(mouse_ptr_w),\n            right: x.saturating_add(mouse_ptr_w), // assuming ~top-left hotspot\n            top: y.saturating_sub(mouse_ptr_h),\n            bottom: y.saturating_add(mouse_ptr_h),\n        }; //Avoid ~ mouse pointer area\n        let out_rect = &mut RECT {\n            left: 0,\n            right: 0,\n            top: 0,\n            bottom: 0,\n        };\n        let ret = unsafe {\n            CalculatePopupWindowPosition(anchorpoint, tt_win_sz, flags, excluderect, out_rect)\n        };\n        if ret != 0 {\n            x = out_rect.left;\n            y = out_rect.top;\n        }\n        let dpi = unsafe { nwg::dpi() };\n        let xx = (x as f64 / (dpi as f64 / 96_f64)).round() as i32; // adjust dpi for layout\n        let yy = (y as f64 / (dpi as f64 / 96_f64)).round() as i32;\n        self.win_tt.set_position(xx, yy);\n        // TODO: somehow still shown a bit too far off from the pointer\n        if log_enabled!(Trace) {\n            let (mx, my) = MXY.get();\n            trace!(\n                \"🖰 @{mx}⋅{my} ↔{mouse_ptr_w}↕{mouse_ptr_h} (upd={}) {x}⋅{y} @ dpi={dpi} → {xx}⋅{yy} {win_ver:?} flags={flags} ex←{}→{}↑{}↓{}\",\n                ret != 0,\n                excluderect.left,\n                excluderect.right,\n                excluderect.top,\n                excluderect.bottom\n            );\n        }\n    }\n    /// Spawn a thread with a new 🖰 pointer watcher\n    /// (that sends a signal back to GUI which in turn moves the tooltip to the new position)\n    fn update_mouse_watcher(&self, tt2m_sndr: ASender<bool>, ticks: u16, poll_time: Duration) {\n        debug!(\"   ✓   update_mouse_watcher\");\n        let gui_tx = self.tt_notice.sender(); // allows notifying GUI on tooltip move updates\n        let (m2tt_sndr0, m2tt_rcvr) = std::sync::mpsc::channel::<bool>();\n        {\n            let mut m2tt_sender = self.m2tt_sender.borrow_mut();\n            *m2tt_sender = Some(m2tt_sndr0.clone());\n        }\n        let _handler = std::thread::spawn(move || -> Result<()> {\n            debug!(\"  ✓ Starting polling for a 🖰 pointer position\");\n            let mut i = 0;\n            while i <= ticks {\n                i += 1;\n                std::thread::sleep(poll_time);\n                match m2tt_rcvr.try_recv() {\n                    Ok(_) => {\n                        debug!(\"extending tooltip watcher instead of launching +1\");\n                        i = 0;\n                    }\n                    Err(TryRecvError::Empty) => {\n                        trace!(\"send signal to reposition\");\n                        gui_tx.notice();\n                    }\n                    Err(TryRecvError::Disconnected) => {\n                        debug!(\"internal: m2tt_sender disconnected, no more 🖰 pointer tracking\");\n                        break;\n                    }\n                }\n            }\n            debug!(\"  ✗ Stopped polling for a 🖰 pointer position\");\n            tt2m_sndr.send(true)?;\n            Ok(())\n        });\n    }\n    /// Show our tooltip-like notification window\n    fn show_tooltip(&self, img: Option<&nwg::Bitmap>) {\n        let app_data = self.app_data.borrow();\n        if !app_data.gui_opts.tooltip_layer_changes {\n            return;\n        };\n        if img.is_none() && !app_data.gui_opts.tooltip_show_blank {\n            self.win_tt.set_visible(false);\n            return;\n        };\n        static IS_INIT: OnceLock<bool> = OnceLock::new();\n        if IS_INIT.get().is_none() {\n            // layered win needs a special call after being initialized to appear\n            let _ = IS_INIT.set(true);\n            debug!(\"win_tt hasn't been shown as a layered window\");\n            let win_id = self\n                .win_tt\n                .handle\n                .hwnd()\n                .expect(\"win_tt should be a valid/existing window!\");\n            show_layered_win(win_id);\n        } else {\n            debug!(\"win_tt has been shown as a layered window\");\n        }\n        self.win_tt_ifr.set_bitmap(img);\n        {\n            let mut m_ptr_wh = self.m_ptr_wh.borrow_mut();\n            *m_ptr_wh = get_mouse_ptr_size(false);\n        } // 🖰 pointer size so tooltip doesn't overlap\n        // don't adjust for dpi in internal calculations\n        self.update_tooltip_pos();\n        self.win_tt.set_visible(true);\n        if app_data.gui_opts.tooltip_duration != 0 {\n            self.win_tt_timer.start()\n        };\n\n        if let Some((tt2m_sndr, tt2m_rcvr)) = &self.tt2m_channel {\n            let mut start = false;\n            match tt2m_rcvr.try_recv() {\n                Ok(_) => {\n                    debug!(\"launch a new thread\");\n                    start = true;\n                }\n                Err(TryRecvError::Empty) => {\n                    if let Some(m2tt_sender) = self.m2tt_sender.borrow().as_ref() {\n                        trace!(\"send signal to extend\");\n                        m2tt_sender.send(true).unwrap_or_else(|_| {\n                            error!(\"internal: couldn't send a signal to the 🖰 pointer watcher!\")\n                        });\n                    } else {\n                        debug!(\n                            \"no message and no m2tt_sender_o, so no thread should be running, launch a new thread!\"\n                        );\n                        start = true;\n                    }\n                }\n                Err(TryRecvError::Disconnected) => {\n                    error!(\"internal: tt2m_channel disconnected, no more 🖰 pointer tracking\")\n                }\n            }\n            let duration = 16;\n            let poll_time = Duration::from_millis(duration);\n            let ticks =\n                (app_data.gui_opts.tooltip_duration as f64 / duration as f64).round() as u16;\n            debug!(\n                \"will tick for {ticks} every {duration} ms to match user {}\",\n                app_data.gui_opts.tooltip_duration\n            );\n            if start {\n                self.update_mouse_watcher(tt2m_sndr.clone(), ticks, poll_time);\n            }\n        } else {\n            error!(\"internal: m2tt_sender doesn't exist can't track 🖰 pointer without it!\");\n        }\n    }\n    /// Hide our tooltip-like notification window\n    fn hide_tooltip(&self) {\n        self.win_tt.set_visible(false)\n    }\n    fn show_menu(&self) {\n        self.update_tray_icon_cfg_group(false);\n        let (x, y) = nwg::GlobalCursor::position();\n        self.tray_menu.popup(x, y);\n    }\n    /// Add a ✓ (or highlight the icon) to the currently active config.\n    /// Runs on opening of the list of configs menu\n    fn update_tray_icon_cfg(\n        &self,\n        menu_item_cfg: &mut nwg::MenuItem,\n        cfg_p: &PathBuf,\n        is_active: bool,\n    ) -> Result<()> {\n        let mut img_dyn = self.img_dyn.borrow_mut();\n        if img_dyn.contains_key(cfg_p) {\n            // check if menu group icon needs to be updated to match active\n            if is_active\n                && let Some(icn) = img_dyn.get(cfg_p).and_then(|maybe_icn| maybe_icn.as_ref())\n            {\n                self.tray_1cfg_m.set_bitmap(Some(&icn.tray))\n            }\n        } else {\n            trace!(\"config menu item icon missing, read config and add it (or nothing) {cfg_p:?}\");\n            if let Ok(cfg) = cfg::new_from_file(cfg_p)\n                && let Some(cfg_icon_s) = cfg.options.gui_opts.tray_icon\n            {\n                debug!(\"loaded config without a tray icon {cfg_p:?}\");\n                if let Ok(icn) = self.set_menu_item_cfg_icon(menu_item_cfg, &cfg_icon_s, cfg_p) {\n                    if is_active {\n                        self.tray_1cfg_m.set_bitmap(Some(&icn.tray));\n                    }\n                    // update currently active config's icon in the combo menu\n                    debug!(\"✓set icon {cfg_p:?}\");\n                    let _ = img_dyn.insert(cfg_p.clone(), Some(icn));\n                } else {\n                    bail!(\"✗couldn't get a valid icon\")\n                }\n            } else {\n                bail!(\"✗icon not configured\")\n            }\n        }\n        Ok(())\n    }\n    fn update_tray_icon_cfg_group(&self, force: bool) {\n        if let Some(cfg) = CFG.get() {\n            if let Some(k) = cfg.try_lock() {\n                let idx_cfg = k.cur_cfg_idx;\n                let mut tray_item_dyn = self.tray_item_dyn.borrow_mut();\n                let h_cfg_i = &mut tray_item_dyn[idx_cfg];\n                let is_check = h_cfg_i.checked();\n                if !is_check || force {\n                    let cfg_p = &k.cfg_paths[idx_cfg];\n                    debug!(\n                        \"✗ mismatch idx_cfg={idx_cfg:?} {} {:?} cfg_p={cfg_p:?}\",\n                        if is_check { \"✓\" } else { \"✗\" },\n                        h_cfg_i.handle\n                    );\n                    h_cfg_i.set_checked(true);\n                    if let Err(e) = self.update_tray_icon_cfg(h_cfg_i, cfg_p, true) {\n                        debug!(\"{e:?} {cfg_p:?}\");\n                        let mut img_dyn = self.img_dyn.borrow_mut();\n                        img_dyn.insert(cfg_p.clone(), None);\n                        self.tray_1cfg_m.set_bitmap(None); // can't update menu, so remove combo\n                        // menu icon\n                    };\n                } else {\n                    debug!(\"gui cfg selection matches active config\");\n                };\n            } else {\n                debug!(\n                    \"✗ kanata config is locked, can't get current config (likely the gui changed the layer and is still holding the lock, it will update the icon)\"\n                );\n            }\n        };\n    }\n    fn check_active(&self) {\n        if let Some(cfg) = CFG.get() {\n            let k = cfg.lock();\n            let idx_cfg = k.cur_cfg_idx;\n            let mut tray_item_dyn = self.tray_item_dyn.borrow_mut();\n            for (i, h_cfg_i) in tray_item_dyn.iter_mut().enumerate() {\n                // 1 if missing an icon, read config to get one\n                let cfg_p = &k.cfg_paths[i];\n                trace!(\"     →→→→ i={i:?} {:?} cfg_p={cfg_p:?}\", h_cfg_i.handle);\n                let is_active = i == idx_cfg;\n                if let Err(e) = self.update_tray_icon_cfg(h_cfg_i, cfg_p, is_active) {\n                    debug!(\"{e:?} {cfg_p:?}\");\n                    let mut img_dyn = self.img_dyn.borrow_mut();\n                    img_dyn.insert(cfg_p.clone(), None);\n                    if is_active {\n                        self.tray_1cfg_m.set_bitmap(None);\n                    } // update currently active config's icon in the combo menu\n                };\n                // 2 if wrong GUI checkmark, correct it\n                if h_cfg_i.checked() && !is_active {\n                    debug!(\"uncheck i{} act{}\", i, idx_cfg);\n                    h_cfg_i.set_checked(false);\n                }\n                if !h_cfg_i.checked() && is_active {\n                    debug!(\"  check i{} act{}\", i, idx_cfg);\n                    h_cfg_i.set_checked(true);\n                }\n            }\n        } else {\n            error!(\"no CFG var that contains active kanata config\");\n        };\n    }\n    /// Check if tooltip data is changed, and update tooltip window size / timer duration\n    fn update_tooltip_data(&self, k: &Kanata) -> bool {\n        let mut app_data = self.app_data.borrow_mut();\n        let mut clear = false;\n        if app_data.tt_duration_pre != k.gui_opts.tooltip_duration {\n            app_data.gui_opts.tooltip_duration = k.gui_opts.tooltip_duration;\n            clear = true;\n            app_data.tt_duration_pre = k.gui_opts.tooltip_duration;\n            trace!(\"timer duration changed, updating\");\n            self.win_tt_timer.set_interval(Duration::from_millis(\n                (k.gui_opts.tooltip_duration.saturating_add(1)).into(),\n            ));\n            self.win_tt_timer.set_lifetime(Some(Duration::from_millis(\n                (k.gui_opts.tooltip_duration.saturating_add(TTTIMER_L)).into(),\n            )));\n        }\n        if !(app_data.tt_size_pre.0 == k.gui_opts.tooltip_size.0\n            && app_data.tt_size_pre.1 == k.gui_opts.tooltip_size.1)\n        {\n            app_data.tt_size_pre = k.gui_opts.tooltip_size;\n            clear = true;\n            app_data.gui_opts.tooltip_size = k.gui_opts.tooltip_size;\n            trace!(\"tooltip_size duration changed, updating\");\n            let dpi = unsafe { nwg::dpi() };\n            let icn_sz_tt_i = (k.gui_opts.tooltip_size.0, k.gui_opts.tooltip_size.1);\n            let w = (icn_sz_tt_i.0 as f64 / (dpi as f64 / 96_f64)).round() as u32;\n            let h = (icn_sz_tt_i.1 as f64 / (dpi as f64 / 96_f64)).round() as u32;\n            self.win_tt.set_size(w, h);\n\n            let icn_sz_tt_i = (\n                k.gui_opts.tooltip_size.0 as u32,\n                k.gui_opts.tooltip_size.1 as u32,\n            );\n            // todo: replace with a no-margin NWG config when it's available\n            let padx = (k.gui_opts.tooltip_size.0 as f64 / 6_f64).round() as i32;\n            let pady = (k.gui_opts.tooltip_size.1 as f64 / 6_f64).round() as i32;\n            trace!(\n                \"kanata tooltip size = {icn_sz_tt_i:?}, ttsize = {w}⋅{h} offset = {padx}⋅{pady}\"\n            );\n            self.win_tt_ifr.set_size(icn_sz_tt_i.0, icn_sz_tt_i.1);\n            self.win_tt_ifr.set_position(-padx, -pady);\n        }\n        clear\n    }\n    /// Reload config file, currently active (`i=None`) or matching a given `i` index\n    fn reload_cfg(&self, i: Option<usize>) -> Result<()> {\n        use nwg::TrayNotificationFlags as f_tray;\n        let mut msg_title = \"\".to_string();\n        let mut msg_content = \"\".to_string();\n        let mut flags = f_tray::empty();\n        if let Some(cfg) = CFG.get() {\n            let mut k = cfg.lock();\n            let paths = &k.cfg_paths;\n            let idx_cfg = match i {\n                Some(idx) => {\n                    if idx < paths.len() {\n                        idx\n                    } else {\n                        error!(\n                            \"Invalid config index {} while kanata has only {} configs loaded\",\n                            idx + 1,\n                            paths.len()\n                        );\n                        k.cur_cfg_idx\n                    }\n                }\n                None => k.cur_cfg_idx,\n            };\n            let path_cur = &paths[idx_cfg];\n            let path_cur_s = path_cur.display().to_string();\n            let path_cur_cc = path_cur.clone();\n            msg_content += &path_cur_s;\n            let cfg_name = &path_cur\n                .file_name()\n                .unwrap_or_else(|| OsStr::new(\"\"))\n                .to_string_lossy()\n                .to_string();\n            if log_enabled!(Debug) {\n                let cfg_icon = &k.gui_opts.tray_icon;\n                let cfg_icon_s = cfg_icon.clone().unwrap_or(\"✗\".to_string());\n                let layer_id = k.layout.b().current_layer();\n                let layer_name = &k.layer_info[layer_id].name;\n                let layer_icon = &k.layer_info[layer_id].icon;\n                let layer_icon_s = layer_icon.clone().unwrap_or(\"✗\".to_string());\n                debug!(\n                    \"pre reload tray_icon={} layer_name={} layer_icon={}\",\n                    cfg_icon_s, layer_name, layer_icon_s\n                );\n            }\n            match i {\n                Some(idx) => {\n                    if let Ok(()) = k.live_reload_n(idx) {\n                        msg_title += &(\"🔄 \\\"\".to_owned() + cfg_name + \"\\\" loaded\");\n                        flags |= f_tray::USER_ICON;\n                    } else {\n                        msg_title += &(\"🔄 \\\"\".to_owned() + cfg_name + \"\\\" NOT loaded\");\n                        flags |= f_tray::ERROR_ICON | f_tray::LARGE_ICON;\n                        {\n                            let app_data = self.app_data.borrow();\n                            if app_data.gui_opts.notify_cfg_reload_silent {\n                                flags |= f_tray::SILENT;\n                            }\n                        }\n                        self.tray.show(\n                            &msg_content,\n                            Some(&msg_title),\n                            Some(flags),\n                            Some(&self.icon),\n                        );\n                        bail!(\"{msg_content}\");\n                    }\n                }\n                None => {\n                    if let Ok(()) = k.live_reload() {\n                        msg_title += &(\"🔄 \\\"\".to_owned() + cfg_name + \"\\\" reloaded\");\n                        flags |= f_tray::USER_ICON;\n                    } else {\n                        msg_title += &(\"🔄 \\\"\".to_owned() + cfg_name + \"\\\" NOT reloaded\");\n                        flags |= f_tray::ERROR_ICON | f_tray::LARGE_ICON;\n                        {\n                            let app_data = self.app_data.borrow();\n                            if app_data.gui_opts.notify_cfg_reload_silent {\n                                flags |= f_tray::SILENT;\n                            }\n                        }\n                        self.tray.show(\n                            &msg_content,\n                            Some(&msg_title),\n                            Some(flags),\n                            Some(&self.icon),\n                        );\n                        bail!(\"{msg_content}\");\n                    }\n                }\n            };\n            let cfg_icon = &k.gui_opts.tray_icon;\n            let layer_id = k.layout.b().current_layer();\n            let layer_name = &k.layer_info[layer_id].name;\n            let layer_icon = &k.layer_info[layer_id].icon;\n            let mut cfg_layer_pkey = PathBuf::new(); // path key\n            cfg_layer_pkey.push(path_cur_cc.clone());\n            cfg_layer_pkey.push(PRE_LAYER.to_owned() + layer_name); //:invalid path marker,\n            // so should be safe to use as\n            // a separator\n            let cfg_layer_pkey_s = cfg_layer_pkey.display().to_string();\n            if log_enabled!(Debug) {\n                let layer_icon_s = layer_icon.clone().unwrap_or(\"✗\".to_string());\n                debug!(\n                    \"pos reload tray_icon={:?} layer_name={:?} layer_icon={:?}\",\n                    cfg_icon, layer_name, layer_icon_s\n                );\n            }\n\n            let _ = self.update_tooltip_data(&k); // check for changes before they're overwritten ↓\n            {\n                *self.app_data.borrow_mut() = update_app_data(&k)?;\n            }\n            self.tray.set_tip(&cfg_layer_pkey_s); // update tooltip to point to the newer config\n            let clear = i.is_none();\n            self.update_tray_icon(\n                cfg_layer_pkey,\n                &cfg_layer_pkey_s,\n                layer_name,\n                layer_icon,\n                path_cur_cc,\n                clear,\n            )\n        } else {\n            msg_title += \"✗ Config NOT reloaded, no CFG\";\n            warn!(\"{}\", msg_title);\n            flags |= f_tray::ERROR_ICON;\n        };\n        flags |= f_tray::LARGE_ICON; // todo: fails without this, must have SM_CXICON x SM_CYICON?\n        {\n            let app_data = self.app_data.borrow();\n            if app_data.gui_opts.notify_cfg_reload_silent {\n                flags |= f_tray::SILENT;\n            }\n        }\n        self.tray.show(\n            &msg_content,\n            Some(&msg_title),\n            Some(flags),\n            Some(&self.icon),\n        );\n        Ok(())\n    }\n    fn reload_layer_icon(&self) {\n        let _ = self.reload_cfg_or_layer_icon(false);\n    }\n    /// Show OS notification message with an error coming from WinDbgLogger\n    fn notify_error(&self) {\n        let app_data = self.app_data.borrow();\n        if !app_data.gui_opts.notify_error {\n            return;\n        };\n        use nwg::TrayNotificationFlags as f_tray;\n        let mut msg_title = \"\".to_string();\n        let mut msg_content = \"\".to_string();\n        let mut flags = f_tray::empty();\n        if let Some(gui_msg_rx) = &self.err_recv {\n            match gui_msg_rx.try_recv() {\n                Ok((title, msg)) => {\n                    msg_title += &title;\n                    msg_content += &msg;\n                }\n                Err(TryRecvError::Empty) => {\n                    msg_title += \"internal\";\n                    msg_content += \"channel to receive errors is Empty\";\n                }\n                Err(TryRecvError::Disconnected) => {\n                    msg_title += \"internal\";\n                    msg_content += \"channel to receive errors is Disconnected\";\n                }\n            }\n        } else {\n            msg_title += \"internal\";\n            msg_content += \"SystemTray is supposed to have a valid 'err_recv' field value\"\n        }\n        flags |= f_tray::ERROR_ICON;\n        if app_data.gui_opts.notify_cfg_reload_silent {\n            flags |= f_tray::SILENT;\n        }\n        let msg_title = strip_ansi_escapes::strip_str(&msg_title);\n        let msg_content = strip_ansi_escapes::strip_str(&msg_content);\n        self.tray.show(\n            &msg_content,\n            Some(&msg_title),\n            Some(flags),\n            Some(&self.icon),\n        );\n    }\n    /// Update tray icon data on config reload\n    fn reload_cfg_icon(&self) {\n        let _ = self.reload_cfg_or_layer_icon(true);\n    }\n    /// Update tray icon data on layer change (and config reload)\n    fn reload_cfg_or_layer_icon(&self, is_cfg: bool) -> Result<()> {\n        if let Some(cfg) = CFG.get() {\n            if let Some(k) = cfg.try_lock() {\n                let paths = &k.cfg_paths;\n                let idx_cfg = k.cur_cfg_idx;\n                let path_cur = &paths[idx_cfg];\n                let path_cur_cc = path_cur.clone();\n                let cfg_icon = &k.gui_opts.tray_icon;\n                let layer_id = k.layout.b().current_layer();\n                let layer_name = &k.layer_info[layer_id].name;\n                let layer_icon = &k.layer_info[layer_id].icon;\n\n                let mut cfg_layer_pkey = PathBuf::new(); // path key\n                cfg_layer_pkey.push(path_cur_cc.clone());\n                cfg_layer_pkey.push(PRE_LAYER.to_owned() + layer_name); //:invalid path marker,\n                // so should be safe\n                // to use as a separator\n                let cfg_layer_pkey_s = cfg_layer_pkey.display().to_string();\n                if log_enabled!(Debug) {\n                    let cfg_name = &path_cur\n                        .file_name()\n                        .unwrap_or_else(|| OsStr::new(\"\"))\n                        .to_string_lossy()\n                        .to_string();\n                    let cfg_icon_s = layer_icon.clone().unwrap_or(\"✗\".to_string());\n                    let layer_icon_s = cfg_icon.clone().unwrap_or(\"✗\".to_string());\n                    debug!(\n                        \"✓ layer changed to ‘{}’ with icon ‘{}’ @ ‘{}’ tray_icon ‘{}’\",\n                        layer_name, layer_icon_s, cfg_name, cfg_icon_s\n                    );\n                }\n\n                let clear = self.update_tooltip_data(&k);\n                if is_cfg {\n                    *self.app_data.borrow_mut() = update_app_data(&k)?;\n                }\n                if is_cfg {\n                    let app_data = self.app_data.borrow();\n                    if app_data.gui_opts.notify_cfg_reload {\n                        use nwg::TrayNotificationFlags as f_tray;\n                        let cfg_name = &path_cur\n                            .file_name()\n                            .unwrap_or_else(|| OsStr::new(\"\"))\n                            .to_string_lossy()\n                            .to_string();\n                        let msg_title = \"🔄 \\\"\".to_owned() + cfg_name + \"\\\" re-loaded\";\n                        let msg_content = &path_cur.display().to_string();\n                        let mut flags = f_tray::empty() | f_tray::USER_ICON | f_tray::LARGE_ICON;\n                        if app_data.gui_opts.notify_cfg_reload_silent {\n                            flags |= f_tray::SILENT;\n                        }\n                        self.tray.show(\n                            msg_content,\n                            Some(&msg_title),\n                            Some(flags),\n                            Some(&self.icon),\n                        );\n                    }\n                }\n                self.tray.set_tip(&cfg_layer_pkey_s);\n                self.update_tray_icon(\n                    cfg_layer_pkey,\n                    &cfg_layer_pkey_s,\n                    layer_name,\n                    layer_icon,\n                    path_cur_cc,\n                    clear,\n                )\n            } else {\n                debug!(\n                    \"✗ kanata config is locked, can't get current layer (likely the gui changed the layer and is still holding the lock, it will update the icon)\"\n                );\n            }\n        } else {\n            warn!(\"✗ Layer indicator NOT changed, no CFG\");\n        };\n        Ok(())\n    }\n    /// Update tray icon data given various config/layer info\n    /// * `cfg_layer_pkey` - \"path␤🗍: layer_name\" unique icon id\n    /// * `path_cur_cc` - \"path\" without the layer name\n    /// * `clear` - reset stored icon cached paths/files\n    fn update_tray_icon(\n        &self,\n        cfg_layer_pkey: PathBuf,\n        cfg_layer_pkey_s: &str,\n        layer_name: &str,\n        layer_icon: &Option<String>,\n        path_cur_cc: PathBuf,\n        clear: bool,\n    ) {\n        let mut img_dyn = self.img_dyn.borrow_mut(); // update the tray icons\n        let mut icon_act_key = self.icon_act_key.borrow_mut(); // update the tray icon active path\n        let mut icon_0_key = self.icon_0_key.borrow_mut(); // update the tray tooltip layer0 path\n        if clear {\n            *img_dyn = Default::default();\n            *icon_act_key = Default::default();\n            *icon_0_key = Some(cfg_layer_pkey.clone());\n            debug!(\"reloading active config, clearing img_dyn/_active cache\");\n        }\n        let app_data = self.app_data.borrow();\n        let skip_tt = app_data.gui_opts.tooltip_no_base\n            && icon_0_key\n                .as_ref()\n                .filter(|p| **p == cfg_layer_pkey)\n                .is_some();\n        if icon_0_key.is_none() {\n            warn!(\"internal bug?: icon_0_key should never be empty?\")\n        }\n        if let Some(icn_opt) = img_dyn.get(&cfg_layer_pkey) {\n            // 1a config+layer path has already been checked\n            if let Some(icn) = icn_opt {\n                self.tray.set_icon(&icn.icon);\n                *icon_act_key = Some(cfg_layer_pkey);\n                if !skip_tt {\n                    self.show_tooltip(Some(&icn.tooltip));\n                }\n            } else {\n                info!(\n                    \"no icon found, using default for config+layer = {}\",\n                    cfg_layer_pkey_s\n                );\n                self.tray.set_icon(&self.icon);\n                *icon_act_key = Some(cfg_layer_pkey);\n                self.show_tooltip(None);\n            }\n        } else if layer_icon.is_some() || app_data.gui_opts.icon_match_layer_name {\n            let layer_icon = match layer_icon {\n                Some(layer_icon_inner) => {\n                    trace!(\"configured layer icon = {}\", layer_icon_inner);\n                    layer_icon_inner\n                }\n                None => {\n                    trace!(\n                        \"no configured layer icon, checking its name = {}\",\n                        layer_name\n                    );\n                    layer_name\n                }\n            };\n            // 1b cfg+layer path hasn't been checked, but layer has an icon configured...\n            // or configured to check its name, so check it\n            if let Some(ico_p) = get_icon_p(\n                layer_icon,\n                layer_name,\n                \"\",\n                &path_cur_cc,\n                &app_data.gui_opts.icon_match_layer_name,\n            ) {\n                if let Ok(icn) = self.get_icon_from_file(ico_p) {\n                    info!(\n                        \"✓ Using an icon from this config+layer: {}\",\n                        cfg_layer_pkey_s\n                    );\n                    self.tray.set_icon(&icn.icon);\n                    if !skip_tt {\n                        self.show_tooltip(Some(&icn.tooltip));\n                    }\n                    let _ = img_dyn.insert(cfg_layer_pkey.clone(), Some(icn));\n                    *icon_act_key = Some(cfg_layer_pkey);\n                } else {\n                    warn!(\n                        \"✗ Invalid icon file \\\"{layer_icon}\\\" from this config+layer: {}\",\n                        cfg_layer_pkey_s\n                    );\n                    let _ = img_dyn.insert(cfg_layer_pkey.clone(), None);\n                    self.tray.set_icon(&self.icon);\n                    *icon_act_key = Some(cfg_layer_pkey);\n                    self.show_tooltip(None);\n                }\n            } else {\n                warn!(\n                    \"✗ Invalid icon path \\\"{layer_icon}\\\" from this config+layer: {}\",\n                    cfg_layer_pkey_s\n                );\n                let _ = img_dyn.insert(cfg_layer_pkey.clone(), None);\n                self.tray.set_icon(&self.icon);\n                *icon_act_key = Some(cfg_layer_pkey);\n                self.show_tooltip(None);\n            }\n        } else if img_dyn.contains_key(&path_cur_cc) {\n            // 2a no layer icon configured, but config icon exists, use it\n            if let Some(icn) = img_dyn.get(&path_cur_cc).unwrap() {\n                self.tray.set_icon(&icn.icon);\n                *icon_act_key = Some(path_cur_cc);\n                self.show_tooltip(None);\n            } else {\n                info!(\n                    \"no icon found, using default for config: {}\",\n                    path_cur_cc.display().to_string()\n                );\n                self.tray.set_icon(&self.icon);\n                *icon_act_key = Some(path_cur_cc);\n                self.show_tooltip(None);\n            }\n        } else {\n            // 2b no layer icon configured, no config icon, use config path\n            let cfg_icon_p = if let Some(cfg_icon) = &app_data.cfg_icon {\n                cfg_icon\n            } else {\n                \"\"\n            };\n            if let Some(ico_p) = get_icon_p(\n                \"\",\n                layer_name,\n                cfg_icon_p,\n                &path_cur_cc,\n                &app_data.gui_opts.icon_match_layer_name,\n            ) {\n                if let Ok(icn) = self.get_icon_from_file(ico_p) {\n                    info!(\n                        \"✓ Using an icon from this config: {}\",\n                        path_cur_cc.display().to_string()\n                    );\n                    self.tray.set_icon(&icn.icon);\n                    if !skip_tt {\n                        self.show_tooltip(Some(&icn.tooltip));\n                    }\n                    let _ = img_dyn.insert(cfg_layer_pkey.clone(), Some(icn));\n                    *icon_act_key = Some(cfg_layer_pkey);\n                } else {\n                    warn!(\n                        \"✗ Invalid icon file \\\"{cfg_icon_p}\\\" from this config: {}\",\n                        cfg_layer_pkey.display().to_string()\n                    );\n                    let _ = img_dyn.insert(cfg_layer_pkey.clone(), None);\n                    *icon_act_key = Some(cfg_layer_pkey);\n                    self.tray.set_icon(&self.icon);\n                    self.show_tooltip(None);\n                }\n            } else {\n                warn!(\n                    \"✗ Invalid icon path \\\"{cfg_icon_p}\\\" from this config: {}\",\n                    cfg_layer_pkey.display().to_string()\n                );\n                let _ = img_dyn.insert(cfg_layer_pkey.clone(), None);\n                *icon_act_key = Some(cfg_layer_pkey);\n                self.tray.set_icon(&self.icon);\n                self.show_tooltip(None);\n            }\n        }\n    }\n    fn exit(&self) {\n        let handlers = self.handlers_dyn.borrow();\n        for handler in handlers.iter() {\n            nwg::unbind_event_handler(handler);\n        }\n        nwg::stop_thread_dispatch();\n    }\n\n    fn build_win_tt(&self) -> Result<nwg::Window, nwg::NwgError> {\n        let f_style = wf::POPUP;\n        let f_ex = WS_CLICK_THRU\n       | WS_EX_NOACTIVATE   //0x8000000L top-level win doesn't become foreground win on user click\n       | WS_EX_TOOLWINDOW   // remove from the taskbar (floating toolbar)\n       ;\n\n        let mut window: nwg::Window = Default::default();\n        let dpi = unsafe { nwg::dpi() };\n        let app_data = self.app_data.borrow();\n        let icn_sz_tt_i = (\n            app_data.gui_opts.tooltip_size.0,\n            app_data.gui_opts.tooltip_size.1,\n        );\n        let w = (icn_sz_tt_i.0 as f64 / (dpi as f64 / 96_f64)).round() as i32;\n        let h = (icn_sz_tt_i.1 as f64 / (dpi as f64 / 96_f64)).round() as i32;\n        trace!(\"Active Kanata Layer win size = {w}⋅{h}\");\n        nwg::Window::builder()\n            .title(\"Active Kanata Layer\")\n            .size((w, h))\n            .position((0, 0))\n            .center(false)\n            .topmost(true)\n            .maximized(false)\n            .minimized(false)\n            .flags(f_style)\n            .ex_flags(f_ex)\n            .icon(None)\n            .accept_files(false)\n            .build(&mut window)?;\n        Ok(window)\n    }\n}\n\npub mod system_tray_ui {\n    use super::*;\n    use native_windows_gui::{self as nwg, MousePressEvent};\n    use std::cell::RefCell;\n    use std::ops::Deref;\n    use std::rc::Rc;\n    use windows_sys::Win32::UI::Shell::SIID_DELETE;\n\n    pub struct SystemTrayUi {\n        inner: Rc<SystemTray>,\n        handler_def: RefCell<Vec<nwg::EventHandler>>,\n    }\n\n    impl nwg::NativeUi<SystemTrayUi> for SystemTray {\n        fn build_ui(mut d: SystemTray) -> Result<SystemTrayUi, nwg::NwgError> {\n            use nwg::Event as E;\n\n            let app_data = d.app_data.borrow().clone();\n            d.tray_item_dyn = RefCell::new(Default::default());\n            d.handlers_dyn = RefCell::new(Default::default());\n            d.decoder = Default::default();\n            nwg::ImageDecoder::builder().build(&mut d.decoder)?;\n            // Resources\n            d.embed = Default::default();\n            d.embed = nwg::EmbedResource::load(None)?;\n            nwg::Icon::builder()\n                .source_embed(Some(&d.embed))\n                .source_embed_str(Some(\"iconMain\"))\n                .strict(true) /*use sys, not panic, if missing*/\n                .build(&mut d.icon)?;\n\n            let (sndr, rcvr) = std::sync::mpsc::channel();\n            d.tt2m_channel = Some((sndr, rcvr));\n            let (sndr, rcvr) = std::sync::mpsc::channel();\n            d.err_recv = Some(rcvr);\n            if GUI_ERR_MSG_TX.set(sndr).is_err() {\n                warn!(\"Someone else set our ‘GUI_ERR_MSG_TX’\");\n            };\n\n            // Controls\n            nwg::MessageWindow::builder().build(&mut d.window)?;\n            nwg::Notice::builder()\n                .parent(&d.window)\n                .build(&mut d.layer_notice)?;\n            nwg::Notice::builder()\n                .parent(&d.window)\n                .build(&mut d.cfg_notice)?;\n            nwg::Menu::builder()\n                .parent(&d.window)\n                .popup(true) /*context menu*/\t//\n                .build(&mut d.tray_menu)?;\n            nwg::Notice::builder()\n                .parent(&d.window)\n                .build(&mut d.tt_notice)?;\n            nwg::Notice::builder()\n                .parent(&d.window)\n                .build(&mut d.err_notice)?;\n            nwg::Notice::builder()\n                .parent(&d.window)\n                .build(&mut d.exit_notice)?;\n            nwg::Menu::builder()\n                .parent(&d.tray_menu)\n                .text(\"&F Load config\") //\n                .build(&mut d.tray_1cfg_m)?;\n            nwg::MenuItem::builder()\n                .parent(&d.tray_menu)\n                .text(\"&R Reload config\") //\n                .build(&mut d.tray_2reload)?;\n            nwg::MenuItem::builder()\n                .parent(&d.tray_menu)\n                .text(\"&X Exit\\t‹⎈␠⎋\") //\n                .build(&mut d.tray_3exit)?;\n\n            if app_data.gui_opts.tooltip_layer_changes {\n                d.win_tt = d.build_win_tt().expect(\"Tooltip window\");\n                nwg::AnimationTimer::builder()\n                    .parent(&d.window)\n                    .interval(Duration::from_millis(\n                        (app_data.gui_opts.tooltip_duration.saturating_add(1)).into(),\n                    ))\n                    .lifetime(Some(Duration::from_millis(\n                        (app_data.gui_opts.tooltip_duration + TTTIMER_L).into(),\n                    )))\n                    .max_tick(None)\n                    .active(false)\n                    .build(&mut d.win_tt_timer)?;\n\n                let icn_sz_tt_i = (\n                    app_data.gui_opts.tooltip_size.0 as i32,\n                    app_data.gui_opts.tooltip_size.1 as i32,\n                );\n                // todo: replace with a no-margin NWG config when it's available\n                let padx = (app_data.gui_opts.tooltip_size.0 as f64 / 6_f64).round() as i32;\n                let pady = (app_data.gui_opts.tooltip_size.1 as f64 / 6_f64).round() as i32;\n                let pad = (-padx, -pady);\n                trace!(\"kanata tooltip size = {icn_sz_tt_i:?}, offset = {padx}⋅{pady}\");\n                let mut cfg_icon_bmp_tray = Default::default();\n                nwg::Bitmap::builder()\n                    .source_embed(Some(&d.embed))\n                    .source_embed_str(Some(\"imgMain\"))\n                    .strict(true)\n                    .size(Some(ICN_SZ_MENU.into()))\n                    .build(&mut cfg_icon_bmp_tray)?;\n                nwg::ImageFrame::builder()\n                    .parent(&d.win_tt)\n                    .size(icn_sz_tt_i)\n                    .position(pad)\n                    .build(&mut d.win_tt_ifr)?;\n            }\n\n            let mut tmp_bitmap = Default::default();\n            nwg::Bitmap::builder()\n                .source_embed(Some(&d.embed))\n                .source_embed_str(Some(\"imgReload\"))\n                .strict(true)\n                .size(Some(ICN_SZ_MENU.into()))\n                .build(&mut tmp_bitmap)?;\n            let img_exit = nwg::Bitmap::from_system_icon(SIID_DELETE);\n            d.tray_2reload.set_bitmap(Some(&tmp_bitmap));\n            d.tray_3exit.set_bitmap(Some(&img_exit));\n            d.img_reload = tmp_bitmap;\n            d.img_exit = img_exit;\n\n            let mut main_tray_icon_l = Default::default();\n            let mut main_tray_icon_is = false;\n            {\n                let mut tray_item_dyn = d.tray_item_dyn.borrow_mut();\n                let mut img_dyn = d.img_dyn.borrow_mut();\n                let mut icon_act_key = d.icon_act_key.borrow_mut();\n                let mut icon_0_key = d.icon_0_key.borrow_mut();\n                const MENU_ACC: &str = \"1234567890ASDFGQWERTZXCVBYUIOPHJKLNM\";\n                const M_E: usize = MENU_ACC.len() - 1;\n                let layer0_icon_s = &app_data.layer0_icon.clone().unwrap_or(\"\".to_string());\n                let cfg_icon_s = &app_data.cfg_icon.clone().unwrap_or(\"\".to_string());\n                if !(app_data.cfg_p).is_empty() {\n                    for (i, cfg_p) in app_data.cfg_p.iter().enumerate() {\n                        let i_acc = match i {\n                            // accelerators from 1–0, A–Z starting from home row for easier presses\n                            0..=M_E => format!(\"&{} \", &MENU_ACC[i..i + 1]),\n                            _ => \"  \".to_string(),\n                        };\n                        let cfg_name = &cfg_p\n                            .file_name()\n                            .unwrap_or_else(|| OsStr::new(\"\"))\n                            .to_string_lossy()\n                            .to_string(); //kanata.kbd\n                        let menu_text = format!(\"{cfg_name}\\t{i_acc}\"); // kanata.kbd &1\n                        let mut menu_item = Default::default();\n                        if i == 0 {\n                            nwg::MenuItem::builder()\n                                .parent(&d.tray_1cfg_m)\n                                .text(&menu_text)\n                                .check(true)\n                                .build(&mut menu_item)?;\n                        } else {\n                            nwg::MenuItem::builder()\n                                .parent(&d.tray_1cfg_m)\n                                .text(&menu_text)\n                                .build(&mut menu_item)?;\n                        }\n                        if i == 0 {\n                            // add icons if exists, hashed by config path\n                            // (for active config, others will create on load)\n                            if let Some(ico_p) = get_icon_p(\n                                layer0_icon_s,\n                                &app_data.layer0_name,\n                                cfg_icon_s,\n                                cfg_p,\n                                &app_data.gui_opts.icon_match_layer_name,\n                            ) {\n                                let mut cfg_layer_pkey = PathBuf::new(); // path key\n                                cfg_layer_pkey.push(cfg_p.clone());\n                                cfg_layer_pkey.push(PRE_LAYER.to_owned() + &app_data.layer0_name);\n                                let cfg_layer_pkey_s = cfg_layer_pkey.display().to_string();\n                                *icon_0_key = Some(cfg_layer_pkey.clone());\n                                if let Ok(icn) = d.get_icon_from_file(&ico_p) {\n                                    debug!(\"✓ main 0 config: using icon for {}\", cfg_layer_pkey_s);\n                                    main_tray_icon_l = icn.tray.copy_as_icon();\n                                    main_tray_icon_is = true;\n                                    let _ = img_dyn.insert(cfg_layer_pkey, Some(icn));\n                                } else {\n                                    info!(\n                                        \"✗ main 0 icon ✓ icon path, will be using DEFAULT icon for {:?}\",\n                                        cfg_p\n                                    );\n                                    let _ = img_dyn.insert(cfg_layer_pkey, None);\n                                }\n                            } else {\n                                debug!(\"✗ main 0 config: using DEFAULT icon for {:?}\", cfg_p);\n                                let mut cfg_icon_bmp_tray = Default::default();\n                                let mut cfg_icon_bmp_tt = Default::default();\n                                let mut cfg_icon_bmp_icon = Default::default();\n                                nwg::Bitmap::builder()\n                                    .source_embed(Some(&d.embed))\n                                    .source_embed_str(Some(\"imgMain\"))\n                                    .strict(true)\n                                    .size(Some(ICN_SZ_MENU.into()))\n                                    .build(&mut cfg_icon_bmp_tray)?;\n                                nwg::Bitmap::builder()\n                                    .source_embed(Some(&d.embed))\n                                    .source_embed_str(Some(\"imgMain\"))\n                                    .strict(true)\n                                    .size(Some(ICN_SZ_TT.into()))\n                                    .build(&mut cfg_icon_bmp_tt)?;\n                                nwg::Icon::builder()\n                                    .source_embed(Some(&d.embed))\n                                    .source_embed_str(Some(\"iconMain\"))\n                                    .strict(true)\n                                    .build(&mut cfg_icon_bmp_icon)?;\n                                let _ = img_dyn.insert(\n                                    cfg_p.clone(),\n                                    Some(Icn {\n                                        tray: cfg_icon_bmp_tray,\n                                        tooltip: cfg_icon_bmp_tt,\n                                        icon: cfg_icon_bmp_icon,\n                                    }),\n                                );\n                                *icon_act_key = Some(cfg_p.clone());\n                            }\n                            // Set tray menu config item icons, ignores layers since these\n                            // are per config\n                            if let Ok(icn) =\n                                d.set_menu_item_cfg_icon(&mut menu_item, cfg_icon_s, cfg_p)\n                            {\n                                // show currently active config's icon in the combo menu\n                                d.tray_1cfg_m.set_bitmap(Some(&icn.tray));\n                                let _ = img_dyn.insert(cfg_p.clone(), Some(icn));\n                            } else {\n                                let _ = img_dyn.insert(cfg_p.clone(), None);\n                            }\n                        }\n                        tray_item_dyn.push(menu_item);\n                    }\n                } else {\n                    warn!(\"Didn't get any config paths from Kanata!\")\n                }\n            }\n            let main_tray_icon = match main_tray_icon_is {\n                true => Some(&main_tray_icon_l),\n                false => Some(&d.icon),\n            };\n            nwg::TrayNotification::builder()\n                .parent(&d.window)\n                .icon(main_tray_icon)\n                .tip(Some(&app_data.tooltip))\n                .build(&mut d.tray)?;\n\n            let ui = SystemTrayUi {\n                // Wrap-up\n                inner: Rc::new(d),\n                handler_def: Default::default(),\n            };\n\n            let evt_ui = Rc::downgrade(&ui.inner); // Events\n            let handle_events = move |evt, _evt_data, handle| {\n                if let Some(evt_ui) = evt_ui.upgrade() {\n                    match evt {\n                        E::OnNotice =>\n                            if handle == evt_ui.layer_notice {\n                                SystemTray::reload_layer_icon(&evt_ui);\n                            } else if handle == evt_ui.cfg_notice {\n                                SystemTray::reload_cfg_icon(&evt_ui);\n                            } else if handle == evt_ui.err_notice {\n                                SystemTray::notify_error(&evt_ui);\n                            } else if handle == evt_ui.exit_notice {\n                                SystemTray::exit(&evt_ui);\n                            } else if handle == evt_ui.tt_notice {\n                                SystemTray::update_tooltip_pos(&evt_ui);}\n                        E::OnWindowClose =>\n                            if handle == evt_ui.window {SystemTray::exit  (&evt_ui);}\n                        E::OnMousePress(MousePressEvent::MousePressLeftUp) =>\n                            if handle == evt_ui.tray {SystemTray::show_menu(&evt_ui);}\n                        E::OnContextMenu/*🖰›*/ =>\n                            if handle == evt_ui.tray {SystemTray::show_menu(&evt_ui);}\n                        E::OnTimerStop/*🕐*/ => {SystemTray::hide_tooltip(&evt_ui);}\n                        E::OnMenuHover =>\n                            if        handle == evt_ui.tray_1cfg_m {\n                                SystemTray::check_active(&evt_ui);}\n                        E::OnMenuItemSelected =>\n                            if        handle == evt_ui.tray_2reload   {\n                            let _ = SystemTray::reload_cfg(&evt_ui,None);\n                            SystemTray::update_tray_icon_cfg_group(&evt_ui,true);\n                        } else if handle == evt_ui.tray_3exit     {SystemTray::exit  (&evt_ui);\n                        } else if let\n                            ControlHandle::MenuItem(_parent, _id) = handle {\n                              {let tray_item_dyn    = &evt_ui.tray_item_dyn.borrow(); //\n                              for (i, h_cfg) in tray_item_dyn.iter().enumerate() {\n                                if &handle == h_cfg {\n                                    for h_cfg_j in tray_item_dyn.iter() {\n                                      if h_cfg_j.checked() {h_cfg_j.set_checked(false);} }\n                                      // uncheck others\n                                    h_cfg.set_checked(true); // check self\n                                  let _ = SystemTray::reload_cfg(&evt_ui,Some(i)); // depends\n                                }\n                              }\n                            }\n                          }\n                      _ => {}\n                    }\n                }\n            };\n            ui.handler_def\n                .borrow_mut()\n                .push(nwg::full_bind_event_handler(\n                    &ui.window.handle,\n                    handle_events,\n                ));\n            Ok(ui)\n        }\n    }\n\n    impl Drop for SystemTrayUi {\n        /// To make sure that everything is freed without issues, the default handler\n        /// must be unbound.\n        fn drop(&mut self) {\n            let mut handlers = self.handler_def.borrow_mut();\n            for handler in handlers.drain(0..) {\n                nwg::unbind_event_handler(&handler);\n            }\n        }\n    }\n    impl Deref for SystemTrayUi {\n        type Target = SystemTray;\n        fn deref(&self) -> &Self::Target {\n            &self.inner\n        }\n    }\n}\nuse winapi::um::winuser::{\n    SetLayeredWindowAttributes, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW,\n    WS_EX_TRANSPARENT,\n};\npub const WS_CLICK_THRU: u32 = WS_EX_LAYERED | WS_EX_TRANSPARENT;\n\nuse nwg::WindowFlags as wf;\n/// Build a tooltip-like window to notify of user events\nfn show_layered_win(win_id: HWND) {\n    use winapi::um::wingdi::RGB;\n    use winapi::um::winuser::LWA_ALPHA;\n    let cr_key: COLORREF = RGB(0, 0, 0);\n    let b_alpha: BYTE = 255;\n    let dw_flags: DWORD = LWA_ALPHA;\n    unsafe {\n        SetLayeredWindowAttributes(win_id, cr_key, b_alpha, dw_flags);\n    } // layered window doesn't appear w/o this call\n}\n\npub fn update_app_data(k: &MutexGuard<Kanata>) -> Result<SystemTrayData> {\n    let paths = &k.cfg_paths;\n    let path_cur = &paths[0];\n    let layer0_id = k.layout.b().current_layer();\n    let layer0_name = &k.layer_info[layer0_id].name;\n    let layer0_icon = &k.layer_info[layer0_id].icon;\n    Ok(SystemTrayData {\n        tooltip: path_cur.display().to_string(),\n        cfg_p: paths.clone(),\n        cfg_icon: k.gui_opts.tray_icon.clone(),\n        layer0_name: layer0_name.clone(),\n        layer0_icon: layer0_icon.clone(),\n        gui_opts: k.gui_opts.clone(),\n        tt_duration_pre: k.gui_opts.tooltip_duration,\n        tt_size_pre: k.gui_opts.tooltip_size,\n    })\n}\npub fn build_tray(cfg: &Arc<Mutex<Kanata>>) -> Result<system_tray_ui::SystemTrayUi> {\n    let k = cfg.lock();\n    let app_data = update_app_data(&k)?;\n    let app = SystemTray {\n        app_data: RefCell::new(app_data),\n        ..Default::default()\n    };\n    Ok(SystemTray::build_ui(app)?)\n}\n\npub use log::*;\npub use std::io::{IsTerminal, stdout};\npub use winapi::shared::minwindef::BOOL;\npub use winapi::um::wincon::{ATTACH_PARENT_PROCESS, AttachConsole, FreeConsole};\n\nuse once_cell::sync::Lazy;\npub static IS_TERM: Lazy<bool> = Lazy::new(|| stdout().is_terminal());\npub static IS_CONSOLE: Lazy<bool> =\n    Lazy::new(|| unsafe { AttachConsole(ATTACH_PARENT_PROCESS) != 0i32 });\n"
  },
  {
    "path": "src/gui/win_dbg_logger/mod.rs",
    "content": "#![allow(non_upper_case_globals)]\n//! A logger for use with Windows debuggers.\n//!\n//! This crate integrates with the ubiquitous [`log`] crate and can be used with the [`simplelog`] crate.\n//!\n//! Windows allows applications to output a string directly to debuggers. This is very useful in\n//! situations where other forms of logging are not available.\n//! For example, stderr is not available for GUI apps.\n//!\n//! Windows provides the `OutputDebugString` entry point, which allows apps to print a debug string.\n//! Internally, `OutputDebugString` is implemented by raising an SEH exception, which the debugger\n//! catches and handles.\n//!\n//! Raising an exception has a significant cost, when run under a debugger, because the debugger\n//! halts all threads in the target process. So you should avoid using this logger for high rates\n//! of output, because doing so will slow down your app.\n//!\n//! Like many Windows entry points, `OutputDebugString` is actually two entry points:\n//! `OutputDebugStringA` (multi-byte encodings) and\n//! `OutputDebugStringW` (UTF-16). In most cases, the `*A` version is implemented using a \"thunk\"\n//! which converts its arguments to UTF-16 and then calls the `*W` version. However,\n//! `OutputDebugStringA` is one of the few entry points where the opposite is true.\n//!\n//! This crate can be compiled and used on non-Windows platforms, but it does nothing.\n//! This is intended to minimize the impact on code that takes a dependency on this crate.\n//!\n//! # Example\n//!\n//! ```rust\n//! use log::{debug, info};\n//!\n//! fn do_cool_stuff() {\n//!    info!(\"Hello, world!\");\n//!    debug!(\"Hello, world, in detail!\");\n//! }\n//!\n//! fn main() {\n//!     log::set_logger(&kanata_state_machine::gui::WINDBG_LOGGER).unwrap();\n//!     log::set_max_level(log::LevelFilter::Debug);\n//!\n//!     do_cool_stuff();\n//! }\n//! ```\n\nuse log::{Level, LevelFilter, Metadata, Record};\n\n/// This implements `log::Log`, and so can be used as a logging provider.\n/// It forwards log messages to the Windows `OutputDebugString` API.\n#[derive(Copy, Clone)]\npub struct WinDbgLogger {\n    level: LevelFilter,\n    /// Allow for `WinDbgLogger` to possibly have more fields in the future\n    _priv: (),\n}\n\n/// This is a static instance of `WinDbgLogger`. Since `WinDbgLogger` contains no state,\n/// this can be directly registered using `log::set_logger`, e.g.:\n///\n/// ```\n/// log::set_logger(&kanata_state_machine::gui::WINDBG_LOGGER).unwrap(); // Initialize\n/// log::set_max_level(log::LevelFilter::Debug);\n///\n/// use log::{info, debug}; // Import\n///\n/// info!(\"Hello, world!\"); debug!(\"Hello, world, in detail!\"); // Use to log\n/// ```\npub static WINDBG_LOGGER: WinDbgLogger = WinDbgLogger {\n    level: LevelFilter::Trace,\n    _priv: (),\n};\npub static WINDBG_L1: WinDbgLogger = WinDbgLogger {\n    level: LevelFilter::Error,\n    _priv: (),\n};\npub static WINDBG_L2: WinDbgLogger = WinDbgLogger {\n    level: LevelFilter::Warn,\n    _priv: (),\n};\npub static WINDBG_L3: WinDbgLogger = WinDbgLogger {\n    level: LevelFilter::Info,\n    _priv: (),\n};\npub static WINDBG_L4: WinDbgLogger = WinDbgLogger {\n    level: LevelFilter::Debug,\n    _priv: (),\n};\npub static WINDBG_L5: WinDbgLogger = WinDbgLogger {\n    level: LevelFilter::Trace,\n    _priv: (),\n};\npub static WINDBG_L0: WinDbgLogger = WinDbgLogger {\n    level: LevelFilter::Off,\n    _priv: (),\n};\n\n#[cfg(all(target_os = \"windows\", feature = \"gui\"))]\npub fn windbg_simple_combo(\n    log_lvl: LevelFilter,\n    noti_lvl: LevelFilter,\n) -> Box<dyn simplelog::SharedLogger> {\n    set_noti_lvl(noti_lvl);\n    match log_lvl {\n        LevelFilter::Error => Box::new(WINDBG_L1),\n        LevelFilter::Warn => Box::new(WINDBG_L2),\n        LevelFilter::Info => Box::new(WINDBG_L3),\n        LevelFilter::Debug => Box::new(WINDBG_L4),\n        LevelFilter::Trace => Box::new(WINDBG_L5),\n        LevelFilter::Off => Box::new(WINDBG_L0),\n    }\n}\n#[cfg(all(target_os = \"windows\", feature = \"gui\"))]\nimpl simplelog::SharedLogger for WinDbgLogger {\n    // allows using with simplelog's CombinedLogger\n    fn level(&self) -> LevelFilter {\n        self.level\n    }\n    fn config(&self) -> Option<&simplelog::Config> {\n        None\n    }\n    fn as_log(self: Box<Self>) -> Box<dyn log::Log> {\n        Box::new(*self)\n    }\n}\n\n/// Convert logging levels to shorter and more visible icons\npub fn iconify(lvl: log::Level) -> char {\n    match lvl {\n        Level::Error => '❗',\n        Level::Warn => '⚠',\n        Level::Info => 'ⓘ',\n        Level::Debug => 'ⓓ',\n        Level::Trace => 'ⓣ',\n    }\n}\n\nuse std::sync::OnceLock;\npub fn is_thread_state() -> &'static bool {\n    set_thread_state(false)\n}\npub fn set_thread_state(is: bool) -> &'static bool {\n    // accessor function to avoid get_or_init on every call\n    // (lazycell allows doing that without an extra function)\n    static CELL: OnceLock<bool> = OnceLock::new();\n    CELL.get_or_init(|| is)\n}\npub fn get_noti_lvl() -> &'static LevelFilter {\n    set_noti_lvl(LevelFilter::Off)\n}\npub fn set_noti_lvl(lvl: LevelFilter) -> &'static LevelFilter {\n    static CELL: OnceLock<LevelFilter> = OnceLock::new();\n    CELL.get_or_init(|| lvl)\n}\n\nuse regex::Regex;\nmacro_rules! regex {\n    ($re:literal $(,)?) => {{\n        static RE: OnceLock<regex::Regex> = OnceLock::new();\n        RE.get_or_init(|| regex::Regex::new($re).unwrap())\n    }};\n}\nfn clean_name(path: Option<&str>) -> String {\n    let re_ext: &Regex = regex!(r\"\\..*$\"); // shorten source file name, no src/ no .rs ext\n    let re_src: &Regex = regex!(r\"src[\\\\/]\");\n    // remove extension and src paths\n    if let Some(p) = path {\n        re_src.replace(&re_ext.replace(p, \"\"), \"\").to_string()\n    } else {\n        \"?\".to_string()\n    }\n}\n\n#[cfg(target_os = \"windows\")]\nuse winapi::um::processthreadsapi::GetCurrentThreadId;\nimpl log::Log for WinDbgLogger {\n    fn enabled(&self, metadata: &Metadata) -> bool {\n        metadata.level() <= self.level\n    }\n\n    fn log(&self, record: &Record) {\n        #[cfg(not(target_os = \"windows\"))]\n        let thread_id = \"\";\n        #[cfg(target_os = \"windows\")]\n        let thread_id = if *is_thread_state() {\n            format!(\"{}¦\", unsafe { GetCurrentThreadId() })\n        } else {\n            \"\".to_string()\n        };\n        if self.enabled(record.metadata()) {\n            let s = format!(\n                \"{}{}{}:{} {}\",\n                thread_id,\n                iconify(record.level()),\n                clean_name(record.file()),\n                record.line().unwrap_or(0),\n                record.args()\n            );\n            #[cfg(all(target_os = \"windows\", feature = \"gui\"))]\n            {\n                use crate::gui::win::*;\n                let title = format!(\n                    \"{}{}:{}\",\n                    thread_id,\n                    clean_name(record.file()),\n                    record.line().unwrap_or(0)\n                );\n                let msg = format!(\"{}\", record.args());\n                if record.level() <= *get_noti_lvl() {\n                    show_err_msg_nofail(title, msg);\n                }\n            }\n            output_debug_string(&s);\n        }\n    }\n\n    fn flush(&self) {}\n}\n\n/// Calls the `OutputDebugString` API to log a string.\n///\n/// On non-Windows platforms, this function does nothing.\n///\n/// See [`OutputDebugStringW`](https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-outputdebugstringw).\npub fn output_debug_string(s: &str) {\n    #[cfg(windows)]\n    {\n        let len = s.encode_utf16().count() + 1;\n        let mut s_utf16: Vec<u16> = Vec::with_capacity(len);\n        s_utf16.extend(s.encode_utf16());\n        s_utf16.push(0);\n        unsafe {\n            OutputDebugStringW(&s_utf16[0]);\n        }\n    }\n    #[cfg(not(windows))]\n    {\n        let _ = s;\n    }\n}\n\n#[cfg(windows)]\nunsafe extern \"system\" {\n    fn OutputDebugStringW(chars: *const u16);\n    fn IsDebuggerPresent() -> i32;\n}\n\n/// Checks whether a debugger is attached to the current process.\n///\n/// On non-Windows platforms, this function always returns `false`.\n///\n/// See [`IsDebuggerPresent`](https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-isdebuggerpresent).\npub fn is_debugger_present() -> bool {\n    #[cfg(windows)]\n    {\n        unsafe { IsDebuggerPresent() != 0 }\n    }\n    #[cfg(not(windows))]\n    {\n        false\n    }\n}\n\n/// Sets the `WinDbgLogger` as the currently-active logger.\n///\n/// If an error occurs when registering `WinDbgLogger` as the current logger, this function will\n/// output a warning and will return normally. It will not panic.\n/// This behavior was chosen because `WinDbgLogger` is intended for use in debugging.\n/// Panicking would disrupt debugging and introduce new failure modes. It would also create\n/// problems for mixed-mode debugging, where Rust code is linked with C/C++ code.\npub fn init() {\n    match log::set_logger(&WINDBG_LOGGER) {\n        Ok(()) => {} //↓ there's really nothing we can do about it.\n        Err(_) => {\n            output_debug_string(\n                \"Warning: Failed to register WinDbgLogger as the current Rust logger.\\r\\n\",\n            );\n        }\n    }\n}\n\nmacro_rules! define_init_at_level {\n    ($func:ident, $level:ident) => {\n        /// This can be called from C/C++ code to register the debug logger.\n        ///\n        /// For Windows DLLs that have statically linked an instance of `win_dbg_logger` into\n        /// them, `DllMain` should call `win_dbg_logger_init_<level>()` from the `DLL_PROCESS_ATTACH`\n        /// handler, e.g.:\n        ///\n        /// ```ignore\n        /// extern \"C\" void __cdecl rust_win_dbg_logger_init_debug(); // Calls into Rust code\n        /// BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD reason, LPVOID reserved) {\n        ///   switch (reason) {\n        ///     case DLL_PROCESS_ATTACH:\n        ///       rust_win_dbg_logger_init_debug();\n        ///       // ...\n        ///   }\n        ///   // ...\n        /// }\n        /// ```\n        ///\n        /// For Windows executables that have statically linked an instance of `win_dbg_logger`\n        /// into them, call `win_dbg_logger_init_<level>()` during app startup.\n        #[unsafe(no_mangle)]\n        pub extern \"C\" fn $func() {\n            init();\n            log::set_max_level(LevelFilter::$level);\n        }\n    };\n}\n\ndefine_init_at_level!(rust_win_dbg_logger_init_trace, Trace);\ndefine_init_at_level!(rust_win_dbg_logger_init_info, Info);\ndefine_init_at_level!(rust_win_dbg_logger_init_debug, Debug);\ndefine_init_at_level!(rust_win_dbg_logger_init_warn, Warn);\ndefine_init_at_level!(rust_win_dbg_logger_init_error, Error);\n"
  },
  {
    "path": "src/gui/win_dbg_logger/win_dbg_logger.toml",
    "content": "[package]\nname = \"win_dbg_logger\"\nversion = \"0.1.0\"\nauthors = [\"Arlie Davis <ardavis@microsoft.com>\"]\nedition = \"2018\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/sivadeilra/win_dbg_logger\"\ndescription = \"A logger for use with Windows debuggers.\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nlog      \t= \"0.4.*\"\nwinapi   \t= {version=\"0.3.9\", features=[\"processthreadsapi\",]}\nregex    \t= {version=\"1.10.4\"}\nsimplelog\t= {version=\"0.12.0\", optional=true}\n\n[features]\nsimple_shared\t= [\"simplelog\"]\n"
  },
  {
    "path": "src/gui/win_nwg_ext/license-MIT",
    "content": "The MIT License (MIT)\n=====================\n\nCopyright © `2024` `Niccolò Betto`\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the “Software”), to deal in the Software without\nrestriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\n"
  },
  {
    "path": "src/gui/win_nwg_ext/license-nwg-MIT",
    "content": "MIT License\n\nCopyright (c) 2019 Gabriel Dube\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "src/gui/win_nwg_ext/mod.rs",
    "content": "// based on https://github.com/lynxnb/wsl-usb-manager/blob/master/src/gui/nwg_ext.rs\nuse native_windows_gui as nwg;\nuse native_windows_gui::ControlHandle;\nuse std::{mem::size_of, ptr};\nuse winapi::ctypes::c_int;\nuse winapi::shared::windef::HWND;\n\nuse windows_sys::Win32::Foundation::HANDLE;\nuse windows_sys::Win32::Graphics::Gdi::DeleteObject;\nuse windows_sys::Win32::UI::Shell::{\n    SHGSI_ICON, SHGSI_SMALLICON, SHGetStockIconInfo, SHSTOCKICONID, SHSTOCKICONINFO,\n};\nuse windows_sys::Win32::UI::WindowsAndMessaging::{\n    CopyImage, DestroyIcon, GetIconInfoExW, HMENU, ICONINFOEXW, IMAGE_BITMAP, LR_CREATEDIBSECTION,\n    MENUITEMINFOW, MF_BYCOMMAND, MIIM_BITMAP, SetMenuItemInfoW,\n};\n\n/// Extends [`nwg::Bitmap`] with additional functionality.\npub trait BitmapEx {\n    fn from_system_icon(icon: SHSTOCKICONID) -> nwg::Bitmap;\n}\n\nimpl BitmapEx for nwg::Bitmap {\n    /// Creates a bitmap from a [`SHSTOCKICONID`] system icon ID.\n    fn from_system_icon(icon: SHSTOCKICONID) -> nwg::Bitmap {\n        // Retrieve the icon\n        let mut stock_icon_info = SHSTOCKICONINFO {\n            cbSize: std::mem::size_of::<SHSTOCKICONINFO>() as u32,\n            hIcon: 0,\n            iSysImageIndex: 0,\n            iIcon: 0,\n            szPath: [0; 260],\n        };\n        unsafe {\n            SHGetStockIconInfo(\n                icon,\n                SHGSI_ICON | SHGSI_SMALLICON,\n                &mut stock_icon_info as *mut _,\n            );\n        }\n\n        // Retrieve the bitmap for the icon\n        let mut icon_info = ICONINFOEXW {\n            cbSize: std::mem::size_of::<ICONINFOEXW>() as u32,\n            fIcon: 0,\n            xHotspot: 0,\n            yHotspot: 0,\n            hbmMask: 0,\n            hbmColor: 0,\n            wResID: 0,\n            szModName: [0; 260],\n            szResName: [0; 260],\n        };\n        unsafe {\n            GetIconInfoExW(stock_icon_info.hIcon, &mut icon_info as *mut _);\n        }\n\n        // Create a copy of the bitmap with transparent background from the icon bitmap\n        let hbitmap = unsafe {\n            CopyImage(\n                icon_info.hbmColor as HANDLE,\n                IMAGE_BITMAP,\n                0,\n                0,\n                LR_CREATEDIBSECTION,\n            )\n        };\n\n        // Delete the unused icon and bitmaps\n        unsafe {\n            DeleteObject(icon_info.hbmMask);\n            DeleteObject(icon_info.hbmColor);\n            DestroyIcon(stock_icon_info.hIcon);\n        };\n\n        if hbitmap == 0 {\n            panic!(\"Failed to create bitmap from system icon\");\n        } else {\n            #[allow(unused)]\n            struct Bitmap {\n                handle: HANDLE,\n                owned: bool,\n            }\n\n            let bitmap = Bitmap {\n                handle: hbitmap as HANDLE,\n                owned: true,\n            };\n\n            // Ugly hack to set the private `owned` field inside nwg::Bitmap to true\n            #[allow(clippy::missing_transmute_annotations)]\n            unsafe {\n                std::mem::transmute(bitmap)\n            }\n        }\n    }\n}\n\n/// Extends [`nwg::Menu`] with additional functionality.\npub trait MenuEx {\n    fn set_bitmap(&self, bitmap: Option<&nwg::Bitmap>);\n}\nimpl MenuEx for nwg::Menu {\n    /// Sets a bitmap to be displayed on a menu. Pass `None` to remove the bitmap\n    fn set_bitmap(&self, bitmap: Option<&nwg::Bitmap>) {\n        let (hmenu_par, hmenu) = self.handle.hmenu().unwrap();\n        let hbitmap = match bitmap {\n            Some(b) => b.handle as HANDLE,\n            None => 0,\n        };\n\n        let menu_item_info = MENUITEMINFOW {\n            cbSize: size_of::<MENUITEMINFOW>() as u32,\n            fMask: MIIM_BITMAP,\n            hbmpItem: hbitmap,\n            fType: 0,\n            fState: 0,\n            hSubMenu: 0,\n            hbmpChecked: 0,\n            hbmpUnchecked: 0,\n            dwTypeData: ptr::null_mut(),\n            wID: 0,\n            dwItemData: 0,\n            cch: 0,\n        };\n        unsafe {\n            SetMenuItemInfoW(\n                hmenu_par as HMENU,\n                hmenu as u32,\n                MF_BYCOMMAND as i32,\n                &menu_item_info as *const _,\n            );\n        }\n    }\n}\n\n/// Extends [`nwg::MenuItem`] with additional functionality.\npub trait MenuItemEx {\n    fn set_bitmap(&self, bitmap: Option<&nwg::Bitmap>);\n}\n\nimpl MenuItemEx for nwg::MenuItem {\n    /// Sets a bitmap to be displayed on a menu item. Pass `None` to remove the bitmap.\n    fn set_bitmap(&self, bitmap: Option<&nwg::Bitmap>) {\n        let (hmenu, item_id) = self.handle.hmenu_item().unwrap();\n        let hbitmap = match bitmap {\n            Some(b) => b.handle as HANDLE,\n            None => 0,\n        };\n\n        let menu_item_info = MENUITEMINFOW {\n            cbSize: std::mem::size_of::<MENUITEMINFOW>() as u32,\n            fMask: MIIM_BITMAP,\n            fType: 0,\n            fState: 0,\n            wID: 0,\n            hSubMenu: 0,\n            hbmpChecked: 0,\n            hbmpUnchecked: 0,\n            dwItemData: 0,\n            dwTypeData: std::ptr::null_mut(),\n            cch: 0,\n            hbmpItem: hbitmap,\n        };\n\n        unsafe {\n            SetMenuItemInfoW(\n                hmenu as HMENU,\n                item_id,\n                MF_BYCOMMAND as i32,\n                &menu_item_info as *const _,\n            );\n        }\n    }\n}\n\npub trait WindowEx {\n    fn set_position_ex(&self, x: i32, y: i32);\n}\npub fn dpi() -> i32 {\n    // prevents GDI DC resource leak\n    use winapi::um::wingdi::GetDeviceCaps;\n    use winapi::um::wingdi::LOGPIXELSX;\n    use winapi::um::winuser::{GetDC, ReleaseDC};\n    let screen = unsafe { GetDC(std::ptr::null_mut()) };\n    let dpi = unsafe { GetDeviceCaps(screen, LOGPIXELSX) };\n    let _ = unsafe { ReleaseDC(std::ptr::null_mut(), screen) };\n    dpi\n}\npub fn logical_to_physical(x: i32, y: i32) -> (i32, i32) {\n    use muldiv::MulDiv;\n    use winapi::um::winuser::USER_DEFAULT_SCREEN_DPI;\n    let dpi = dpi();\n    let x = x.mul_div_round(dpi, USER_DEFAULT_SCREEN_DPI).unwrap_or(x);\n    let y = y.mul_div_round(dpi, USER_DEFAULT_SCREEN_DPI).unwrap_or(y);\n    (x, y)\n}\n/// # Safety\n/// The `handle` param must be a valid pointer to a window handle returned by some winapi call.\n/// Failure to do so probably won't be UB because the handle is passed to a WinAPI call\n/// which is expected to handle these cases safely, but seems worth noting anyway.\npub unsafe fn set_window_position(handle: HWND, x: i32, y: i32) {\n    use winapi::um::winuser::SetWindowPos;\n    use winapi::um::winuser::{SWP_NOACTIVATE, SWP_NOOWNERZORDER, SWP_NOSIZE, SWP_NOZORDER};\n    let (x, y) = logical_to_physical(x, y);\n    unsafe {\n        SetWindowPos(\n            handle,\n            ptr::null_mut(),\n            x as c_int,\n            y as c_int,\n            0,\n            0,\n            SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER,\n        );\n    }\n}\nconst NOT_BOUND: &str = \"Window is not yet bound to a winapi object\";\nconst BAD_HANDLE: &str = \"INTERNAL ERROR: Window handle is not HWND!\";\npub fn check_hwnd(handle: &ControlHandle, not_bound: &str, bad_handle: &str) -> HWND {\n    use winapi::um::winuser::IsWindow;\n    if handle.blank() {\n        panic!(\"{}\", not_bound);\n    }\n    match handle.hwnd() {\n        Some(hwnd) => match unsafe { IsWindow(hwnd) } {\n            0 => {\n                panic!(\n                    \"The window handle is no longer valid. This usually means the control was freed by the OS\"\n                );\n            }\n            _ => hwnd,\n        },\n        None => {\n            panic!(\"{}\", bad_handle);\n        }\n    }\n}\n\nimpl WindowEx for nwg::Window {\n    /// Set the position of the button in the parent window\n    fn set_position_ex(&self, x: i32, y: i32) {\n        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);\n        unsafe { set_window_position(handle, x, y) }\n    }\n}\n"
  },
  {
    "path": "src/kanata/caps_word.rs",
    "content": "use kanata_keyberon::key_code::KeyCode;\nuse rustc_hash::FxHashSet as HashSet;\n\nuse kanata_parser::custom_action::CapsWordCfg;\n\n#[derive(Debug)]\npub struct CapsWordState {\n    /// Keys that will trigger an `lsft` key to be added to the active keys if present in the\n    /// currently active keys.\n    pub keys_to_capitalize: HashSet<KeyCode>,\n    /// An extra list of keys that should **not** terminate the caps_word state, in addition to\n    /// keys_to_capitalize, but which don't trigger a capitalization.\n    pub keys_nonterminal: HashSet<KeyCode>,\n    /// The configured timeout for caps_word.\n    pub timeout: u16,\n    /// The number of ticks remaining for caps_word, after which its state should be cleared. The\n    /// number of ticks gets reset back to `timeout` when `tick_maybe_add_lsft` is called. The reason\n    /// for having this timeout at all is in case somebody was in the middle of typing a word, had\n    /// to go do something, and forgot that caps_word was active. Having this timeout means that\n    /// shift won't be active for their next keypress.\n    pub timeout_ticks: u16,\n}\n\n#[derive(PartialEq, Eq, Debug, Clone, Copy)]\npub enum CapsWordNextState {\n    Active,\n    End,\n}\n\nuse CapsWordNextState::*;\n\nimpl CapsWordState {\n    pub(crate) fn new(cfg: &CapsWordCfg) -> Self {\n        Self {\n            keys_to_capitalize: cfg.keys_to_capitalize.iter().copied().collect(),\n            keys_nonterminal: cfg.keys_nonterminal.iter().copied().collect(),\n            timeout: cfg.timeout,\n            timeout_ticks: cfg.timeout,\n        }\n    }\n\n    pub(crate) fn tick_maybe_add_lsft(\n        &mut self,\n        active_keys: &mut Vec<KeyCode>,\n    ) -> CapsWordNextState {\n        if self.timeout_ticks == 0 {\n            log::trace!(\"caps-word ended\");\n            return End;\n        }\n        for kc in active_keys.iter() {\n            if !self.keys_to_capitalize.contains(kc) && !self.keys_nonterminal.contains(kc) {\n                return End;\n            }\n        }\n        if active_keys\n            .last()\n            .map(|kc| self.keys_to_capitalize.contains(kc))\n            .unwrap_or(false)\n        {\n            active_keys.insert(0, KeyCode::LShift);\n        }\n        if !active_keys.is_empty() {\n            self.timeout_ticks = self.timeout;\n        }\n        self.timeout_ticks = self.timeout_ticks.saturating_sub(1);\n        Active\n    }\n}\n"
  },
  {
    "path": "src/kanata/cfg_forced.rs",
    "content": "//! Options in the configuration file that are overidden/forced to some value other than what's in\n//! the configuration file, with the primary example being CLI arguments.\n\nuse std::sync::OnceLock;\n\nstatic LOG_LAYER_CHANGES: OnceLock<bool> = OnceLock::new();\n\n/// Force the log_layer_changes configuration to some value.\n/// This can only be called up to once. Panics if called a second time.\npub fn force_log_layer_changes(v: bool) {\n    LOG_LAYER_CHANGES\n        .set(v)\n        .expect(\"force cfg fns can only be called once\");\n}\n\n/// Get the forced log_layer_changes configuration if it was set.\npub fn get_forced_log_layer_changes() -> Option<bool> {\n    LOG_LAYER_CHANGES.get().copied()\n}\n"
  },
  {
    "path": "src/kanata/clipboard.rs",
    "content": "use super::*;\n\n#[cfg(not(any(target_arch = \"wasm32\", target_os = \"android\")))]\npub use real::*;\n#[cfg(not(any(target_arch = \"wasm32\", target_os = \"android\")))]\nmod real {\n    use super::*;\n    use std::sync::LazyLock;\n\n    use parking_lot::Mutex;\n\n    pub type SavedClipboardData = HashMap<u16, ClipboardData>;\n    #[derive(Debug, Clone)]\n    pub enum ClipboardData {\n        Text(String),\n        Image(arboard::ImageData<'static>),\n    }\n    use ClipboardData::*;\n\n    static CLIPBOARD: LazyLock<Mutex<arboard::Clipboard>> = LazyLock::new(|| {\n        for _ in 0..10 {\n            let c = arboard::Clipboard::new();\n            if let Ok(goodclip) = c {\n                log::trace!(\"clipboard init\");\n                return Mutex::new(goodclip);\n            }\n            std::thread::sleep(std::time::Duration::from_millis(25));\n        }\n        panic!(\"could not initialize clipboard\");\n    });\n\n    pub(crate) fn clpb_set(clipboard_string: &str) {\n        for _ in 0..10 {\n            match CLIPBOARD.lock().set_text(clipboard_string) {\n                Ok(()) => {\n                    log::trace!(\"clipboard set to {clipboard_string}\");\n                    return;\n                }\n                Err(e) => {\n                    log::error!(\"error setting clipboard: {e:?}\");\n                }\n            }\n            std::thread::sleep(std::time::Duration::from_millis(25));\n        }\n    }\n\n    pub(crate) fn clpb_cmd_set(cmd_and_args: &[&str]) {\n        let mut newclip = None;\n        for _ in 0..10 {\n            match CLIPBOARD.lock().get_text() {\n                Ok(cliptext) => {\n                    newclip = Some(run_cmd_get_stdout(cmd_and_args, cliptext.as_str()));\n                    break;\n                }\n                Err(e) => {\n                    if matches!(e, arboard::Error::ContentNotAvailable) {\n                        log::warn!(\"clipboard is unset or is image data; no-op for cmd-set\");\n                        return;\n                    }\n                    log::error!(\"error setting clipboard: {e:?}\");\n                }\n            }\n            std::thread::sleep(std::time::Duration::from_millis(25));\n        }\n        if let Some(nc) = newclip {\n            clpb_set(&nc);\n        }\n    }\n\n    fn run_cmd_get_stdout(cmd_and_args: &[&str], stdin: &str) -> String {\n        use std::io::Write;\n        use std::process::{Command, Stdio};\n        let mut args = cmd_and_args.iter();\n        let executable = args\n            .next()\n            .expect(\"parsing should have forbidden empty cmd\");\n        log::trace!(\"executing {executable}\");\n        let mut cmd = Command::new(executable);\n        cmd.stdin(Stdio::piped()).stdout(Stdio::piped());\n        for arg in args {\n            log::trace!(\"arg is {arg}\");\n            cmd.arg(arg);\n        }\n        let mut child = match cmd.spawn() {\n            Ok(c) => c,\n            Err(e) => {\n                log::warn!(\"failed to spawn cmd, returning empty string for cmd-set: {e:?}\");\n                return String::new();\n            }\n        };\n\n        let child_stdin = child.stdin.as_mut().unwrap();\n        if let Err(e) = child_stdin.write_all(stdin.as_bytes()) {\n            log::warn!(\"failed to write to stdin: {e:?}\");\n        }\n        child\n            .wait_with_output()\n            .map(|out| String::from_utf8_lossy(&out.stdout).to_string())\n            .unwrap_or_else(|e| {\n                log::error!(\"failed to execute cmd: {e:?}\");\n                String::new()\n            })\n    }\n\n    pub(crate) fn clpb_save(id: u16, save_data: &mut SavedClipboardData) {\n        for _ in 0..10 {\n            match CLIPBOARD.lock().get_text() {\n                Ok(cliptext) => {\n                    log::trace!(\"saving to id {id}: {cliptext}\");\n                    save_data.insert(id, Text(cliptext));\n                    return;\n                }\n                Err(e) => {\n                    if matches!(e, arboard::Error::ContentNotAvailable) {\n                        // ContentNotAvailable could be an image or missing data\n                        break;\n                    }\n                    log::error!(\"error setting clipboard: {e:?}\");\n                }\n            }\n            std::thread::sleep(std::time::Duration::from_millis(25));\n        }\n        for _ in 0..10 {\n            match CLIPBOARD.lock().get_image() {\n                Ok(clipimg) => {\n                    log::trace!(\"saving to id {id}: <imgdata>\");\n                    save_data.insert(id, Image(clipimg));\n                }\n                Err(e) => {\n                    if matches!(e, arboard::Error::ContentNotAvailable) {\n                        break;\n                    }\n                    log::error!(\"error setting clipboard: {e:?}\");\n                }\n            }\n            std::thread::sleep(std::time::Duration::from_millis(25));\n        }\n    }\n\n    pub(crate) fn clpb_restore(id: u16, save_data: &SavedClipboardData) {\n        let Some(restore_data) = save_data.get(&id) else {\n            log::warn!(\"tried to set clipboard with missing data in id {id}, doing nothing\");\n            return;\n        };\n        for _ in 0..10 {\n            let e = match restore_data {\n                Text(s) => match CLIPBOARD.lock().set_text(s) {\n                    Ok(()) => {\n                        log::trace!(\"restored clipboard with id {id}: {s}\");\n                        return;\n                    }\n                    Err(e) => e,\n                },\n                Image(img) => match CLIPBOARD.lock().set_image(img.clone()) {\n                    Ok(()) => {\n                        log::trace!(\"restored clipboard with id {id}: <imgdata>\");\n                        return;\n                    }\n                    Err(e) => e,\n                },\n            };\n            log::error!(\"error setting clipboard: {e:?}\");\n            std::thread::sleep(std::time::Duration::from_millis(25));\n        }\n    }\n\n    pub(crate) fn clpb_save_set(id: u16, content: &str, save_data: &mut SavedClipboardData) {\n        log::trace!(\"setting save id {id} with {content}\");\n        save_data.insert(id, Text(content.into()));\n    }\n\n    #[test]\n    fn test_set() {\n        let mut sd = SavedClipboardData::default();\n        clpb_save_set(1, \"hi\", &mut sd);\n        if let Text(s) = sd.get(&1).unwrap() {\n            assert_eq!(s.as_str(), \"hi\");\n        } else {\n            panic!(\"did not expect image data\");\n        }\n        assert!(!sd.contains_key(&2));\n    }\n\n    pub(crate) fn clpb_save_cmd_set(\n        id: u16,\n        cmd_and_args: &[&str],\n        save_data: &mut SavedClipboardData,\n    ) {\n        let stdin_content = match save_data.get(&id) {\n            Some(slot_data) => match slot_data {\n                Text(s) => s.as_str(),\n                Image(_) => \"\",\n            },\n            None => \"\",\n        };\n        let content = run_cmd_get_stdout(cmd_and_args, stdin_content);\n        log::trace!(\"setting save id {id} with {content}\");\n        save_data.insert(id, Text(content));\n    }\n\n    #[test]\n    #[cfg(target_os = \"windows\")]\n    fn test_save_cmd_set() {\n        let mut sd = SavedClipboardData::default();\n        sd.insert(1, Text(\"one\".into()));\n        clpb_save_cmd_set(\n            1,\n            &[\n                \"powershell.exe\".into(),\n                \"-c\".into(),\n                \"$v = ($Input | Select-Object -First 1); Write-Host -NoNewLine \\\"$v $v\\\"\".into(),\n            ],\n            &mut sd,\n        );\n\n        if let Text(s) = sd.get(&1).unwrap() {\n            assert_eq!(\"one one\", s.as_str());\n        } else {\n            panic!(\"did not expect image data\");\n        }\n        assert!(!sd.contains_key(&2));\n\n        clpb_save_cmd_set(\n            3,\n            &[\n                \"powershell.exe\".into(),\n                \"-c\".into(),\n                \"Write-Host -NoNewLine 'wat'\".into(),\n            ],\n            &mut sd,\n        );\n        if let Text(s) = sd.get(&3).unwrap() {\n            assert_eq!(\"wat\", s.as_str());\n        } else {\n            panic!(\"did not expect image data\");\n        }\n    }\n\n    pub(crate) fn clpb_save_swap(id1: u16, id2: u16, save_data: &mut SavedClipboardData) {\n        let data1 = save_data.remove(&id1);\n        let data2 = save_data.remove(&id2);\n        if let Some(d) = data1 {\n            save_data.insert(id2, d);\n        }\n        if let Some(d) = data2 {\n            save_data.insert(id1, d);\n        }\n    }\n\n    #[test]\n    fn test_swap() {\n        let mut sd = SavedClipboardData::default();\n        sd.insert(1, Text(\"one\".into()));\n        sd.insert(2, Text(\"two\".into()));\n        clpb_save_swap(1, 2, &mut sd);\n        if let Text(s) = sd.get(&1).unwrap() {\n            assert_eq!(s.as_str(), \"two\");\n        } else {\n            panic!(\"did not expect image data\");\n        }\n        if let Text(s) = sd.get(&2).unwrap() {\n            assert_eq!(s.as_str(), \"one\");\n        } else {\n            panic!(\"did not expect image data\");\n        }\n\n        sd.insert(3, Text(\"three\".into()));\n        clpb_save_swap(3, 4, &mut sd);\n        assert!(!sd.contains_key(&3));\n        if let Text(s) = sd.get(&4).unwrap() {\n            assert_eq!(s.as_str(), \"three\");\n        } else {\n            panic!(\"did not expect image data\");\n        }\n\n        sd.insert(6, Text(\"six\".into()));\n        clpb_save_swap(5, 6, &mut sd);\n        if let Text(s) = sd.get(&5).unwrap() {\n            assert_eq!(s.as_str(), \"six\");\n        } else {\n            panic!(\"did not expect image data\");\n        }\n        assert!(!sd.contains_key(&6));\n\n        clpb_save_swap(7, 8, &mut sd);\n        assert!(!sd.contains_key(&7));\n        assert!(!sd.contains_key(&8));\n    }\n}\n\n#[cfg(any(target_arch = \"wasm32\", target_os = \"android\"))]\npub use fake::*;\n#[cfg(any(target_arch = \"wasm32\", target_os = \"android\"))]\nmod fake {\n    #![allow(unused)]\n    use super::*;\n    pub type SavedClipboardData = HashMap<u16, ClipboardData>;\n    #[derive(Debug, Clone)]\n    pub enum ClipboardData {\n        Text(String),\n        Text2(String),\n    }\n\n    pub(crate) fn clpb_set(clipboard_string: &str) {}\n\n    pub(crate) fn clpb_cmd_set(cmd_and_args: &[&str]) {}\n\n    pub(crate) fn clpb_save(id: u16, save_data: &mut SavedClipboardData) {}\n\n    pub(crate) fn clpb_restore(id: u16, save_data: &SavedClipboardData) {}\n\n    pub(crate) fn clpb_save_set(id: u16, content: &str, save_data: &mut SavedClipboardData) {}\n\n    pub(crate) fn clpb_save_cmd_set(\n        id: u16,\n        cmd_and_args: &[&str],\n        save_data: &mut SavedClipboardData,\n    ) {\n    }\n\n    pub(crate) fn clpb_save_swap(id1: u16, id2: u16, save_data: &mut SavedClipboardData) {}\n}\n"
  },
  {
    "path": "src/kanata/cmd.rs",
    "content": "#![cfg_attr(feature = \"simulated_output\", allow(dead_code, unused_imports))]\n\nuse std::fmt::Write;\n\nuse kanata_parser::cfg::parse_mod_prefix;\nuse kanata_parser::cfg::sexpr::*;\nuse kanata_parser::keys::*;\n\n// local log prefix\nconst LP: &str = \"cmd-out:\";\n\n#[cfg(not(feature = \"simulated_output\"))]\npub(super) fn run_cmd_in_thread(\n    cmd_and_args: Vec<String>,\n    log_level: Option<log::Level>,\n    error_log_level: Option<log::Level>,\n) -> std::thread::JoinHandle<()> {\n    std::thread::spawn(move || {\n        let mut args = cmd_and_args.iter();\n        let mut printable_cmd = String::new();\n        let executable = args\n            .next()\n            .expect(\"parsing should have forbidden empty cmd\");\n        write!(\n            printable_cmd,\n            \"Program: {}, Arguments:\",\n            executable.as_str()\n        )\n        .expect(\"write to string should succeed\");\n        let mut cmd = std::process::Command::new(executable);\n        for arg in args {\n            cmd.arg(arg);\n            printable_cmd.push(' ');\n            printable_cmd.push_str(arg.as_str());\n        }\n        if let Some(level) = log_level {\n            log::log!(level, \"Running cmd: {}\", printable_cmd);\n        }\n        match cmd.output() {\n            Ok(output) => {\n                if let Some(level) = log_level {\n                    log::log!(\n                        level,\n                        \"Successfully ran cmd: {}\\nstdout:\\n{}\\nstderr:\\n{}\",\n                        printable_cmd,\n                        String::from_utf8_lossy(&output.stdout),\n                        String::from_utf8_lossy(&output.stderr)\n                    );\n                };\n            }\n            Err(e) => {\n                if let Some(level) = error_log_level {\n                    log::log!(\n                        level,\n                        \"Failed to execute program {:?}: {}\",\n                        cmd.get_program(),\n                        e\n                    )\n                }\n            }\n        };\n    })\n}\n\npub(super) type Item = KeyAction;\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]\npub(super) enum KeyAction {\n    Press(OsCode),\n    Release(OsCode),\n    Delay(u16),\n}\nuse KeyAction::*;\nuse kanata_keyberon::key_code::KeyCode;\n\nfn empty() -> std::vec::IntoIter<Item> {\n    vec![].into_iter()\n}\n\nfn from_sexpr(sexpr: Vec<SExpr>) -> std::vec::IntoIter<Item> {\n    let mut items = vec![];\n    let mut remainder = sexpr.as_slice();\n    while !remainder.is_empty() {\n        remainder = parse_items(remainder, &mut items);\n    }\n    items.into_iter()\n}\n\nfn parse_items<'a>(exprs: &'a [SExpr], items: &mut Vec<Item>) -> &'a [SExpr] {\n    match &exprs[0] {\n        SExpr::Atom(osc) => match str_to_oscode(&osc.t) {\n            Some(osc) => {\n                items.push(Press(osc));\n                items.push(Release(osc));\n                &exprs[1..]\n            }\n            None => {\n                use std::str::FromStr;\n                match u16::from_str(&osc.t) {\n                    Ok(delay) => {\n                        items.push(Delay(delay));\n                        &exprs[1..]\n                    }\n                    Err(_) => try_parse_chord(&osc.t, exprs, items),\n                }\n            }\n        },\n        SExpr::List(sexprs) => {\n            let mut remainder = sexprs.t.as_slice();\n            while !remainder.is_empty() {\n                remainder = parse_items(remainder, items);\n            }\n            &exprs[1..]\n        }\n    }\n}\n\nfn try_parse_chord<'a>(chord: &str, exprs: &'a [SExpr], items: &mut Vec<Item>) -> &'a [SExpr] {\n    match parse_mod_prefix(chord) {\n        Ok((mods, osc)) => match osc.is_empty() {\n            true => try_parse_chorded_list(&mods, chord, &exprs[1..], items),\n            false => {\n                try_parse_chorded_key(&mods, osc, chord, items);\n                &exprs[1..]\n            }\n        },\n        Err(e) => {\n            log::warn!(\"{LP} found invalid chord {chord}: {}\", e.msg);\n            &exprs[1..]\n        }\n    }\n}\n\nfn try_parse_chorded_key(mods: &[KeyCode], osc: &str, chord: &str, items: &mut Vec<Item>) {\n    if mods.is_empty() {\n        log::warn!(\"{LP} found invalid key: {osc}\");\n        return;\n    }\n    match str_to_oscode(osc) {\n        Some(osc) => {\n            for mod_kc in mods.iter().copied() {\n                items.push(Press(mod_kc.into()));\n            }\n            items.push(Press(osc));\n            for mod_kc in mods.iter().copied() {\n                items.push(Release(mod_kc.into()));\n            }\n            items.push(Release(osc));\n        }\n        None => {\n            log::warn!(\"{LP} found chord {chord} with invalid key: {osc}\");\n        }\n    };\n}\n\nfn try_parse_chorded_list<'a>(\n    mods: &[KeyCode],\n    chord: &str,\n    exprs: &'a [SExpr],\n    items: &mut Vec<Item>,\n) -> &'a [SExpr] {\n    if exprs.is_empty() {\n        log::warn!(\n            \"{LP} found chord modifiers with no attached key or list - ignoring it: {chord}\"\n        );\n        return exprs;\n    }\n    match &exprs[0] {\n        SExpr::Atom(osc) => {\n            log::warn!(\"{LP} expected list after {chord}, got string {}\", &osc.t);\n            exprs\n        }\n        SExpr::List(subexprs) => {\n            for mod_kc in mods.iter().copied() {\n                items.push(Press(mod_kc.into()));\n            }\n            let mut remainder = subexprs.t.as_slice();\n            while !remainder.is_empty() {\n                remainder = parse_items(remainder, items);\n            }\n            for mod_kc in mods.iter().copied() {\n                items.push(Release(mod_kc.into()));\n            }\n            &exprs[1..]\n        }\n    }\n}\n\n#[cfg(not(feature = \"simulated_output\"))]\npub(super) fn keys_for_cmd_output(cmd_and_args: &[&str]) -> impl Iterator<Item = Item> {\n    let mut args = cmd_and_args.iter();\n    let mut cmd = std::process::Command::new(\n        args.next()\n            .expect(\"parsing should have forbidden empty cmd\"),\n    );\n    for arg in args {\n        cmd.arg(arg);\n    }\n    let output = match cmd.output() {\n        Ok(o) => o,\n        Err(e) => {\n            log::error!(\"Failed to execute cmd: {e}\");\n            return empty();\n        }\n    };\n    log::debug!(\"{LP} stderr: {}\", String::from_utf8_lossy(&output.stderr));\n    let stdout = String::from_utf8_lossy(&output.stdout);\n    match parse(&stdout, \"cmd\") {\n        Ok(lists) => match lists.len() {\n            0 => {\n                log::warn!(\"{LP} got zero top-level S-expression from cmd, expected 1:\\n{stdout}\");\n                empty()\n            }\n            1 => from_sexpr(lists.into_iter().next().expect(\"len 1\").t),\n            _ => {\n                log::warn!(\n                    \"{LP} got multiple top-level S-expression from cmd, expected 1:\\n{stdout}\"\n                );\n                empty()\n            }\n        },\n        Err(e) => {\n            log::warn!(\n                \"{LP} could not parse an S-expression from cmd:\\n{stdout}\\n{}\",\n                e.msg\n            );\n            empty()\n        }\n    }\n}\n\n#[cfg(feature = \"simulated_output\")]\npub(super) fn keys_for_cmd_output(cmd_and_args: &[&str]) -> impl Iterator<Item = Item> {\n    println!(\"cmd-keys:{cmd_and_args:?}\");\n    [].iter().copied()\n}\n\n#[cfg(feature = \"simulated_output\")]\npub(super) fn run_cmd_in_thread(\n    cmd_and_args: Vec<String>,\n    _log_level: Option<log::Level>,\n    _error_log_level: Option<log::Level>,\n) -> std::thread::JoinHandle<()> {\n    std::thread::spawn(move || {\n        println!(\"cmd:{cmd_and_args:?}\");\n    })\n}\n"
  },
  {
    "path": "src/kanata/dynamic_macro.rs",
    "content": "use std::collections::VecDeque;\n\nuse kanata_keyberon::layout::Event;\nuse kanata_parser::cfg::ReplayDelayBehaviour;\nuse kanata_parser::keys::OsCode;\nuse rustc_hash::FxHashMap as HashMap;\nuse rustc_hash::FxHashSet as HashSet;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum DynamicMacroItem {\n    Press((OsCode, u16)),\n    Release((OsCode, u16)),\n    EndMacro(u16),\n}\n\npub struct DynamicMacroReplayState {\n    active_macros: HashSet<u16>,\n    delay_remaining: u16,\n    macro_items: VecDeque<DynamicMacroItem>,\n}\n\npub struct DynamicMacroRecordState {\n    starting_macro_id: u16,\n    waiting_event: Option<(OsCode, WaitingEventType)>,\n    macro_items: Vec<DynamicMacroItem>,\n    current_delay: u16,\n}\n\nenum WaitingEventType {\n    Press,\n    Release,\n}\n\nimpl DynamicMacroRecordState {\n    fn new(macro_id: u16) -> Self {\n        Self {\n            starting_macro_id: macro_id,\n            waiting_event: None,\n            macro_items: vec![],\n            current_delay: 0,\n        }\n    }\n\n    fn add_release_for_all_unreleased_presses(&mut self) {\n        let mut pressed_oscs = HashSet::default();\n        for item in self.macro_items.iter() {\n            match item {\n                DynamicMacroItem::Press((osc, _)) => {\n                    pressed_oscs.insert(*osc);\n                }\n                DynamicMacroItem::Release((osc, _)) => {\n                    pressed_oscs.remove(osc);\n                }\n                DynamicMacroItem::EndMacro(_) => {}\n            };\n        }\n        // Hopefully release order doesn't matter here. A HashSet is being used, meaning release order is arbitrary.\n        for osc in pressed_oscs.into_iter() {\n            self.macro_items.push(DynamicMacroItem::Release((osc, 0)));\n        }\n    }\n\n    fn add_event(&mut self, osc: OsCode, evtype: WaitingEventType) {\n        if let Some(pending_event) = self.waiting_event.take() {\n            match pending_event.1 {\n                WaitingEventType::Press => self.macro_items.push(DynamicMacroItem::Press((\n                    pending_event.0,\n                    self.current_delay,\n                ))),\n                WaitingEventType::Release => self.macro_items.push(DynamicMacroItem::Release((\n                    pending_event.0,\n                    self.current_delay,\n                ))),\n            };\n        }\n        self.current_delay = 0;\n        self.waiting_event = Some((osc, evtype));\n    }\n}\n\n/// A replay event for a dynamically recorded macro.\n/// Note that the key event and the subsequent delay must be processed together.\n/// Otherwise there will be real-world time gap between event and the delay,\n/// which results in an inaccurate simulation of the keyberon state machine.\n#[derive(Clone, Copy, PartialEq, Eq)]\npub struct ReplayEvent(Event, u16);\n\nimpl ReplayEvent {\n    pub fn key_event(self) -> Event {\n        self.0\n    }\n    pub fn delay(self) -> u16 {\n        self.1\n    }\n}\n\npub fn tick_record_state(record_state: &mut Option<DynamicMacroRecordState>) {\n    if let Some(state) = record_state {\n        state.current_delay = state.current_delay.saturating_add(1);\n    }\n}\n\n#[derive(Clone, Copy, PartialEq, Eq)]\npub struct ReplayBehaviour {\n    pub delay: ReplayDelayBehaviour,\n}\n\npub fn tick_replay_state(\n    replay_state: &mut Option<DynamicMacroReplayState>,\n    replay_behaviour: ReplayBehaviour,\n) -> Option<ReplayEvent> {\n    if let Some(state) = replay_state {\n        state.delay_remaining = state.delay_remaining.saturating_sub(1);\n        if state.delay_remaining == 0 {\n            state.delay_remaining = 5;\n            match state.macro_items.pop_front() {\n                None => {\n                    *replay_state = None;\n                    log::debug!(\"finished macro replay\");\n                    None\n                }\n                Some(i) => match i {\n                    DynamicMacroItem::Press((key, delay)) => {\n                        let event = Event::Press(0, key.into());\n                        let delay = match replay_behaviour.delay {\n                            ReplayDelayBehaviour::Constant => 0,\n                            ReplayDelayBehaviour::Recorded => {\n                                state.delay_remaining = delay;\n                                delay\n                            }\n                        };\n                        Some(ReplayEvent(event, delay))\n                    }\n                    DynamicMacroItem::Release((key, delay)) => {\n                        let event = Event::Release(0, key.into());\n                        let delay = match replay_behaviour.delay {\n                            ReplayDelayBehaviour::Constant => 0,\n                            ReplayDelayBehaviour::Recorded => {\n                                state.delay_remaining = delay;\n                                delay\n                            }\n                        };\n                        Some(ReplayEvent(event, delay))\n                    }\n                    DynamicMacroItem::EndMacro(macro_id) => {\n                        state.active_macros.remove(&macro_id);\n                        None\n                    }\n                },\n            }\n        } else {\n            None\n        }\n    } else {\n        None\n    }\n}\n\npub fn begin_record_macro(\n    macro_id: u16,\n    record_state: &mut Option<DynamicMacroRecordState>,\n) -> Option<(u16, Vec<DynamicMacroItem>)> {\n    match record_state.take() {\n        None => {\n            log::info!(\"starting dynamic macro {macro_id} recording\");\n            *record_state = Some(DynamicMacroRecordState::new(macro_id));\n            None\n        }\n        Some(mut state) => {\n            if let Some(pending_event) = state.waiting_event.take() {\n                match pending_event.1 {\n                    WaitingEventType::Press => state.macro_items.push(DynamicMacroItem::Press((\n                        pending_event.0,\n                        state.current_delay,\n                    ))),\n                    WaitingEventType::Release => state.macro_items.push(DynamicMacroItem::Release(\n                        (pending_event.0, state.current_delay),\n                    )),\n                };\n            }\n            // remove the last item, since it's almost certainly a \"macro\n            // record\" key press action which we don't want to keep.\n            state.macro_items.remove(state.macro_items.len() - 1);\n            state.add_release_for_all_unreleased_presses();\n\n            if state.starting_macro_id == macro_id {\n                log::info!(\n                    \"same macro id pressed. saving and stopping dynamic macro {} recording\",\n                    state.starting_macro_id\n                );\n                *record_state = None;\n            } else {\n                log::info!(\n                    \"saving dynamic macro {} recording then starting new macro recording {macro_id}\",\n                    state.starting_macro_id,\n                );\n                *record_state = Some(DynamicMacroRecordState::new(macro_id));\n            }\n            Some((state.starting_macro_id, state.macro_items))\n        }\n    }\n}\n\npub fn record_press(\n    record_state: &mut Option<DynamicMacroRecordState>,\n    osc: OsCode,\n    max_presses: u16,\n) -> Option<(u16, Vec<DynamicMacroItem>)> {\n    if let Some(state) = record_state {\n        // This is not 100% accurate since there may be multiple presses before any of\n        // their relesease are received. But it's probably good enough in practice.\n        //\n        // The presses are defined so that a user cares about the number of keys rather\n        // than events. So rather than the user multiplying by 2 in their config after\n        // considering the number of keys they want, kanata does the multiplication\n        // instead.\n        if state.macro_items.len() > usize::from(max_presses) * 2 {\n            log::warn!(\n                \"saving and stopping dynamic macro {} recording due to exceeding limit\",\n                state.starting_macro_id,\n            );\n            state.add_release_for_all_unreleased_presses();\n            let state = record_state.take().unwrap();\n            Some((state.starting_macro_id, state.macro_items))\n        } else {\n            log::debug!(\"delay to press: {}\", state.current_delay);\n            state.add_event(osc, WaitingEventType::Press);\n            None\n        }\n    } else {\n        None\n    }\n}\n\npub fn record_release(record_state: &mut Option<DynamicMacroRecordState>, osc: OsCode) {\n    if let Some(state) = record_state {\n        log::debug!(\"delay to release: {}\", state.current_delay);\n        state.add_event(osc, WaitingEventType::Release);\n    }\n}\n\npub fn stop_macro(\n    record_state: &mut Option<DynamicMacroRecordState>,\n    num_actions_to_remove: u16,\n) -> Option<(u16, Vec<DynamicMacroItem>)> {\n    if let Some(mut state) = record_state.take() {\n        if let Some(pending_event) = state.waiting_event.take() {\n            match pending_event.1 {\n                WaitingEventType::Press => state.macro_items.push(DynamicMacroItem::Press((\n                    pending_event.0,\n                    state.current_delay,\n                ))),\n                WaitingEventType::Release => state.macro_items.push(DynamicMacroItem::Release((\n                    pending_event.0,\n                    state.current_delay,\n                ))),\n            };\n        }\n        // remove the last item independently of `num_actions_to_remove`\n        // since it's almost certainly a \"macro record stop\" key press\n        // action which we don't want to keep.\n        state.macro_items.remove(state.macro_items.len() - 1);\n        log::info!(\n            \"saving and stopping dynamic macro {} recording with {num_actions_to_remove} actions at the end removed\",\n            state.starting_macro_id,\n        );\n        state.macro_items.truncate(\n            state\n                .macro_items\n                .len()\n                .saturating_sub(usize::from(num_actions_to_remove)),\n        );\n        state.add_release_for_all_unreleased_presses();\n        Some((state.starting_macro_id, state.macro_items))\n    } else {\n        None\n    }\n}\n\npub fn play_macro(\n    macro_id: u16,\n    replay_state: &mut Option<DynamicMacroReplayState>,\n    recorded_macros: &HashMap<u16, Vec<DynamicMacroItem>>,\n) {\n    match replay_state {\n        None => {\n            log::info!(\"replaying macro {macro_id}\");\n            *replay_state = recorded_macros.get(&macro_id).map(|macro_items| {\n                let mut active_macros = HashSet::default();\n                active_macros.insert(macro_id);\n                log::debug!(\"playing macro {macro_items:?}\");\n                DynamicMacroReplayState {\n                    active_macros,\n                    delay_remaining: 0,\n                    macro_items: macro_items.clone().into(),\n                }\n            });\n        }\n        Some(state) => {\n            if state.active_macros.contains(&macro_id) {\n                log::warn!(\"refusing to recurse into macro {macro_id}\");\n            } else if let Some(items) = recorded_macros.get(&macro_id) {\n                log::debug!(\"prepending macro {macro_id} items to current replay\");\n                log::debug!(\"playing macro {items:?}\");\n                state.active_macros.insert(macro_id);\n                state\n                    .macro_items\n                    .push_front(DynamicMacroItem::EndMacro(macro_id));\n                for item in items.iter().copied().rev() {\n                    state.macro_items.push_front(item);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/kanata/key_repeat.rs",
    "content": "use super::*;\n\nimpl Kanata {\n    /// This compares the active keys in the keyberon layout against the potential key outputs for\n    /// corresponding physical key in the configuration. If any of keyberon active keys match any\n    /// potential physical key output, write the repeat event to the OS.\n    pub(super) fn handle_repeat(&mut self, event: &KeyEvent) -> Result<()> {\n        let ret = self.handle_repeat_actual(event);\n        // The cur_keys Vec is re-used for processing, for efficiency reasons to avoid allocation.\n        // Unlike prev_keys which has useful info for the next call to handle_time_ticks, cur_keys\n        // can be reused and cleared — it just needs to be empty for the next handle_time_ticks\n        // call.\n        self.cur_keys.clear();\n        ret\n    }\n\n    pub(super) fn handle_repeat_actual(&mut self, event: &KeyEvent) -> Result<()> {\n        if let Some(state) = self.sequence_state.get_active() {\n            // While in non-visible sequence mode, don't send key repeats. I can't imagine it's a\n            // helpful use case for someone trying to type in a sequence that they want to rely on\n            // key repeats to finish a sequence. I suppose one might want to do repeat in order to\n            // try and cancel an input sequence... I'll wait for a user created issue to deal with\n            // this.\n            //\n            // It should be noted that even with visible-backspaced, key repeat does not interact\n            // with the sequence; the key is output with repeat as normal. Which might be\n            // surprising/unexpected. It's technically fixable but I don't want to add the code to\n            // do that if nobody needs it.\n            if state.sequence_input_mode != SequenceInputMode::VisibleBackspaced {\n                return Ok(());\n            }\n        }\n        self.cur_keys.extend(self.layout.b().keycodes());\n        self.overrides.override_keys(\n            &mut self.cur_keys,\n            &mut self.override_states,\n            self.layout.b().current_layer() as u16,\n        );\n\n        // Prioritize checking the active layer in case a layer-while-held is active.\n        let active_held_layers = self.layout.bm().trans_resolution_layer_order();\n        let mut held_layer_active = false;\n        for layer in active_held_layers {\n            held_layer_active = true;\n            if let Some(outputs_for_key) = self.key_outputs[usize::from(layer)].get(&event.code) {\n                log::debug!(\"key outs for active layer-while-held: {outputs_for_key:?};\");\n                for osc in outputs_for_key.iter().rev().copied() {\n                    let kc = osc.into();\n                    if self.cur_keys.contains(&kc)\n                        || self.unshifted_keys.contains(&kc)\n                        || self.unmodded_keys.contains(&kc)\n                    {\n                        log::debug!(\"repeat    {:?}\", KeyCode::from(osc));\n                        if let Err(e) = write_key(&mut self.kbd_out, osc, KeyValue::Repeat) {\n                            bail!(\"could not write key {e:?}\")\n                        }\n                        return Ok(());\n                    }\n                }\n            }\n        }\n        if held_layer_active {\n            log::debug!(\"empty layer-while-held outputs, probably transparent\");\n        }\n\n        if let Some(outputs_for_key) =\n            self.key_outputs[self.layout.bm().default_layer].get(&event.code)\n        {\n            // Try matching a key on the default layer.\n            //\n            // This code executes in two cases:\n            // 1. current layer is the default layer\n            // 2. current layer is layer-while-held but did not find a match in the code above, e.g. a\n            //    transparent key was pressed.\n            log::debug!(\"key outs for default layer: {outputs_for_key:?};\");\n            for osc in outputs_for_key.iter().rev().copied() {\n                let kc = osc.into();\n                if self.cur_keys.contains(&kc)\n                    || self.unshifted_keys.contains(&kc)\n                    || self.unmodded_keys.contains(&kc)\n                {\n                    log::debug!(\"repeat    {:?}\", KeyCode::from(osc));\n                    if let Err(e) = write_key(&mut self.kbd_out, osc, KeyValue::Repeat) {\n                        bail!(\"could not write key {e:?}\")\n                    }\n                    return Ok(());\n                }\n            }\n        }\n\n        // Reached here and have not exited yet.\n        // Check the standard key output itself because default layer might also be transparent\n        // and have delegated to defsrc handling.\n        log::debug!(\"checking defsrc output\");\n        let kc = event.code.into();\n        if (self.cur_keys.contains(&kc)\n            || self.unshifted_keys.contains(&kc)\n            || self.unmodded_keys.contains(&kc))\n            && let Err(e) = write_key(&mut self.kbd_out, event.code, KeyValue::Repeat)\n        {\n            bail!(\"could not write key {e:?}\");\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/kanata/linux.rs",
    "content": "#![cfg_attr(\n    feature = \"simulated_output\",\n    allow(dead_code, unused_imports, unused_variables, unused_mut)\n)]\n\nuse anyhow::{Result, anyhow, bail};\nuse evdev::{EventSummary, InputEvent, RelativeAxisCode};\nuse log::info;\nuse parking_lot::Mutex;\nuse std::convert::TryFrom;\nuse std::sync::Arc;\nuse std::sync::mpsc::SyncSender as Sender;\n\nuse super::*;\n\nimpl Kanata {\n    /// Enter an infinite loop that listens for OS key events and sends them to the processing\n    /// thread.\n    pub fn event_loop(kanata: Arc<Mutex<Self>>, tx: Sender<KeyEvent>) -> Result<()> {\n        info!(\"entering the event loop\");\n\n        let k = kanata.lock();\n        let allow_hardware_repeat = k.allow_hardware_repeat;\n        let mouse_movement_key = k.mouse_movement_key.clone();\n        let mut kbd_in = match KbdIn::new(\n            &k.kbd_in_paths,\n            k.continue_if_no_devices,\n            k.include_names.clone(),\n            k.exclude_names.clone(),\n            k.device_detect_mode,\n        ) {\n            Ok(kbd_in) => kbd_in,\n            Err(e) => {\n                bail!(\"failed to open keyboard device(s): {}\", e)\n            }\n        };\n\n        // In some environments, this needs to be done after the input device grab otherwise it\n        // does not work on kanata startup.\n        Kanata::set_repeat_rate(k.x11_repeat_rate)?;\n        drop(k);\n\n        loop {\n            let events = kbd_in.read().map_err(|e| anyhow!(\"failed read: {}\", e))?;\n            log::trace!(\"event count: {}\\nevents:\\n{events:?}\", events.len());\n\n            for in_event in events.iter().copied() {\n                if let Some(ms_mvmt_key) = *mouse_movement_key.lock()\n                    && let EventSummary::RelativeAxis(_, _, _) = in_event.destructure()\n                {\n                    let fake_event = KeyEvent::new(ms_mvmt_key, KeyValue::Tap);\n                    if let Err(e) = tx.try_send(fake_event) {\n                        bail!(\"failed to send on channel: {}\", e)\n                    }\n                }\n\n                let key_event = match KeyEvent::try_from(in_event) {\n                    Ok(ev) => ev,\n                    _ => {\n                        // Pass-through non-key and non-scroll events\n                        let mut kanata = kanata.lock();\n                        #[cfg(not(feature = \"simulated_output\"))]\n                        kanata\n                            .kbd_out\n                            .write_raw(in_event)\n                            .map_err(|e| anyhow!(\"failed write: {}\", e))?;\n                        continue;\n                    }\n                };\n\n                check_for_exit(&key_event);\n\n                if key_event.value == KeyValue::Repeat && !allow_hardware_repeat {\n                    continue;\n                }\n\n                if key_event.value == KeyValue::Tap {\n                    // Scroll event for sure. Only scroll events produce Tap.\n                    if !handle_scroll(&kanata, in_event, key_event.code, &events)? {\n                        continue;\n                    }\n                }\n\n                match key_event.value {\n                    KeyValue::Release => {\n                        PRESSED_KEYS.lock().remove(&key_event.code);\n                    }\n                    KeyValue::Press => {\n                        PRESSED_KEYS.lock().insert(key_event.code);\n                    }\n                    _ => {}\n                }\n\n                // Handle normal keypresses.\n                // Check if this keycode is mapped in the configuration.\n                // If it hasn't been mapped, send it immediately.\n                if !MAPPED_KEYS.lock().contains(&key_event.code) {\n                    let mut kanata = kanata.lock();\n                    #[cfg(not(feature = \"simulated_output\"))]\n                    kanata\n                        .kbd_out\n                        .write_raw(in_event)\n                        .map_err(|e| anyhow!(\"failed write: {}\", e))?;\n                    continue;\n                };\n\n                // Send key events to the processing loop\n                if let Err(e) = tx.try_send(key_event) {\n                    bail!(\"failed to send on channel: {}\", e)\n                }\n            }\n        }\n    }\n\n    pub fn check_release_non_physical_shift(&mut self) -> Result<()> {\n        Ok(())\n    }\n\n    pub fn set_repeat_rate(s: Option<KeyRepeatSettings>) -> Result<()> {\n        if let Some(s) = s {\n            log::info!(\n                \"Using xset to set X11 repeat delay to {} and repeat rate to {}\",\n                s.delay,\n                s.rate,\n            );\n            let cmd_output = std::process::Command::new(\"xset\")\n                .args([\n                    \"r\",\n                    \"rate\",\n                    s.delay.to_string().as_str(),\n                    s.rate.to_string().as_str(),\n                ])\n                .output()\n                .map_err(|e| {\n                    log::error!(\"failed to run xset: {e:?}\");\n                    e\n                })?;\n            log::info!(\n                \"xset stdout: {}\",\n                String::from_utf8_lossy(&cmd_output.stdout)\n            );\n            log::info!(\n                \"xset stderr: {}\",\n                String::from_utf8_lossy(&cmd_output.stderr)\n            );\n        }\n        Ok(())\n    }\n}\n\n/// Returns true if the scroll event should be sent to the processing loop, otherwise returns\n/// false.\nfn handle_scroll(\n    kanata: &Mutex<Kanata>,\n    in_event: InputEvent,\n    code: OsCode,\n    all_events: &[InputEvent],\n) -> Result<bool> {\n    let direction: MWheelDirection = code.try_into().unwrap();\n    let scroll_distance = in_event.value().unsigned_abs() as u16;\n    match in_event.destructure() {\n        EventSummary::RelativeAxis(_, axis_type, _) => {\n            match axis_type {\n                RelativeAxisCode::REL_WHEEL | RelativeAxisCode::REL_HWHEEL => {\n                    if MAPPED_KEYS.lock().contains(&code) {\n                        return Ok(true);\n                    }\n                    // If we just used `write_raw` here, some of the scrolls issued by kanata would be\n                    // REL_WHEEL_HI_RES + REL_WHEEL and some just REL_WHEEL and an issue like this one\n                    // would happen: https://github.com/jtroo/kanata/issues/395\n                    //\n                    // So to fix this case, we need to use `scroll` which will also send hi-res scrolls\n                    // along normal scrolls.\n                    //\n                    // However, if this is a normal scroll event, it may be sent alongside a hi-res\n                    // scroll event. In this scenario, the hi-res event should be used to call\n                    // scroll, and not the normal event. Otherwise, too much scrolling will happen.\n                    let mut kanata = kanata.lock();\n                    if !all_events.iter().any(|ev| {\n                        matches!(\n                            ev.destructure(),\n                            EventSummary::RelativeAxis(\n                                _,\n                                RelativeAxisCode::REL_WHEEL_HI_RES\n                                    | RelativeAxisCode::REL_HWHEEL_HI_RES,\n                                _\n                            )\n                        )\n                    }) {\n                        kanata\n                            .kbd_out\n                            .scroll(direction, scroll_distance * HI_RES_SCROLL_UNITS_IN_LO_RES)\n                            .map_err(|e| anyhow!(\"failed write: {}\", e))?;\n                    }\n                    Ok(false)\n                }\n                RelativeAxisCode::REL_WHEEL_HI_RES | RelativeAxisCode::REL_HWHEEL_HI_RES => {\n                    if !MAPPED_KEYS.lock().contains(&code) {\n                        // Passthrough if the scroll wheel event is not mapped\n                        // in the configuration.\n                        let mut kanata = kanata.lock();\n                        kanata\n                            .kbd_out\n                            .scroll(direction, scroll_distance)\n                            .map_err(|e| anyhow!(\"failed write: {}\", e))?;\n                    }\n                    // Kanata will not handle high resolution scroll events for now.\n                    // Full notch scrolling only.\n                    Ok(false)\n                }\n                _ => unreachable!(\"expect to be handling a wheel event\"),\n            }\n        }\n        _ => unreachable!(\"expect to be handling a wheel event\"),\n    }\n}\n"
  },
  {
    "path": "src/kanata/macos.rs",
    "content": "use super::*;\nuse anyhow::{Result, anyhow, bail};\nuse log::info;\nuse parking_lot::Mutex;\nuse std::convert::TryFrom;\nuse std::sync::Arc;\nuse std::sync::mpsc::SyncSender as Sender;\nuse std::time::Duration;\n\nimpl Kanata {\n    /// Enter an infinite loop that listens for OS key events and sends them to the processing thread.\n    ///\n    /// Contains a recovery mechanism: if the DriverKit output connection drops\n    /// (daemon crash, not installed, etc.), input devices are released so the\n    /// keyboard returns to normal operation. When the connection recovers,\n    /// devices are re-seized and remapping resumes.\n    ///\n    /// Recovery uses `regrab_input()` rather than recreating `KbdIn` to avoid\n    /// re-initializing the pqrs client (via `init_sink()`). A second client\n    /// causes duplicate connection callbacks that race with the IOHIDManager,\n    /// leading to \"exclusive access\" errors on the input device.\n    pub fn event_loop(kanata: Arc<Mutex<Self>>, tx: Sender<KeyEvent>) -> Result<()> {\n        info!(\"entering the event loop\");\n\n        let k = kanata.lock();\n        let allow_hardware_repeat = k.allow_hardware_repeat;\n        let include_names = k.include_names.clone();\n        let exclude_names = k.exclude_names.clone();\n        drop(k);\n\n        let mut kb = match KbdIn::new(include_names, exclude_names) {\n            Ok(kbd_in) => kbd_in,\n            Err(e) => bail!(\"failed to open keyboard device(s): {}\", e),\n        };\n\n        {\n            let kanata = kanata.lock();\n            if !kanata\n                .kbd_out\n                .wait_until_ready(Some(Duration::from_secs(10)))\n            {\n                log::warn!(\n                    \"output backend not ready after 10s. Key output may fail until the backend recovers.\"\n                );\n            }\n        }\n\n        info!(\"keyboard grabbed, entering event processing loop\");\n\n        loop {\n            // --- Event processing loop ---\n            let needs_recovery = loop {\n                // Check output health before blocking on input\n                if !kanata.lock().kbd_out.output_ready() {\n                    log::warn!(\"output backend unavailable — releasing input devices\");\n                    break true;\n                }\n\n                let event = match kb.read() {\n                    Ok(ev) => ev,\n                    Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {\n                        // Pipe closed by release_input_only() — expected during recovery\n                        log::info!(\"input pipe EOF — devices were released\");\n                        break true;\n                    }\n                    Err(e) => return Err(anyhow!(\"failed read: {}\", e)),\n                };\n\n                let mut key_event = match KeyEvent::try_from(event) {\n                    Ok(ev) => ev,\n                    _ => {\n                        log::debug!(\"{event:?} is unrecognized!\");\n                        let mut kanata = kanata.lock();\n                        match kanata.kbd_out.write(event) {\n                            Ok(()) => continue,\n                            Err(e) if e.kind() == std::io::ErrorKind::NotConnected => {\n                                log::warn!(\n                                    \"output backend unavailable during write — releasing input devices\"\n                                );\n                                break true;\n                            }\n                            Err(e) => return Err(anyhow!(\"failed write: {}\", e)),\n                        }\n                    }\n                };\n\n                check_for_exit(&key_event);\n\n                if key_event.value == KeyValue::Repeat && !allow_hardware_repeat {\n                    continue;\n                }\n\n                if !MAPPED_KEYS.lock().contains(&key_event.code) {\n                    log::debug!(\"{key_event:?} is not mapped\");\n                    let mut kanata = kanata.lock();\n                    match kanata.kbd_out.write(event) {\n                        Ok(()) => continue,\n                        Err(e) if e.kind() == std::io::ErrorKind::NotConnected => {\n                            log::warn!(\n                                \"output backend unavailable during write — releasing input devices\"\n                            );\n                            break true;\n                        }\n                        Err(e) => return Err(anyhow!(\"failed write: {}\", e)),\n                    }\n                }\n\n                log::debug!(\"sending {key_event:?} to processing loop\");\n\n                match key_event.value {\n                    KeyValue::Release => {\n                        PRESSED_KEYS.lock().remove(&key_event.code);\n                    }\n                    KeyValue::Press => {\n                        let mut pressed_keys = PRESSED_KEYS.lock();\n                        if pressed_keys.contains(&key_event.code) {\n                            key_event.value = KeyValue::Repeat;\n                        } else {\n                            pressed_keys.insert(key_event.code);\n                        }\n                    }\n                    _ => {}\n                }\n                tx.try_send(key_event)?;\n            };\n\n            if !needs_recovery {\n                break Ok(());\n            }\n\n            // --- Release input so the keyboard works normally (unseized) ---\n            kb.release_input();\n\n            info!(\n                \"Input devices released. Keyboard is usable (without remapping). \\\n                 Waiting for the output backend to recover...\"\n            );\n\n            // --- Wait for the output backend to re-establish the connection ---\n            loop {\n                if kanata\n                    .lock()\n                    .kbd_out\n                    .wait_until_ready(Some(Duration::from_millis(500)))\n                {\n                    // Let the direct DriverKit backend finish its callback sequence\n                    // before we re-seize input devices. Seizing too early can race\n                    // with IOKit enumeration triggered by those callbacks.\n                    std::thread::sleep(Duration::from_secs(1));\n                    info!(\"output backend recovered — re-grabbing input devices\");\n                    break;\n                }\n            }\n\n            {\n                let mut kanata = kanata.lock();\n                kanata\n                    .kbd_out\n                    .release_tracked_output_keys(\"output-backend-recovery\");\n            }\n            PRESSED_KEYS.lock().clear();\n\n            // Re-seize input devices using regrab_input() which creates a fresh\n            // pipe and listener thread without re-initializing the sink client.\n            if !kb.regrab_input() {\n                bail!(\"failed to re-grab keyboard devices after DriverKit recovery\");\n            }\n\n            info!(\"keyboard grabbed, entering event processing loop\");\n\n            // Back to the event processing loop.\n        }\n    }\n\n    pub fn check_release_non_physical_shift(&mut self) -> Result<()> {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/kanata/millisecond_counting.rs",
    "content": "use web_time::Instant;\n\npub struct MillisecondCountResult {\n    pub last_tick: Instant,\n    pub ms_elapsed: u128,\n    pub ms_remainder_in_ns: u128,\n}\n\npub fn count_ms_elapsed(\n    last_tick: Instant,\n    now: Instant,\n    prev_ms_remainder_in_ns: u128,\n) -> MillisecondCountResult {\n    const NS_IN_MS: u128 = 1_000_000;\n    let ns_elapsed = now.duration_since(last_tick).as_nanos();\n    let ns_elapsed_with_rem = ns_elapsed + prev_ms_remainder_in_ns;\n    let ms_elapsed = ns_elapsed_with_rem / NS_IN_MS;\n    let ms_remainder_in_ns = ns_elapsed_with_rem % NS_IN_MS;\n\n    let last_tick = match ms_elapsed {\n        0 => last_tick,\n        _ => now,\n    };\n    MillisecondCountResult {\n        last_tick,\n        ms_elapsed,\n        ms_remainder_in_ns,\n    }\n}\n\n#[test]\nfn ms_counts_0_elapsed_correctly() {\n    use std::time::Duration;\n    let last_tick = Instant::now();\n    let now = last_tick + Duration::from_nanos(999999);\n    let result = count_ms_elapsed(last_tick, now, 0);\n    assert_eq!(0, result.ms_elapsed);\n    assert_eq!(last_tick, result.last_tick);\n    assert_eq!(999999, result.ms_remainder_in_ns);\n}\n\n#[test]\nfn ms_counts_1_elapsed_correctly() {\n    use std::time::Duration;\n    let last_tick = Instant::now();\n    let now = last_tick + Duration::from_nanos(1234567);\n    let result = count_ms_elapsed(last_tick, now, 0);\n    assert_eq!(1, result.ms_elapsed);\n    assert_eq!(now, result.last_tick);\n    assert_eq!(234567, result.ms_remainder_in_ns);\n}\n\n#[test]\nfn ms_counts_1_then_2_elapsed_correctly() {\n    use std::time::Duration;\n    let last_tick = Instant::now();\n    let now = last_tick + Duration::from_micros(1750);\n    let result = count_ms_elapsed(last_tick, now, 0);\n    assert_eq!(1, result.ms_elapsed);\n    assert_eq!(now, result.last_tick);\n    assert_eq!(750000, result.ms_remainder_in_ns);\n    let last_tick = result.last_tick;\n    let now = last_tick + Duration::from_micros(1750);\n    let result = count_ms_elapsed(last_tick, now, result.ms_remainder_in_ns);\n    assert_eq!(2, result.ms_elapsed);\n    assert_eq!(now, result.last_tick);\n    assert_eq!(500000, result.ms_remainder_in_ns);\n}\n"
  },
  {
    "path": "src/kanata/mod.rs",
    "content": "//! Implements the glue between OS input/output and keyberon state management.\n\n#[cfg(all(target_os = \"windows\", feature = \"gui\"))]\nuse crate::gui::win::*;\nuse anyhow::{Result, bail};\nuse kanata_parser::sequences::*;\nuse log::{error, info};\nuse parking_lot::Mutex;\nuse std::sync::mpsc::{Receiver, SyncSender as Sender, TryRecvError};\n\n/// Reorders events so modifiers are processed first on press, last on release.\nfn collect_and_sort_events(\n    first_event: KeyEvent,\n    rx: &Receiver<KeyEvent>,\n    events: &mut Vec<KeyEvent>,\n) {\n    events.clear();\n    events.push(first_event);\n    while let Ok(ev) = rx.try_recv() {\n        events.push(ev);\n    }\n    if events.len() > 1 {\n        log::debug!(\"collected {} events, reordering\", events.len());\n        events.sort_by(|a, b| {\n            let a_is_mod = a.code.is_modifier();\n            let b_is_mod = b.code.is_modifier();\n            // Same modifier status: preserve original order\n            if a_is_mod == b_is_mod {\n                return std::cmp::Ordering::Equal;\n            }\n            let a_is_press = matches!(a.value, KeyValue::Press | KeyValue::Repeat);\n            let b_is_press = matches!(b.value, KeyValue::Press | KeyValue::Repeat);\n            // Different event types (press vs release): preserve original order\n            // to maintain valid total ordering\n            if a_is_press != b_is_press {\n                return std::cmp::Ordering::Equal;\n            }\n            // Same event type, different modifier status: reorder\n            if a_is_press {\n                // Press/Repeat: modifiers first\n                if a_is_mod {\n                    std::cmp::Ordering::Less\n                } else {\n                    std::cmp::Ordering::Greater\n                }\n            } else {\n                // Release: modifiers last\n                if a_is_mod {\n                    std::cmp::Ordering::Greater\n                } else {\n                    std::cmp::Ordering::Less\n                }\n            }\n        });\n        log::debug!(\"reordered: {:?}\", events);\n    }\n}\n\n#[cfg(any(\n    feature = \"passthru_ahk\",\n    all(feature = \"simulated_input\", feature = \"simulated_output\")\n))]\nuse std::sync::mpsc::Sender as ASender;\n\nuse kanata_keyberon::action::ReleasableState;\nuse kanata_keyberon::key_code::*;\nuse kanata_keyberon::layout::{CustomEvent, Event, Layout, State};\n\nuse std::path::PathBuf;\nuse std::sync::Arc;\nuse std::time;\n\n#[cfg(feature = \"tcp_server\")]\nuse crate::SocketAddrWrapper;\nuse crate::ValidatedArgs;\nuse crate::oskbd::{KeyEvent, *};\n#[cfg(feature = \"tcp_server\")]\nuse crate::tcp_server::simple_sexpr_to_json_array;\nuse kanata_parser::cfg;\nuse kanata_parser::cfg::list_actions::*;\nuse kanata_parser::cfg::*;\nuse kanata_parser::custom_action::*;\npub use kanata_parser::keys::*;\nuse kanata_tcp_protocol::ServerMessage;\n\nmod clipboard;\nuse clipboard::*;\n\nmod dynamic_macro;\nuse dynamic_macro::*;\n\nmod key_repeat;\n\nmod millisecond_counting;\npub use millisecond_counting::*;\n\nmod scroll;\nuse scroll::*;\n\nmod sequences;\nuse sequences::*;\n\npub mod cfg_forced;\nuse cfg_forced::*;\n\n#[cfg(feature = \"cmd\")]\nmod cmd;\n#[cfg(feature = \"cmd\")]\nuse cmd::*;\n\n#[cfg(target_os = \"windows\")]\nmod windows;\n#[cfg(target_os = \"windows\")]\npub use windows::*;\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\nmod linux;\n\n#[cfg(target_os = \"macos\")]\nmod macos;\n\nmod output_logic;\nuse output_logic::*;\n\n#[cfg(target_os = \"unknown\")]\nmod unknown;\n\nmod caps_word;\npub use caps_word::*;\n\ntype HashSet<T> = rustc_hash::FxHashSet<T>;\ntype HashMap<K, V> = rustc_hash::FxHashMap<K, V>;\n\n/// State of pressed keys on the physical keyboard.\n///\n/// Notably this is not what keys kanata is outputting as pressed.\n#[cfg(not(all(target_os = \"windows\", not(feature = \"interception_driver\"))))]\npub(crate) static PRESSED_KEYS: Lazy<Mutex<HashSet<OsCode>>> =\n    Lazy::new(|| Mutex::new(HashSet::default()));\n#[cfg(all(target_os = \"windows\", not(feature = \"interception_driver\")))]\npub(crate) static PRESSED_KEYS: Lazy<Mutex<HashMap<OsCode, web_time::Instant>>> =\n    Lazy::new(|| Mutex::new(HashMap::default()));\n\n/// Exit code to use when emergency exit (LCtrl+Space+Escape) is triggered.\n/// Configurable via --emergency-exit-code CLI argument. Default is 0.\npub static EMERGENCY_EXIT_CODE: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);\n\npub struct Kanata {\n    /// Handle to some OS keyboard output mechanism.\n    pub kbd_out: KbdOut,\n    /// Paths to one or more configuration files that define kanata's behaviour.\n    pub cfg_paths: Vec<PathBuf>,\n    /// Index into `cfg_paths`, used to know which file to live reload. Changes when cycling\n    /// through the configuration files.\n    pub cur_cfg_idx: usize,\n    /// Files included via (include \"path\") in the configuration.\n    /// The potential key outputs of every key input. Used for managing key repeat.\n    pub key_outputs: cfg::KeyOutputs,\n    /// Handle to the keyberon library layout.\n    pub layout: cfg::KanataLayout,\n    /// Reusable vec (to save on allocations) that stores the currently active output keys.\n    /// This can be cleared and reused in various procedures as buffer space.\n    pub cur_keys: Vec<KeyCode>,\n    /// Reusable vec (to save on allocations) that stores the active output keys from the previous\n    /// tick. This must only be updated once per tick and must not be modified outside of the one\n    /// procedure that updates it.\n    pub prev_keys: Vec<KeyCode>,\n    /// Used for printing layer info to the info log when changing layers.\n    pub layer_info: Vec<LayerInfo>,\n    /// Used to track when a layer change occurs.\n    pub prev_layer: usize,\n    /// Vertical scrolling state tracker. Is Some(...) when a vertical scrolling action is active\n    /// and None otherwise.\n    pub scroll_state: Option<ScrollState>,\n    /// Horizontal scrolling state. Is Some(...) when a horizontal scrolling action is active and\n    /// None otherwise.\n    pub hscroll_state: Option<ScrollState>,\n    /// Vertical mouse movement state. Is Some(...) when vertical mouse movement is active and None\n    /// otherwise.\n    pub move_mouse_state_vertical: Option<MoveMouseState>,\n    /// Horizontal mouse movement state. Is Some(...) when horizontal mouse movement is active and\n    /// None otherwise.\n    pub move_mouse_state_horizontal: Option<MoveMouseState>,\n    /// A list of mouse speed modifiers in percentages by which mouse travel distance is scaled.\n    pub move_mouse_speed_modifiers: Vec<u16>,\n    /// The user configuration for backtracking to find valid sequences. See\n    /// <../../docs/sequence-adding-chords-ideas.md> for more info.\n    pub sequence_backtrack_modcancel: bool,\n    /// The user configuration for sequences be permanently on.\n    pub sequence_always_on: bool,\n    /// Default sequence input mode for use with always-on.\n    pub sequence_input_mode: SequenceInputMode,\n    /// Default sequence timeout for use with always-on.\n    pub sequence_timeout: u16,\n    /// Tracks sequence progress. Is Some(...) when in sequence mode and None otherwise.\n    pub sequence_state: SequenceState,\n    /// Valid sequences defined in the user configuration.\n    pub sequences: cfg::KeySeqsToFKeys,\n    /// Stores the user recored dynamic macros.\n    pub dynamic_macros: HashMap<u16, Vec<DynamicMacroItem>>,\n    /// Tracks the progress of an active dynamic macro. Is Some(...) when a dynamic macro is being\n    /// replayed and None otherwise.\n    pub dynamic_macro_replay_state: Option<DynamicMacroReplayState>,\n    /// Tracks the inputs for a dynamic macro recording. Is Some(...) when a dynamic macro is\n    /// being recorded and None otherwise.\n    pub dynamic_macro_record_state: Option<DynamicMacroRecordState>,\n    /// Global overrides defined in the user configuration.\n    pub overrides: Overrides,\n    /// Reusable allocations to help with computing whether overrides are active based on key\n    /// outputs.\n    pub override_states: OverrideStates,\n    /// Time of the last tick to know how many tick iterations to run, to achieve a 1ms tick\n    /// interval more closely.\n    last_tick: web_time::Instant,\n    /// Tracks the non-whole-millisecond gaps between ticks to know when to do another tick\n    /// iteration without sleeping, to achive a 1ms tick interval more closely.\n    time_remainder: u128,\n    /// Is true if a live reload was requested by the user and false otherwise.\n    live_reload_requested: bool,\n    #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n    /// Linux input paths in the user configuration.\n    pub kbd_in_paths: Vec<String>,\n    #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n    /// Tracks the Linux user configuration to continue or abort if no devices are found.\n    continue_if_no_devices: bool,\n    #[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"macos\"))]\n    /// Tracks the Linux/Macos user configuration for device names (instead of paths) that should be\n    /// included for interception and processing by kanata.\n    pub include_names: Option<Vec<String>>,\n    #[cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"macos\"))]\n    /// Tracks the Linux/Macos user configuration for device names (instead of paths) that should be\n    /// excluded for interception and processing by kanata.\n    pub exclude_names: Option<Vec<String>>,\n    #[cfg(target_os = \"windows\")]\n    /// Tracks whether Kanata should try to synchronize keystates with the Windows OS.\n    /// Has no effect on Interception. Fixes some use cases related to admin window permissions and\n    /// potentially locking via Win+L.\n    pub windows_sync_keystates: bool,\n    #[cfg(all(feature = \"interception_driver\", target_os = \"windows\"))]\n    /// Used to know which input device to treat as a mouse for intercepting and processing inputs\n    /// by kanata.\n    intercept_mouse_hwids: Option<Vec<[u8; HWID_ARR_SZ]>>,\n    #[cfg(all(feature = \"interception_driver\", target_os = \"windows\"))]\n    /// Used to know which mouse input devices to exclude from processing inputs by kanata. This is\n    /// mutually exclusive from `intercept_mouse_hwids` and kanata will panic if both are included.\n    intercept_mouse_hwids_exclude: Option<Vec<[u8; HWID_ARR_SZ]>>,\n    #[cfg(all(feature = \"interception_driver\", target_os = \"windows\"))]\n    /// Used to know which input device to treat as a keyboard for intercepting and processing inputs\n    /// by kanata.\n    intercept_kb_hwids: Option<Vec<[u8; HWID_ARR_SZ]>>,\n    #[cfg(all(feature = \"interception_driver\", target_os = \"windows\"))]\n    /// Used to know which keyboard input devices to exclude from processing inputs by kanata. This\n    /// is mutually exclusive from `intercept_kb_hwids` and kanata will panic if both are included.\n    intercept_kb_hwids_exclude: Option<Vec<[u8; HWID_ARR_SZ]>>,\n    /// User configuration to do logging of layer changes or not.\n    log_layer_changes: bool,\n    /// Tracks the caps-word state. Is Some(...) if caps-word is active and None otherwise.\n    pub caps_word: Option<CapsWordState>,\n    /// Config items from `defcfg`.\n    #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n    pub x11_repeat_rate: Option<KeyRepeatSettings>,\n    /// Determines what types of devices to grab based on autodetection mode.\n    #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n    pub device_detect_mode: DeviceDetectMode,\n    /// Fake key actions that are waiting for a certain duration of kanata idling.\n    pub waiting_for_idle: HashSet<FakeKeyOnIdle>,\n    /// Fake key actions that are waiting for a certain duration of physical keyboard idling,\n    /// as opposed to `waiting_for_idle`, for which non-idling is extended by ongoing kanata state\n    /// processing, such as `caps_word` or `one_shot` terminations.\n    pub waiting_for_physical_idle: HashSet<FakeKeyOnIdle>,\n    /// Fake key actions that are being held and are pending release.\n    /// The key is the coordinate and the value is the number of ticks until release should be\n    /// done.\n    pub vkeys_pending_release: HashMap<Coord, u16>,\n    /// Number of ticks since kanata was idle.\n    pub ticks_since_idle: u16,\n    /// Number of ticks since physical keyboards were all idle.\n    pub ticks_since_physical_idle: u16,\n    /// If a mousemove action is active and another mousemove action is activated,\n    /// reuse the acceleration state.\n    movemouse_inherit_accel_state: bool,\n    /// Removes jaggedneess of vertical and horizontal mouse movements when used\n    /// simultaneously at the cost of increased mousemove actions latency.\n    movemouse_smooth_diagonals: bool,\n    /// If movemouse_smooth_diagonals is enabled, the previous mouse actions\n    /// gets stored in this buffer and if the next movemouse action is opposite axis\n    /// than the one stored in the buffer, both events are outputted at the same time.\n    movemouse_buffer: Option<(Axis, CalculatedMouseMove)>,\n    override_release_on_activation: bool,\n    /// Configured maximum for dynamic macro recording, to protect users from themselves if they\n    /// have accidentally left it on.\n    dynamic_macro_max_presses: u16,\n    /// Determines behaviour of replayed dynamic macros.\n    dynamic_macro_replay_behaviour: ReplayBehaviour,\n    /// Keys that should be unmodded. If non-empty, any modifier should be cleared.\n    unmodded_keys: Vec<KeyCode>,\n    /// Modifiers to be cleared in case the above is non-empty.\n    unmodded_mods: UnmodMods,\n    /// Keys that should be unshifted. If non-empty, left+right shift keys should be cleared.\n    unshifted_keys: Vec<KeyCode>,\n    /// Keep track of last pressed key for [`CustomAction::Repeat`].\n    last_pressed_key: KeyCode,\n    /// Names of fake keys mapped to their index in the fake keys row\n    pub virtual_keys: HashMap<String, usize>,\n    /// The maximum value of switch's key-timing item in the configuration.\n    pub switch_max_key_timing: u16,\n    #[cfg(feature = \"tcp_server\")]\n    tcp_server_address: Option<SocketAddrWrapper>,\n    #[cfg(all(target_os = \"windows\", feature = \"gui\"))]\n    /// Various GUI-related options.\n    pub gui_opts: CfgOptionsGui,\n    pub allow_hardware_repeat: bool,\n    /// When > 0, it means macros should be cancelled on the next press.\n    /// Upon cancelling this should be set to 0.\n    pub macro_on_press_cancel_duration: u32,\n    /// Stores user's saved clipboard contents.\n    pub saved_clipboard_content: SavedClipboardData,\n    // if set, key taps of this code are sent whenever mouse movement events are passed through\n    #[cfg(any(\n        all(target_os = \"windows\", feature = \"interception_driver\"),\n        target_os = \"linux\",\n        target_os = \"android\",\n        target_os = \"unknown\"\n    ))]\n    mouse_movement_key: Arc<Mutex<Option<OsCode>>>,\n    /// Time when kanata started (for uptime tracking)\n    #[cfg(feature = \"tcp_server\")]\n    start_time: web_time::Instant,\n    /// Whether last reload was successful\n    #[cfg(feature = \"tcp_server\")]\n    last_reload_ok: bool,\n}\n\n#[derive(PartialEq, Clone, Copy)]\npub enum Axis {\n    Vertical,\n    Horizontal,\n}\n\nimpl From<MoveDirection> for Axis {\n    fn from(val: MoveDirection) -> Axis {\n        match val {\n            MoveDirection::Up | MoveDirection::Down => Axis::Vertical,\n            MoveDirection::Left | MoveDirection::Right => Axis::Horizontal,\n        }\n    }\n}\n\n/// Represents reload actions that need to be processed after releasing borrows\nenum ReloadAction {\n    Reload,\n    ReloadNext,\n    ReloadPrev,\n    ReloadNum(usize),\n    ReloadFile(String),\n}\n\n#[derive(Clone, Copy)]\npub struct CalculatedMouseMove {\n    pub direction: MoveDirection,\n    pub distance: u16,\n}\n\npub struct MoveMouseState {\n    pub direction: MoveDirection,\n    pub interval: u16,\n    pub ticks_until_move: u16,\n    pub distance: u16,\n    pub move_mouse_accel_state: Option<MoveMouseAccelState>,\n}\n\n#[derive(Clone, Copy)]\npub struct MoveMouseAccelState {\n    pub accel_ticks_from_min: u16,\n    pub accel_ticks_until_max: u16,\n    pub accel_increment: f64,\n    pub min_distance: u16,\n    pub max_distance: u16,\n}\n\nuse once_cell::sync::Lazy;\n\nstatic MAPPED_KEYS: Lazy<Mutex<cfg::MappedKeys>> =\n    Lazy::new(|| Mutex::new(cfg::MappedKeys::default()));\n\nconst LINUX_PERMISSIONS_ERROR: &str = \"Failed to open the output uinput device. Make sure you added the user executing kanata to the 'uinput' group and that the 'uinput' group is configured correctly.\\nSee for more detail: https://github.com/jtroo/kanata/blob/main/docs/setup-linux.md\";\n\nimpl Kanata {\n    pub fn new(args: &ValidatedArgs) -> Result<Self> {\n        let cfg = match cfg::new_from_file(&args.paths[0]) {\n            Ok(c) => c,\n            Err(e) => {\n                log::error!(\"{e:?}\");\n                bail!(\"failed to parse file\");\n            }\n        };\n\n        let kbd_out = match KbdOut::new(\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            &args.symlink_path,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            cfg.options.linux_opts.linux_use_trackpoint_property,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            &cfg.options.linux_opts.linux_output_name,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            match cfg.options.linux_opts.linux_output_bus_type {\n                LinuxCfgOutputBusType::BusUsb => evdev::BusType::BUS_USB,\n                LinuxCfgOutputBusType::BusI8042 => evdev::BusType::BUS_I8042,\n                LinuxCfgOutputBusType::BusVirtual => evdev::BusType::BUS_VIRTUAL,\n            },\n        ) {\n            Ok(kbd_out) => kbd_out,\n            Err(err) => {\n                error!(\"{LINUX_PERMISSIONS_ERROR}\");\n                bail!(err)\n            }\n        };\n\n        #[cfg(target_os = \"windows\")]\n        unsafe {\n            log::info!(\"Asking Windows to improve timer precision\");\n            if winapi::um::timeapi::timeBeginPeriod(1) == winapi::um::mmsystem::TIMERR_NOCANDO {\n                bail!(\"failed to improve timer precision\");\n            }\n        }\n\n        #[cfg(target_os = \"windows\")]\n        unsafe {\n            log::info!(\"Asking Windows to increase process priority\");\n            winapi::um::processthreadsapi::SetPriorityClass(\n                winapi::um::processthreadsapi::GetCurrentProcess(),\n                winapi::um::winbase::REALTIME_PRIORITY_CLASS,\n            );\n        }\n\n        update_kbd_out(&cfg.options, &kbd_out)?;\n\n        #[cfg(target_os = \"windows\")]\n        set_win_altgr_behaviour(cfg.options.windows_opts.windows_altgr);\n\n        *MAPPED_KEYS.lock() = cfg.mapped_keys;\n        #[cfg(feature = \"zippychord\")]\n        {\n            zch().zch_configure(cfg.zippy.unwrap_or_default());\n        }\n\n        Ok(Self {\n            kbd_out,\n            cfg_paths: args.paths.clone(),\n            cur_cfg_idx: 0,\n            key_outputs: cfg.key_outputs,\n            layout: cfg.layout,\n            layer_info: cfg.layer_info,\n            cur_keys: Vec::new(),\n            prev_keys: Vec::new(),\n            prev_layer: 0,\n            scroll_state: None,\n            hscroll_state: None,\n            move_mouse_state_vertical: None,\n            move_mouse_state_horizontal: None,\n            move_mouse_speed_modifiers: Vec::new(),\n            sequence_backtrack_modcancel: cfg.options.sequence_backtrack_modcancel,\n            sequence_always_on: cfg.options.sequence_always_on,\n            sequence_input_mode: cfg.options.sequence_input_mode,\n            sequence_timeout: cfg.options.sequence_timeout,\n            sequence_state: SequenceState::new(),\n            sequences: cfg.sequences,\n            last_tick: web_time::Instant::now(),\n            time_remainder: 0,\n            live_reload_requested: false,\n            overrides: cfg.overrides,\n            override_states: OverrideStates::new(),\n            #[cfg(target_os = \"macos\")]\n            include_names: cfg.options.macos_opts.macos_dev_names_include,\n            #[cfg(target_os = \"macos\")]\n            exclude_names: cfg.options.macos_opts.macos_dev_names_exclude,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            kbd_in_paths: cfg.options.linux_opts.linux_dev,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            continue_if_no_devices: cfg.options.linux_opts.linux_continue_if_no_devs_found,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            include_names: cfg.options.linux_opts.linux_dev_names_include,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            exclude_names: cfg.options.linux_opts.linux_dev_names_exclude,\n            #[cfg(target_os = \"windows\")]\n            windows_sync_keystates: cfg.options.windows_opts.sync_keystates,\n            #[cfg(all(feature = \"interception_driver\", target_os = \"windows\"))]\n            intercept_mouse_hwids: cfg.options.wintercept_opts.windows_interception_mouse_hwids,\n            #[cfg(all(feature = \"interception_driver\", target_os = \"windows\"))]\n            intercept_mouse_hwids_exclude: cfg\n                .options\n                .wintercept_opts\n                .windows_interception_mouse_hwids_exclude,\n            #[cfg(all(feature = \"interception_driver\", target_os = \"windows\"))]\n            intercept_kb_hwids: cfg\n                .options\n                .wintercept_opts\n                .windows_interception_keyboard_hwids,\n            #[cfg(all(feature = \"interception_driver\", target_os = \"windows\"))]\n            intercept_kb_hwids_exclude: cfg\n                .options\n                .wintercept_opts\n                .windows_interception_keyboard_hwids_exclude,\n            dynamic_macro_replay_state: None,\n            dynamic_macro_record_state: None,\n            dynamic_macros: Default::default(),\n            log_layer_changes: get_forced_log_layer_changes()\n                .unwrap_or(cfg.options.log_layer_changes),\n            caps_word: None,\n            movemouse_smooth_diagonals: cfg.options.movemouse_smooth_diagonals,\n            override_release_on_activation: cfg.options.override_release_on_activation,\n            movemouse_inherit_accel_state: cfg.options.movemouse_inherit_accel_state,\n            dynamic_macro_max_presses: cfg.options.dynamic_macro_max_presses,\n            dynamic_macro_replay_behaviour: ReplayBehaviour {\n                delay: cfg.options.dynamic_macro_replay_delay_behaviour,\n            },\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            x11_repeat_rate: cfg.options.linux_opts.linux_x11_repeat_delay_rate,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            device_detect_mode: cfg\n                .options\n                .linux_opts\n                .linux_device_detect_mode\n                .expect(\"parser should default to some\"),\n            waiting_for_idle: HashSet::default(),\n            waiting_for_physical_idle: HashSet::default(),\n            vkeys_pending_release: HashMap::default(),\n            ticks_since_idle: 0,\n            ticks_since_physical_idle: 0,\n            movemouse_buffer: None,\n            unmodded_keys: vec![],\n            unmodded_mods: UnmodMods::empty(),\n            unshifted_keys: vec![],\n            last_pressed_key: KeyCode::No,\n            virtual_keys: cfg.fake_keys,\n            switch_max_key_timing: cfg.switch_max_key_timing,\n            #[cfg(feature = \"tcp_server\")]\n            tcp_server_address: args.tcp_server_address.clone(),\n            #[cfg(all(target_os = \"windows\", feature = \"gui\"))]\n            gui_opts: cfg.options.gui_opts,\n            allow_hardware_repeat: cfg.options.allow_hardware_repeat,\n            macro_on_press_cancel_duration: 0,\n            saved_clipboard_content: Default::default(),\n            #[cfg(any(\n                all(target_os = \"windows\", feature = \"interception_driver\"),\n                any(target_os = \"linux\", target_os = \"android\"),\n                target_os = \"unknown\"\n            ))]\n            mouse_movement_key: Arc::new(Mutex::new(cfg.options.mouse_movement_key)),\n            #[cfg(feature = \"tcp_server\")]\n            start_time: web_time::Instant::now(),\n            #[cfg(feature = \"tcp_server\")]\n            last_reload_ok: true,\n        })\n    }\n\n    /// Create a new configuration from a file, wrapped in an Arc<Mutex<_>>\n    pub fn new_arc(args: &ValidatedArgs) -> Result<Arc<Mutex<Self>>> {\n        Ok(Arc::new(Mutex::new(Self::new(args)?)))\n    }\n\n    pub fn new_from_str(cfg: &str, file_content: HashMap<String, String>) -> Result<Self> {\n        let cfg = match cfg::new_from_str(cfg, file_content) {\n            Ok(c) => c,\n            Err(e) => {\n                bail!(\"{e:?}\");\n            }\n        };\n\n        let kbd_out = match KbdOut::new(\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            &None,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            cfg.options.linux_opts.linux_use_trackpoint_property,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            &cfg.options.linux_opts.linux_output_name,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            match cfg.options.linux_opts.linux_output_bus_type {\n                LinuxCfgOutputBusType::BusUsb => evdev::BusType::BUS_USB,\n                LinuxCfgOutputBusType::BusI8042 => evdev::BusType::BUS_I8042,\n                LinuxCfgOutputBusType::BusVirtual => evdev::BusType::BUS_VIRTUAL,\n            },\n        ) {\n            Ok(kbd_out) => kbd_out,\n            Err(err) => {\n                error!(\"{LINUX_PERMISSIONS_ERROR}\");\n                bail!(err)\n            }\n        };\n\n        *MAPPED_KEYS.lock() = cfg.mapped_keys;\n        #[cfg(feature = \"zippychord\")]\n        {\n            zch().zch_configure(cfg.zippy.unwrap_or_default());\n        }\n\n        Ok(Self {\n            kbd_out,\n            cfg_paths: vec![\"config string\".into()],\n            cur_cfg_idx: 0,\n            key_outputs: cfg.key_outputs,\n            layout: cfg.layout,\n            layer_info: cfg.layer_info,\n            cur_keys: Vec::new(),\n            prev_keys: Vec::new(),\n            prev_layer: 0,\n            scroll_state: None,\n            hscroll_state: None,\n            move_mouse_state_vertical: None,\n            move_mouse_state_horizontal: None,\n            move_mouse_speed_modifiers: Vec::new(),\n            sequence_backtrack_modcancel: cfg.options.sequence_backtrack_modcancel,\n            sequence_always_on: cfg.options.sequence_always_on,\n            sequence_input_mode: cfg.options.sequence_input_mode,\n            sequence_timeout: cfg.options.sequence_timeout,\n            sequence_state: SequenceState::new(),\n            sequences: cfg.sequences,\n            last_tick: web_time::Instant::now(),\n            time_remainder: 0,\n            live_reload_requested: false,\n            overrides: cfg.overrides,\n            override_states: OverrideStates::new(),\n            #[cfg(target_os = \"macos\")]\n            include_names: cfg.options.macos_opts.macos_dev_names_include,\n            #[cfg(target_os = \"macos\")]\n            exclude_names: cfg.options.macos_opts.macos_dev_names_exclude,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            kbd_in_paths: cfg.options.linux_opts.linux_dev,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            continue_if_no_devices: cfg.options.linux_opts.linux_continue_if_no_devs_found,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            include_names: cfg.options.linux_opts.linux_dev_names_include,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            exclude_names: cfg.options.linux_opts.linux_dev_names_exclude,\n            #[cfg(target_os = \"windows\")]\n            windows_sync_keystates: cfg.options.windows_opts.sync_keystates,\n            #[cfg(all(feature = \"interception_driver\", target_os = \"windows\"))]\n            intercept_mouse_hwids: cfg.options.wintercept_opts.windows_interception_mouse_hwids,\n            #[cfg(all(feature = \"interception_driver\", target_os = \"windows\"))]\n            intercept_mouse_hwids_exclude: cfg\n                .options\n                .wintercept_opts\n                .windows_interception_mouse_hwids_exclude,\n            #[cfg(all(feature = \"interception_driver\", target_os = \"windows\"))]\n            intercept_kb_hwids: cfg\n                .options\n                .wintercept_opts\n                .windows_interception_keyboard_hwids,\n            #[cfg(all(feature = \"interception_driver\", target_os = \"windows\"))]\n            intercept_kb_hwids_exclude: cfg\n                .options\n                .wintercept_opts\n                .windows_interception_keyboard_hwids_exclude,\n            dynamic_macro_replay_state: None,\n            dynamic_macro_record_state: None,\n            dynamic_macros: Default::default(),\n            log_layer_changes: get_forced_log_layer_changes()\n                .unwrap_or(cfg.options.log_layer_changes),\n            caps_word: None,\n            movemouse_smooth_diagonals: cfg.options.movemouse_smooth_diagonals,\n            override_release_on_activation: cfg.options.override_release_on_activation,\n            movemouse_inherit_accel_state: cfg.options.movemouse_inherit_accel_state,\n            dynamic_macro_max_presses: cfg.options.dynamic_macro_max_presses,\n            dynamic_macro_replay_behaviour: ReplayBehaviour {\n                delay: cfg.options.dynamic_macro_replay_delay_behaviour,\n            },\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            x11_repeat_rate: cfg.options.linux_opts.linux_x11_repeat_delay_rate,\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            device_detect_mode: cfg\n                .options\n                .linux_opts\n                .linux_device_detect_mode\n                .expect(\"parser should default to some\"),\n            waiting_for_idle: HashSet::default(),\n            waiting_for_physical_idle: HashSet::default(),\n            vkeys_pending_release: HashMap::default(),\n            ticks_since_idle: 0,\n            ticks_since_physical_idle: 0,\n            movemouse_buffer: None,\n            unmodded_keys: vec![],\n            unmodded_mods: UnmodMods::empty(),\n            unshifted_keys: vec![],\n            last_pressed_key: KeyCode::No,\n            virtual_keys: cfg.fake_keys,\n            switch_max_key_timing: cfg.switch_max_key_timing,\n            #[cfg(feature = \"tcp_server\")]\n            tcp_server_address: None,\n            #[cfg(all(target_os = \"windows\", feature = \"gui\"))]\n            gui_opts: cfg.options.gui_opts,\n            allow_hardware_repeat: cfg.options.allow_hardware_repeat,\n            macro_on_press_cancel_duration: 0,\n            saved_clipboard_content: Default::default(),\n            #[cfg(any(\n                all(target_os = \"windows\", feature = \"interception_driver\"),\n                target_os = \"linux\",\n                target_os = \"android\",\n                target_os = \"unknown\"\n            ))]\n            mouse_movement_key: Arc::new(Mutex::new(cfg.options.mouse_movement_key)),\n            #[cfg(feature = \"tcp_server\")]\n            start_time: web_time::Instant::now(),\n            #[cfg(feature = \"tcp_server\")]\n            last_reload_ok: true,\n        })\n    }\n\n    #[cfg(any(\n        feature = \"passthru_ahk\",\n        all(feature = \"simulated_input\", feature = \"simulated_output\")\n    ))]\n    pub fn new_with_output_channel(\n        args: &ValidatedArgs,\n        tx: Option<ASender<InputEvent>>,\n    ) -> Result<Arc<Mutex<Self>>> {\n        let mut k = Self::new(args)?;\n        k.kbd_out.tx_kout = tx;\n        Ok(Arc::new(Mutex::new(k)))\n    }\n\n    fn do_live_reload(&mut self, _tx: &Option<Sender<ServerMessage>>) -> Result<()> {\n        let cfg = match cfg::new_from_file(&self.cfg_paths[self.cur_cfg_idx]) {\n            Ok(c) => c,\n            Err(e) => {\n                log::error!(\"{e:?}\");\n                #[cfg(feature = \"tcp_server\")]\n                {\n                    self.last_reload_ok = false;\n                }\n                bail!(\"failed to parse config file\");\n            }\n        };\n        update_kbd_out(&cfg.options, &self.kbd_out)?;\n        #[cfg(target_os = \"windows\")]\n        set_win_altgr_behaviour(cfg.options.windows_opts.windows_altgr);\n        self.sequence_backtrack_modcancel = cfg.options.sequence_backtrack_modcancel;\n        self.sequence_always_on = cfg.options.sequence_always_on;\n        self.sequence_input_mode = cfg.options.sequence_input_mode;\n        self.sequence_timeout = cfg.options.sequence_timeout;\n        self.layout = cfg.layout;\n        self.key_outputs = cfg.key_outputs;\n        self.layer_info = cfg.layer_info;\n        self.sequences = cfg.sequences;\n        self.overrides = cfg.overrides;\n        self.log_layer_changes =\n            get_forced_log_layer_changes().unwrap_or(cfg.options.log_layer_changes);\n        self.movemouse_smooth_diagonals = cfg.options.movemouse_smooth_diagonals;\n        self.override_release_on_activation = cfg.options.override_release_on_activation;\n        self.movemouse_inherit_accel_state = cfg.options.movemouse_inherit_accel_state;\n        self.dynamic_macro_max_presses = cfg.options.dynamic_macro_max_presses;\n        self.dynamic_macro_replay_behaviour = ReplayBehaviour {\n            delay: cfg.options.dynamic_macro_replay_delay_behaviour,\n        };\n        self.switch_max_key_timing = cfg.switch_max_key_timing;\n        self.virtual_keys = cfg.fake_keys;\n        #[cfg(target_os = \"windows\")]\n        {\n            self.windows_sync_keystates = cfg.options.windows_opts.sync_keystates;\n        }\n        #[cfg(all(target_os = \"windows\", feature = \"gui\"))]\n        {\n            self.gui_opts.tray_icon = cfg.options.gui_opts.tray_icon;\n            self.gui_opts.icon_match_layer_name = cfg.options.gui_opts.icon_match_layer_name;\n            self.gui_opts.tooltip_layer_changes = cfg.options.gui_opts.tooltip_layer_changes;\n            self.gui_opts.tooltip_no_base = cfg.options.gui_opts.tooltip_no_base;\n            self.gui_opts.tooltip_show_blank = cfg.options.gui_opts.tooltip_show_blank;\n            self.gui_opts.tooltip_duration = cfg.options.gui_opts.tooltip_duration;\n            self.gui_opts.notify_cfg_reload = cfg.options.gui_opts.notify_cfg_reload;\n            self.gui_opts.notify_cfg_reload_silent = cfg.options.gui_opts.notify_cfg_reload_silent;\n            self.gui_opts.notify_error = cfg.options.gui_opts.notify_error;\n            self.gui_opts.tooltip_size = cfg.options.gui_opts.tooltip_size;\n        }\n        #[cfg(feature = \"zippychord\")]\n        {\n            zch().zch_configure(cfg.zippy.unwrap_or_default());\n        }\n\n        *MAPPED_KEYS.lock() = cfg.mapped_keys;\n        #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n        Kanata::set_repeat_rate(cfg.options.linux_opts.linux_x11_repeat_delay_rate)?;\n        log::info!(\"Live reload successful\");\n        #[cfg(feature = \"tcp_server\")]\n        if let Some(tx) = _tx {\n            match tx.try_send(ServerMessage::ConfigFileReload {\n                new: self.cfg_paths[self.cur_cfg_idx]\n                    .to_str()\n                    .unwrap()\n                    .to_string(),\n            }) {\n                Ok(_) => {}\n                Err(error) => {\n                    log::error!(\n                        \"could not send ConfigFileReload event notification: {}\",\n                        error\n                    );\n                }\n            }\n        }\n\n        let cur_layer = self.layout.bm().current_layer();\n        self.prev_layer = cur_layer;\n        self.print_layer(cur_layer);\n        self.macro_on_press_cancel_duration = 0;\n\n        #[cfg(any(\n            all(target_os = \"windows\", feature = \"interception_driver\"),\n            target_os = \"linux\",\n            target_os = \"android\",\n            target_os = \"unknown\"\n        ))]\n        {\n            #[cfg(all(target_os = \"windows\", feature = \"interception_driver\"))]\n            {\n                if self.mouse_movement_key.lock().is_none()\n                    && cfg.options.mouse_movement_key.is_some()\n                {\n                    log::warn!(\n                        \"defcfg option mouse-movement-key will not take effect until kanata is restarted!\"\n                    );\n                }\n            }\n\n            *self.mouse_movement_key.lock() = cfg.options.mouse_movement_key;\n        }\n\n        PRESSED_KEYS.lock().clear();\n\n        #[cfg(feature = \"tcp_server\")]\n        if let Some(tx) = _tx {\n            let new = self.layer_info[cur_layer].name.clone();\n            match tx.try_send(ServerMessage::LayerChange { new }) {\n                Ok(_) => {}\n                Err(error) => {\n                    log::error!(\"could not send LayerChange event notification: {}\", error);\n                }\n            }\n        }\n        #[cfg(all(target_os = \"windows\", feature = \"gui\"))]\n        send_gui_cfg_notice();\n\n        #[cfg(feature = \"tcp_server\")]\n        {\n            self.last_reload_ok = true;\n        }\n\n        Ok(())\n    }\n\n    /// Update keyberon layout state for press/release, handle repeat separately\n    pub fn handle_input_event(&mut self, event: &KeyEvent) -> Result<()> {\n        log::debug!(\"process recv ev {event:?}\");\n        let evc: u16 = event.code.into();\n        self.ticks_since_idle = 0;\n        let kbrn_ev = match event.value {\n            KeyValue::Press => {\n                if let Some((macro_id, recorded_macro)) = record_press(\n                    &mut self.dynamic_macro_record_state,\n                    event.code,\n                    self.dynamic_macro_max_presses,\n                ) {\n                    self.dynamic_macros.insert(macro_id, recorded_macro);\n                }\n                if self.macro_on_press_cancel_duration > 0 {\n                    log::debug!(\"cancelling all macros: other press\");\n                    self.macro_on_press_cancel_duration = 0;\n                    let layout = self.layout.bm();\n                    layout.active_sequences.clear();\n                    layout.states.retain(|s| {\n                        !matches!(s, State::FakeKey { .. } | State::RepeatingSequence { .. })\n                    });\n                }\n                Event::Press(0, evc)\n            }\n            KeyValue::Release => {\n                record_release(&mut self.dynamic_macro_record_state, event.code);\n                Event::Release(0, evc)\n            }\n            KeyValue::Repeat => {\n                let ret = self.handle_repeat(event);\n                return ret;\n            }\n            KeyValue::Tap => {\n                self.layout.bm().event(Event::Press(0, evc));\n                self.layout.bm().event(Event::Release(0, evc));\n                return Ok(());\n            }\n            KeyValue::WakeUp => {\n                return Ok(());\n            }\n        };\n        self.layout.bm().event(kbrn_ev);\n        Ok(())\n    }\n\n    /// Returns the number of ms elapsed for the procesing loop according to current monotonic time\n    /// and stored internal state. Mutates the internal time-tracking state.\n    pub fn get_ms_elapsed(&mut self) -> u128 {\n        let now = web_time::Instant::now();\n        let ms_count_result = count_ms_elapsed(self.last_tick, now, self.time_remainder);\n        let ms_elapsed = ms_count_result.ms_elapsed;\n        self.time_remainder = ms_count_result.ms_remainder_in_ns;\n        self.last_tick = match ms_elapsed {\n            0..=10 => ms_count_result.last_tick,\n            // If too many ms elapsed, probably doing a tight loop of something that's quite\n            // expensive, e.g. click spamming. To avoid a growing ms_elapsed due to trying and\n            // failing to catch up, reset last_tick to the \"actual now\" instead the \"past now\"\n            // even though that means ticks will be missed - meaning there will be fewer than\n            // 1000 ticks in 1ms on average. In practice, there will already be fewer than 1000\n            // ticks in 1ms when running expensive operations, this just avoids having tens to\n            // thousands of ticks all happening as soon as the expensive operations end.\n            _ => web_time::Instant::now(),\n        };\n\n        ms_elapsed\n    }\n\n    /// Advance keyberon layout state and send events based on changes to its state.\n    /// Returns the number of ticks that elapsed.\n    fn handle_time_ticks(&mut self, tx: &Option<Sender<ServerMessage>>) -> Result<u16> {\n        let ms_elapsed = self.get_ms_elapsed();\n        self.tick_ms(ms_elapsed, tx)?;\n\n        if self.live_reload_requested\n            && ((self.prev_keys.is_empty() && self.cur_keys.is_empty())\n                || self.ticks_since_idle > 1000)\n        {\n            // Note regarding the ticks_since_idle check above:\n            // After 1 second if live reload is still not done, there might be a key in a stuck\n            // state. One known instance where this happens is Win+L to lock the screen in\n            // Windows with the LLHOOK mechanism. The release of Win and L keys will not be\n            // caught by the kanata process when on the lock screen. However, the OS knows that\n            // these keys have released - only the kanata state is wrong. And since kanata has\n            // a key in a stuck state, without this 1s fallback, live reload would never\n            // activate. Having this fallback allows live reload to happen which resets the\n            // kanata states.\n            self.live_reload_requested = false;\n            if let Err(e) = self.do_live_reload(tx) {\n                log::error!(\"live reload failed {e}\");\n            }\n        }\n\n        #[cfg(feature = \"perf_logging\")]\n        log::info!(\"ms elapsed: {ms_elapsed}\");\n        // Note regarding `as` casting. It doesn't really matter if the result would truncate and\n        // end up being wrong. Prefer to do the cheaper operation, as compared to doing the min of\n        // u16::MAX and ms_elapsed.\n        Ok(ms_elapsed as u16)\n    }\n\n    pub fn tick_ms(&mut self, ms_elapsed: u128, _tx: &Option<Sender<ServerMessage>>) -> Result<()> {\n        let mut extra_ticks: u16 = 0;\n        for _ in 0..ms_elapsed {\n            self.tick_states(_tx)?;\n            if let Some(event) = tick_replay_state(\n                &mut self.dynamic_macro_replay_state,\n                self.dynamic_macro_replay_behaviour,\n            ) {\n                self.layout.bm().event(event.key_event());\n                extra_ticks = extra_ticks.saturating_add(event.delay());\n                log::debug!(\"dyn macro extra ticks: {extra_ticks}, ms_elapsed: {ms_elapsed}\");\n            }\n        }\n        for i in 0..(extra_ticks.saturating_sub(ms_elapsed as u16)) {\n            self.tick_states(_tx)?;\n            if tick_replay_state(\n                &mut self.dynamic_macro_replay_state,\n                self.dynamic_macro_replay_behaviour,\n            )\n            .is_some()\n            {\n                log::error!(\"overshot to next event at iteration #{i}, the code is broken!\");\n                break;\n            }\n        }\n        Ok(())\n    }\n\n    fn tick_held_vkeys(&mut self) {\n        if self.vkeys_pending_release.is_empty() {\n            return;\n        }\n        let layout = self.layout.bm();\n        self.vkeys_pending_release.retain(|coord, deadline| {\n            *deadline = deadline.saturating_sub(1);\n            match deadline {\n                0 => {\n                    layout.event(Event::Release(coord.x, coord.y));\n                    false\n                }\n                _ => true,\n            }\n        });\n    }\n\n    fn tick_states(&mut self, _tx: &Option<Sender<ServerMessage>>) -> Result<()> {\n        self.live_reload_requested |= self.handle_keystate_changes(_tx)?;\n        self.handle_scrolling()?;\n        self.handle_move_mouse()?;\n        self.tick_sequence_state()?;\n        self.tick_idle_timeout();\n        self.tick_physical_idle_timeout();\n        self.macro_on_press_cancel_duration = self.macro_on_press_cancel_duration.saturating_sub(1);\n        tick_record_state(&mut self.dynamic_macro_record_state);\n        zippy_tick(self.caps_word.is_some());\n        self.prev_keys.clear();\n        self.prev_keys.append(&mut self.cur_keys);\n        self.tick_held_vkeys();\n        #[cfg(feature = \"simulated_output\")]\n        {\n            self.kbd_out.tick();\n        }\n        Ok(())\n    }\n\n    fn handle_scrolling(&mut self) -> Result<()> {\n        if let Some((direction, distance)) = update_scrollstate_get_result(&mut self.scroll_state) {\n            match distance {\n                0 => {\n                    self.scroll_state = None;\n                }\n                _ => {\n                    self.kbd_out.scroll(direction, distance)?;\n                }\n            }\n        }\n        if let Some((direction, distance)) = update_scrollstate_get_result(&mut self.hscroll_state)\n        {\n            match distance {\n                0 => {\n                    self.hscroll_state = None;\n                }\n                _ => {\n                    self.kbd_out.scroll(direction, distance)?;\n                }\n            }\n        }\n        Ok(())\n    }\n\n    fn handle_move_mouse(&mut self) -> Result<()> {\n        if let Some(mmsv) = &mut self.move_mouse_state_vertical\n            && let Some(mmas) = &mut mmsv.move_mouse_accel_state\n        {\n            if mmas.accel_ticks_until_max != 0 {\n                let increment =\n                    (mmas.accel_increment * f64::from(mmas.accel_ticks_from_min)) as u16;\n                mmsv.distance = mmas.min_distance + increment;\n                mmas.accel_ticks_from_min += 1;\n                mmas.accel_ticks_until_max -= 1;\n            } else {\n                mmsv.distance = mmas.max_distance;\n            }\n        }\n        if let Some(mmsv) = &mut self.move_mouse_state_vertical {\n            if mmsv.ticks_until_move == 0 {\n                mmsv.ticks_until_move = mmsv.interval - 1;\n                let scaled_distance =\n                    apply_mouse_distance_modifiers(mmsv.distance, &self.move_mouse_speed_modifiers);\n                log::debug!(\"handle_move_mouse: scaled vdistance: {}\", scaled_distance);\n\n                let current_move = CalculatedMouseMove {\n                    direction: mmsv.direction,\n                    distance: scaled_distance,\n                };\n\n                if self.movemouse_smooth_diagonals {\n                    let axis: Axis = current_move.direction.into();\n                    match &self.movemouse_buffer {\n                        Some((previous_axis, previous_move)) => {\n                            if axis == *previous_axis {\n                                self.kbd_out.move_mouse(*previous_move)?;\n                                self.movemouse_buffer = Some((axis, current_move));\n                            } else {\n                                self.kbd_out\n                                    .move_mouse_many(&[*previous_move, current_move])?;\n                                self.movemouse_buffer = None;\n                            }\n                        }\n                        None => {\n                            self.movemouse_buffer = Some((axis, current_move));\n                        }\n                    }\n                } else {\n                    self.kbd_out.move_mouse(current_move)?;\n                }\n            } else {\n                mmsv.ticks_until_move -= 1;\n            }\n        }\n        if let Some(mmsh) = &mut self.move_mouse_state_horizontal\n            && let Some(mmas) = &mut mmsh.move_mouse_accel_state\n        {\n            if mmas.accel_ticks_until_max != 0 {\n                let increment =\n                    (mmas.accel_increment * f64::from(mmas.accel_ticks_from_min)) as u16;\n                mmsh.distance = mmas.min_distance + increment;\n                mmas.accel_ticks_from_min += 1;\n                mmas.accel_ticks_until_max -= 1;\n            } else {\n                mmsh.distance = mmas.max_distance;\n            }\n        }\n        if let Some(mmsh) = &mut self.move_mouse_state_horizontal {\n            if mmsh.ticks_until_move == 0 {\n                mmsh.ticks_until_move = mmsh.interval - 1;\n                let scaled_distance =\n                    apply_mouse_distance_modifiers(mmsh.distance, &self.move_mouse_speed_modifiers);\n                log::debug!(\"handle_move_mouse: scaled hdistance: {}\", scaled_distance);\n\n                let current_move = CalculatedMouseMove {\n                    direction: mmsh.direction,\n                    distance: scaled_distance,\n                };\n\n                if self.movemouse_smooth_diagonals {\n                    let axis: Axis = current_move.direction.into();\n                    match &self.movemouse_buffer {\n                        Some((previous_axis, previous_move)) => {\n                            if axis == *previous_axis {\n                                self.kbd_out.move_mouse(*previous_move)?;\n                                self.movemouse_buffer = Some((axis, current_move));\n                            } else {\n                                self.kbd_out\n                                    .move_mouse_many(&[*previous_move, current_move])?;\n                                self.movemouse_buffer = None;\n                            }\n                        }\n                        None => {\n                            self.movemouse_buffer = Some((axis, current_move));\n                        }\n                    }\n                } else {\n                    self.kbd_out.move_mouse(current_move)?;\n                }\n            } else {\n                mmsh.ticks_until_move -= 1;\n            }\n        }\n        Ok(())\n    }\n\n    fn tick_sequence_state(&mut self) -> Result<()> {\n        if let Some(state) = self.sequence_state.get_active() {\n            state.ticks_until_timeout -= 1;\n            if state.ticks_until_timeout == 0 {\n                log::debug!(\"sequence timeout; exiting sequence state\");\n                cancel_sequence(state, &mut self.kbd_out)?;\n            }\n        }\n        Ok(())\n    }\n\n    fn tick_idle_timeout(&mut self) {\n        if self.waiting_for_idle.is_empty() {\n            return;\n        }\n        self.waiting_for_idle.retain(|wfd| {\n            if self.ticks_since_idle >= wfd.idle_duration {\n                // Process this and return false so that it is not retained.\n                let layout = self.layout.bm();\n                let Coord { x, y } = wfd.coord;\n                handle_fakekey_action(wfd.action, layout, x, y);\n                false\n            } else {\n                true\n            }\n        })\n    }\n\n    fn tick_physical_idle_timeout(&mut self) {\n        if self.waiting_for_physical_idle.is_empty() {\n            return;\n        }\n        self.waiting_for_physical_idle.retain(|wfd| {\n            if self.ticks_since_physical_idle >= wfd.idle_duration {\n                // Process this and return false so that it is not retained.\n                let layout = self.layout.bm();\n                let Coord { x, y } = wfd.coord;\n                handle_fakekey_action(wfd.action, layout, x, y);\n                false\n            } else {\n                true\n            }\n        })\n    }\n\n    /// Sends OS key events according to the change in key state between the current and the\n    /// previous keyberon keystate. Also processes any custom actions.\n    ///\n    /// Updates self.cur_keys.\n    ///\n    /// Returns whether live reload was requested.\n    fn handle_keystate_changes(&mut self, _tx: &Option<Sender<ServerMessage>>) -> Result<bool> {\n        let layout = self.layout.bm();\n        let custom_event = layout.tick();\n\n        #[cfg(feature = \"tcp_server\")]\n        if let Some(hold_info) = layout.tap_hold_tracker.take_hold_activated()\n            && hold_info.coord.0 == NORMAL_KEY_ROW\n            && let Some(tx) = _tx\n        {\n            let osc = OsCode::from(hold_info.coord.1);\n            let key = osc.to_string().to_lowercase();\n            log::debug!(\"HoldActivated: key={key} coord={:?}\", hold_info.coord);\n            match tx.try_send(ServerMessage::HoldActivated { key }) {\n                Ok(_) => {}\n                Err(error) => {\n                    log::error!(\"could not send HoldActivated event: {}\", error);\n                }\n            }\n        }\n        #[cfg(feature = \"tcp_server\")]\n        if let Some(tap_info) = layout.tap_hold_tracker.take_tap_activated()\n            && tap_info.coord.0 == NORMAL_KEY_ROW\n            && let Some(tx) = _tx\n        {\n            let osc = OsCode::from(tap_info.coord.1);\n            let key = osc.to_string().to_lowercase();\n            log::debug!(\"TapActivated: key={key} coord={:?}\", tap_info.coord);\n            match tx.try_send(ServerMessage::TapActivated { key }) {\n                Ok(_) => {}\n                Err(error) => {\n                    log::error!(\"could not send TapActivated event: {}\", error);\n                }\n            }\n        }\n\n        let mut live_reload_requested = false;\n        let cur_keys = &mut self.cur_keys;\n        cur_keys.extend(layout.keycodes());\n        let mut reverse_release_order = false;\n\n        // Deal with unmodded. Unlike other custom actions, this should come before key presses and\n        // releases. I don't quite remember why custom actions come after the key processing, but I\n        // remember that it is intentional. However, since unmodded needs to modify the key lists,\n        // it should come before.\n        match custom_event {\n            CustomEvent::Press(custacts) => {\n                for custact in custacts.iter() {\n                    match custact {\n                        CustomAction::Unmodded { keys, mods } => {\n                            self.unmodded_keys.extend(keys.iter());\n                            self.unmodded_mods = *mods;\n                        }\n                        CustomAction::Unshifted { keys } => {\n                            self.unshifted_keys.extend(keys.iter());\n                        }\n                        _ => {}\n                    }\n                }\n            }\n            CustomEvent::Release(custacts) => {\n                for custact in custacts.iter() {\n                    match custact {\n                        CustomAction::Unmodded { keys, mods: _ } => {\n                            self.unmodded_keys.retain(|k| !keys.contains(k));\n                        }\n                        CustomAction::Unshifted { keys } => {\n                            self.unshifted_keys.retain(|k| !keys.contains(k));\n                        }\n                        CustomAction::ReverseReleaseOrder => {\n                            reverse_release_order = true;\n                        }\n                        _ => {}\n                    }\n                }\n            }\n            _ => {}\n        }\n        if !self.unmodded_keys.is_empty() {\n            for mod_key in self.unmodded_mods.iter() {\n                let kc = match mod_key {\n                    UnmodMods::LSft => KeyCode::LShift,\n                    UnmodMods::RSft => KeyCode::RShift,\n                    UnmodMods::LAlt => KeyCode::LAlt,\n                    UnmodMods::RAlt => KeyCode::RAlt,\n                    UnmodMods::LCtl => KeyCode::LCtrl,\n                    UnmodMods::RCtl => KeyCode::RCtrl,\n                    UnmodMods::LMet => KeyCode::LGui,\n                    UnmodMods::RMet => KeyCode::RGui,\n                    _ => unreachable!(\"all bits of u8 should be covered\"), // test_unmodmods_bits\n                };\n                cur_keys.retain(|k| *k != kc);\n            }\n            cur_keys.extend(self.unmodded_keys.iter());\n        }\n        if !self.unshifted_keys.is_empty() {\n            cur_keys.retain(|k| !matches!(k, KeyCode::LShift | KeyCode::RShift));\n            cur_keys.extend(self.unshifted_keys.iter());\n        }\n\n        self.overrides.override_keys(\n            cur_keys,\n            &mut self.override_states,\n            layout.current_layer() as u16,\n        );\n        mark_overridden_nonmodkeys_for_eager_erasure(&self.override_states, &mut layout.states);\n        if self.override_release_on_activation {\n            for removed in self.override_states.removed_oscs() {\n                if !removed.is_modifier() {\n                    layout.states.retain(|s| {\n                        s.release_state(ReleasableState::KeyCode(removed.into()))\n                            .is_some()\n                    });\n                }\n            }\n        }\n\n        if let Some(caps_word) = &mut self.caps_word\n            && caps_word.tick_maybe_add_lsft(cur_keys) == CapsWordNextState::End\n        {\n            self.caps_word = None;\n        }\n\n        // Release keys that do not exist in the current state but exist in the previous state.\n        // This used to use a HashSet but it was changed to a Vec because the order of operations\n        // matters.\n        //\n        // BUG(sequences):\n        //\n        // With hidden-delay-type or hidden-suppressed,\n        // sequences will unexpectedly send releases\n        // for the presses that would otherwise have happened.\n        // This is because the press is skipped but the keys make it\n        // into `self.prev_keys` and the OS release event is sent in the code below.\n        //\n        // There haven't been any reports of negative consequences of this behaviour,\n        // but it is unusual and ideally wouldn't happen, so I tried to fix it anyway.\n        // But I was unsuccessful. Approach tried:\n        //\n        // - clear `self.cur_keys` and `layout.states` of outputted keys\n        //   when a sequence is active, for the impacted sequence modes.\n        //\n        // This approach fails because it keeping `layout.states` intact\n        // is necessary to complete chorded sequences, e.g. `S-(a b c)`.\n        // Clearing the `lsft` means the above sequence is impossible to complete.\n        //\n        // Another approach that might work, which has not been attempted,\n        // is to keep track of oskbd events that have actually been sent.\n        // Then, a release can only be sent if an un-released corresponding press\n        // has been pressed in the past.\n        // However, this doesn't seem worth the:\n        //\n        // - runtime cost\n        // - work involved to add the code\n        // - ongoing burden of maintaining that code\n        //\n        // Given that there appears to be no practical negative consequences for this bug\n        // remaining.\n        log::trace!(\"{:?}\", &self.prev_keys);\n        let mut fwd_release = self.prev_keys.iter();\n        let mut rev_release = self.prev_keys.iter().rev();\n        let keys: &mut dyn Iterator<Item = &KeyCode> = match reverse_release_order {\n            false => &mut fwd_release,\n            true => &mut rev_release,\n        };\n        for k in keys {\n            if cur_keys.contains(k) {\n                continue;\n            }\n            log::debug!(\"key release   {:?}\", k);\n            if let Err(e) = release_key(&mut self.kbd_out, k.into()) {\n                bail!(\"failed to release key: {:?}\", e);\n            }\n        }\n\n        if cur_keys.is_empty()\n            && !self.prev_keys.is_empty()\n            && let Some(state) = self.sequence_state.get_active()\n        {\n            use kanata_parser::trie::GetOrDescendentExistsResult::*;\n            state.overlapped_sequence.push(KEY_OVERLAP_MARKER);\n            match self\n                .sequences\n                .get_or_descendant_exists(&state.overlapped_sequence)\n            {\n                HasValue((i, j)) => {\n                    do_successful_sequence_termination(\n                        &mut self.kbd_out,\n                        state,\n                        layout,\n                        i,\n                        j,\n                        EndSequenceType::Overlap,\n                    )?;\n                }\n                NotInTrie => {\n                    // Overwrite overlapped with non-overlapped tracking\n                    state.overlapped_sequence.clear();\n                    state\n                        .overlapped_sequence\n                        .extend(state.sequence.iter().copied());\n                }\n                InTrie => {}\n            }\n        }\n\n        // Press keys that exist in the current state but are missing from the previous state.\n        // Comment above regarding Vec/HashSet also applies here.\n        log::trace!(\"{cur_keys:?}\");\n        for k in cur_keys.iter() {\n            if self.prev_keys.contains(k) {\n                log::trace!(\"{k:?} is old press\");\n                continue;\n            }\n            // Note - keyberon can return duplicates of a key in the keycodes()\n            // iterator. Instead of trying to fix it in the keyberon library, It\n            // seems better to fix it in the kanata logic. Keyberon iterates over\n            // its internal state array with very simple filtering logic when\n            // calling keycodes(). It would be troublesome to add deduplication\n            // logic there and is easier to add here since we already have\n            // allocations and logic.\n            self.prev_keys.push(*k);\n            self.last_pressed_key = *k;\n\n            if self.sequence_always_on && self.sequence_state.is_inactive() {\n                self.sequence_state\n                    .activate(self.sequence_input_mode, self.sequence_timeout);\n            }\n\n            if let Some(state) = self.sequence_state.get_active() {\n                do_sequence_press_logic(\n                    state,\n                    k,\n                    get_mod_mask_for_cur_keys(cur_keys),\n                    &mut self.kbd_out,\n                    &self.sequences,\n                    self.sequence_backtrack_modcancel,\n                    layout,\n                )?;\n            } else {\n                log::debug!(\"key press     {:?}\", k);\n                if let Err(e) = press_key(&mut self.kbd_out, k.into()) {\n                    bail!(\"failed to press key: {:?}\", e);\n                }\n            }\n        }\n\n        // Handle custom events. This used to be in a separate function but lifetime issues cause\n        // it to now be here.\n        match custom_event {\n            CustomEvent::Press(custacts) => {\n                #[cfg(feature = \"cmd\")]\n                let mut cmds = vec![];\n                let mut prev_mouse_btn = None;\n                let mut reload_action: Option<ReloadAction> = None;\n                for custact in custacts.iter() {\n                    match custact {\n                        // For unicode, only send on the press. No repeat action is supported for this for\n                        // now.\n                        CustomAction::Unicode(c) => self.kbd_out.send_unicode(*c)?,\n                        CustomAction::LiveReload => {\n                            reload_action = Some(ReloadAction::Reload);\n                        }\n                        CustomAction::LiveReloadNext => {\n                            reload_action = Some(ReloadAction::ReloadNext);\n                        }\n                        CustomAction::LiveReloadPrev => {\n                            reload_action = Some(ReloadAction::ReloadPrev);\n                        }\n                        CustomAction::LiveReloadNum(n) => {\n                            reload_action = Some(ReloadAction::ReloadNum(usize::from(*n)));\n                        }\n                        CustomAction::LiveReloadFile(path) => {\n                            reload_action = Some(ReloadAction::ReloadFile(path.to_string()));\n                        }\n                        CustomAction::Mouse(btn) => {\n                            log::debug!(\"click     {:?}\", btn);\n                            if let Some(pbtn) = prev_mouse_btn {\n                                log::debug!(\"unclick   {:?}\", pbtn);\n                                self.kbd_out.release_btn(pbtn)?;\n                            }\n                            self.kbd_out.click_btn(*btn)?;\n                            prev_mouse_btn = Some(*btn);\n                        }\n                        CustomAction::MouseTap(btn) => {\n                            log::debug!(\"click     {:?}\", btn);\n                            self.kbd_out.click_btn(*btn)?;\n                            log::debug!(\"unclick   {:?}\", btn);\n                            self.kbd_out.release_btn(*btn)?;\n                        }\n                        CustomAction::MWheel {\n                            direction,\n                            interval,\n                            distance,\n                            inertial_scroll_params,\n                        } => match direction {\n                            MWheelDirection::Up | MWheelDirection::Down => {\n                                self.scroll_state = Some(ScrollState {\n                                    direction: *direction,\n                                    distance: *distance,\n                                    ticks_until_scroll: 0,\n                                    interval: *interval,\n                                    scroll_accel_state: inertial_scroll_params.as_ref().map(|isp|\n                                        ScrollAccelState {\n                                        deceleration_multiplier: isp.deceleration_multiplier.0,\n                                        acceleration_multiplier: isp.acceleration_multiplier.0,\n                                        max_velocity: isp.maximum_velocity.0,\n                                        current_velocity: isp.initial_velocity.0,\n                                        scroll_released: false,\n                                        }\n                                    ),\n                                })\n                            }\n                            MWheelDirection::Left | MWheelDirection::Right => {\n                                self.hscroll_state = Some(ScrollState {\n                                    direction: *direction,\n                                    distance: *distance,\n                                    ticks_until_scroll: 0,\n                                    interval: *interval,\n                                    scroll_accel_state: None,\n                                })\n                            }\n                        },\n                        CustomAction::MWheelNotch { direction } => {\n                            self.kbd_out\n                                .scroll(*direction, HI_RES_SCROLL_UNITS_IN_LO_RES)?;\n                        }\n                        CustomAction::MoveMouse {\n                            direction,\n                            interval,\n                            distance,\n                        } => match direction {\n                            MoveDirection::Up | MoveDirection::Down => {\n                                self.move_mouse_state_vertical = Some(MoveMouseState {\n                                    direction: *direction,\n                                    distance: *distance,\n                                    ticks_until_move: 0,\n                                    interval: *interval,\n                                    move_mouse_accel_state: None,\n                                })\n                            }\n                            MoveDirection::Left | MoveDirection::Right => {\n                                self.move_mouse_state_horizontal = Some(MoveMouseState {\n                                    direction: *direction,\n                                    distance: *distance,\n                                    ticks_until_move: 0,\n                                    interval: *interval,\n                                    move_mouse_accel_state: None,\n                                })\n                            }\n                        },\n                        CustomAction::MoveMouseAccel {\n                            direction,\n                            interval,\n                            accel_time,\n                            min_distance,\n                            max_distance,\n                        } => {\n                            let move_mouse_accel_state = match (\n                                self.movemouse_inherit_accel_state,\n                                &self.move_mouse_state_horizontal,\n                                &self.move_mouse_state_vertical,\n                            ) {\n                                (\n                                    true,\n                                    Some(MoveMouseState {\n                                        move_mouse_accel_state: Some(s),\n                                        ..\n                                    }),\n                                    _,\n                                )\n                                | (\n                                    true,\n                                    _,\n                                    Some(MoveMouseState {\n                                        move_mouse_accel_state: Some(s),\n                                        ..\n                                    }),\n                                ) => *s,\n                                _ => {\n                                    let f_max_distance: f64 = *max_distance as f64;\n                                    let f_min_distance: f64 = *min_distance as f64;\n                                    let f_accel_time: f64 = *accel_time as f64;\n                                    let increment =\n                                        (f_max_distance - f_min_distance) / f_accel_time;\n\n                                    MoveMouseAccelState {\n                                        accel_ticks_from_min: 0,\n                                        accel_ticks_until_max: *accel_time,\n                                        accel_increment: increment,\n                                        min_distance: *min_distance,\n                                        max_distance: *max_distance,\n                                    }\n                                }\n                            };\n\n                            match direction {\n                                MoveDirection::Up | MoveDirection::Down => {\n                                    self.move_mouse_state_vertical = Some(MoveMouseState {\n                                        direction: *direction,\n                                        distance: *min_distance,\n                                        ticks_until_move: 0,\n                                        interval: *interval,\n                                        move_mouse_accel_state: Some(move_mouse_accel_state),\n                                    })\n                                }\n                                MoveDirection::Left | MoveDirection::Right => {\n                                    self.move_mouse_state_horizontal = Some(MoveMouseState {\n                                        direction: *direction,\n                                        distance: *min_distance,\n                                        ticks_until_move: 0,\n                                        interval: *interval,\n                                        move_mouse_accel_state: Some(move_mouse_accel_state),\n                                    })\n                                }\n                            }\n                        }\n                        CustomAction::MoveMouseSpeed { speed } => {\n                            self.move_mouse_speed_modifiers.push(*speed);\n                            log::debug!(\n                                \"movemousespeed modifiers: {:?}\",\n                                self.move_mouse_speed_modifiers\n                            );\n                        }\n                        CustomAction::Cmd(_cmd) => {\n                            #[cfg(feature = \"cmd\")]\n                            cmds.push((\n                                Some(log::Level::Info),\n                                Some(log::Level::Error),\n                                Vec::from_iter(_cmd.iter().map(|s| s.to_string())),\n                            ));\n                        }\n                        CustomAction::CmdLog(_log_level, _error_log_level, _cmd) => {\n                            #[cfg(feature = \"cmd\")]\n                            cmds.push((\n                                _log_level.get_level(),\n                                _error_log_level.get_level(),\n                                Vec::from_iter(_cmd.iter().map(|s| s.to_string())),\n                            ));\n                        }\n                        CustomAction::CmdOutputKeys(_cmd) => {\n                            #[cfg(feature = \"cmd\")]\n                            {\n                                // Maybe improvement in the future:\n                                // A delay here, as in KeyAction::Delay, will pause the entire\n                                // state machine loop. That is _probably_ OK, but ideally this\n                                // would be done in a separate thread or somehow\n                                for key_action in keys_for_cmd_output(_cmd) {\n                                    match key_action {\n                                        KeyAction::Press(osc) => press_key(&mut self.kbd_out, osc)?,\n                                        KeyAction::Release(osc) => {\n                                            release_key(&mut self.kbd_out, osc)?\n                                        }\n                                        KeyAction::Delay(delay) => std::thread::sleep(\n                                            std::time::Duration::from_millis(u64::from(delay)),\n                                        ),\n                                    }\n                                }\n                            }\n                        }\n                        CustomAction::PushMessage(_message) => {\n                            log::debug!(\"Action push-msg\");\n                            #[cfg(feature = \"tcp_server\")]\n                            if let Some(tx) = _tx {\n                                let message = simple_sexpr_to_json_array(_message);\n                                log::debug!(\"Action push-msg message: {}\", message);\n                                match tx.try_send(ServerMessage::MessagePush { message }) {\n                                    Ok(_) => {}\n                                    Err(error) => {\n                                        log::error!(\n                                            \"could not send {} event notification: {}\",\n                                            PUSH_MESSAGE,\n                                            error\n                                        );\n                                    }\n                                }\n                            }\n                            #[cfg(feature = \"tcp_server\")]\n                            if self.tcp_server_address.is_none() {\n                                log::warn!(\"{} was used, but TCP server is not running. did you specify a port?\", PUSH_MESSAGE);\n                            }\n                            #[cfg(not(feature = \"tcp_server\"))]\n                            log::warn!(\n                                \"{} was used, but Kanata was compiled with TCP server disabled.\",\n                                PUSH_MESSAGE\n                            );\n                        }\n                        CustomAction::FakeKey { coord, action } => {\n                            let (x, y) = (coord.x, coord.y);\n                            log::debug!(\n                                \"fake key on press   {action:?} {:?},{x:?},{y:?} {:?}\",\n                                layout.default_layer,\n                                layout.layers[layout.default_layer][x as usize][y as usize]\n                            );\n                            handle_fakekey_action(*action, layout, x, y);\n                        }\n                        CustomAction::Delay(delay) => {\n                            log::debug!(\"on-press: sleeping for {delay} ms\");\n                            std::thread::sleep(time::Duration::from_millis((*delay).into()));\n                        }\n                        CustomAction::SequenceCancel => {\n                            if let Some(state) = self.sequence_state.get_active() {\n                                log::debug!(\"pressed cancel sequence key\");\n                                cancel_sequence(state, &mut self.kbd_out)?;\n                            }\n                        }\n                        CustomAction::SequenceLeader(timeout, input_mode) => {\n                            if self.sequence_state.is_inactive() {\n                                log::debug!(\"entering sequence mode\");\n                                self.sequence_state.activate(*input_mode, *timeout);\n                            } else if *input_mode == SequenceInputMode::HiddenSuppressed {\n                                log::debug!(\"retriggering sequence mode\");\n                                self.sequence_state.activate(*input_mode, *timeout);\n                            }\n                        }\n                        CustomAction::SequenceNoerase(noerase_count) => {\n                            if let Some(state) = self.sequence_state.get_active() {\n                                log::debug!(\"pressed cancel sequence key\");\n                                add_noerase(state, *noerase_count);\n                            }\n                        }\n                        CustomAction::Repeat => {\n                            let keycode = self.last_pressed_key;\n                            let osc: OsCode = keycode.into();\n                            log::debug!(\"repeating a keypress {osc:?}\");\n                            let mut do_caps_word = false;\n                            if !cur_keys.contains(&KeyCode::LShift)\n                                && let Some(ref mut cw) = self.caps_word {\n                                    cur_keys.push(keycode);\n                                    let prev_len = cur_keys.len();\n                                    cw.tick_maybe_add_lsft(cur_keys);\n                                    if cur_keys.len() > prev_len {\n                                        do_caps_word = true;\n                                        press_key(&mut self.kbd_out, OsCode::KEY_LEFTSHIFT)?;\n                                    }\n                                }\n                            // Release key in case the most recently pressed key is still pressed.\n                            release_key(&mut self.kbd_out, osc)?;\n                            press_key(&mut self.kbd_out, osc)?;\n                            release_key(&mut self.kbd_out, osc)?;\n                            if do_caps_word {\n                                self.kbd_out.release_key(OsCode::KEY_LEFTSHIFT)?;\n                            }\n                        }\n                        CustomAction::DynamicMacroRecord(macro_id) => {\n                            if let Some((macro_id, prev_recorded_macro)) =\n                                begin_record_macro(*macro_id, &mut self.dynamic_macro_record_state)\n                            {\n                                log::debug!(\"saving macro {prev_recorded_macro:?}\");\n                                self.dynamic_macros.insert(macro_id, prev_recorded_macro);\n                            }\n                        }\n                        CustomAction::DynamicMacroRecordStop(num_actions_to_remove) => {\n                            if let Some((macro_id, prev_recorded_macro)) = stop_macro(\n                                &mut self.dynamic_macro_record_state,\n                                *num_actions_to_remove,\n                            ) {\n                                log::debug!(\"saving macro {prev_recorded_macro:?}\");\n                                self.dynamic_macros.insert(macro_id, prev_recorded_macro);\n                            }\n                        }\n                        CustomAction::DynamicMacroPlay(macro_id) => {\n                            play_macro(\n                                *macro_id,\n                                &mut self.dynamic_macro_replay_state,\n                                &self.dynamic_macros,\n                            );\n                        }\n                        CustomAction::CancelMacroOnNextPress(duration) => {\n                            self.macro_on_press_cancel_duration = *duration;\n                        }\n                        CustomAction::SendArbitraryCode(code) => {\n                            #[cfg(all(not(feature = \"simulated_output\"), target_os = \"windows\"))]\n                            {\n                                self.kbd_out.write_code_raw(*code, KeyValue::Press)?;\n                            }\n                            #[cfg(any(feature = \"simulated_output\", not(target_os = \"windows\")))]\n                            {\n                                self.kbd_out.write_code(*code as u32, KeyValue::Press)?;\n                            }\n                        }\n                        CustomAction::CapsWord(cfg) => match cfg.repress_behaviour {\n                            CapsWordRepressBehaviour::Overwrite => {\n                                log::trace!(\"caps-word overwrite\");\n                                self.caps_word = Some(CapsWordState::new(cfg));\n                            }\n                            CapsWordRepressBehaviour::Toggle => {\n                                log::trace!(\"caps-word toggle\");\n                                self.caps_word = match self.caps_word {\n                                    Some(_) => None,\n                                    None => Some(CapsWordState::new(cfg)),\n                                };\n                            }\n                        },\n                        CustomAction::SetMouse { x, y } => {\n                            self.kbd_out.set_mouse(*x, *y)?;\n                        }\n                        CustomAction::FakeKeyOnIdle(fkd) => {\n                            self.ticks_since_idle = 0;\n                            self.waiting_for_idle.insert(*fkd);\n                        }\n                        CustomAction::FakeKeyOnPhysicalIdle(fkd) => {\n                            self.ticks_since_physical_idle = 0;\n                            self.waiting_for_physical_idle.insert(*fkd);\n                        }\n                        CustomAction::FakeKeyHoldForDuration(fk_hfd) => {\n                            let duration = fk_hfd.hold_duration;\n                            self.vkeys_pending_release.entry(fk_hfd.coord)\n                                .and_modify(|d| *d = duration)\n                                .or_insert_with(|| {\n                                    let Coord { x, y } = fk_hfd.coord;\n                                    layout.event(Event::Press(x, y));\n                                    duration\n                                });\n                        }\n                        CustomAction::ClipboardSet(clipboard_string) => {\n                            clpb_set(clipboard_string);\n                        }\n                        CustomAction::ClipboardCmdSet(cmd_params) => {\n                            clpb_cmd_set(cmd_params);\n                        }\n                        CustomAction::ClipboardSave(id) => {\n                            clpb_save(*id, &mut self.saved_clipboard_content);\n                        }\n                        CustomAction::ClipboardRestore(id) => {\n                            clpb_restore(*id, &self.saved_clipboard_content);\n                        }\n                        CustomAction::ClipboardSaveSet(id, clipboard_string) => {\n                            clpb_save_set(*id, clipboard_string, &mut self.saved_clipboard_content);\n                        }\n                        CustomAction::ClipboardSaveCmdSet(id, cmd_params) => {\n                            clpb_save_cmd_set(*id, cmd_params, &mut self.saved_clipboard_content);\n                        }\n                        CustomAction::ClipboardSaveSwap(id1, id2) => {\n                            clpb_save_swap(*id1, *id2, &mut self.saved_clipboard_content);\n                        }\n                        CustomAction::FakeKeyOnRelease { .. }\n                        | CustomAction::DelayOnRelease(_)\n                        | CustomAction::Unmodded { .. }\n                        | CustomAction::Unshifted { .. }\n                        // Note: ReverseReleaseOrder is already handled earlier on.\n                        | CustomAction::ReverseReleaseOrder\n                        | CustomAction::CancelMacroOnRelease => {}\n                    }\n                }\n                #[cfg(feature = \"cmd\")]\n                run_multi_cmd(cmds);\n\n                // Process reload actions after releasing the layout borrow\n                if let Some(action) = reload_action {\n                    let reload_succeeded = match action {\n                        ReloadAction::Reload => {\n                            self.request_live_reload();\n                            true\n                        }\n                        ReloadAction::ReloadNext => {\n                            self.request_live_reload_next();\n                            true\n                        }\n                        ReloadAction::ReloadPrev => {\n                            self.request_live_reload_prev();\n                            true\n                        }\n                        ReloadAction::ReloadNum(n) => {\n                            if let Err(e) = self.request_live_reload_num(n) {\n                                log::error!(\"{}\", e);\n                                false\n                            } else {\n                                true\n                            }\n                        }\n                        ReloadAction::ReloadFile(path) => {\n                            if let Err(e) = self.request_live_reload_file(path) {\n                                log::error!(\"{}\", e);\n                                false\n                            } else {\n                                true\n                            }\n                        }\n                    };\n\n                    if reload_succeeded {\n                        live_reload_requested = true;\n                    }\n                }\n            }\n\n            CustomEvent::Release(custacts) => {\n                // Unclick only the last mouse button\n                if let Some(Err(e)) = custacts\n                    .iter()\n                    .fold(None, |pbtn, ac| match ac {\n                        CustomAction::Mouse(btn) => Some(btn),\n                        CustomAction::MWheel { direction, .. } => {\n                            match direction {\n                                MWheelDirection::Up | MWheelDirection::Down => {\n                                    if let Some(ss) = &mut self.scroll_state\n                                        && ss.direction == *direction\n                                    {\n                                        ss.distance = 0;\n                                        if let Some(acs) = &mut ss.scroll_accel_state {\n                                            acs.scroll_released = true\n                                        }\n                                    }\n                                }\n                                MWheelDirection::Left | MWheelDirection::Right => {\n                                    if let Some(ss) = &mut self.hscroll_state\n                                        && ss.direction == *direction\n                                    {\n                                        ss.distance = 0;\n                                        if let Some(acs) = &mut ss.scroll_accel_state {\n                                            acs.scroll_released = true\n                                        }\n                                    }\n                                }\n                            }\n                            pbtn\n                        }\n                        CustomAction::MoveMouse { direction, .. }\n                        | CustomAction::MoveMouseAccel { direction, .. } => {\n                            match direction {\n                                MoveDirection::Up | MoveDirection::Down => {\n                                    if let Some(move_mouse_state_vertical) =\n                                        &self.move_mouse_state_vertical\n                                        && move_mouse_state_vertical.direction == *direction\n                                    {\n                                        self.move_mouse_state_vertical = None;\n                                    }\n                                }\n                                MoveDirection::Left | MoveDirection::Right => {\n                                    if let Some(move_mouse_state_horizontal) =\n                                        &self.move_mouse_state_horizontal\n                                        && move_mouse_state_horizontal.direction == *direction\n                                    {\n                                        self.move_mouse_state_horizontal = None;\n                                    }\n                                }\n                            }\n                            if self.movemouse_smooth_diagonals {\n                                self.movemouse_buffer = None\n                            }\n                            pbtn\n                        }\n                        CustomAction::MoveMouseSpeed { speed, .. } => {\n                            if let Some(idx) = self\n                                .move_mouse_speed_modifiers\n                                .iter()\n                                .position(|s| *s == *speed)\n                            {\n                                self.move_mouse_speed_modifiers.remove(idx);\n                            }\n                            log::debug!(\n                                \"movemousespeed modifiers: {:?}\",\n                                self.move_mouse_speed_modifiers\n                            );\n                            pbtn\n                        }\n                        CustomAction::DelayOnRelease(delay) => {\n                            log::debug!(\"on-release: sleeping for {delay} ms\");\n                            std::thread::sleep(time::Duration::from_millis((*delay).into()));\n                            pbtn\n                        }\n                        CustomAction::FakeKeyOnRelease { coord, action } => {\n                            let (x, y) = (coord.x, coord.y);\n                            log::debug!(\"fake key on release {action:?} {x:?},{y:?}\");\n                            handle_fakekey_action(*action, layout, x, y);\n                            pbtn\n                        }\n                        CustomAction::CancelMacroOnRelease => {\n                            log::debug!(\"cancelling all macros: releasable macro\");\n                            layout.active_sequences.clear();\n                            self.macro_on_press_cancel_duration = 0;\n                            layout.states.retain(|s| {\n                                !matches!(\n                                    s,\n                                    State::FakeKey { .. } | State::RepeatingSequence { .. }\n                                )\n                            });\n                            pbtn\n                        }\n                        CustomAction::SendArbitraryCode(code) => {\n                            if let Err(e) = {\n                                #[cfg(all(\n                                    not(feature = \"simulated_output\"),\n                                    target_os = \"windows\"\n                                ))]\n                                {\n                                    self.kbd_out.write_code_raw(*code, KeyValue::Release)\n                                }\n                                #[cfg(any(\n                                    feature = \"simulated_output\",\n                                    not(target_os = \"windows\")\n                                ))]\n                                {\n                                    self.kbd_out.write_code(*code as u32, KeyValue::Release)\n                                }\n                            } {\n                                log::error!(\"failed to release arbitrary code {e:?}\");\n                            }\n                            pbtn\n                        }\n                        _ => pbtn,\n                    })\n                    .map(|btn| {\n                        log::debug!(\"unclick   {:?}\", btn);\n                        self.kbd_out.release_btn(*btn)\n                    })\n                {\n                    bail!(e);\n                }\n            }\n            _ => {}\n        };\n\n        self.check_handle_layer_change(_tx);\n        self.check_release_non_physical_shift()?;\n        Ok(live_reload_requested)\n    }\n\n    #[cfg(feature = \"tcp_server\")]\n    pub fn change_layer(&mut self, layer_name: String) {\n        for (i, l) in self.layer_info.iter().enumerate() {\n            if l.name == layer_name {\n                self.layout.bm().set_default_layer(i);\n                return;\n            }\n        }\n    }\n\n    /// Request a live reload of the current configuration file.\n    pub fn request_live_reload(&mut self) {\n        self.live_reload_requested = true;\n        log::info!(\n            \"Requested live reload of file: {}\",\n            self.cfg_paths[self.cur_cfg_idx].display()\n        );\n    }\n\n    /// Handle a client command from TCP server and return a result.\n    /// This centralizes validation logic and provides proper error messages.\n    #[cfg(feature = \"tcp_server\")]\n    pub fn handle_client_command(\n        &mut self,\n        command: kanata_tcp_protocol::ClientMessage,\n    ) -> Result<()> {\n        use kanata_tcp_protocol::ClientMessage;\n\n        match command {\n            ClientMessage::Reload { .. } => {\n                self.request_live_reload();\n                Ok(())\n            }\n            ClientMessage::ReloadNext { .. } => {\n                self.request_live_reload_next();\n                Ok(())\n            }\n            ClientMessage::ReloadPrev { .. } => {\n                self.request_live_reload_prev();\n                Ok(())\n            }\n            ClientMessage::ReloadNum { index, .. } => self.request_live_reload_num(index),\n            ClientMessage::ReloadFile { path, .. } => self.request_live_reload_file(path),\n            _ => {\n                // For non-reload commands, we don't validate here - they're handled directly in tcp_server\n                Ok(())\n            }\n        }\n    }\n\n    /// Request a live reload of the next configuration file.\n    pub fn request_live_reload_next(&mut self) {\n        self.live_reload_requested = true;\n        self.cur_cfg_idx = if self.cur_cfg_idx == self.cfg_paths.len() - 1 {\n            0\n        } else {\n            self.cur_cfg_idx + 1\n        };\n        log::info!(\n            \"Requested live reload of next file: {}\",\n            self.cfg_paths[self.cur_cfg_idx].display()\n        );\n    }\n\n    /// Request a live reload of the previous configuration file.\n    pub fn request_live_reload_prev(&mut self) {\n        self.live_reload_requested = true;\n        if self.cur_cfg_idx == 0 {\n            self.cur_cfg_idx = self.cfg_paths.len() - 1;\n        } else {\n            self.cur_cfg_idx -= 1;\n        }\n        log::info!(\n            \"Requested live reload of previous file: {}\",\n            self.cfg_paths[self.cur_cfg_idx].display()\n        );\n    }\n\n    /// Request a live reload of the configuration file at the specified index.\n    pub fn request_live_reload_num(&mut self, index: usize) -> Result<()> {\n        if index >= self.cfg_paths.len() {\n            bail!(\n                \"config index {} out of bounds: only {} configs available\",\n                index,\n                self.cfg_paths.len()\n            );\n        }\n        self.live_reload_requested = true;\n        self.cur_cfg_idx = index;\n        log::info!(\n            \"Requested live reload of config file {}: {}\",\n            index,\n            self.cfg_paths[self.cur_cfg_idx].display()\n        );\n        Ok(())\n    }\n\n    /// Request a live reload of the specified configuration file.\n    pub fn request_live_reload_file(&mut self, path: String) -> Result<()> {\n        let new_path = std::path::PathBuf::from(&path);\n        if !new_path.exists() {\n            bail!(\"config file does not exist: {}\", path);\n        }\n        self.live_reload_requested = true;\n        self.cfg_paths.push(new_path);\n        self.cur_cfg_idx = self.cfg_paths.len() - 1;\n        log::info!(\n            \"Requested live reload of file: {}\",\n            self.cfg_paths[self.cur_cfg_idx].display()\n        );\n        Ok(())\n    }\n\n    #[allow(unused_variables)]\n    /// Prints the layer. If the TCP server is enabled, then this will also send a notification to\n    /// all connected clients.\n    fn check_handle_layer_change(&mut self, tx: &Option<Sender<ServerMessage>>) {\n        let cur_layer = self.layout.bm().current_layer();\n        if cur_layer != self.prev_layer {\n            let new = self.layer_info[cur_layer].name.clone();\n            self.prev_layer = cur_layer;\n            self.print_layer(cur_layer);\n\n            #[cfg(feature = \"tcp_server\")]\n            if let Some(tx) = tx {\n                match tx.try_send(ServerMessage::LayerChange { new }) {\n                    Ok(_) => {}\n                    Err(error) => {\n                        log::error!(\"could not send event notification: {}\", error);\n                    }\n                }\n            }\n            #[cfg(all(target_os = \"windows\", feature = \"gui\"))]\n            send_gui_notice();\n        }\n    }\n\n    fn print_layer(&self, layer: usize) {\n        if self.log_layer_changes {\n            log::info!(\"Entered layer:\\n\\n{}\", self.layer_info[layer].cfg_text);\n        }\n    }\n\n    #[cfg(feature = \"tcp_server\")]\n    /// Get engine uptime in seconds\n    pub fn get_uptime_s(&self) -> u64 {\n        self.start_time.elapsed().as_secs()\n    }\n\n    #[cfg(feature = \"tcp_server\")]\n    /// Check if a reload has completed (regardless of success or failure)\n    pub fn is_reload_complete(&self) -> bool {\n        !self.live_reload_requested\n    }\n\n    #[cfg(feature = \"tcp_server\")]\n    /// Whether the most recent reload succeeded.\n    pub fn last_reload_succeeded(&self) -> bool {\n        self.last_reload_ok\n    }\n\n    #[cfg(feature = \"tcp_server\")]\n    pub fn start_notification_loop(\n        rx: Receiver<ServerMessage>,\n        clients: crate::tcp_server::Connections,\n    ) {\n        use std::io::Write;\n        info!(\"listening for event notifications to relay to connected clients\");\n        std::thread::spawn(move || {\n            loop {\n                match rx.recv() {\n                    Err(_) => {\n                        panic!(\"channel disconnected\")\n                    }\n                    Ok(event) => {\n                        let notification = event.as_bytes();\n                        let mut clients = clients.lock();\n                        let mut stale_clients = vec![];\n                        for (id, client) in &mut *clients {\n                            match client.write_all(&notification) {\n                                Ok(_) => {\n                                    log::debug!(\"layer change notification sent\");\n                                }\n                                Err(e) => {\n                                    log::warn!(\n                                        \"removing tcp client where write failed: {id}, {e:?}\"\n                                    );\n                                    // the client is no longer connected, let's remove them\n                                    stale_clients.push(id.clone());\n                                }\n                            }\n                        }\n\n                        for id in &stale_clients {\n                            log::warn!(\"removing disconnected tcp client: {id}\");\n                            clients.remove(id);\n                        }\n                    }\n                }\n            }\n        });\n    }\n\n    #[cfg(not(feature = \"tcp_server\"))]\n    pub fn start_notification_loop(\n        _rx: Receiver<ServerMessage>,\n        _clients: crate::tcp_server::Connections,\n    ) {\n    }\n\n    /// Starts a new thread that processes OS key events and advances the keyberon layout's state.\n    pub fn start_processing_loop(\n        kanata: Arc<Mutex<Self>>,\n        rx: Receiver<KeyEvent>,\n        tx: Option<Sender<ServerMessage>>,\n        nodelay: bool,\n    ) {\n        info!(\"entering the processing loop\");\n        std::thread::spawn(move || {\n            if !nodelay {\n                info!(\"Init: catching only releases and sending immediately\");\n                for _ in 0..500 {\n                    if let Ok(kev) = rx.try_recv()\n                        && kev.value == KeyValue::Release\n                    {\n                        let mut k = kanata.lock();\n                        info!(\"Init: releasing {:?}\", kev.code);\n                        k.kbd_out.release_key(kev.code).expect(\"key released\");\n                    }\n                    std::thread::sleep(time::Duration::from_millis(1));\n                }\n            }\n            let mut ms_elapsed = 0;\n\n            info!(\"Starting kanata proper\");\n\n            #[cfg(not(feature = \"passthru_ahk\"))]\n            info!(\n                \"You may forcefully exit kanata by pressing lctl+spc+esc at any time. \\\n                        These keys refer to defsrc input, meaning BEFORE kanata remaps keys.\"\n            );\n\n            #[cfg(all(not(feature = \"interception_driver\"), target_os = \"windows\"))]\n            let mut idle_clear_happened = false;\n            #[cfg(all(not(feature = \"interception_driver\"), target_os = \"windows\"))]\n            let mut last_input_time = web_time::Instant::now();\n\n            let mut events = Vec::new();\n            let err = loop {\n                let can_block = {\n                    let mut k = kanata.lock();\n                    k.can_block_update_idle_waiting(ms_elapsed)\n                };\n                if can_block {\n                    #[cfg(all(\n                        target_os = \"windows\",\n                        not(feature = \"interception_driver\"),\n                        not(feature = \"simulated_input\"),\n                    ))]\n                    kanata.lock().win_synchronize_keystates();\n\n                    log::trace!(\"blocking on channel\");\n                    match rx.recv() {\n                        Ok(kev) => {\n                            collect_and_sort_events(kev, &rx, &mut events);\n\n                            let mut k = kanata.lock();\n                            let now = web_time::Instant::now()\n                                .checked_sub(time::Duration::from_millis(1))\n                                .expect(\"subtract 1ms from current time\");\n\n                            #[cfg(all(\n                                not(feature = \"interception_driver\"),\n                                target_os = \"windows\"\n                            ))]\n                            clear_states_from_inactivity(\n                                &mut k,\n                                now,\n                                last_input_time,\n                                &mut idle_clear_happened,\n                            );\n                            k.last_tick = now;\n\n                            // Check for live reload BEFORE processing the key event\n                            if k.live_reload_requested\n                                && ((k.prev_keys.is_empty() && k.cur_keys.is_empty())\n                                    || k.ticks_since_idle > 1000)\n                            {\n                                k.live_reload_requested = false;\n                                if let Err(e) = k.do_live_reload(&tx) {\n                                    log::error!(\"live reload failed {e}\");\n                                }\n                            }\n\n                            #[cfg(feature = \"perf_logging\")]\n                            let start = web_time::Instant::now();\n\n                            let mut event_error = None;\n                            for ev in &events {\n                                if let Err(e) = k.handle_input_event(ev) {\n                                    event_error = Some(e);\n                                    break;\n                                }\n                            }\n                            if let Some(e) = event_error {\n                                break e;\n                            }\n                            #[cfg(all(\n                                not(feature = \"interception_driver\"),\n                                target_os = \"windows\"\n                            ))]\n                            {\n                                last_input_time = now;\n                            }\n                            #[cfg(all(\n                                not(feature = \"interception_driver\"),\n                                target_os = \"windows\"\n                            ))]\n                            {\n                                idle_clear_happened = false;\n                            }\n\n                            #[cfg(feature = \"perf_logging\")]\n                            log::info!(\n                                \"[PERF]: handle key event: {} ns\",\n                                (start.elapsed()).as_nanos()\n                            );\n                            #[cfg(feature = \"perf_logging\")]\n                            let start = web_time::Instant::now();\n\n                            match k.handle_time_ticks(&tx) {\n                                Ok(ms) => ms_elapsed = ms,\n                                Err(e) => break e,\n                            };\n\n                            #[cfg(feature = \"perf_logging\")]\n                            log::info!(\n                                \"[PERF]: handle time ticks: {} ns\",\n                                (start.elapsed()).as_nanos()\n                            );\n                        }\n                        Err(_) => {\n                            log::error!(\"channel disconnected\");\n                            return;\n                        }\n                    }\n                } else {\n                    match rx.try_recv() {\n                        Ok(kev) => {\n                            collect_and_sort_events(kev, &rx, &mut events);\n\n                            let mut k = kanata.lock();\n                            // Check for live reload BEFORE processing the key event\n                            if k.live_reload_requested\n                                && ((k.prev_keys.is_empty() && k.cur_keys.is_empty())\n                                    || k.ticks_since_idle > 1000)\n                            {\n                                k.live_reload_requested = false;\n                                if let Err(e) = k.do_live_reload(&tx) {\n                                    log::error!(\"live reload failed {e}\");\n                                }\n                            }\n\n                            #[cfg(feature = \"perf_logging\")]\n                            let start = web_time::Instant::now();\n\n                            let mut event_error = None;\n                            for ev in &events {\n                                if let Err(e) = k.handle_input_event(ev) {\n                                    event_error = Some(e);\n                                    break;\n                                }\n                            }\n                            if let Some(e) = event_error {\n                                break e;\n                            }\n                            #[cfg(all(\n                                not(feature = \"interception_driver\"),\n                                target_os = \"windows\"\n                            ))]\n                            {\n                                last_input_time = web_time::Instant::now();\n                            }\n                            #[cfg(all(\n                                not(feature = \"interception_driver\"),\n                                target_os = \"windows\"\n                            ))]\n                            {\n                                idle_clear_happened = false;\n                            }\n\n                            #[cfg(feature = \"perf_logging\")]\n                            log::info!(\n                                \"[PERF]: handle key event: {} ns\",\n                                (start.elapsed()).as_nanos()\n                            );\n                            #[cfg(feature = \"perf_logging\")]\n                            let start = web_time::Instant::now();\n\n                            match k.handle_time_ticks(&tx) {\n                                Ok(ms) => ms_elapsed = ms,\n                                Err(e) => break e,\n                            };\n\n                            #[cfg(feature = \"perf_logging\")]\n                            log::info!(\n                                \"[PERF]: handle time ticks: {} ns\",\n                                (start.elapsed()).as_nanos()\n                            );\n                        }\n                        Err(TryRecvError::Empty) => {\n                            let mut k = kanata.lock();\n\n                            #[cfg(feature = \"perf_logging\")]\n                            let start = web_time::Instant::now();\n\n                            match k.handle_time_ticks(&tx) {\n                                Ok(ms) => ms_elapsed = ms,\n                                Err(e) => break e,\n                            };\n\n                            #[cfg(feature = \"perf_logging\")]\n                            log::info!(\n                                \"[PERF]: handle time ticks: {} ns\",\n                                (start.elapsed()).as_nanos()\n                            );\n\n                            #[cfg(all(\n                                not(feature = \"interception_driver\"),\n                                target_os = \"windows\"\n                            ))]\n                            clear_states_from_inactivity(\n                                &mut k,\n                                web_time::Instant::now(),\n                                last_input_time,\n                                &mut idle_clear_happened,\n                            );\n\n                            drop(k);\n                            std::thread::sleep(time::Duration::from_millis(1));\n                        }\n                        Err(TryRecvError::Disconnected) => {\n                            log::error!(\"channel disconnected\");\n                            return;\n                        }\n                    }\n                }\n            };\n            panic!(\"processing loop encountered error {err:?}\")\n        });\n    }\n\n    /// Returns `true` if kanata's processing thread loop can block on the channel instead of doing\n    /// a non-blocking channel read and then sleeping for ~1ms.\n    ///\n    /// In addition to doing the logic for the above, this mutates the `waiting_for_idle` state\n    /// used by the `on-idle` action for virtual keys.\n    pub fn can_block_update_idle_waiting(&mut self, ms_elapsed: u16) -> bool {\n        let k = self;\n        let is_idle = k.is_idle();\n        // Note: checking waiting_for_idle can not be part of the computation for\n        // is_idle() since incrementing ticks_since_idle is dependent on the return\n        // value of is_idle().\n        let counting_idle_ticks = !k.waiting_for_idle.is_empty() || k.live_reload_requested;\n        if !is_idle {\n            k.ticks_since_idle = 0;\n        } else if is_idle && counting_idle_ticks {\n            k.ticks_since_idle = k.ticks_since_idle.saturating_add(ms_elapsed);\n            #[cfg(feature = \"perf_logging\")]\n            log::info!(\"ticks since idle: {}\", k.ticks_since_idle);\n        }\n\n        let counting_physical_idle_ticks = if k.waiting_for_physical_idle.is_empty() {\n            false\n        } else {\n            let is_physical_idle = PRESSED_KEYS.lock().is_empty();\n            if is_physical_idle {\n                k.ticks_since_physical_idle =\n                    k.ticks_since_physical_idle.saturating_add(ms_elapsed);\n            } else {\n                k.ticks_since_physical_idle = 0;\n            }\n            true\n        };\n\n        // NOTE: this check must not be part of `is_idle` because its falsiness\n        // does not mean that kanata is in a non-idle state, just that we\n        // haven't done enough ticks yet to properly compute key-timing.\n        let passed_max_switch_timing_check = k\n            .layout\n            .b()\n            .historical_keys\n            .iter_hevents()\n            .next()\n            .map(|he| he.ticks_since_occurrence >= k.switch_max_key_timing)\n            .unwrap_or(true);\n        let chordsv2_accepts_chords = k\n            .layout\n            .b()\n            .chords_v2\n            .as_ref()\n            .map(|cv2| cv2.accepts_chords_chv2())\n            .unwrap_or(true);\n        is_idle\n            && !counting_idle_ticks\n            && !counting_physical_idle_ticks\n            && passed_max_switch_timing_check\n            && chordsv2_accepts_chords\n    }\n\n    pub fn is_idle(&self) -> bool {\n        let pressed_keys_means_not_idle =\n            !self.waiting_for_idle.is_empty() || self.live_reload_requested;\n        self.layout.b().queue.is_empty()\n            && zippy_is_idle()\n            && self.layout.b().waiting.is_none()\n            && self.layout.b().last_press_tracker.tap_hold_timeout == 0\n            && (self.layout.b().oneshot.timeout == 0 || self.layout.b().oneshot.keys.is_empty())\n            && self.layout.b().active_sequences.is_empty()\n            && self.layout.b().tap_dance_eager.is_none()\n            && self.layout.b().action_queue.is_empty()\n            && self.sequence_state.is_inactive()\n            && self.scroll_state.is_none()\n            && self.hscroll_state.is_none()\n            && self.move_mouse_state_vertical.is_none()\n            && self.macro_on_press_cancel_duration == 0\n            && self.move_mouse_state_horizontal.is_none()\n            && self.dynamic_macro_replay_state.is_none()\n            && self.caps_word.is_none()\n            && self.vkeys_pending_release.is_empty()\n            && !self.layout.b().states.iter().any(|s| {\n                matches!(s, State::SeqCustomPending(_) | State::SeqCustomActive(_))\n                    || (pressed_keys_means_not_idle && matches!(s, State::NormalKey { .. }))\n            })\n            && self\n                .layout\n                .b()\n                .chords_v2\n                .as_ref()\n                .map(|cv2| cv2.is_idle_chv2())\n                .unwrap_or(true)\n    }\n}\n\n#[test]\nfn test_unmodmods_bits() {\n    assert_eq!(UnmodMods::empty().bits(), 0u8);\n    assert_eq!(UnmodMods::all().bits(), 255u8);\n}\n\n#[cfg(feature = \"cmd\")]\nfn run_multi_cmd(cmds: Vec<(Option<log::Level>, Option<log::Level>, Vec<String>)>) {\n    std::thread::spawn(move || {\n        for (cmd_log_level, cmd_error_log_level, cmd) in cmds {\n            if let Err(e) = run_cmd_in_thread(cmd, cmd_log_level, cmd_error_log_level).join() {\n                log::error!(\"problem joining thread {:?}\", e);\n            }\n        }\n    });\n}\n\nfn apply_mouse_distance_modifiers(initial_distance: u16, mods: &Vec<u16>) -> u16 {\n    let mut scaled_distance = initial_distance;\n    for &modifier in mods {\n        scaled_distance = u16::max(\n            1,\n            f32::min(\n                scaled_distance as f32 * (modifier as f32 / 100f32),\n                u16::MAX as f32,\n            )\n            .round() as u16,\n        );\n    }\n    scaled_distance\n}\n\n#[test]\nfn apply_speed_modifiers() {\n    assert_eq!(apply_mouse_distance_modifiers(15, &vec![]), 15);\n\n    assert_eq!(apply_mouse_distance_modifiers(10, &vec![200u16]), 20);\n    assert_eq!(apply_mouse_distance_modifiers(20, &vec![50u16]), 10);\n\n    assert_eq!(apply_mouse_distance_modifiers(5, &vec![33u16]), 2); // 1.65\n    assert_eq!(apply_mouse_distance_modifiers(100, &vec![99u16]), 99);\n\n    // Clamping\n    assert_eq!(\n        apply_mouse_distance_modifiers(65535, &vec![65535u16]),\n        65535\n    );\n    assert_eq!(apply_mouse_distance_modifiers(1, &vec![1u16]), 1);\n\n    // Nice, round calculations equal themselves\n    assert_eq!(\n        apply_mouse_distance_modifiers(10, &vec![50u16, 200u16]),\n        apply_mouse_distance_modifiers(10, &vec![200u16, 50u16])\n    );\n\n    // 33% of 20\n    assert_eq!(apply_mouse_distance_modifiers(10, &vec![200u16, 33u16]), 7);\n    // 200% of 3\n    assert_eq!(apply_mouse_distance_modifiers(10, &vec![33u16, 200u16]), 6);\n}\n\n#[cfg(feature = \"passthru_ahk\")]\n/// Clean kanata's state without exiting\npub fn clean_state(kanata: &Arc<Mutex<Kanata>>, tick: u128) -> Result<()> {\n    let mut k = kanata.lock();\n    #[cfg(all(not(feature = \"interception_driver\"), target_os = \"windows\"))]\n    let layout = k.layout.bm();\n    #[cfg(all(not(feature = \"interception_driver\"), target_os = \"windows\"))]\n    release_normalkey_states(layout);\n    k.tick_ms(tick, &None)?;\n    #[cfg(not(any(target_os = \"linux\", target_os = \"android\")))]\n    {\n        let mut k_pressed = PRESSED_KEYS.lock();\n        for key_os in k_pressed.clone() {\n            #[cfg(not(all(target_os = \"windows\", not(feature = \"interception_driver\"))))]\n            k.kbd_out.release_key(key_os)?;\n            #[cfg(all(target_os = \"windows\", not(feature = \"interception_driver\")))]\n            k.kbd_out.release_key(key_os.0)?;\n        }\n        k_pressed.clear();\n    }\n    Ok(())\n}\n\n/// Checks if kanata should exit based on the fixed key combination of:\n/// Lctl+Spc+Esc\nfn check_for_exit(_event: &KeyEvent) {\n    #[cfg(not(feature = \"passthru_ahk\"))]\n    {\n        use std::sync::atomic::{AtomicBool, Ordering::SeqCst};\n        static IS_LCL_PRESSED: AtomicBool = AtomicBool::new(false);\n        static IS_SPC_PRESSED: AtomicBool = AtomicBool::new(false);\n        static IS_ESC_PRESSED: AtomicBool = AtomicBool::new(false);\n        let is_pressed = match _event.value {\n            KeyValue::Press => true,\n            KeyValue::Release => false,\n            _ => return,\n        };\n        match _event.code {\n            OsCode::KEY_ESC => IS_ESC_PRESSED.store(is_pressed, SeqCst),\n            OsCode::KEY_SPACE => IS_SPC_PRESSED.store(is_pressed, SeqCst),\n            OsCode::KEY_LEFTCTRL => IS_LCL_PRESSED.store(is_pressed, SeqCst),\n            _ => return,\n        }\n        const EXIT_MSG: &str = \"pressed LControl+Space+Escape, exiting\";\n        if IS_ESC_PRESSED.load(SeqCst) && IS_SPC_PRESSED.load(SeqCst) && IS_LCL_PRESSED.load(SeqCst)\n        {\n            log::info!(\"{EXIT_MSG}\");\n            #[cfg(all(target_os = \"windows\", feature = \"gui\"))]\n            {\n                #[cfg(not(feature = \"interception_driver\"))]\n                native_windows_gui::stop_thread_dispatch();\n                #[cfg(feature = \"interception_driver\")]\n                send_gui_exit_notice(); // interception driver is running in another thread to allow\n                // GUI take the main one, so it's calling check_for_exit\n                // from a thread that has no access to the main one, so\n                // can't stop main thread's dispatch\n            }\n            // macOS: Direct exit (no special signal handling)\n            #[cfg(target_os = \"macos\")]\n            {\n                let code = EMERGENCY_EXIT_CODE.load(std::sync::atomic::Ordering::SeqCst);\n                std::process::exit(code);\n            }\n            // Linux/Android: Use SIGTERM to trigger signal handler for cleanup\n            #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n            {\n                signal_hook::low_level::raise(signal_hook::consts::SIGTERM).expect(\"raise signal\");\n            }\n            // Windows non-GUI: Direct exit (no cleanup needed)\n            #[cfg(all(target_os = \"windows\", not(feature = \"gui\")))]\n            {\n                let code = EMERGENCY_EXIT_CODE.load(std::sync::atomic::Ordering::SeqCst);\n                std::process::exit(code);\n            }\n            // Unsupported platforms: panic to indicate emergency exit isn't implemented\n            #[cfg(not(any(\n                target_os = \"macos\",\n                target_os = \"linux\",\n                target_os = \"android\",\n                target_os = \"windows\"\n            )))]\n            {\n                panic!(\"{EXIT_MSG}\");\n            }\n        }\n    }\n}\n\nfn update_kbd_out(_cfg: &CfgOptions, _kbd_out: &KbdOut) -> Result<()> {\n    #[cfg(all(\n        not(feature = \"simulated_output\"),\n        any(target_os = \"linux\", target_os = \"android\")\n    ))]\n    {\n        _kbd_out.update_unicode_termination(_cfg.linux_opts.linux_unicode_termination);\n        _kbd_out.update_unicode_u_code(_cfg.linux_opts.linux_unicode_u_code);\n    }\n    Ok(())\n}\n\npub fn handle_fakekey_action<'a, const C: usize, const R: usize, T>(\n    action: FakeKeyAction,\n    layout: &mut Layout<'a, C, R, T>,\n    x: u8,\n    y: u16,\n) where\n    T: 'a + std::fmt::Debug + Copy,\n{\n    match action {\n        FakeKeyAction::Press => layout.event(Event::Press(x, y)),\n        FakeKeyAction::Release => layout.event(Event::Release(x, y)),\n        FakeKeyAction::Tap => {\n            layout.event(Event::Press(x, y));\n            layout.event(Event::Release(x, y));\n        }\n        FakeKeyAction::Toggle => {\n            match states_has_coord(&layout.states, x, y) {\n                true => layout.event(Event::Release(x, y)),\n                false => layout.event(Event::Press(x, y)),\n            };\n        }\n    };\n}\n\nfn states_has_coord<T>(states: &[State<T>], x: u8, y: u16) -> bool {\n    states.iter().any(|s| match s {\n        State::NormalKey { coord, .. }\n        | State::LayerModifier { coord, .. }\n        | State::Custom { coord, .. }\n        | State::NoOpInput { coord }\n        | State::RepeatingSequence { coord, .. } => *coord == (x, y),\n        State::FakeKey { .. }\n        | State::SeqCustomPending(_)\n        | State::SeqCustomActive(_)\n        | State::Tombstone => false,\n    })\n}\n\n#[cfg(all(not(feature = \"interception_driver\"), target_os = \"windows\"))]\nfn release_normalkey_states<'a, const C: usize, const R: usize, T>(layout: &mut Layout<'a, C, R, T>)\nwhere\n    T: 'a + std::fmt::Debug + Copy,\n{\n    let mut coords_to_release = vec![];\n    for state in layout.states.iter().copied() {\n        match state {\n            State::NormalKey {\n                coord: (NORMAL_KEY_ROW, y),\n                ..\n            }\n            | State::LayerModifier {\n                coord: (NORMAL_KEY_ROW, y),\n                ..\n            }\n            | State::Custom {\n                coord: (NORMAL_KEY_ROW, y),\n                ..\n            }\n            | State::RepeatingSequence {\n                coord: (NORMAL_KEY_ROW, y),\n                ..\n            } => {\n                coords_to_release.push((NORMAL_KEY_ROW, y));\n            }\n            _ => {}\n        }\n    }\n    for coord in coords_to_release.into_iter() {\n        layout.event(Event::Release(coord.0, coord.1));\n    }\n}\n\n#[cfg(test)]\nmod collect_and_sort_events_tests {\n    use super::*;\n    use kanata_parser::keys::OsCode;\n    use std::sync::mpsc::sync_channel;\n\n    fn make_event(code: OsCode, value: KeyValue) -> KeyEvent {\n        KeyEvent { code, value }\n    }\n\n    #[test]\n    fn single_event_unchanged() {\n        let (_tx, rx) = sync_channel::<KeyEvent>(10);\n        let first = make_event(OsCode::KEY_A, KeyValue::Press);\n\n        let mut result = Vec::new();\n        collect_and_sort_events(first, &rx, &mut result);\n\n        assert_eq!(result.len(), 1);\n        assert_eq!(result[0].code, OsCode::KEY_A);\n    }\n\n    #[test]\n    fn modifiers_first_on_press() {\n        let (tx, rx) = sync_channel::<KeyEvent>(10);\n        let first = make_event(OsCode::KEY_A, KeyValue::Press);\n        tx.send(make_event(OsCode::KEY_LEFTCTRL, KeyValue::Press))\n            .unwrap();\n        tx.send(make_event(OsCode::KEY_B, KeyValue::Press)).unwrap();\n\n        let mut result = Vec::new();\n        collect_and_sort_events(first, &rx, &mut result);\n\n        assert_eq!(result.len(), 3);\n        assert_eq!(result[0].code, OsCode::KEY_LEFTCTRL);\n        assert_eq!(result[1].code, OsCode::KEY_A);\n        assert_eq!(result[2].code, OsCode::KEY_B);\n    }\n\n    #[test]\n    fn modifiers_last_on_release() {\n        let (tx, rx) = sync_channel::<KeyEvent>(10);\n        let first = make_event(OsCode::KEY_A, KeyValue::Release);\n        tx.send(make_event(OsCode::KEY_LEFTCTRL, KeyValue::Release))\n            .unwrap();\n        tx.send(make_event(OsCode::KEY_B, KeyValue::Release))\n            .unwrap();\n\n        let mut result = Vec::new();\n        collect_and_sort_events(first, &rx, &mut result);\n\n        assert_eq!(result.len(), 3);\n        assert_eq!(result[0].code, OsCode::KEY_A);\n        assert_eq!(result[1].code, OsCode::KEY_B);\n        assert_eq!(result[2].code, OsCode::KEY_LEFTCTRL);\n    }\n\n    #[test]\n    fn multiple_modifiers_on_press() {\n        let (tx, rx) = sync_channel::<KeyEvent>(10);\n        let first = make_event(OsCode::KEY_A, KeyValue::Press);\n        tx.send(make_event(OsCode::KEY_LEFTCTRL, KeyValue::Press))\n            .unwrap();\n        tx.send(make_event(OsCode::KEY_LEFTSHIFT, KeyValue::Press))\n            .unwrap();\n        tx.send(make_event(OsCode::KEY_B, KeyValue::Press)).unwrap();\n\n        let mut result = Vec::new();\n        collect_and_sort_events(first, &rx, &mut result);\n\n        assert_eq!(result.len(), 4);\n        assert_eq!(result[0].code, OsCode::KEY_LEFTCTRL);\n        assert_eq!(result[1].code, OsCode::KEY_LEFTSHIFT);\n        assert_eq!(result[2].code, OsCode::KEY_A);\n        assert_eq!(result[3].code, OsCode::KEY_B);\n    }\n\n    #[test]\n    fn multiple_modifiers_on_release() {\n        let (tx, rx) = sync_channel::<KeyEvent>(10);\n        let first = make_event(OsCode::KEY_A, KeyValue::Release);\n        tx.send(make_event(OsCode::KEY_LEFTCTRL, KeyValue::Release))\n            .unwrap();\n        tx.send(make_event(OsCode::KEY_LEFTSHIFT, KeyValue::Release))\n            .unwrap();\n        tx.send(make_event(OsCode::KEY_B, KeyValue::Release))\n            .unwrap();\n\n        let mut result = Vec::new();\n        collect_and_sort_events(first, &rx, &mut result);\n\n        assert_eq!(result.len(), 4);\n        assert_eq!(result[0].code, OsCode::KEY_A);\n        assert_eq!(result[1].code, OsCode::KEY_B);\n        assert_eq!(result[2].code, OsCode::KEY_LEFTCTRL);\n        assert_eq!(result[3].code, OsCode::KEY_LEFTSHIFT);\n    }\n\n    #[test]\n    fn repeat_treated_like_press() {\n        let (tx, rx) = sync_channel::<KeyEvent>(10);\n        let first = make_event(OsCode::KEY_A, KeyValue::Repeat);\n        tx.send(make_event(OsCode::KEY_LEFTCTRL, KeyValue::Repeat))\n            .unwrap();\n\n        let mut result = Vec::new();\n        collect_and_sort_events(first, &rx, &mut result);\n\n        assert_eq!(result.len(), 2);\n        assert_eq!(result[0].code, OsCode::KEY_LEFTCTRL);\n        assert_eq!(result[1].code, OsCode::KEY_A);\n    }\n\n    #[test]\n    fn all_modifiers_no_reorder_needed() {\n        let (tx, rx) = sync_channel::<KeyEvent>(10);\n        let first = make_event(OsCode::KEY_LEFTCTRL, KeyValue::Press);\n        tx.send(make_event(OsCode::KEY_LEFTSHIFT, KeyValue::Press))\n            .unwrap();\n        tx.send(make_event(OsCode::KEY_LEFTALT, KeyValue::Press))\n            .unwrap();\n\n        let mut result = Vec::new();\n        collect_and_sort_events(first, &rx, &mut result);\n\n        assert_eq!(result.len(), 3);\n        assert_eq!(result[0].code, OsCode::KEY_LEFTCTRL);\n        assert_eq!(result[1].code, OsCode::KEY_LEFTSHIFT);\n        assert_eq!(result[2].code, OsCode::KEY_LEFTALT);\n    }\n\n    #[test]\n    fn all_non_modifiers_no_reorder_needed() {\n        let (tx, rx) = sync_channel::<KeyEvent>(10);\n        let first = make_event(OsCode::KEY_A, KeyValue::Press);\n        tx.send(make_event(OsCode::KEY_B, KeyValue::Press)).unwrap();\n        tx.send(make_event(OsCode::KEY_C, KeyValue::Press)).unwrap();\n\n        let mut result = Vec::new();\n        collect_and_sort_events(first, &rx, &mut result);\n\n        assert_eq!(result.len(), 3);\n        assert_eq!(result[0].code, OsCode::KEY_A);\n        assert_eq!(result[1].code, OsCode::KEY_B);\n        assert_eq!(result[2].code, OsCode::KEY_C);\n    }\n\n    #[test]\n    fn mixed_press_release_preserves_interleaving() {\n        let (tx, rx) = sync_channel::<KeyEvent>(10);\n        let first = make_event(OsCode::KEY_A, KeyValue::Press);\n        tx.send(make_event(OsCode::KEY_LEFTCTRL, KeyValue::Release))\n            .unwrap();\n        tx.send(make_event(OsCode::KEY_B, KeyValue::Release))\n            .unwrap();\n        tx.send(make_event(OsCode::KEY_LEFTSHIFT, KeyValue::Press))\n            .unwrap();\n\n        let mut result = Vec::new();\n        collect_and_sort_events(first, &rx, &mut result);\n\n        assert_eq!(result.len(), 4);\n        assert_eq!(result[0].code, OsCode::KEY_A);\n        assert_eq!(result[1].code, OsCode::KEY_B);\n        assert_eq!(result[2].code, OsCode::KEY_LEFTCTRL);\n        assert_eq!(result[3].code, OsCode::KEY_LEFTSHIFT);\n    }\n}\n\n#[cfg(all(test, feature = \"tcp_server\"))]\nmod tcp_layer_change_tests {\n    use super::*;\n    use std::sync::mpsc::{TryRecvError, sync_channel};\n    use std::time::Duration;\n\n    fn collect_layer_changes(rx: &Receiver<ServerMessage>) -> Vec<String> {\n        let mut changes = Vec::new();\n        loop {\n            match rx.try_recv() {\n                Ok(ServerMessage::LayerChange { new }) => changes.push(new),\n                Ok(_) => {}\n                Err(TryRecvError::Empty) => break,\n                Err(TryRecvError::Disconnected) => break,\n            }\n        }\n        changes\n    }\n\n    #[test]\n    fn direct_held_layer_press_and_release_emit_layer_changes() {\n        let mut k = Kanata::new_from_str(\n            r\"\n(defsrc a)\n(deflayer base\n  (layer-while-held nav))\n(deflayer nav\n  b)\n            \",\n            Default::default(),\n        )\n        .expect(\"failed to parse cfg\");\n        let (tx, rx) = sync_channel::<ServerMessage>(10);\n        let tx = Some(tx);\n\n        k.handle_input_event(&KeyEvent {\n            code: OsCode::KEY_A,\n            value: KeyValue::Press,\n        })\n        .expect(\"press should succeed\");\n        k.last_tick = web_time::Instant::now() - Duration::from_millis(1);\n        k.handle_time_ticks(&tx).expect(\"press tick should succeed\");\n\n        assert_eq!(collect_layer_changes(&rx), vec![\"nav\"]);\n\n        k.handle_input_event(&KeyEvent {\n            code: OsCode::KEY_A,\n            value: KeyValue::Release,\n        })\n        .expect(\"release should succeed\");\n        k.last_tick = web_time::Instant::now() - Duration::from_millis(1);\n        k.handle_time_ticks(&tx)\n            .expect(\"release tick should succeed\");\n\n        assert_eq!(collect_layer_changes(&rx), vec![\"base\"]);\n    }\n\n    #[test]\n    fn oneshot_held_layer_timeout_emits_both_transitions_within_one_tick_batch() {\n        let mut k = Kanata::new_from_str(\n            r\"\n(defsrc a)\n(deflayer base\n  (one-shot 2 (layer-while-held nav)))\n(deflayer nav\n  b)\n            \",\n            Default::default(),\n        )\n        .expect(\"failed to parse cfg\");\n        let (tx, rx) = sync_channel::<ServerMessage>(10);\n        let tx = Some(tx);\n\n        k.handle_input_event(&KeyEvent {\n            code: OsCode::KEY_A,\n            value: KeyValue::Press,\n        })\n        .expect(\"press should succeed\");\n        k.handle_input_event(&KeyEvent {\n            code: OsCode::KEY_A,\n            value: KeyValue::Release,\n        })\n        .expect(\"release should succeed\");\n        k.last_tick = web_time::Instant::now() - Duration::from_millis(5);\n        k.handle_time_ticks(&tx)\n            .expect(\"batched timeout ticks should succeed\");\n\n        assert_eq!(collect_layer_changes(&rx), vec![\"nav\", \"base\"]);\n    }\n\n    #[test]\n    fn oneshot_held_layer_consumed_by_keypress_emits_both_transitions_within_one_tick_batch() {\n        let mut k = Kanata::new_from_str(\n            r\"\n(defsrc a b)\n(deflayer base\n  (one-shot 20 (layer-while-held nav))\n  XX)\n(deflayer nav\n  _\n  (layer-while-held base))\n            \",\n            Default::default(),\n        )\n        .expect(\"failed to parse cfg\");\n        let (tx, rx) = sync_channel::<ServerMessage>(10);\n        let tx = Some(tx);\n\n        k.handle_input_event(&KeyEvent {\n            code: OsCode::KEY_A,\n            value: KeyValue::Press,\n        })\n        .expect(\"oneshot press should succeed\");\n        k.handle_input_event(&KeyEvent {\n            code: OsCode::KEY_A,\n            value: KeyValue::Release,\n        })\n        .expect(\"oneshot release should succeed\");\n        k.handle_input_event(&KeyEvent {\n            code: OsCode::KEY_B,\n            value: KeyValue::Press,\n        })\n        .expect(\"consumer press should succeed\");\n        k.handle_input_event(&KeyEvent {\n            code: OsCode::KEY_B,\n            value: KeyValue::Release,\n        })\n        .expect(\"consumer release should succeed\");\n        k.last_tick = web_time::Instant::now() - Duration::from_millis(5);\n        k.handle_time_ticks(&tx)\n            .expect(\"batched consume ticks should succeed\");\n\n        assert_eq!(collect_layer_changes(&rx), vec![\"nav\", \"base\"]);\n    }\n}\n"
  },
  {
    "path": "src/kanata/output_logic/zippychord.rs",
    "content": "use super::*;\n\nuse kanata_parser::subset::GetOrIsSubsetOfKnownKey::*;\n\nuse std::sync::Arc;\nuse std::sync::Mutex;\nuse std::sync::MutexGuard;\n\n// Maybe-todos:\n// ---\n// Feature-parity: suffixes - only active while disabled, to complete a word.\n// Feature-parity: prefix vs. non-prefix. Assuming smart spacing is implemented and enabled,\n//                 standard activations would output space one outputs space, but not prefixes.\n//                 I guess can be done in parser.\n\nstatic ZCH: Lazy<Mutex<ZchState>> = Lazy::new(|| Mutex::new(Default::default()));\n\npub(crate) fn zch() -> MutexGuard<'static, ZchState> {\n    match ZCH.lock() {\n        Ok(guard) => guard,\n        Err(poisoned) => {\n            let mut inner = poisoned.into_inner();\n            inner.zchd.zchd_reset();\n            inner\n        }\n    }\n}\n\n#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]\nenum ZchEnabledState {\n    #[default]\n    Enabled,\n    WaitEnable,\n    Disabled,\n}\n\n#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]\nenum ZchLastPressClassification {\n    #[default]\n    IsChord,\n    NotChord,\n}\n\n#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]\nenum ZchSmartSpaceState {\n    #[default]\n    Inactive,\n    Sent,\n}\n\n#[derive(Debug, Default)]\nstruct ZchDynamicState {\n    /// Input to compare against configured available chords to output.\n    zchd_input_keys: ZchInputKeys,\n    /// Whether chording should be enabled or disabled.\n    /// Chording will be disabled if:\n    /// - further presses cannot possibly activate a chord\n    /// - a release happens with no chord having been activated\n    ///\n    /// Once disabled, chording will be enabled when:\n    /// - all keys have been released\n    /// - zchd_ticks_until_enabled shrinks to 0\n    zchd_enabled_state: ZchEnabledState,\n    /// Is Some when a chord has been activated which has possible follow-up chords.\n    /// E.g. dy -> day\n    ///      dy 1 -> Monday\n    ///      dy 2 -> Tuesday\n    /// Using the example above, when dy has been activated, the `1` and `2` activations will be\n    /// contained within `zchd_prioritized_chords`. This is cleared if the input is such that an\n    /// activation is no longer possible.\n    zchd_prioritized_chords: Option<Arc<parking_lot::Mutex<ZchPossibleChords>>>,\n    /// Tracks the prior output character count\n    /// because it may need to be erased (see `zchd_prioritized_chords).\n    zchd_prior_activation_output_count: i16,\n    /// Tracks the number of characters typed to complete an activation, which will be erased if an\n    /// activation completes succesfully.\n    zchd_characters_to_delete_on_next_activation: i16,\n    /// Tracks past activation for additional computation.\n    zchd_prior_activation: Option<Arc<ZchChordOutput>>,\n    /// Tracker for time until prior state change to know if potential stale data should be\n    /// cleared. This is a contingency in case of bugs or weirdness with OS interactions, e.g.\n    /// Windows lock screen weirdness.\n    ///\n    /// This counts upwards to a \"reset state\" number.\n    zchd_ticks_since_state_change: u16,\n    /// Zch has a time delay between being disabled->pending-enabled->truly-enabled to mitigate\n    /// against unintended activations. This counts downwards from a configured number until 0, and\n    /// at 0 the state transitions from pending-enabled to truly-enabled if applicable.\n    zchd_ticks_until_enabled: u16,\n    /// There is a deadline between the first press happening and a chord activation being\n    /// possible; after which if a chord has not been activated, zippychording is disabled. This\n    /// state is the counter for this deadline.\n    zchd_ticks_until_disable: u16,\n    /// Track number of activations within the same hold.\n    zchd_same_hold_activation_count: u16,\n    /// Current state of caps-word, which is a factor in handling capitalization.\n    zchd_is_caps_word_active: bool,\n    /// Current state of lsft which is a factor in handling capitalization.\n    zchd_is_lsft_active: bool,\n    /// Current state of rsft which is a factor in handling capitalization.\n    zchd_is_rsft_active: bool,\n    /// Current state of altgr which is a factor in smart space erasure.\n    zchd_is_altgr_active: bool,\n    /// Tracks whether last press was part of a chord or not.\n    /// Upon releasing keys, this state determines if zippychording should remain enabled or\n    /// disabled.\n    zchd_last_press: ZchLastPressClassification,\n    /// Tracks smart spacing state so punctuation characters\n    /// can know whether a space needs to be erased or not.\n    zchd_smart_space_state: ZchSmartSpaceState,\n}\n\nimpl ZchDynamicState {\n    fn zchd_tick(&mut self, is_caps_word_active: bool) {\n        const TICKS_UNTIL_FORCE_STATE_RESET: u16 = 10000;\n        self.zchd_ticks_since_state_change += 1;\n        self.zchd_is_caps_word_active = is_caps_word_active;\n        match self.zchd_enabled_state {\n            ZchEnabledState::WaitEnable => {\n                self.zchd_ticks_until_enabled = self.zchd_ticks_until_enabled.saturating_sub(1);\n                if self.zchd_ticks_until_enabled == 0 {\n                    log::debug!(\"zippy wait enable->enable\");\n                    self.zchd_enabled_state = ZchEnabledState::Enabled;\n                    self.zchd_ticks_until_disable = 0;\n                }\n            }\n            ZchEnabledState::Enabled => {\n                // Only run disable-check logic if ticks is already greater than zero, because zero\n                // means deadline has never been triggered by any press.\n                if self.zchd_ticks_until_disable > 0 {\n                    self.zchd_ticks_until_disable = self.zchd_ticks_until_disable.saturating_sub(1);\n                    if self.zchd_ticks_until_disable == 0 {\n                        log::debug!(\"zippy enable->disable\");\n                        self.zchd_soft_reset();\n                    }\n                }\n            }\n            ZchEnabledState::Disabled => {}\n        }\n        if self.zchd_ticks_since_state_change > TICKS_UNTIL_FORCE_STATE_RESET {\n            self.zchd_reset();\n        }\n    }\n\n    fn zchd_state_change(&mut self, cfg: &ZchConfig) {\n        self.zchd_ticks_since_state_change = 0;\n        self.zchd_ticks_until_enabled = cfg.zch_cfg_ticks_wait_enable;\n    }\n\n    fn zchd_activate_chord_deadline(&mut self, deadline_ticks: u16) {\n        if self.zchd_ticks_until_disable == 0 {\n            self.zchd_ticks_until_disable = deadline_ticks;\n        }\n    }\n\n    fn zchd_restart_deadline(&mut self, deadline_ticks: u16) {\n        self.zchd_ticks_until_disable = deadline_ticks;\n    }\n\n    /// Clean up the state, potentially causing inaccuracies with regards to what the user is\n    /// currently still pressing.\n    fn zchd_reset(&mut self) {\n        log::debug!(\"zchd reset state\");\n        self.zchd_soft_reset();\n        self.zchd_is_caps_word_active = false;\n        self.zchd_is_lsft_active = false;\n        self.zchd_is_rsft_active = false;\n        self.zchd_is_altgr_active = false;\n        self.zchd_last_press = ZchLastPressClassification::IsChord;\n        self.zchd_enabled_state = ZchEnabledState::Enabled;\n    }\n\n    fn zchd_soft_reset(&mut self) {\n        log::debug!(\"zchd soft reset state\");\n        self.zchd_last_press = ZchLastPressClassification::NotChord;\n        self.zchd_enabled_state = ZchEnabledState::Disabled;\n        self.zchd_input_keys.zchik_clear();\n        self.zchd_ticks_since_state_change = 0;\n        self.zchd_ticks_until_disable = 0;\n        self.zchd_ticks_until_enabled = 0;\n        self.zchd_smart_space_state = ZchSmartSpaceState::Inactive;\n        self.zchd_clear_history();\n    }\n\n    fn zchd_clear_history(&mut self) {\n        log::debug!(\"zchd clear historical data\");\n        self.zchd_characters_to_delete_on_next_activation = 0;\n        self.zchd_prioritized_chords = None;\n        self.zchd_prior_activation = None;\n        self.zchd_prior_activation_output_count = 0;\n    }\n\n    /// Returns true if dynamic zch state is such that idling optimization can activate.\n    fn zchd_is_idle(&self) -> bool {\n        let is_idle = self.zchd_enabled_state == ZchEnabledState::Enabled\n            && self.zchd_input_keys.zchik_is_empty();\n        log::trace!(\"zch is idle: {is_idle}\");\n        is_idle\n    }\n\n    fn zchd_press_key(&mut self, osc: OsCode) {\n        self.zchd_input_keys.zchik_insert(osc);\n    }\n\n    fn zchd_release_key(&mut self, osc: OsCode) {\n        self.zchd_input_keys.zchik_remove(osc);\n        match (self.zchd_last_press, self.zchd_input_keys.zchik_is_empty()) {\n            (ZchLastPressClassification::NotChord, true) => {\n                log::debug!(\"all released->zippy wait enable\");\n                self.zchd_enabled_state = ZchEnabledState::WaitEnable;\n                self.zchd_clear_history();\n            }\n            (ZchLastPressClassification::NotChord, false) => {\n                log::debug!(\"release but not all->zippy disable\");\n                self.zchd_soft_reset();\n            }\n            (ZchLastPressClassification::IsChord, true) => {\n                log::debug!(\"all released->zippy enabled\");\n                if self.zchd_prioritized_chords.is_none() {\n                    log::debug!(\"no continuation->zippy clear key erase state\");\n                    self.zchd_clear_history();\n                }\n                self.zchd_characters_to_delete_on_next_activation = 0;\n                self.zchd_ticks_until_disable = 0;\n                self.zchd_enabled_state = ZchEnabledState::Enabled;\n                self.zchd_same_hold_activation_count = 0;\n            }\n            (ZchLastPressClassification::IsChord, false) => {\n                log::debug!(\"some released->zippy enabled\");\n                self.zchd_ticks_until_disable = 0;\n            }\n        }\n    }\n}\n\n#[derive(Debug, Default)]\npub(crate) struct ZchState {\n    /// Dynamic state. Maybe doesn't make sense to separate this from zch_chords and to instead\n    /// just flatten the structures.\n    zchd: ZchDynamicState,\n    /// Chords configured by the user. This is fixed at runtime other than live-reloads replacing\n    /// the state.\n    zch_chords: ZchPossibleChords,\n    /// Options to configure behaviour.\n    zch_cfg: ZchConfig,\n}\n\nimpl ZchState {\n    /// Configure zippychord behaviour.\n    pub(crate) fn zch_configure(&mut self, cfg: (ZchPossibleChords, ZchConfig)) {\n        self.zch_chords = cfg.0;\n        self.zch_cfg = cfg.1;\n        self.zchd.zchd_reset();\n    }\n\n    /// Zch handling for key presses.\n    pub(crate) fn zch_press_key(\n        &mut self,\n        kb: &mut KbdOut,\n        osc: OsCode,\n    ) -> Result<(), std::io::Error> {\n        if self.zch_chords.is_empty() {\n            return kb.press_key(osc);\n        }\n        match osc {\n            OsCode::KEY_LEFTSHIFT => {\n                self.zchd.zchd_is_lsft_active = true;\n                return kb.press_key(osc);\n            }\n            OsCode::KEY_RIGHTSHIFT => {\n                self.zchd.zchd_is_rsft_active = true;\n                return kb.press_key(osc);\n            }\n            OsCode::KEY_RIGHTALT => {\n                self.zchd.zchd_is_altgr_active = true;\n                return kb.press_key(osc);\n            }\n            osc if osc.is_zippy_ignored() => {\n                return kb.press_key(osc);\n            }\n            _ => {}\n        }\n        if self.zchd.zchd_smart_space_state == ZchSmartSpaceState::Sent\n            && self\n                .zch_cfg\n                .zch_cfg_smart_space_punctuation\n                .contains(&match (\n                    self.zchd.zchd_is_lsft_active | self.zchd.zchd_is_rsft_active,\n                    self.zchd.zchd_is_altgr_active,\n                ) {\n                    (false, false) => ZchOutput::Lowercase(osc),\n                    (true, false) => ZchOutput::Uppercase(osc),\n                    (false, true) => ZchOutput::AltGr(osc),\n                    (true, true) => ZchOutput::ShiftAltGr(osc),\n                })\n        {\n            self.zchd.zchd_characters_to_delete_on_next_activation -= 1;\n            kb.press_key(OsCode::KEY_BACKSPACE)?;\n            kb.release_key(OsCode::KEY_BACKSPACE)?;\n        }\n        self.zchd.zchd_smart_space_state = ZchSmartSpaceState::Inactive;\n        if self.zchd.zchd_enabled_state != ZchEnabledState::Enabled {\n            return kb.press_key(osc);\n        }\n\n        // Zippychording is enabled. Ensure the deadline to disable it if no chord activates is\n        // active.\n        self.zchd\n            .zchd_activate_chord_deadline(self.zch_cfg.zch_cfg_ticks_chord_deadline);\n        self.zchd.zchd_state_change(&self.zch_cfg);\n        self.zchd.zchd_press_key(osc);\n\n        // There might be an activation.\n        // - delete typed keys\n        // - output activation\n        //\n        // Key deletion needs to remove typed keys as well as past activations that need to be\n        // cleaned up, e.g. either the antecedent in a \"combo chord\" or an eagerly-activated\n        // chord using fewer keys, but user has still held that chord and pressed further keys,\n        // activating a chord with the same+extra keys.\n        let mut activation = Neither;\n        if let Some(pchords) = &self.zchd.zchd_prioritized_chords {\n            activation = pchords\n                .lock()\n                .0\n                .ssm_get_or_is_subset_ksorted(self.zchd.zchd_input_keys.zchik_keys());\n        }\n        let mut is_prioritized_activation = false;\n        if !matches!(activation, HasValue(..)) {\n            activation = self\n                .zch_chords\n                .0\n                .ssm_get_or_is_subset_ksorted(self.zchd.zchd_input_keys.zchik_keys());\n        } else {\n            is_prioritized_activation = true;\n        }\n\n        match activation {\n            HasValue(a) => {\n                // Find the longest common prefix length between the prior activation and the new\n                // activation. This value affects both:\n                // - the number of backspaces that need to be done\n                // - the number of characters that actually need to be typed by the activation\n                let common_prefix_len_from_past_activation = if !is_prioritized_activation\n                    && self.zchd.zchd_same_hold_activation_count == 0\n                {\n                    0\n                } else {\n                    self.zchd\n                        .zchd_prior_activation\n                        .as_ref()\n                        .map(|prior_activation| {\n                            let current_activation_output = &a.zch_output;\n                            let mut len: i16 = 0;\n                            for (past, current) in prior_activation\n                                .zch_output\n                                .iter()\n                                .copied()\n                                .zip(current_activation_output.iter().copied())\n                            {\n                                if past.osc() == OsCode::KEY_BACKSPACE\n                                    || current.osc() == OsCode::KEY_BACKSPACE\n                                    || past != current\n                                {\n                                    break;\n                                }\n                                len += 1;\n                            }\n                            len\n                        })\n                        .unwrap_or(0)\n                };\n                self.zchd.zchd_prior_activation = Some(a.clone());\n                self.zchd.zchd_same_hold_activation_count += 1;\n\n                self.zchd\n                    .zchd_restart_deadline(self.zch_cfg.zch_cfg_ticks_chord_deadline);\n                if !a.zch_output.is_empty() {\n                    // Zippychording eagerly types characters that form a chord and also eagerly\n                    // outputs chords that are of a maybe-to-be-activated-later chord with more\n                    // participating keys. This procedure erases both classes of typed characters\n                    // in order to have the correct typed output for this chord activation.\n                    for _ in 0..(self.zchd.zchd_characters_to_delete_on_next_activation\n                        + if is_prioritized_activation {\n                            self.zchd.zchd_prior_activation_output_count\n                        } else {\n                            0\n                        }\n                        - common_prefix_len_from_past_activation)\n                    {\n                        kb.press_key(OsCode::KEY_BACKSPACE)?;\n                        kb.release_key(OsCode::KEY_BACKSPACE)?;\n                    }\n                    self.zchd.zchd_characters_to_delete_on_next_activation = 0;\n                    self.zchd.zchd_prior_activation_output_count =\n                        ZchOutput::display_len(&a.zch_output);\n                } else {\n                    // Followup chords may consist of an empty output; eventually in the followup\n                    // chain has an activation output that is not empty. For empty outputs, do not\n                    // do any backspacing.\n                    self.zchd.zchd_characters_to_delete_on_next_activation += 1;\n                    self.zchd.zchd_prior_activation_output_count +=\n                        self.zchd.zchd_input_keys.zchik_keys().len() as i16;\n                    kb.press_key(osc)?;\n                }\n\n                self.zchd\n                    .zchd_prioritized_chords\n                    .clone_from(&a.zch_followups);\n                let mut released_sft = false;\n                #[cfg(feature = \"interception_driver\")]\n                let mut send_count = 0;\n                if self.zchd.zchd_is_altgr_active && !a.zch_output.is_empty() {\n                    kb.release_key(OsCode::KEY_RIGHTALT)?;\n                }\n                for key_to_send in a\n                    .zch_output\n                    .iter()\n                    .copied()\n                    .skip(common_prefix_len_from_past_activation as usize)\n                {\n                    #[cfg(feature = \"interception_driver\")]\n                    {\n                        // Note: every 5 keys on Windows Interception, do a sleep because\n                        // sending too quickly apparently causes weird behaviour...\n                        // I guess there's some buffer in the Interception code that is filling up.\n                        send_count += 1;\n                        if send_count % 5 == 0 {\n                            std::thread::sleep(std::time::Duration::from_millis(1));\n                        }\n                    }\n\n                    match key_to_send {\n                        ZchOutput::Lowercase(osc) | ZchOutput::NoEraseLowercase(osc) => {\n                            type_osc(osc, kb, &self.zchd)?;\n                        }\n                        ZchOutput::Uppercase(osc) | ZchOutput::NoEraseUppercase(osc) => {\n                            maybe_press_sft_during_activation(released_sft, kb, &self.zchd)?;\n                            type_osc(osc, kb, &self.zchd)?;\n                            maybe_release_sft_during_activation(released_sft, kb, &self.zchd)?;\n                        }\n                        ZchOutput::AltGr(osc) | ZchOutput::NoEraseAltGr(osc) => {\n                            // A note regarding maybe_press|release_sft\n                            // in contrast to always pressing|releasing altgr:\n                            //\n                            // The maybe-logic is valuable with Shift to capitalize the first\n                            // typed output during activation.\n                            // However, altgr - if already held -\n                            // does not seem useful to keep held on the first typed output so it is\n                            // always released at the beginning and pressed at the end if it was\n                            // previously being held.\n                            kb.press_key(OsCode::KEY_RIGHTALT)?;\n                            type_osc(osc, kb, &self.zchd)?;\n                            kb.release_key(OsCode::KEY_RIGHTALT)?;\n                        }\n                        ZchOutput::ShiftAltGr(osc) | ZchOutput::NoEraseShiftAltGr(osc) => {\n                            kb.press_key(OsCode::KEY_RIGHTALT)?;\n                            maybe_press_sft_during_activation(released_sft, kb, &self.zchd)?;\n                            type_osc(osc, kb, &self.zchd)?;\n                            maybe_release_sft_during_activation(released_sft, kb, &self.zchd)?;\n                            kb.release_key(OsCode::KEY_RIGHTALT)?;\n                        }\n                    };\n\n                    self.zchd.zchd_characters_to_delete_on_next_activation +=\n                        key_to_send.output_char_count();\n\n                    if !released_sft && !self.zchd.zchd_is_caps_word_active {\n                        released_sft = true;\n                        if self.zchd.zchd_is_lsft_active {\n                            kb.release_key(OsCode::KEY_LEFTSHIFT)?;\n                        }\n                        if self.zchd.zchd_is_rsft_active {\n                            kb.release_key(OsCode::KEY_RIGHTSHIFT)?;\n                        }\n                    }\n                }\n\n                if self.zch_cfg.zch_cfg_smart_space != ZchSmartSpaceCfg::Disabled\n                    && a.zch_output\n                        .last()\n                        .map(|out| !matches!(out.osc(), OsCode::KEY_SPACE | OsCode::KEY_BACKSPACE))\n                        .unwrap_or(false /* if output is empty, don't do smart spacing */)\n                {\n                    if self.zch_cfg.zch_cfg_smart_space == ZchSmartSpaceCfg::Full {\n                        self.zchd.zchd_smart_space_state = ZchSmartSpaceState::Sent;\n                    }\n\n                    // It might look unusual to add to both.\n                    // This is correct to do.\n                    // zchd_prior_activation_output_count only applies to followup activations,\n                    // which should only occur after a full release+repress of a new chord.\n                    // The full release will set zchd_characters_to_delete_on_next_activation to 0.\n                    // Overlapping chords do not use zchd_prior_activation_output_count but\n                    // instead keep track of characters to delete via\n                    // zchd_characters_to_delete_on_next_activation,\n                    // which is incremented both by typing characters\n                    // to achieve a chord in the first place,\n                    // as well as by chord activations that are overlapped\n                    // by the intended final chord.\n                    self.zchd.zchd_prior_activation_output_count += 1;\n                    self.zchd.zchd_characters_to_delete_on_next_activation += 1;\n\n                    kb.press_key(OsCode::KEY_SPACE)?;\n                    kb.release_key(OsCode::KEY_SPACE)?;\n                }\n\n                if !self.zchd.zchd_is_caps_word_active {\n                    // When expanding, lsft/rsft will be released after the first press.\n                    if self.zchd.zchd_is_lsft_active {\n                        kb.press_key(OsCode::KEY_LEFTSHIFT)?;\n                    }\n                    if self.zchd.zchd_is_rsft_active {\n                        kb.press_key(OsCode::KEY_RIGHTSHIFT)?;\n                    }\n                }\n                if self.zchd.zchd_is_altgr_active && !a.zch_output.is_empty() {\n                    kb.press_key(OsCode::KEY_RIGHTALT)?;\n                }\n\n                // Note: it is incorrect to clear input keys.\n                // Zippychord will eagerly output chords even if there is an overlapping chord that\n                // may be activated later by an additional keypress before any releases happen.\n                // E.g.\n                // ab => Abba\n                // abc => Alphabet\n                //\n                // If (b a) are typed, \"Abba\" is outputted.\n                // If (b a) are continued to be held and (c) is subsequently pressed,\n                // \"Abba\" gets erased and \"Alphabet\" is outputted.\n                //\n                // WRONG:\n                // self.zchd.zchd_input_keys.zchik_clear()\n\n                self.zchd.zchd_last_press = ZchLastPressClassification::IsChord;\n                Ok(())\n            }\n\n            IsSubset => {\n                self.zchd.zchd_last_press = ZchLastPressClassification::NotChord;\n                self.zchd.zchd_characters_to_delete_on_next_activation += 1;\n                kb.press_key(osc)\n            }\n\n            Neither => {\n                self.zchd.zchd_soft_reset();\n                kb.press_key(osc)\n            }\n        }\n    }\n\n    // Zch handling for key releases.\n    pub(crate) fn zch_release_key(\n        &mut self,\n        kb: &mut KbdOut,\n        osc: OsCode,\n    ) -> Result<(), std::io::Error> {\n        if self.zch_chords.is_empty() {\n            return kb.release_key(osc);\n        }\n        match osc {\n            OsCode::KEY_LEFTSHIFT => {\n                self.zchd.zchd_is_lsft_active = false;\n            }\n            OsCode::KEY_RIGHTSHIFT => {\n                self.zchd.zchd_is_rsft_active = false;\n            }\n            OsCode::KEY_RIGHTALT => {\n                self.zchd.zchd_is_altgr_active = false;\n            }\n            _ => {}\n        }\n        if osc.is_zippy_ignored() {\n            return kb.release_key(osc);\n        }\n        self.zchd.zchd_state_change(&self.zch_cfg);\n        self.zchd.zchd_release_key(osc);\n        kb.release_key(osc)\n    }\n\n    /// Tick the zch output state.\n    pub(crate) fn zch_tick(&mut self, is_caps_word_active: bool) {\n        self.zchd.zchd_tick(is_caps_word_active);\n    }\n\n    /// Returns true if zch state has no further processing so the idling optimization can\n    /// activate.\n    pub(crate) fn zch_is_idle(&self) -> bool {\n        self.zchd.zchd_is_idle()\n    }\n}\n\nfn type_osc(osc: OsCode, kb: &mut KbdOut, zchd: &ZchDynamicState) -> Result<(), std::io::Error> {\n    if zchd.zchd_input_keys.zchik_contains(osc) {\n        kb.release_key(osc)?;\n        kb.press_key(osc)?;\n    } else {\n        kb.press_key(osc)?;\n        kb.release_key(osc)?;\n    }\n    Ok(())\n}\n\nfn maybe_press_sft_during_activation(\n    sft_already_released: bool,\n    kb: &mut KbdOut,\n    zchd: &ZchDynamicState,\n) -> Result<(), std::io::Error> {\n    if !zchd.zchd_is_caps_word_active\n        && (sft_already_released || !zchd.zchd_is_lsft_active && !zchd.zchd_is_rsft_active)\n    {\n        kb.press_key(OsCode::KEY_LEFTSHIFT)?;\n    }\n    Ok(())\n}\n\nfn maybe_release_sft_during_activation(\n    sft_already_released: bool,\n    kb: &mut KbdOut,\n    zchd: &ZchDynamicState,\n) -> Result<(), std::io::Error> {\n    if !zchd.zchd_is_caps_word_active\n        && (sft_already_released || !zchd.zchd_is_lsft_active && !zchd.zchd_is_rsft_active)\n    {\n        kb.release_key(OsCode::KEY_LEFTSHIFT)?;\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "src/kanata/output_logic.rs",
    "content": "use super::*;\n\n#[cfg(feature = \"zippychord\")]\nmod zippychord;\n#[cfg(feature = \"zippychord\")]\npub(crate) use zippychord::*;\n\n// Functions to send keys except those that fall in the ignorable range.\n// And also have been repurposed to have additional logic to send mouse events, out of convenience.\n//\n// POTENTIAL PROBLEM - G-keys:\n// Some keys are ignored because they are *probably* unused,\n// or otherwise are probably in an unergonomic, far away key position,\n// so if you're using kanata, you can now stop using those keys and\n// do something better!\n//\n// I should probably let people turn this off if they really want to,\n// but I don't like how that would require extra code.\n// I'll defer to YAGNI and add docs, and let people report problems if\n// they want a fix 🐝.\n//\n// The keys ignored are intentionally the upper numbers of KEY_MACROX.\n// The Linux input-event-codes.h file mentions G1-G18 and S1-S30\n// as keys that might use these codes.\n//\n// Logitech still makes devices with G-keys\n// but the S-keys are apparently from the\n// \"Microsoft SideWinder X6 Keyboard\"\n// which appears to no longer be in production.\n//\n// Thus based on my reading, 18 is the highest macro key\n// that can be assumed to be used by devices still in production.\npub(super) const KEY_IGNORE_MIN: u16 = 0x2a4; // KEY_MACRO21\npub(super) const KEY_IGNORE_MAX: u16 = 0x2ad; // KEY_MACRO30\npub(super) fn write_key(kb: &mut KbdOut, osc: OsCode, val: KeyValue) -> Result<(), std::io::Error> {\n    match u16::from(osc) {\n        KEY_IGNORE_MIN..=KEY_IGNORE_MAX => Ok(()),\n        _ => kb.write_key(osc, val),\n    }\n}\npub(super) fn press_key(kb: &mut KbdOut, osc: OsCode) -> Result<(), std::io::Error> {\n    use OsCode::*;\n    match u16::from(osc) {\n        KEY_IGNORE_MIN..=KEY_IGNORE_MAX => Ok(()),\n        _ => match osc {\n            BTN_LEFT | BTN_RIGHT | BTN_MIDDLE | BTN_SIDE | BTN_EXTRA => {\n                let btn = osc_to_btn(osc);\n                kb.click_btn(btn)\n            }\n            MouseWheelUp | MouseWheelDown | MouseWheelLeft | MouseWheelRight => {\n                let direction = osc_to_wheel_direction(osc);\n                kb.scroll(direction, HI_RES_SCROLL_UNITS_IN_LO_RES)\n            }\n            _ => post_filter_press(kb, osc),\n        },\n    }\n}\npub(super) fn release_key(kb: &mut KbdOut, osc: OsCode) -> Result<(), std::io::Error> {\n    use OsCode::*;\n    match u16::from(osc) {\n        KEY_IGNORE_MIN..=KEY_IGNORE_MAX => Ok(()),\n        _ => match osc {\n            BTN_LEFT | BTN_RIGHT | BTN_MIDDLE | BTN_SIDE | BTN_EXTRA => {\n                let btn = osc_to_btn(osc);\n                kb.release_btn(btn)\n            }\n            MouseWheelUp | MouseWheelDown | MouseWheelLeft | MouseWheelRight => {\n                // no-op: these are handled as scroll events in the press but scroll has no notion\n                // of release.\n                Ok(())\n            }\n            _ => post_filter_release(kb, osc),\n        },\n    }\n}\nfn osc_to_btn(osc: OsCode) -> Btn {\n    use Btn::*;\n    use OsCode::*;\n    match osc {\n        BTN_LEFT => Left,\n        BTN_RIGHT => Right,\n        BTN_MIDDLE => Mid,\n        BTN_EXTRA => Forward,\n        BTN_SIDE => Backward,\n        _ => unreachable!(\"called osc_to_btn with bad value {osc}\"),\n    }\n}\nfn osc_to_wheel_direction(osc: OsCode) -> MWheelDirection {\n    use MWheelDirection::*;\n    use OsCode::*;\n    match osc {\n        MouseWheelUp => Up,\n        MouseWheelDown => Down,\n        MouseWheelLeft => Left,\n        MouseWheelRight => Right,\n        _ => unreachable!(\"called osc_to_wheel_direction with bad value {osc}\"),\n    }\n}\n\nfn post_filter_press(kb: &mut KbdOut, osc: OsCode) -> Result<(), std::io::Error> {\n    #[cfg(not(feature = \"zippychord\"))]\n    {\n        kb.press_key(osc)\n    }\n    #[cfg(feature = \"zippychord\")]\n    {\n        zch().zch_press_key(kb, osc)\n    }\n}\n\nfn post_filter_release(kb: &mut KbdOut, osc: OsCode) -> Result<(), std::io::Error> {\n    #[cfg(not(feature = \"zippychord\"))]\n    {\n        kb.release_key(osc)\n    }\n    #[cfg(feature = \"zippychord\")]\n    {\n        zch().zch_release_key(kb, osc)\n    }\n}\n\npub(super) fn zippy_is_idle() -> bool {\n    #[cfg(not(feature = \"zippychord\"))]\n    {\n        true\n    }\n    #[cfg(feature = \"zippychord\")]\n    {\n        zch().zch_is_idle()\n    }\n}\n\npub(super) fn zippy_tick(_caps_word_is_active: bool) {\n    #[cfg(feature = \"zippychord\")]\n    {\n        zch().zch_tick(_caps_word_is_active)\n    }\n}\n"
  },
  {
    "path": "src/kanata/scroll.rs",
    "content": "use super::*;\n\npub struct ScrollState {\n    pub direction: MWheelDirection,\n    pub interval: u16,\n    pub ticks_until_scroll: u16,\n    pub distance: u16,\n    pub scroll_accel_state: Option<ScrollAccelState>,\n}\n\npub struct ScrollAccelState {\n    pub deceleration_multiplier: f32,\n    pub acceleration_multiplier: f32,\n    pub max_velocity: f32,\n    pub current_velocity: f32,\n    pub scroll_released: bool,\n}\n\npub(crate) fn update_scrollstate_get_result(\n    state: &mut Option<ScrollState>,\n) -> Option<(MWheelDirection, u16)> {\n    let Some(state) = state else {\n        return None;\n    };\n    if state.ticks_until_scroll == 0 {\n        state.ticks_until_scroll = state.interval - 1;\n        let direction = state.direction;\n        let distance = state.distance;\n\n        Some(match &mut state.scroll_accel_state {\n            Some(acs) => match acs.scroll_released {\n                false => {\n                    let new_velocity = f32::min(\n                        acs.max_velocity,\n                        acs.current_velocity * acs.acceleration_multiplier,\n                    );\n                    acs.current_velocity = new_velocity;\n                    (direction, new_velocity as u16)\n                }\n                true => {\n                    let mut new_velocity = acs.current_velocity * acs.deceleration_multiplier;\n                    if new_velocity < 5.0 {\n                        new_velocity = 0.0;\n                    }\n                    acs.current_velocity = new_velocity;\n                    (direction, new_velocity as u16)\n                }\n            },\n            None => (direction, distance),\n        })\n    } else {\n        state.ticks_until_scroll -= 1;\n        None\n    }\n}\n"
  },
  {
    "path": "src/kanata/sequences.rs",
    "content": "use super::*;\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\npub enum SequenceActivity {\n    Inactive,\n    Active,\n}\n\nuse SequenceActivity::*;\n\npub struct SequenceState {\n    /// Unmangled sequence of keys pressed for hidden-delay-type.\n    pub raw_oscs: Vec<OsCode>,\n    /// Keeps track of standard sequence state.\n    /// This includes regular keys, e.g. `a b c`\n    /// and chorded keys, e.g. `S-(d e f)`.\n    pub sequence: Vec<u16>,\n    /// Keeps track of overlapping sequence state.\n    /// E.g. able to detect `O-(g h i)`\n    pub overlapped_sequence: Vec<u16>,\n    /// Determines the handling of keys while sequence state is in progress.\n    pub sequence_input_mode: SequenceInputMode,\n    /// Starts from `sequence_timeout` and ticks down\n    /// approximately every millisecond.\n    /// At 0 the sequence state terminates.\n    pub ticks_until_timeout: u16,\n    /// User-configured sequence timeout setting.\n    pub sequence_timeout: u16,\n    /// Whether the sequence is active or not.\n    pub activity: SequenceActivity,\n    /// Counter to reduce number of backspaces typed.\n    noerase_count: u16,\n}\n\nimpl SequenceState {\n    pub fn new() -> Self {\n        Self {\n            raw_oscs: vec![],\n            sequence: vec![],\n            overlapped_sequence: vec![],\n            sequence_input_mode: SequenceInputMode::HiddenSuppressed,\n            ticks_until_timeout: 0,\n            sequence_timeout: 0,\n            activity: Inactive,\n            noerase_count: 0,\n        }\n    }\n\n    /// Updates the sequence state parameters, clears buffers, and sets the state to active.\n    pub fn activate(&mut self, input_mode: SequenceInputMode, timeout: u16) {\n        self.sequence_input_mode = input_mode;\n        self.sequence_timeout = timeout;\n        self.ticks_until_timeout = timeout;\n        self.raw_oscs.clear();\n        self.sequence.clear();\n        self.overlapped_sequence.clear();\n        self.activity = Active;\n        self.noerase_count = 0;\n    }\n\n    pub fn is_active(&self) -> bool {\n        self.activity == Active\n    }\n\n    pub fn get_active(&mut self) -> Option<&mut Self> {\n        match self.activity {\n            Active => Some(self),\n            Inactive => None,\n        }\n    }\n\n    pub fn is_inactive(&self) -> bool {\n        self.activity == Inactive\n    }\n}\n\nimpl Default for SequenceState {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\npub(super) fn get_mod_mask_for_cur_keys(cur_keys: &[KeyCode]) -> u16 {\n    cur_keys\n        .iter()\n        .copied()\n        .fold(0, |a, v| a | mod_mask_for_keycode(v))\n}\n\npub(super) enum EndSequenceType {\n    Standard,\n    Overlap,\n}\n\npub(super) fn do_sequence_press_logic(\n    state: &mut SequenceState,\n    k: &KeyCode,\n    mod_mask: u16,\n    kbd_out: &mut KbdOut,\n    sequences: &kanata_parser::trie::Trie<(u8, u16)>,\n    sequence_backtrack_modcancel: bool,\n    layout: &mut BorrowedKLayout,\n) -> Result<(), anyhow::Error> {\n    state.ticks_until_timeout = state.sequence_timeout;\n    let osc = OsCode::from(*k);\n    state.raw_oscs.push(osc);\n    use kanata_parser::trie::GetOrDescendentExistsResult::*;\n    let pushed_into_seq = {\n        // Transform to OsCode and convert modifiers other than altgr/ralt\n        // (same key different names) to the left version, since that's\n        // how chords get transformed when building up sequences.\n        let base = u16::from(match osc {\n            OsCode::KEY_RIGHTSHIFT => OsCode::KEY_LEFTSHIFT,\n            OsCode::KEY_RIGHTMETA => OsCode::KEY_LEFTMETA,\n            OsCode::KEY_RIGHTCTRL => OsCode::KEY_LEFTCTRL,\n            osc => osc,\n        });\n        base | mod_mask\n    };\n    match state.sequence_input_mode {\n        SequenceInputMode::VisibleBackspaced => {\n            press_key(kbd_out, osc)?;\n        }\n        SequenceInputMode::HiddenSuppressed | SequenceInputMode::HiddenDelayType => {}\n    }\n    log::debug!(\"sequence got {k:?}\");\n    state.sequence.push(pushed_into_seq);\n    let pushed_into_overlap_seq = (pushed_into_seq & MASK_KEYCODES) | KEY_OVERLAP_MARKER;\n    state.overlapped_sequence.push(pushed_into_overlap_seq);\n    let mut res = sequences.get_or_descendant_exists(&state.sequence);\n\n    // Check for invalid termination of standard variant of sequence state.\n    // Can potentially backtrack and overwrite modded keystates as well as overlap keystates, which\n    // might exist in the sequence because of an earlier invalid termination where the standard\n    // sequence got filled in with overlap sequence data.\n    let mut is_invalid_termination_standard = false;\n    if res == NotInTrie {\n        is_invalid_termination_standard = {\n            let mut no_valid_seqs = true;\n            // If applicable, check again with modifier bits unset.\n            for i in (0..state.sequence.len()).rev() {\n                // Note: proper bounds are immediately above.\n                // Can't use iter_mut due to borrowing issues.\n                if state.sequence[i] == KEY_OVERLAP_MARKER {\n                    state.sequence.remove(i);\n                } else if sequence_backtrack_modcancel {\n                    state.sequence[i] &= MASK_KEYCODES;\n                } else {\n                    state.sequence[i] &= !KEY_OVERLAP_MARKER;\n                }\n                res = sequences.get_or_descendant_exists(&state.sequence);\n                if res != NotInTrie {\n                    no_valid_seqs = false;\n                    break;\n                }\n            }\n            no_valid_seqs\n        };\n    }\n\n    // Check for invalid termination of overlap variant of sequence state.\n    // This variant does not backtrack today because I haven't figured out how to do that easily.\n    // It does do some attempts to stay valid by modifying the tail of the sequence though.\n    let mut res_overlapped = sequences.get_or_descendant_exists(&state.overlapped_sequence);\n    let is_invalid_termination_overlapped = if res_overlapped == NotInTrie {\n        // Try ending the overlapping and push overlapping seq again.\n        let index_of_last = state.overlapped_sequence.len() - 1;\n        state.overlapped_sequence[index_of_last] = KEY_OVERLAP_MARKER;\n        state.overlapped_sequence.push(pushed_into_overlap_seq);\n        res_overlapped = sequences.get_or_descendant_exists(&state.overlapped_sequence);\n        let index_of_last = index_of_last + 1;\n        if res_overlapped == NotInTrie {\n            // Try checking the trie after setting the latest key to not have the overlapping\n            // marker.\n            state.overlapped_sequence[index_of_last] = pushed_into_seq;\n            res_overlapped = sequences.get_or_descendant_exists(&state.overlapped_sequence);\n            if res_overlapped == NotInTrie {\n                if pushed_into_seq & MASK_KEYCODES == pushed_into_seq {\n                    // Avoid calling get_or_descendant_exists if there is no difference, to save on\n                    // doing work checking in the trie.\n                    true\n                } else {\n                    // Try unmodded `pushed_into_seq`.\n                    state.overlapped_sequence[index_of_last] = pushed_into_seq & MASK_KEYCODES;\n                    res_overlapped = sequences.get_or_descendant_exists(&state.overlapped_sequence);\n                    res_overlapped == NotInTrie\n                }\n            } else {\n                false\n            }\n        } else {\n            false\n        }\n    } else {\n        false\n    };\n\n    match (\n        is_invalid_termination_standard,\n        is_invalid_termination_overlapped,\n    ) {\n        (false, false) => {}\n        (false, true) => {\n            log::debug!(\"overlap seq is invalid; filling with standard seq\");\n            // Overwrite overlapped with non-overlapped tracking\n            state.overlapped_sequence.clear();\n            state\n                .overlapped_sequence\n                .extend(state.sequence.iter().copied());\n            res_overlapped = sequences.get_or_descendant_exists(&state.overlapped_sequence);\n        }\n        (true, false) => {\n            log::debug!(\"standard seq is invalid; filling with overlap seq\");\n            state.sequence.clear();\n            state\n                .sequence\n                .extend(state.overlapped_sequence.iter().copied());\n            if state.sequence.last().copied().unwrap_or(0) != KEY_OVERLAP_MARKER\n                && state.overlapped_sequence.last().copied().unwrap_or(0) >= KEY_OVERLAP_MARKER\n            {\n                // Always treat non-overlapping sequence as if overlap state has\n                // ended; if overlapped_sequence itself has an overlap state.\n                state.sequence.push(KEY_OVERLAP_MARKER);\n            }\n            res = sequences.get_or_descendant_exists(&state.sequence);\n        }\n        (true, true) => {\n            // One more try for backtracking: check for validity by removing from the front.\n            while res == NotInTrie && !state.sequence.is_empty() {\n                state.sequence.remove(0);\n                res = sequences.get_or_descendant_exists(&state.sequence);\n            }\n            if res == NotInTrie || state.sequence.is_empty() {\n                log::debug!(\"invalid keys for seq\");\n                cancel_sequence(state, kbd_out)?;\n            }\n        }\n    }\n\n    // Check for successful sequence termination.\n    if let HasValue((i, j)) = res_overlapped {\n        // First, check for a valid simultaneous completion.\n        // Simultaneous completion should take priority.\n        do_successful_sequence_termination(kbd_out, state, layout, i, j, EndSequenceType::Overlap)?;\n    } else if let HasValue((i, j)) = res {\n        // Try terminating the overlapping and check if simultaneous termination worked.\n        // Simultaneous completion should take priority.\n        state.overlapped_sequence.push(KEY_OVERLAP_MARKER);\n        if let HasValue((oi, oj)) = sequences.get_or_descendant_exists(&state.overlapped_sequence) {\n            do_successful_sequence_termination(\n                kbd_out,\n                state,\n                layout,\n                oi,\n                oj,\n                EndSequenceType::Overlap,\n            )?;\n        } else {\n            do_successful_sequence_termination(\n                kbd_out,\n                state,\n                layout,\n                i,\n                j,\n                EndSequenceType::Standard,\n            )?;\n        }\n    }\n    Ok(())\n}\n\nuse kanata_keyberon::key_code::KeyCode::*;\n\npub(super) fn do_successful_sequence_termination(\n    kbd_out: &mut KbdOut,\n    state: &mut SequenceState,\n    layout: &mut Layout<'_, 767, 2, &&[&CustomAction]>,\n    i: u8,\n    j: u16,\n    seq_type: EndSequenceType,\n) -> Result<(), anyhow::Error> {\n    log::debug!(\"sequence complete; tapping fake key\");\n    state.activity = Inactive;\n    let sequence = match seq_type {\n        EndSequenceType::Standard => &state.sequence,\n        EndSequenceType::Overlap => &state.overlapped_sequence,\n    };\n    match state.sequence_input_mode {\n        SequenceInputMode::HiddenSuppressed | SequenceInputMode::HiddenDelayType => {}\n        SequenceInputMode::VisibleBackspaced => {\n            // Release mod keys and backspace because they can cause backspaces to mess up.\n            layout.states.retain(|s| match s {\n                State::NormalKey { keycode, .. } => {\n                    if matches!(keycode, LCtrl | RCtrl | LAlt | RAlt | LGui | RGui) {\n                        // Ignore the error, ugly to return it from retain, and\n                        // this is very unlikely to happen anyway.\n                        let _ = release_key(kbd_out, keycode.into());\n                        false\n                    } else {\n                        true\n                    }\n                }\n                _ => true,\n            });\n            for k in sequence.iter().copied() {\n                // Check for pressed modifiers and don't input backspaces for\n                // those since they don't output characters that can be\n                // backspaced.\n                if k == KEY_OVERLAP_MARKER {\n                    continue;\n                };\n                let osc = OsCode::from(k & MASK_KEYCODES);\n                match osc {\n                    // Known bug: most non-characters-outputting keys are not\n                    // listed. I'm too lazy to list them all. Just use\n                    // character-outputting keys (and modifiers) in sequences\n                    // please! Or switch to a different input mode? It doesn't\n                    // really make sense to use non-typing characters other\n                    // than modifiers does it? Since those would probably be\n                    // further away from the home row, so why use them? If one\n                    // desired to fix this, a shorter list of keys would\n                    // probably be the list of keys that **do** output\n                    // characters than those that don't.\n                    osc if osc.is_modifier() => continue,\n                    osc if matches!(u16::from(osc), KEY_IGNORE_MIN..=KEY_IGNORE_MAX) => continue,\n                    _ => {\n                        if state.noerase_count > 0 {\n                            state.noerase_count -= 1;\n                        } else {\n                            kbd_out.press_key(OsCode::KEY_BACKSPACE)?;\n                            kbd_out.release_key(OsCode::KEY_BACKSPACE)?;\n                        }\n                    }\n                }\n            }\n        }\n    }\n    for k in sequence.iter().copied() {\n        if k == KEY_OVERLAP_MARKER {\n            continue;\n        };\n        let kc = KeyCode::from(OsCode::from(k & MASK_KEYCODES));\n        layout.states.retain(|s| match s {\n            State::NormalKey { keycode, .. } => kc != *keycode,\n            _ => true,\n        });\n    }\n    layout.event_to_front(Event::Release(i, j));\n    layout.event_to_front(Event::Press(i, j));\n    Ok(())\n}\n\npub(super) fn cancel_sequence(state: &mut SequenceState, kbd_out: &mut KbdOut) -> Result<()> {\n    state.activity = Inactive;\n    log::debug!(\"sequence cancelled\");\n    match state.sequence_input_mode {\n        SequenceInputMode::HiddenDelayType => {\n            for osc in state.raw_oscs.iter().copied() {\n                // BUG: chorded_hidden_delay_type\n                press_key(kbd_out, osc)?;\n                release_key(kbd_out, osc)?;\n            }\n        }\n        SequenceInputMode::HiddenSuppressed | SequenceInputMode::VisibleBackspaced => {}\n    }\n    Ok(())\n}\n\npub(super) fn add_noerase(state: &mut SequenceState, noerase_count: u16) {\n    state.noerase_count += noerase_count;\n}\n"
  },
  {
    "path": "src/kanata/unknown.rs",
    "content": "use super::*;\n\nimpl Kanata {\n    pub fn check_release_non_physical_shift(&mut self) -> Result<()> {\n        // Silence warning\n        check_for_exit(&KeyEvent::new(OsCode::KEY_UNKNOWN, KeyValue::Release));\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/kanata/windows/exthook.rs",
    "content": "use parking_lot::Mutex;\nuse std::convert::TryFrom;\nuse std::sync::Arc;\nuse std::sync::mpsc::{Receiver, SyncSender as Sender, TryRecvError, sync_channel};\nuse std::time;\n\nuse super::PRESSED_KEYS;\nuse crate::kanata::*;\n\nimpl Kanata {\n    /// Initialize the callback that is passed to the Windows low level hook to receive key events and run the native_windows_gui event loop.\n    pub fn event_loop(_cfg: Arc<Mutex<Self>>, tx: Sender<KeyEvent>) -> Result<()> {\n        let (preprocess_tx, preprocess_rx) = sync_channel(100);\n        start_event_preprocessor(preprocess_rx, tx);\n\n        let _ = KeyboardHook::set_input_cb(move |input_event| {\n            // →true if input event was handled, false otherwise, informs input_ev_listener whether to look for the output key event\n            let mut key_event = match KeyEvent::try_from(input_event) {\n                // InputEvent{code:u32      , up   :bool}\n                Ok(ev) => ev, // KeyEvent  {code:OsCode   , value:KeyValue}\n                _ => return false,\n            }; // Some(OsCode::KEY_0)←0x30        Release0 Press1 Repeat2 Tap WakeUp\n            check_for_exit(&key_event); //noop\n\n            let oscode = OsCode::from(input_event.code);\n            if !MAPPED_KEYS.lock().contains(&oscode) {\n                return false;\n            }\n            log::debug!(\"event loop: {}\", key_event);\n            match key_event.value {\n                // Unlike Linux, Windows does not use a separate value for repeat. However, our code needs to differentiate between initial press and repeat press.\n                KeyValue::Release => {\n                    PRESSED_KEYS.lock().remove(&key_event.code);\n                }\n                KeyValue::Press => {\n                    let mut pressed_keys = PRESSED_KEYS.lock();\n                    if pressed_keys.contains_key(&key_event.code) {\n                        key_event.value = KeyValue::Repeat;\n                    } else {\n                        pressed_keys.insert(key_event.code, web_time::Instant::now());\n                    }\n                }\n                _ => {}\n            }\n            try_send_panic(&preprocess_tx, key_event); // Send input_events to the preprocessing loop. Panic if channel somehow gets full or if channel disconnects. Typing input should never trigger a panic based on the channel getting full, assuming regular operation of the program and some other bug isn't the problem. I've tried to crash the program by pressing as many keys on my keyboard at the same time as I could, but was unable to.\n            #[cfg(feature = \"perf_logging\")]\n            debug!(\n                \" 🕐{}μs sent msg to tx→rx@start_processing_loop from event loop@KeyboardHook::set_input_cb\",\n                (start.elapsed()).as_micros()\n            );\n            true\n        });\n        Ok(())\n    }\n}\n\nfn try_send_panic(tx: &Sender<KeyEvent>, kev: KeyEvent) {\n    if let Err(e) = tx.try_send(kev) {\n        panic!(\"failed to send on channel: {e:?}\")\n    }\n}\n\nfn start_event_preprocessor(preprocess_rx: Receiver<KeyEvent>, process_tx: Sender<KeyEvent>) {\n    #[derive(Debug, Clone, Copy, PartialEq)]\n    enum LctlState {\n        Pressed,\n        Released,\n        Pending,\n        PendingReleased,\n        None,\n    }\n\n    std::thread::spawn(move || {\n        let mut lctl_state = LctlState::None;\n        loop {\n            match preprocess_rx.try_recv() {\n                Ok(kev) => match (*ALTGR_BEHAVIOUR.lock(), kev) {\n                    (AltGrBehaviour::DoNothing, _) => try_send_panic(&process_tx, kev),\n                    (\n                        AltGrBehaviour::AddLctlRelease,\n                        KeyEvent {\n                            value: KeyValue::Release,\n                            code: OsCode::KEY_RIGHTALT,\n                            ..\n                        },\n                    ) => {\n                        log::debug!(\"altgr add: adding lctl release\");\n                        try_send_panic(&process_tx, kev);\n                        try_send_panic(\n                            &process_tx,\n                            KeyEvent::new(OsCode::KEY_LEFTCTRL, KeyValue::Release),\n                        );\n                        PRESSED_KEYS.lock().remove(&OsCode::KEY_LEFTCTRL);\n                    }\n                    (\n                        AltGrBehaviour::CancelLctlPress,\n                        KeyEvent {\n                            value: KeyValue::Press,\n                            code: OsCode::KEY_LEFTCTRL,\n                            ..\n                        },\n                    ) => {\n                        log::debug!(\"altgr cancel: lctl state->pressed\");\n                        lctl_state = LctlState::Pressed;\n                    }\n                    (\n                        AltGrBehaviour::CancelLctlPress,\n                        KeyEvent {\n                            value: KeyValue::Release,\n                            code: OsCode::KEY_LEFTCTRL,\n                            ..\n                        },\n                    ) => match lctl_state {\n                        LctlState::Pressed => {\n                            log::debug!(\"altgr cancel: lctl state->released\");\n                            lctl_state = LctlState::Released;\n                        }\n                        LctlState::Pending => {\n                            log::debug!(\"altgr cancel: lctl state->pending-released\");\n                            lctl_state = LctlState::PendingReleased;\n                        }\n                        LctlState::None => try_send_panic(&process_tx, kev),\n                        _ => {}\n                    },\n                    (\n                        AltGrBehaviour::CancelLctlPress,\n                        KeyEvent {\n                            value: KeyValue::Press,\n                            code: OsCode::KEY_RIGHTALT,\n                            ..\n                        },\n                    ) => {\n                        log::debug!(\"altgr cancel: lctl state->none\");\n                        lctl_state = LctlState::None;\n                        try_send_panic(&process_tx, kev);\n                    }\n                    (_, _) => try_send_panic(&process_tx, kev),\n                },\n                Err(TryRecvError::Empty) => {\n                    if *ALTGR_BEHAVIOUR.lock() == AltGrBehaviour::CancelLctlPress {\n                        match lctl_state {\n                            LctlState::Pressed => {\n                                log::debug!(\"altgr cancel: lctl state->pending\");\n                                lctl_state = LctlState::Pending;\n                            }\n                            LctlState::Released => {\n                                log::debug!(\"altgr cancel: lctl state->pending-released\");\n                                lctl_state = LctlState::PendingReleased;\n                            }\n                            LctlState::Pending => {\n                                log::debug!(\"altgr cancel: lctl state->send\");\n                                try_send_panic(\n                                    &process_tx,\n                                    KeyEvent::new(OsCode::KEY_LEFTCTRL, KeyValue::Press),\n                                );\n                                lctl_state = LctlState::None;\n                            }\n                            LctlState::PendingReleased => {\n                                log::debug!(\"altgr cancel: lctl state->send+release\");\n                                try_send_panic(\n                                    &process_tx,\n                                    KeyEvent::new(OsCode::KEY_LEFTCTRL, KeyValue::Press),\n                                );\n                                try_send_panic(\n                                    &process_tx,\n                                    KeyEvent::new(OsCode::KEY_LEFTCTRL, KeyValue::Release),\n                                );\n                                lctl_state = LctlState::None;\n                            }\n                            _ => {}\n                        }\n                    }\n                    std::thread::sleep(time::Duration::from_millis(1));\n                }\n                Err(TryRecvError::Disconnected) => {\n                    panic!(\"channel disconnected (exthook event_preproces)\")\n                }\n            }\n        }\n    });\n}\n"
  },
  {
    "path": "src/kanata/windows/interception.rs",
    "content": "use anyhow::{Result, anyhow};\nuse kanata_interception as ic;\nuse parking_lot::Mutex;\nuse std::sync::Arc;\nuse std::sync::mpsc::SyncSender as Sender;\n\nuse super::PRESSED_KEYS;\nuse crate::kanata::*;\nuse crate::oskbd::KeyValue;\nuse kanata_parser::keys::OsCode;\n\nimpl Kanata {\n    pub fn event_loop_inner(kanata: Arc<Mutex<Self>>, tx: Sender<KeyEvent>) -> Result<()> {\n        let intrcptn = ic::Interception::new().ok_or_else(|| anyhow!(\"interception driver should init: have you completed the interception driver installation?\"))?;\n        intrcptn.set_filter(ic::is_keyboard, ic::Filter::KeyFilter(ic::KeyFilter::all()));\n        let mut strokes = [ic::Stroke::Keyboard {\n            code: ic::ScanCode::Esc,\n            state: ic::KeyState::empty(),\n            information: 0,\n        }; 32];\n\n        let keyboards_to_intercept_hwids = kanata.lock().intercept_kb_hwids.clone();\n        let keyboards_to_intercept_hwids_exclude = kanata.lock().intercept_kb_hwids_exclude.clone();\n        let mouse_to_intercept_hwids: Option<Vec<[u8; HWID_ARR_SZ]>> =\n            kanata.lock().intercept_mouse_hwids.clone();\n        let mouse_to_intercept_excluded_hwids: Option<Vec<[u8; HWID_ARR_SZ]>> =\n            kanata.lock().intercept_mouse_hwids_exclude.clone();\n        let mouse_movement_key = kanata.lock().mouse_movement_key.clone();\n        if mouse_to_intercept_hwids.is_some() || mouse_to_intercept_excluded_hwids.is_some() {\n            if mouse_movement_key.lock().is_some() {\n                intrcptn.set_filter(ic::is_mouse, ic::Filter::MouseFilter(ic::MouseState::all()));\n            } else {\n                intrcptn.set_filter(\n                    ic::is_mouse,\n                    ic::Filter::MouseFilter(ic::MouseState::all() & (!ic::MouseState::MOVE)),\n                );\n            }\n        }\n        let mut is_dev_interceptable: HashMap<ic::Device, bool> = HashMap::default();\n        loop {\n            let dev = intrcptn.wait();\n            if dev > 0 {\n                let num_strokes = intrcptn.receive(dev, &mut strokes) as usize;\n                for i in 0..num_strokes {\n                    let mut key_event = match strokes[i] {\n                        ic::Stroke::Keyboard { state, .. } => {\n                            if !is_device_interceptable(\n                                dev,\n                                &intrcptn,\n                                &keyboards_to_intercept_hwids,\n                                &keyboards_to_intercept_hwids_exclude,\n                                &mut is_dev_interceptable,\n                            ) {\n                                log::debug!(\"stroke {:?} is from undesired device\", strokes[i]);\n                                intrcptn.send(dev, &strokes[i..i + 1]);\n                                continue;\n                            }\n                            log::debug!(\"got stroke {:?}\", strokes[i]);\n                            let code = match OsCodeWrapper::try_from(strokes[i]) {\n                                Ok(c) => c.0,\n                                _ => {\n                                    log::debug!(\"could not map code to oscode\");\n                                    intrcptn.send(dev, &strokes[i..i + 1]);\n                                    continue;\n                                }\n                            };\n                            let value = match state.contains(ic::KeyState::UP) {\n                                false => KeyValue::Press,\n                                true => KeyValue::Release,\n                            };\n                            KeyEvent { code, value }\n                        }\n                        ic::Stroke::Mouse {\n                            state,\n                            rolling,\n                            flags,\n                            ..\n                        } => {\n                            let allow_this_dev = is_device_interceptable(\n                                dev,\n                                &intrcptn,\n                                &mouse_to_intercept_hwids,\n                                &mouse_to_intercept_excluded_hwids,\n                                &mut is_dev_interceptable,\n                            );\n\n                            if allow_this_dev {\n                                log::trace!(\"checking mouse stroke {:?}\", strokes[i]);\n\n                                if let Some(ms_mvmt_key) = *mouse_movement_key.lock()\n                                    && flags.contains(ic::MouseFlags::MOVE_RELATIVE)\n                                {\n                                    tx.try_send(KeyEvent::new(ms_mvmt_key, KeyValue::Tap))?;\n                                }\n                            }\n\n                            if let (true, Some(event)) =\n                                (allow_this_dev, mouse_state_to_event(state, rolling))\n                            {\n                                event\n                            } else {\n                                intrcptn.send(dev, &strokes[i..i + 1]);\n                                continue;\n                            }\n                        }\n                    };\n                    check_for_exit(&key_event);\n                    if !MAPPED_KEYS.lock().contains(&key_event.code) {\n                        log::debug!(\"{key_event:?} is not mapped\");\n                        intrcptn.send(dev, &strokes[i..i + 1]);\n                        continue;\n                    }\n                    log::debug!(\"sending {key_event:?} to processing loop\");\n                    match key_event.value {\n                        KeyValue::Release => {\n                            PRESSED_KEYS.lock().remove(&key_event.code);\n                        }\n                        KeyValue::Press => {\n                            let mut pressed_keys = PRESSED_KEYS.lock();\n                            if pressed_keys.contains(&key_event.code) {\n                                key_event.value = KeyValue::Repeat;\n                            } else {\n                                pressed_keys.insert(key_event.code);\n                            }\n                        }\n                        _ => {}\n                    }\n                    tx.try_send(key_event)?;\n                }\n            }\n        }\n    }\n    pub fn event_loop(\n        kanata: Arc<Mutex<Self>>,\n        tx: Sender<KeyEvent>,\n        #[cfg(feature = \"gui\")] ui: crate::gui::system_tray_ui::SystemTrayUi,\n    ) -> Result<()> {\n        #[cfg(not(feature = \"gui\"))]\n        {\n            Self::event_loop_inner(kanata, tx)\n        }\n        #[cfg(feature = \"gui\")]\n        {\n            std::thread::spawn(move || -> Result<()> { Self::event_loop_inner(kanata, tx) });\n            let _ui = ui; // prevents thread from panicking on exiting via a GUI\n            native_windows_gui::dispatch_thread_events();\n            Ok(())\n        }\n    }\n}\n\nfn is_device_interceptable(\n    input_dev: ic::Device,\n    intrcptn: &ic::Interception,\n    allowed_hwids: &Option<Vec<[u8; HWID_ARR_SZ]>>,\n    excluded_hwids: &Option<Vec<[u8; HWID_ARR_SZ]>>,\n    cache: &mut HashMap<ic::Device, bool>,\n) -> bool {\n    match (allowed_hwids, excluded_hwids) {\n        (None, None) => true,\n        (Some(allowed), None) => match cache.get(&input_dev) {\n            Some(v) => *v,\n            None => {\n                let mut hwid = [0u8; HWID_ARR_SZ];\n                log::trace!(\"getting hardware id for input dev: {input_dev}\");\n                let res = intrcptn.get_hardware_id(input_dev, &mut hwid);\n                let dev_is_interceptable = allowed.contains(&hwid);\n                log::info!(\n                    \"include check - res {res}; device #{input_dev} is intercepted: {dev_is_interceptable}; hwid {hwid:?} \"\n                );\n                cache.insert(input_dev, dev_is_interceptable);\n                dev_is_interceptable\n            }\n        },\n        (None, Some(excluded)) => match cache.get(&input_dev) {\n            Some(v) => *v,\n            None => {\n                let mut hwid = [0u8; HWID_ARR_SZ];\n                log::trace!(\"getting hardware id for input dev: {input_dev}\");\n                let res = intrcptn.get_hardware_id(input_dev, &mut hwid);\n                let dev_is_interceptable = !excluded.contains(&hwid);\n                log::info!(\n                    \"exclude check - res {res}; device #{input_dev} is intercepted: {dev_is_interceptable}; hwid {hwid:?} \"\n                );\n                cache.insert(input_dev, dev_is_interceptable);\n                dev_is_interceptable\n            }\n        },\n        _ => unreachable!(\"excluded and allowed should be mutually exclusive\"),\n    }\n}\nfn mouse_state_to_event(state: ic::MouseState, rolling: i16) -> Option<KeyEvent> {\n    if state.contains(ic::MouseState::RIGHT_BUTTON_DOWN) {\n        Some(KeyEvent {\n            code: OsCode::BTN_RIGHT,\n            value: KeyValue::Press,\n        })\n    } else if state.contains(ic::MouseState::RIGHT_BUTTON_UP) {\n        Some(KeyEvent {\n            code: OsCode::BTN_RIGHT,\n            value: KeyValue::Release,\n        })\n    } else if state.contains(ic::MouseState::LEFT_BUTTON_DOWN) {\n        Some(KeyEvent {\n            code: OsCode::BTN_LEFT,\n            value: KeyValue::Press,\n        })\n    } else if state.contains(ic::MouseState::LEFT_BUTTON_UP) {\n        Some(KeyEvent {\n            code: OsCode::BTN_LEFT,\n            value: KeyValue::Release,\n        })\n    } else if state.contains(ic::MouseState::MIDDLE_BUTTON_DOWN) {\n        Some(KeyEvent {\n            code: OsCode::BTN_MIDDLE,\n            value: KeyValue::Press,\n        })\n    } else if state.contains(ic::MouseState::MIDDLE_BUTTON_UP) {\n        Some(KeyEvent {\n            code: OsCode::BTN_MIDDLE,\n            value: KeyValue::Release,\n        })\n    } else if state.contains(ic::MouseState::BUTTON_4_DOWN) {\n        Some(KeyEvent {\n            code: OsCode::BTN_SIDE,\n            value: KeyValue::Press,\n        })\n    } else if state.contains(ic::MouseState::BUTTON_4_UP) {\n        Some(KeyEvent {\n            code: OsCode::BTN_SIDE,\n            value: KeyValue::Release,\n        })\n    } else if state.contains(ic::MouseState::BUTTON_5_DOWN) {\n        Some(KeyEvent {\n            code: OsCode::BTN_EXTRA,\n            value: KeyValue::Press,\n        })\n    } else if state.contains(ic::MouseState::BUTTON_5_UP) {\n        Some(KeyEvent {\n            code: OsCode::BTN_EXTRA,\n            value: KeyValue::Release,\n        })\n    } else if state.contains(ic::MouseState::WHEEL) {\n        let osc = if rolling >= 0 {\n            OsCode::MouseWheelUp\n        } else {\n            OsCode::MouseWheelDown\n        };\n        if MAPPED_KEYS.lock().contains(&osc) {\n            Some(KeyEvent {\n                code: osc,\n                value: KeyValue::Tap,\n            })\n        } else {\n            None\n        }\n    } else if state.contains(ic::MouseState::HWHEEL) {\n        let osc = if rolling >= 0 {\n            OsCode::MouseWheelRight\n        } else {\n            OsCode::MouseWheelLeft\n        };\n        if MAPPED_KEYS.lock().contains(&osc) {\n            Some(KeyEvent {\n                code: osc,\n                value: KeyValue::Tap,\n            })\n        } else {\n            None\n        }\n    } else {\n        None\n    }\n}\n"
  },
  {
    "path": "src/kanata/windows/llhook.rs",
    "content": "use parking_lot::Mutex;\nuse std::convert::TryFrom;\nuse std::sync::Arc;\nuse std::sync::mpsc::{Receiver, RecvError, SyncSender as Sender, TryRecvError, sync_channel};\nuse std::time;\n\nuse super::PRESSED_KEYS;\nuse crate::kanata::*;\n\nimpl Kanata {\n    /// Initialize the callback that is passed to the Windows low level hook to receive key events\n    /// and run the native_windows_gui event loop.\n    pub fn event_loop(\n        _cfg: Arc<Mutex<Self>>,\n        tx: Sender<KeyEvent>,\n        #[cfg(all(target_os = \"windows\", feature = \"gui\"))]\n        ui: crate::gui::system_tray_ui::SystemTrayUi,\n    ) -> Result<()> {\n        // Display debug and panic output when launched from a terminal.\n        #[cfg(not(feature = \"gui\"))]\n        unsafe {\n            use winapi::um::wincon::*;\n            if AttachConsole(ATTACH_PARENT_PROCESS) != 0 {\n                panic!(\"Could not attach to console\");\n            }\n        };\n\n        let (preprocess_tx, preprocess_rx) = sync_channel(100);\n        start_event_preprocessor(preprocess_rx, tx);\n        let kb_preprocess_tx = preprocess_tx.clone();\n\n        // This callback should return `false` if the input event is **not** handled by the\n        // callback and `true` if the input event **is** handled by the callback. Returning false\n        // informs the callback caller that the input event should be handed back to the OS for\n        // normal processing.\n        let _kbhook = KeyboardHook::set_input_cb(move |input_event| {\n            let mut key_event = match KeyEvent::try_from(input_event) {\n                Ok(ev) => ev,\n                _ => return false,\n            };\n\n            check_for_exit(&key_event);\n            let oscode = key_event.code;\n            if !MAPPED_KEYS.lock().contains(&oscode) {\n                return false;\n            }\n\n            // Unlike Linux, Windows does not use a separate value for repeat. However, our code\n            // needs to differentiate between initial press and repeat press.\n            log::debug!(\"event loop: {:?}\", key_event);\n            match key_event.value {\n                KeyValue::Release => {\n                    PRESSED_KEYS.lock().remove(&key_event.code);\n                }\n                KeyValue::Press => {\n                    let mut pressed_keys = PRESSED_KEYS.lock();\n                    if let std::collections::hash_map::Entry::Vacant(e) =\n                        pressed_keys.entry(key_event.code)\n                    {\n                        e.insert(web_time::Instant::now());\n                    } else {\n                        key_event.value = KeyValue::Repeat;\n                    }\n                }\n                _ => {}\n            }\n\n            // Send input_events to the preprocessing loop. Panic if channel somehow gets full or if\n            // channel disconnects. Typing input should never trigger a panic based on the channel\n            // getting full, assuming regular operation of the program and some other bug isn't the\n            // problem. I've tried to crash the program by pressing as many keys on my keyboard at\n            // the same time as I could, but was unable to.\n            try_send_panic(&kb_preprocess_tx, key_event);\n            true\n        });\n\n        use OsCode::*;\n        let oscodes_for_mhook_active = &[\n            BTN_LEFT,\n            BTN_RIGHT,\n            BTN_MIDDLE,\n            BTN_SIDE,\n            BTN_EXTRA,\n            MouseWheelUp,\n            MouseWheelDown,\n            MouseWheelLeft,\n            MouseWheelRight,\n        ];\n        let _handle = if oscodes_for_mhook_active\n            .iter()\n            .any(|osc| MAPPED_KEYS.lock().contains(osc))\n        {\n            log::info!(\"Installing mouse hook callback.\");\n            let mousehook = MouseHook::set_input_cb(move |mouse_event| {\n                log::debug!(\"llhook mouse event: {mouse_event:?}\");\n                let key_event = match KeyEvent::try_from(mouse_event) {\n                    Ok(ev) => ev,\n                    _ => return false,\n                };\n                let oscode = key_event.code;\n                if !MAPPED_KEYS.lock().contains(&oscode) {\n                    return false;\n                }\n                log::debug!(\"event loop - mouse: {:?}\", key_event);\n                try_send_panic(&preprocess_tx, key_event);\n                true\n            });\n            log::info!(\"Installed mouse hook callback successfully.\");\n            Some(mousehook)\n        } else {\n            log::info!(\"No mouse inputs were in defsrc on startup. Not activating mouse hook.\");\n            None\n        };\n\n        #[cfg(all(target_os = \"windows\", feature = \"gui\"))]\n        let _ui = ui; // prevents thread from panicking on exiting via a GUI\n        // The event loop is also required for the low-level keyboard hook to work.\n        native_windows_gui::dispatch_thread_events();\n        Ok(())\n    }\n\n    /// # Note\n    ///\n    /// This is disabled by default due to known issues.\n    /// Under some use cases this works just fine and can be enabled.\n    ///\n    /// ## Known issues\n    ///\n    /// - Ralt/lctl may have some issues with AltGr layouts\n    /// - Some software may have a later-stage remapping that changes which VK is active for a\n    ///   given VK that Kanata outputs. E.g. changing to roya/loya modifiers.\n    ///\n    /// # Description\n    ///\n    /// On Windows with LLHOOK/SendInput APIs,\n    /// Kanata does not have as much control\n    /// over the full system's keystates as one would want;\n    /// unlike in Linux or with the Interception driver.\n    /// Sometimes Kanata can miss events; e.g. a release is\n    /// missed and a keystate remains pressed within Kanata (1),\n    /// or a press is missed in Kanata but the release is caught,\n    /// and thus the keystate remains pressed within the Windows system\n    /// because Kanata consumed the release and didn't know what to do about it (2).\n    ///\n    /// For (1), `release_normalkey_states` theoretically fixes the issue\n    /// after 60s of Kanata being idle,\n    /// but that is a long time and doesn't seem to work consistently.\n    /// Unfortunately this does not seem to be easily fixable in all cases.\n    /// For example, a press consumed by Kanata could result in\n    /// **only** a `(layer-while-held ...)` action as the output;\n    /// if the corresponding release were missed,\n    /// Kanata has no information available from the larger Windows system\n    /// to confirm that the physical key is actually released\n    /// but that the process didn't see the event.\n    /// E.g. there is the `GetAsyncKeyState` API\n    /// and this will be useful when the missed release has a key output,\n    /// but not with the layer example.\n    /// There does not appear to be any \"raw input\" mechanism\n    /// to see the snapshot of the current state of physical keyboard keys.\n    ///\n    /// For (2), consider that this might be fixed purely within Kanata's\n    /// event handling and processing, by checking Kanata's active action states,\n    /// and if there are no active states corresponding to a released event,\n    /// to send a release of the original input.\n    /// This would result in extra release events though;\n    /// for example if the `A` key action is `(macro a)`,\n    /// the above logic will result in a second SendInput release event of `A`.\n    /// Instead, this function checks against the outside Windows state.\n    ///\n    /// The solution makes use of the following states:\n    /// - `MAPPED_KEYS` (MK)\n    /// - `GetAsyncKeyState` WinAPI (GKS)\n    /// - `PRESSED_KEYS` (PK)\n    /// - `self.prev_keys` (SPV)\n    ///\n    /// If a discrepancy is detected,\n    /// this procedure releases Windows keys via SendInput\n    /// and/or clears internal Kanata states.\n    ///\n    /// The checks are:\n    /// 1. For all of SPV, check that it is pressed in GKS.\n    ///    If a key is not pressed, find the coordinate of this state.\n    ///    Clear in PK and clear all states with the same coordinate as key output.\n    /// 2. For all keys in MK and active in GKS, check it is in SPV.\n    ///    If not in SPV, call SendInput to release in Windows.\n    #[cfg(not(feature = \"simulated_input\"))]\n    pub(crate) fn win_synchronize_keystates(&mut self) {\n        use kanata_keyberon::layout::*;\n        use winapi::um::winuser::*;\n\n        if !self.windows_sync_keystates {\n            return;\n        }\n\n        log::debug!(\"synchronizing win keystates\");\n        for pvk in self.prev_keys.iter() {\n            // Check 1 : each pvk is expected to be pressed.\n            let osc: OsCode = pvk.into();\n            let vk = i32::from(osc);\n            if vk > 254 {\n                // 254 should be highest valid VK number in Windows OS.\n                continue;\n            }\n            let vk_state = unsafe { GetAsyncKeyState(vk) } as u32;\n            let is_pressed_in_windows = vk_state >= 0b1000000;\n            if is_pressed_in_windows {\n                continue;\n            }\n\n            log::error!(\n                \"Unexpected keycode is pressed in kanata but not in Windows. Clearing kanata states: {pvk}\"\n            );\n            // Need to clear internal state about this key.\n            // find coordinate(s) in keyberon associated with pvk\n            let mut coords_to_clear = Vec::<KCoord>::new();\n            let layout = self.layout.bm();\n            layout.states.retain(|s| {\n                let retain = match s.keycode() {\n                    Some(k) => k != *pvk,\n                    _ => true,\n                };\n                if !retain && let Some(coord) = s.coord() {\n                    coords_to_clear.push(coord);\n                }\n                retain\n            });\n\n            // Clear other states other than keycode associated with a keycode that needs to be\n            // cleaned up.\n            layout.states.retain(|s| match s.coord() {\n                Some(c) => !coords_to_clear.contains(&c),\n                None => true,\n            });\n\n            // Clear PRESSED_KEYS for coordinates associated with real and not virtual keys\n            let mut pressed_keys = PRESSED_KEYS.lock();\n            for osc in coords_to_clear.iter().copied().filter_map(|c| match c {\n                (FAKE_KEY_ROW, _) => None,\n                (_, kc) => Some(OsCode::from(kc)),\n            }) {\n                pressed_keys.remove(&osc);\n            }\n            drop(pressed_keys);\n        }\n\n        let mapped_keys = MAPPED_KEYS.lock();\n        for mapped_osc in mapped_keys.iter().copied() {\n            // Check 2: each active win vk mapped in Kanata should have a value in pvk\n            if matches!(\n                mapped_osc,\n                OsCode::BTN_LEFT\n                    | OsCode::BTN_RIGHT\n                    | OsCode::BTN_MIDDLE\n                    | OsCode::BTN_SIDE\n                    | OsCode::BTN_EXTRA\n            ) {\n                // Skip mouse. Probably not under primary control of Kanata.\n                continue;\n            }\n            let vk = i32::from(mapped_osc);\n            if vk >= 256 {\n                continue;\n            }\n            let vk_state = unsafe { GetAsyncKeyState(vk) } as u32;\n            let is_pressed_in_windows = vk_state >= 0b1000000;\n            if !is_pressed_in_windows {\n                continue;\n            }\n            let vk = vk as u16;\n            let Some(osc) = OsCode::from_u16(vk) else {\n                continue;\n            };\n            if self.prev_keys.contains(&osc.into()) {\n                continue;\n            }\n            log::error!(\n                \"Unexpected keycode is pressed in Windows but not Kanata. Releasing in Windows: {osc}\"\n            );\n            let _ = release_key(&mut self.kbd_out, osc);\n        }\n        drop(mapped_keys);\n    }\n}\n\nfn try_send_panic(tx: &Sender<KeyEvent>, kev: KeyEvent) {\n    if let Err(e) = tx.try_send(kev) {\n        panic!(\"failed to send on channel: {e:?}\")\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq)]\nenum LctlState {\n    Pressed,\n    Released,\n    Pending,\n    PendingReleased,\n    None,\n}\n\n#[derive(Debug, Clone, Copy)]\nenum RecvValue {\n    Ok(KeyEvent),\n    Empty,\n    Disconnected,\n}\n\nenum CanBlock {\n    YesCanBlock,\n    NoMustPeriodicPoll,\n}\n\nfn preprocessor_recv(preprocess_rx: &Receiver<KeyEvent>, can_block: CanBlock) -> RecvValue {\n    match can_block {\n        CanBlock::YesCanBlock => match preprocess_rx.recv() {\n            Ok(kev) => RecvValue::Ok(kev),\n            Err(RecvError) => RecvValue::Disconnected,\n        },\n        CanBlock::NoMustPeriodicPoll => match preprocess_rx.try_recv() {\n            Ok(kev) => RecvValue::Ok(kev),\n            Err(TryRecvError::Empty) => RecvValue::Empty,\n            Err(TryRecvError::Disconnected) => RecvValue::Disconnected,\n        },\n    }\n}\n\nfn start_event_preprocessor(preprocess_rx: Receiver<KeyEvent>, process_tx: Sender<KeyEvent>) {\n    use CanBlock::*;\n    use RecvValue::*;\n    std::thread::spawn(move || {\n        let mut lctl_state = LctlState::None;\n        let mut can_block = NoMustPeriodicPoll;\n        loop {\n            can_block = match preprocessor_recv(&preprocess_rx, can_block) {\n                Ok(kev) => {\n                    match (*ALTGR_BEHAVIOUR.lock(), kev) {\n                        (AltGrBehaviour::DoNothing, _) => {\n                            try_send_panic(&process_tx, kev);\n                        }\n                        (\n                            AltGrBehaviour::AddLctlRelease,\n                            KeyEvent {\n                                value: KeyValue::Release,\n                                code: OsCode::KEY_RIGHTALT,\n                                ..\n                            },\n                        ) => {\n                            log::debug!(\"altgr add: adding lctl release\");\n                            try_send_panic(&process_tx, kev);\n                            try_send_panic(\n                                &process_tx,\n                                KeyEvent::new(OsCode::KEY_LEFTCTRL, KeyValue::Release),\n                            );\n                            PRESSED_KEYS.lock().remove(&OsCode::KEY_LEFTCTRL);\n                        }\n                        (\n                            AltGrBehaviour::CancelLctlPress,\n                            KeyEvent {\n                                value: KeyValue::Press,\n                                code: OsCode::KEY_LEFTCTRL,\n                                ..\n                            },\n                        ) => {\n                            log::debug!(\"altgr cancel: lctl state->pressed\");\n                            lctl_state = LctlState::Pressed;\n                        }\n                        (\n                            AltGrBehaviour::CancelLctlPress,\n                            KeyEvent {\n                                value: KeyValue::Release,\n                                code: OsCode::KEY_LEFTCTRL,\n                                ..\n                            },\n                        ) => match lctl_state {\n                            LctlState::Pressed => {\n                                log::debug!(\"altgr cancel: lctl state->released\");\n                                lctl_state = LctlState::Released;\n                            }\n                            LctlState::Pending => {\n                                log::debug!(\"altgr cancel: lctl state->pending-released\");\n                                lctl_state = LctlState::PendingReleased;\n                            }\n                            LctlState::None => {\n                                try_send_panic(&process_tx, kev);\n                            }\n                            _ => {}\n                        },\n                        (\n                            AltGrBehaviour::CancelLctlPress,\n                            KeyEvent {\n                                value: KeyValue::Press,\n                                code: OsCode::KEY_RIGHTALT,\n                                ..\n                            },\n                        ) => {\n                            log::debug!(\"altgr cancel: lctl state->none\");\n                            lctl_state = LctlState::None;\n                            try_send_panic(&process_tx, kev);\n                        }\n                        (_, _) => {\n                            try_send_panic(&process_tx, kev);\n                        }\n                    }\n                    NoMustPeriodicPoll\n                }\n                Empty => {\n                    can_block = if *ALTGR_BEHAVIOUR.lock() == AltGrBehaviour::CancelLctlPress {\n                        match lctl_state {\n                            LctlState::Pressed => {\n                                log::debug!(\"altgr cancel: lctl state->pending\");\n                                lctl_state = LctlState::Pending;\n                                NoMustPeriodicPoll\n                            }\n                            LctlState::Released => {\n                                log::debug!(\"altgr cancel: lctl state->pending-released\");\n                                lctl_state = LctlState::PendingReleased;\n                                NoMustPeriodicPoll\n                            }\n                            LctlState::Pending => {\n                                log::debug!(\"altgr cancel: lctl state->send\");\n                                try_send_panic(\n                                    &process_tx,\n                                    KeyEvent::new(OsCode::KEY_LEFTCTRL, KeyValue::Press),\n                                );\n                                lctl_state = LctlState::None;\n                                NoMustPeriodicPoll\n                            }\n                            LctlState::PendingReleased => {\n                                log::debug!(\"altgr cancel: lctl state->send+release\");\n                                try_send_panic(\n                                    &process_tx,\n                                    KeyEvent::new(OsCode::KEY_LEFTCTRL, KeyValue::Press),\n                                );\n                                try_send_panic(\n                                    &process_tx,\n                                    KeyEvent::new(OsCode::KEY_LEFTCTRL, KeyValue::Release),\n                                );\n                                lctl_state = LctlState::None;\n                                NoMustPeriodicPoll\n                            }\n                            _ => YesCanBlock,\n                        }\n                    } else {\n                        YesCanBlock\n                    };\n                    std::thread::sleep(time::Duration::from_millis(1));\n                    can_block\n                }\n                Disconnected => {\n                    panic!(\"channel disconnected\")\n                }\n            }\n        }\n    });\n}\n"
  },
  {
    "path": "src/kanata/windows/mod.rs",
    "content": "use anyhow::Result;\n\nuse parking_lot::Mutex;\n\nuse crate::kanata::*;\n\n#[cfg(all(feature = \"simulated_input\", not(feature = \"interception_driver\")))]\nmod exthook;\n#[cfg(all(not(feature = \"simulated_input\"), feature = \"interception_driver\"))]\nmod interception;\n#[cfg(all(not(feature = \"simulated_input\"), not(feature = \"interception_driver\")))]\nmod llhook;\n\npub static ALTGR_BEHAVIOUR: Lazy<Mutex<AltGrBehaviour>> =\n    Lazy::new(|| Mutex::new(AltGrBehaviour::default()));\n\npub fn set_win_altgr_behaviour(b: AltGrBehaviour) {\n    *ALTGR_BEHAVIOUR.lock() = b;\n}\n\nimpl Kanata {\n    #[cfg(all(\n        not(feature = \"interception_driver\"),\n        not(feature = \"simulated_output\"),\n        not(feature = \"win_sendinput_send_scancodes\"),\n    ))]\n    pub fn check_release_non_physical_shift(&mut self) -> Result<()> {\n        fn state_filter(v: &State<'_, &&[&CustomAction]>) -> Option<State<'static, ()>> {\n            match v {\n                State::NormalKey {\n                    keycode,\n                    coord,\n                    flags,\n                } => Some(State::NormalKey::<()> {\n                    keycode: *keycode,\n                    coord: *coord,\n                    flags: *flags,\n                }),\n                State::FakeKey { keycode } => Some(State::FakeKey::<()> { keycode: *keycode }),\n                _ => None,\n            }\n        }\n\n        static PREV_STATES: Lazy<Mutex<Vec<State<'static, ()>>>> = Lazy::new(|| Mutex::new(vec![]));\n        let mut prev_states = PREV_STATES.lock();\n\n        if prev_states.is_empty() {\n            prev_states.extend(\n                self.layout\n                    .bm()\n                    .states\n                    .as_slice()\n                    .iter()\n                    .filter_map(state_filter),\n            );\n            return Ok(());\n        }\n\n        // This is an n^2 loop, but realistically there should be <= 5 states at a given time so\n        // this should not be a problem. State does not implement Hash so can't use a HashSet. A\n        // HashSet might perform worse anyway.\n        for prev_state in prev_states.iter() {\n            let keycode = match prev_state {\n                State::NormalKey { keycode, coord, .. } => {\n                    // Goal of this conditional:\n                    //\n                    // Do not process state if:\n                    // - keycode is neither shift\n                    // - keycode is at the position of either shift\n                    // - state has not yet been released\n                    if !matches!(keycode, KeyCode::LShift | KeyCode::RShift)\n                        || *coord == (NORMAL_KEY_ROW, u16::from(OsCode::KEY_LEFTSHIFT))\n                        || *coord == (NORMAL_KEY_ROW, u16::from(OsCode::KEY_RIGHTSHIFT))\n                        || self\n                            .layout\n                            .bm()\n                            .states\n                            .iter()\n                            .filter_map(state_filter)\n                            .any(|s| s == *prev_state)\n                    {\n                        continue;\n                    } else {\n                        keycode\n                    }\n                }\n                State::FakeKey { keycode } => {\n                    // Goal of this conditional:\n                    //\n                    // Do not process state if:\n                    // - keycode is neither shift\n                    // - state has not yet been released\n                    if !matches!(keycode, KeyCode::LShift | KeyCode::RShift)\n                        || self\n                            .layout\n                            .bm()\n                            .states\n                            .iter()\n                            .filter_map(state_filter)\n                            .any(|s| s == *prev_state)\n                    {\n                        continue;\n                    } else {\n                        keycode\n                    }\n                }\n                _ => continue,\n            };\n            log::debug!(\"lsft-arrowkey workaround: removing {keycode:?} at its typical coordinate\");\n            self.layout.bm().states.retain(|s| match s {\n                State::LayerModifier { coord, .. }\n                | State::Custom { coord, .. }\n                | State::RepeatingSequence { coord, .. }\n                | State::NormalKey { coord, .. } => {\n                    *coord != (NORMAL_KEY_ROW, u16::from(OsCode::from(keycode)))\n                }\n                _ => true,\n            });\n            log::debug!(\"removing {keycode:?} from pressed keys\");\n            PRESSED_KEYS.lock().remove(&keycode.into());\n        }\n\n        prev_states.clear();\n        prev_states.extend(self.layout.bm().states.iter().filter_map(state_filter));\n        Ok(())\n    }\n\n    #[cfg(any(\n        feature = \"interception_driver\",\n        feature = \"simulated_output\",\n        feature = \"win_sendinput_send_scancodes\"\n    ))]\n    pub fn check_release_non_physical_shift(&mut self) -> Result<()> {\n        Ok(())\n    }\n\n    #[cfg(feature = \"gui\")]\n    pub fn live_reload(&mut self) -> Result<()> {\n        self.live_reload_requested = true;\n        self.do_live_reload(&None)?;\n        Ok(())\n    }\n    #[cfg(feature = \"gui\")]\n    pub fn live_reload_n(&mut self, n: usize) -> Result<()> {\n        // can't use in CustomAction::LiveReloadNum(n) due to 2nd mut borrow\n        self.live_reload_requested = true;\n        // let backup_cfg_idx = self.cur_cfg_idx;\n        match self.cfg_paths.get(n) {\n            Some(path) => {\n                self.cur_cfg_idx = n;\n                log::info!(\"Requested live reload of file: {}\", path.display(),);\n            }\n            None => {\n                log::error!(\n                    \"Requested live reload of config file number {}, but only {} config files were passed\",\n                    n + 1,\n                    self.cfg_paths.len()\n                );\n            }\n        }\n        // if let Err(e) = self.do_live_reload(&None) {\n        // self.cur_cfg_idx = backup_cfg_idx; // restore index on fail when. TODO: add when a similar reversion is added to other custom actions\n        // return Err(e)\n        // }\n        self.do_live_reload(&None)?;\n        Ok(())\n    }\n}\n\n/// If kanata has been inactive for long enough, clear all states.\n/// This won't trigger if there are macros running, or if a key is\n/// held down for a long time and is sending OS repeats. The reason\n/// for this code is in case like Win+L which locks the Windows\n/// desktop. When this happens, the Win key and L key will be stuck\n/// as pressed in the kanata state because LLHOOK kanata cannot read\n/// keys in the lock screen or administrator applications. So this\n/// is heuristic to detect such an issue and clear states assuming\n/// that's what happened.\n///\n/// Only states in the normal key row are cleared, since those are\n/// the states that might be stuck. A real use case might be to have\n/// a fake key pressed for a long period of time, so make sure those\n/// are not cleared.\n#[cfg(all(not(feature = \"interception_driver\"), target_os = \"windows\"))]\npub fn clear_states_from_inactivity(\n    k: &mut parking_lot::MutexGuard<Kanata>,\n    now: web_time::Instant,\n    last_input_time: web_time::Instant,\n    idle_clear_happened: &mut bool,\n) {\n    if (now - (last_input_time)) > time::Duration::from_secs(LLHOOK_IDLE_TIME_SECS_CLEAR_INPUTS)\n        && !*idle_clear_happened\n    {\n        *idle_clear_happened = true;\n        log::debug!(\"clearing keyberon normal key states due to inactivity\");\n        let layout = k.layout.bm();\n        release_normalkey_states(layout);\n        let now = web_time::Instant::now();\n        PRESSED_KEYS\n            .lock()\n            .retain(|_, v| now - *v < std::time::Duration::from_secs(5));\n    }\n}\n"
  },
  {
    "path": "src/kanata.exe.manifest.rc",
    "content": "#define RT_MANIFEST 24\n1 RT_MANIFEST \"./target/kanata.exe.manifest\"\niconMain ICON \"../assets/kanata.ico\"\nimgMain IMAGE \"../assets/kanata.ico\"\nimgReload IMAGE \"../assets/reload_32px.png\"\n"
  },
  {
    "path": "src/lib.rs",
    "content": "use anyhow::{Error, Result, anyhow};\nuse std::net::SocketAddr;\nuse std::path::PathBuf;\nuse std::str::FromStr;\n\n#[cfg(all(target_os = \"windows\", feature = \"gui\"))]\npub mod gui;\npub mod kanata;\npub mod oskbd;\npub mod tcp_server;\n#[cfg(test)]\npub mod tests;\n\npub use kanata::*;\npub use kanata_parser::cfg::FAKE_KEY_ROW;\npub use kanata_parser::custom_action::FakeKeyAction;\npub use tcp_server::TcpServer;\n\ntype CfgPath = PathBuf;\n\npub struct ValidatedArgs {\n    pub paths: Vec<CfgPath>,\n    #[cfg(feature = \"tcp_server\")]\n    pub tcp_server_address: Option<SocketAddrWrapper>,\n    #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n    pub symlink_path: Option<String>,\n    pub nodelay: bool,\n}\n\npub fn default_cfg() -> Vec<PathBuf> {\n    let mut cfgs = Vec::new();\n\n    let default = PathBuf::from(\"kanata.kbd\");\n    if default.is_file() {\n        cfgs.push(default);\n    }\n\n    if let Some(config_dir) = dirs::config_dir() {\n        let fallback = config_dir.join(\"kanata\").join(\"kanata.kbd\");\n        if fallback.is_file() {\n            cfgs.push(fallback);\n        }\n    }\n\n    cfgs\n}\n\n#[derive(Debug, Clone)]\npub struct SocketAddrWrapper(SocketAddr);\n\nimpl FromStr for SocketAddrWrapper {\n    type Err = Error;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        let mut address = s.to_string();\n        if let Ok(port) = s.parse::<u16>() {\n            address = format!(\"127.0.0.1:{port}\");\n        }\n        address\n            .parse::<SocketAddr>()\n            .map(SocketAddrWrapper)\n            .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}\"))\n    }\n}\n\nimpl SocketAddrWrapper {\n    pub fn into_inner(self) -> SocketAddr {\n        self.0\n    }\n    pub fn get_ref(&self) -> &SocketAddr {\n        &self.0\n    }\n}\n"
  },
  {
    "path": "src/main.rs",
    "content": "#![cfg_attr(feature = \"gui\", windows_subsystem = \"windows\")]\n// disable default console for a Windows GUI app\nmod main_lib;\n\n#[cfg(not(feature = \"gui\"))]\nuse anyhow::{Result, bail};\n#[cfg(not(feature = \"gui\"))]\nuse clap::Parser;\n#[cfg(not(feature = \"gui\"))]\nuse kanata_parser::cfg;\n#[cfg(not(feature = \"gui\"))]\nuse kanata_state_machine::*;\n#[cfg(not(feature = \"gui\"))]\nuse main_lib::args::Args;\n#[cfg(not(feature = \"gui\"))]\nuse simplelog::{format_description, *};\n\n#[cfg(not(feature = \"gui\"))]\nmod cli {\n    use super::*;\n\n    /// Parse CLI arguments and initialize logging.\n    fn cli_init() -> Result<(ValidatedArgs, Option<String>)> {\n        let args = Args::parse();\n\n        #[cfg(all(target_os = \"macos\", not(feature = \"gui\")))]\n        if args.list {\n            main_lib::list_devices_macos();\n            std::process::exit(0);\n        }\n\n        #[cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(feature = \"gui\")))]\n        if args.list {\n            main_lib::list_devices_linux();\n            std::process::exit(0);\n        }\n\n        #[cfg(all(\n            target_os = \"windows\",\n            feature = \"interception_driver\",\n            not(feature = \"gui\")\n        ))]\n        if args.list {\n            main_lib::list_devices_windows();\n            std::process::exit(0);\n        }\n\n        let config_string = if args.cfg_stdin {\n            use std::io::Read;\n            let mut buf = String::new();\n            std::io::stdin().read_to_string(&mut buf)?;\n            Some(buf)\n        } else {\n            None\n        };\n\n        let cfg_paths = if config_string.is_none() {\n            args.cfg.unwrap_or_else(default_cfg)\n        } else {\n            vec![]\n        };\n\n        let log_lvl = match (args.debug, args.trace, args.quiet) {\n            (_, true, false) => LevelFilter::Trace,\n            (true, false, false) => LevelFilter::Debug,\n            (false, false, false) => LevelFilter::Info,\n            (_, _, true) => LevelFilter::Error,\n        };\n\n        let mut log_cfg = ConfigBuilder::new();\n        if let Err(e) = log_cfg.set_time_offset_to_local() {\n            eprintln!(\"WARNING: could not set log TZ to local: {e:?}\");\n        };\n        log_cfg.set_time_format_custom(format_description!(\n            version = 2,\n            \"[hour]:[minute]:[second].[subsecond digits:4]\"\n        ));\n        CombinedLogger::init(vec![TermLogger::new(\n            log_lvl,\n            log_cfg.build(),\n            TerminalMode::Mixed,\n            ColorChoice::AlwaysAnsi,\n        )])\n        .expect(\"logger can init\");\n\n        log::info!(\"kanata v{} starting\", env!(\"CARGO_PKG_VERSION\"));\n        #[cfg(all(not(feature = \"interception_driver\"), target_os = \"windows\"))]\n        log::info!(\"using LLHOOK+SendInput for keyboard IO\");\n        #[cfg(all(feature = \"interception_driver\", target_os = \"windows\"))]\n        log::info!(\"using the Interception driver for keyboard IO\");\n\n        if config_string.is_none() {\n            if let Some(config_file) = cfg_paths.first() {\n                if !config_file.exists() {\n                    bail!(\n                        \"Could not find the config file ({})\\nFor more info, pass the `-h` or `--help` flags.\",\n                        cfg_paths[0].to_str().unwrap_or(\"?\")\n                    )\n                }\n            } else {\n                bail!(\"No config files provided\\nFor more info, pass the `-h` or `--help` flags.\");\n            }\n        }\n\n        if args.check {\n            log::info!(\"validating config only and exiting\");\n            let status = if let Some(ref cfg_str) = config_string {\n                use rustc_hash::FxHashMap;\n                match cfg::new_from_str(cfg_str, FxHashMap::default()) {\n                    Ok(_) => 0,\n                    Err(e) => {\n                        log::error!(\"{e:?}\");\n                        1\n                    }\n                }\n            } else {\n                match cfg::new_from_file(&cfg_paths[0]) {\n                    Ok(_) => 0,\n                    Err(e) => {\n                        log::error!(\"{e:?}\");\n                        1\n                    }\n                }\n            };\n            std::process::exit(status);\n        }\n\n        #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n        if let Some(wait) = args.wait_device_ms {\n            use std::sync::atomic::Ordering;\n            log::info!(\"Setting device registration wait time to {wait} ms.\");\n            oskbd::WAIT_DEVICE_MS.store(wait, Ordering::SeqCst);\n        }\n\n        if args.log_layer_changes {\n            cfg_forced::force_log_layer_changes(true);\n        }\n\n        // Set emergency exit code from CLI args\n        kanata::EMERGENCY_EXIT_CODE.store(\n            args.emergency_exit_code,\n            std::sync::atomic::Ordering::SeqCst,\n        );\n\n        Ok((\n            ValidatedArgs {\n                paths: cfg_paths,\n                #[cfg(feature = \"tcp_server\")]\n                tcp_server_address: args.tcp_server_address,\n                #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n                symlink_path: args.symlink_path,\n                nodelay: args.nodelay,\n            },\n            config_string,\n        ))\n    }\n\n    pub(crate) fn main_impl() -> Result<()> {\n        let (args, config_string) = cli_init()?;\n\n        let kanata_arc = if let Some(cfg_str) = config_string {\n            use rustc_hash::FxHashMap;\n            let kanata = Kanata::new_from_str(&cfg_str, FxHashMap::default())?;\n            std::sync::Arc::new(parking_lot::Mutex::new(kanata))\n        } else {\n            Kanata::new_arc(&args)?\n        };\n\n        if !args.nodelay {\n            log::info!(\n                \"Sleeping for 2s. Please release all keys and don't press additional ones. Run kanata with --help to see how understand more and how to disable this sleep.\"\n            );\n            std::thread::sleep(std::time::Duration::from_secs(2));\n        }\n\n        // Start a processing loop in another thread and run the event loop in this thread.\n        //\n        // The reason for two different event loops is that the \"event loop\" only listens for\n        // keyboard events, which it sends to the \"processing loop\". The processing loop handles\n        // keyboard events while also maintaining `tick()` calls to keyberon.\n\n        let (tx, rx) = std::sync::mpsc::sync_channel(100);\n\n        let (server, ntx, nrx) = if let Some(address) = {\n            #[cfg(feature = \"tcp_server\")]\n            {\n                args.tcp_server_address\n            }\n            #[cfg(not(feature = \"tcp_server\"))]\n            {\n                None::<SocketAddrWrapper>\n            }\n        } {\n            let mut server = TcpServer::new(address.into_inner(), tx.clone());\n            server.start(kanata_arc.clone());\n            let (ntx, nrx) = std::sync::mpsc::sync_channel(100);\n            (Some(server), Some(ntx), Some(nrx))\n        } else {\n            (None, None, None)\n        };\n\n        Kanata::start_processing_loop(kanata_arc.clone(), rx, ntx, args.nodelay);\n\n        if let (Some(server), Some(nrx)) = (server, nrx) {\n            #[allow(clippy::unit_arg)]\n            Kanata::start_notification_loop(nrx, server.connections);\n        }\n\n        #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n        sd_notify::notify(true, &[sd_notify::NotifyState::Ready])?;\n\n        Kanata::event_loop(kanata_arc, tx)\n    }\n}\n\n#[cfg(not(feature = \"gui\"))]\npub fn main() -> Result<()> {\n    let args = Args::parse();\n    let no_wait = args.no_wait;\n    let ret = cli::main_impl();\n    if let Err(ref e) = ret {\n        log::error!(\"{e}\\n\");\n    }\n    if !no_wait {\n        eprintln!(\"\\nPress enter to exit\");\n        let _ = std::io::stdin().read_line(&mut String::new());\n    }\n    ret\n}\n\n#[cfg(all(feature = \"gui\", target_os = \"windows\"))]\nfn main() {\n    main_lib::win_gui::lib_main_gui();\n}\n\n#[cfg(all(feature = \"gui\", not(target_os = \"windows\")))]\nfn main() {\n    panic!(\"GUI feature is only supported on Windows\");\n}\n"
  },
  {
    "path": "src/main_lib/args.rs",
    "content": "use clap::Parser;\n#[cfg(feature = \"tcp_server\")]\nuse kanata_state_machine::SocketAddrWrapper;\nuse std::path::PathBuf;\n\n#[derive(Parser, Debug)]\n#[command(author, version, verbatim_doc_comment)]\n/// kanata: an advanced software key remapper\n///\n/// kanata remaps key presses to other keys or complex actions depending on the\n/// configuration for that key. You can find the guide for creating a config\n/// file here: https://github.com/jtroo/kanata/blob/main/docs/config.adoc\n///\n/// If you need help, please feel welcome to create an issue or discussion in\n/// the kanata repository: https://github.com/jtroo/kanata\npub struct Args {\n    // Display different platform specific paths based on the target OS\n    #[cfg_attr(\n        target_os = \"windows\",\n        doc = r\"Configuration file(s) to use with kanata. If not specified, defaults to\nkanata.kbd in the current working directory and\n'C:\\Users\\user\\AppData\\Roaming\\kanata\\kanata.kbd'.\"\n    )]\n    #[cfg_attr(\n        target_os = \"macos\",\n        doc = \"Configuration file(s) to use with kanata. If not specified, defaults to\nkanata.kbd in the current working directory and\n'$HOME/Library/Application Support/kanata/kanata.kbd'.\"\n    )]\n    #[cfg_attr(\n        not(any(target_os = \"macos\", target_os = \"windows\")),\n        doc = \"Configuration file(s) to use with kanata. If not specified, defaults to\nkanata.kbd in the current working directory and\n'$XDG_CONFIG_HOME/kanata/kanata.kbd'.\"\n    )]\n    #[arg(short, long, verbatim_doc_comment)]\n    pub cfg: Option<Vec<PathBuf>>,\n\n    /// Read configuration from stdin instead of a file.\n    #[arg(long, verbatim_doc_comment)]\n    pub cfg_stdin: bool,\n\n    /// Port or full address (IP:PORT) to run the optional TCP server on. If blank,\n    /// no TCP port will be listened on.\n    #[cfg(feature = \"tcp_server\")]\n    #[arg(\n        short = 'p',\n        long = \"port\",\n        value_name = \"PORT or IP:PORT\",\n        verbatim_doc_comment\n    )]\n    pub tcp_server_address: Option<SocketAddrWrapper>,\n\n    /// Path for the symlink pointing to the newly-created device. If blank, no\n    /// symlink will be created.\n    #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n    #[arg(short, long, verbatim_doc_comment)]\n    pub symlink_path: Option<String>,\n\n    /// List the keyboards available for grabbing and exit.\n    #[cfg(any(\n        target_os = \"macos\",\n        any(target_os = \"linux\", target_os = \"android\"),\n        all(\n            target_os = \"windows\",\n            feature = \"interception_driver\",\n            not(feature = \"gui\")\n        )\n    ))]\n    #[arg(short, long)]\n    pub list: bool,\n\n    /// Disable logging, except for errors. Takes precedent over debug and trace.\n    #[arg(short, long)]\n    pub quiet: bool,\n\n    /// Enable debug logging.\n    #[arg(short, long)]\n    pub debug: bool,\n\n    /// Enable trace logging; implies --debug as well.\n    #[arg(short, long)]\n    pub trace: bool,\n\n    /// Remove the startup delay.\n    /// In some cases, removing the delay may cause keyboard issues on startup.\n    #[arg(short, long, verbatim_doc_comment)]\n    pub nodelay: bool,\n\n    /// Milliseconds to wait before attempting to register a newly connected\n    /// device. The default is 200.\n    ///\n    /// You may wish to increase this if you have a device that is failing\n    /// to register - the device may be taking too long to become ready.\n    #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n    #[arg(short, long, verbatim_doc_comment)]\n    pub wait_device_ms: Option<u64>,\n\n    /// Validate configuration file and exit\n    #[arg(long, verbatim_doc_comment)]\n    pub check: bool,\n\n    /// Log layer changes even if the configuration file has set the defcfg\n    /// option to false. Useful if you are experimenting with a new\n    /// configuration but want to default to no logging.\n    #[arg(long, verbatim_doc_comment)]\n    pub log_layer_changes: bool,\n\n    /// Skip the \"Press enter to exit\" prompt and exit immediately.\n    /// Useful for running kanata as a background service (e.g., systemd)\n    /// where automatic restart on failure is desired.\n    #[arg(long, verbatim_doc_comment)]\n    pub no_wait: bool,\n\n    /// Exit code to use when emergency exit is triggered (LCtrl+Space+Escape).\n    /// Default is 0 (success). Set to non-zero if your service manager should\n    /// treat emergency exit as a failure and restart.\n    #[arg(long, default_value = \"0\", verbatim_doc_comment)]\n    pub emergency_exit_code: i32,\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn no_wait_flag_default_false() {\n        let args = Args::try_parse_from([\"kanata\"]).unwrap();\n        assert!(!args.no_wait);\n    }\n\n    #[test]\n    fn no_wait_flag_enabled() {\n        let args = Args::try_parse_from([\"kanata\", \"--no-wait\"]).unwrap();\n        assert!(args.no_wait);\n    }\n\n    #[test]\n    fn no_wait_with_other_flags() {\n        let args =\n            Args::try_parse_from([\"kanata\", \"--no-wait\", \"--nodelay\", \"-c\", \"test.kbd\"]).unwrap();\n        assert!(args.no_wait);\n        assert!(args.nodelay);\n    }\n\n    #[test]\n    fn emergency_exit_code_default() {\n        let args = Args::try_parse_from([\"kanata\"]).unwrap();\n        assert_eq!(args.emergency_exit_code, 0);\n    }\n\n    #[test]\n    fn emergency_exit_code_custom() {\n        let args = Args::try_parse_from([\"kanata\", \"--emergency-exit-code\", \"42\"]).unwrap();\n        assert_eq!(args.emergency_exit_code, 42);\n    }\n\n    #[test]\n    fn emergency_exit_code_with_other_flags() {\n        let args = Args::try_parse_from([\n            \"kanata\",\n            \"--emergency-exit-code\",\n            \"1\",\n            \"--no-wait\",\n            \"-c\",\n            \"test.kbd\",\n        ])\n        .unwrap();\n        assert_eq!(args.emergency_exit_code, 1);\n        assert!(args.no_wait);\n    }\n}\n"
  },
  {
    "path": "src/main_lib/mod.rs",
    "content": "pub(crate) mod args;\n\n#[cfg(all(target_os = \"windows\", feature = \"gui\"))]\npub(crate) mod win_gui;\n\n#[cfg(all(target_os = \"macos\", not(feature = \"gui\")))]\npub(crate) fn list_devices_macos() {\n    use karabiner_driverkit::fetch_devices;\n\n    println!(\"Available keyboard devices:\");\n    println!(\"===========================\\n\");\n\n    let kb_list = fetch_devices();\n\n    let print_header = || {\n        println!(\n            \"{:<20} {:<10} {:<10} product_key\",\n            \"hash\", \"vendor_id\", \"product_id\"\n        );\n    };\n\n    let print_line = || {\n        println!(\n            \"{}   {}   {}   {}\",\n            \"-\".repeat(18),\n            \"-\".repeat(8),\n            \"-\".repeat(8),\n            \"-\".repeat(50)\n        );\n    };\n\n    print_header();\n    print_line();\n    for kb in kb_list.iter() {\n        println!(\"{}\", kb);\n    }\n    print_line();\n\n    if kb_list.is_empty() {\n        println!(\"No devices found. Ensure Karabiner-VirtualHIDDevice driver is activated.\");\n        return;\n    }\n\n    println!(\n        \"\\nTo address devices with empty names (product key), hash values can be used in the configuration!\"\n    );\n\n    println!(\"\\nConfiguration example:\");\n    println!(\"  (defcfg\");\n    println!(\"    macos-dev-names-include (\");\n    for kb in kb_list\n        .iter()\n        .map(|k| k.product_key.clone())\n        .filter(|k| !k.trim().is_empty())\n    {\n        println!(\"      \\\"{}\\\"\", kb.trim());\n    }\n    println!(\"    )\");\n    println!(\"    or instead:\");\n    println!(\"    macos-dev-names-include (\");\n    for kb in kb_list.iter().map(|k| k.hash) {\n        println!(\"      \\\"0x{:<16X}\\\"\", kb);\n    }\n    println!(\"    )\");\n    println!(\"  )\");\n}\n\n#[cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(feature = \"gui\")))]\npub(crate) fn list_devices_linux() {\n    use crate::oskbd::discover_devices;\n    use kanata_parser::cfg::DeviceDetectMode;\n\n    println!(\"Available keyboard devices:\");\n    println!(\"===========================\");\n\n    let devices = discover_devices(None, None, DeviceDetectMode::KeyboardOnly);\n\n    if devices.is_empty() {\n        println!(\"No keyboard devices found.\");\n        println!(\"\\nTroubleshooting:\");\n        println!(\"  1. Check permissions: sudo usermod -a -G input $USER\");\n        println!(\"  2. Log out and back in for group changes to take effect\");\n        println!(\"  3. Ensure devices are connected and working\");\n        return;\n    }\n\n    println!(\"Found {} keyboard device(s):\\n\", devices.len());\n\n    for (i, (device, path)) in devices.iter().enumerate() {\n        let name = device.name().unwrap_or(\"Unknown\");\n        let input_id = device.input_id();\n        println!(\"  {}. \\\"{}\\\"\", i + 1, name);\n        println!(\"     Path: {path}\");\n        println!(\n            \"     Vendor ID: {} (0x{:04X}), Product ID: {} (0x{:04X})\",\n            input_id.vendor(),\n            input_id.vendor(),\n            input_id.product(),\n            input_id.product()\n        );\n        println!();\n    }\n\n    println!(\"Configuration example:\");\n    println!(\"  (defcfg\");\n    println!(\"    linux-dev-names-include (\");\n    for (device, _path) in devices.iter() {\n        println!(\"      \\\"{}\\\"\", device.name().unwrap_or(\"Unknown\"));\n    }\n    println!(\"    )\");\n    println!(\"  )\");\n}\n\n#[cfg(all(\n    target_os = \"windows\",\n    feature = \"interception_driver\",\n    not(feature = \"gui\")\n))]\nstruct WindowsDeviceInfo {\n    display_name: String,        // For user display\n    raw_wide_bytes: Vec<u8>,     // For kanata configuration (original wide string bytes)\n    hardware_id: Option<String>, // Parsed hardware ID (e.g., \"HID#VID_046D&PID_C52B\")\n}\n\n#[cfg(all(\n    target_os = \"windows\",\n    feature = \"interception_driver\",\n    not(feature = \"gui\")\n))]\nfn get_device_info(device_handle: winapi::um::winnt::HANDLE) -> Option<WindowsDeviceInfo> {\n    use std::ffi::OsString;\n    use std::os::windows::ffi::OsStringExt;\n    use std::ptr::null_mut;\n    use winapi::shared::minwindef::{PUINT, UINT};\n    use winapi::um::winuser::{GetRawInputDeviceInfoW, RIDI_DEVICENAME};\n\n    unsafe {\n        let mut name_size: UINT = 0;\n\n        // First call to get the required buffer size (in characters, not bytes)\n        GetRawInputDeviceInfoW(\n            device_handle,\n            RIDI_DEVICENAME,\n            null_mut(),\n            &mut name_size as PUINT,\n        );\n\n        if name_size > 0 {\n            // Allocate buffer for wide characters\n            let mut name_buffer: Vec<u16> = vec![0; name_size as usize];\n            let result = GetRawInputDeviceInfoW(\n                device_handle,\n                RIDI_DEVICENAME,\n                name_buffer.as_mut_ptr() as *mut _,\n                &mut name_size as PUINT,\n            );\n\n            if result != u32::MAX {\n                // Truncate buffer to actual data length (result is in bytes, divide by 2 for chars)\n                let actual_char_count = (result / 2) as usize;\n                name_buffer.truncate(actual_char_count);\n\n                // Remove null terminator if present\n                if let Some(&0) = name_buffer.last() {\n                    name_buffer.pop();\n                }\n\n                // Convert to raw bytes (preserve original wide string format)\n                let raw_wide_bytes: Vec<u8> =\n                    name_buffer.iter().flat_map(|&c| c.to_le_bytes()).collect();\n\n                // Create display name using OsString (preserves invalid UTF-16)\n                let os_string = OsString::from_wide(&name_buffer);\n                let display_name = os_string.to_string_lossy().into_owned();\n\n                // Extract hardware ID from display name\n                let hardware_id = extract_hardware_id(&display_name);\n\n                return Some(WindowsDeviceInfo {\n                    display_name,\n                    raw_wide_bytes,\n                    hardware_id,\n                });\n            }\n        }\n    }\n    None\n}\n\n#[cfg(all(\n    target_os = \"windows\",\n    feature = \"interception_driver\",\n    not(feature = \"gui\")\n))]\npub(crate) fn list_devices_windows() {\n    use std::ptr::null_mut;\n    use winapi::shared::minwindef::{PUINT, UINT};\n    use winapi::um::winuser::{GetRawInputDeviceList, RAWINPUTDEVICELIST, RIM_TYPEKEYBOARD};\n\n    println!(\"Available keyboard devices:\");\n    println!(\"===========================\");\n\n    unsafe {\n        // First, get the number of devices\n        let mut num_devices: UINT = 0;\n        let result = GetRawInputDeviceList(\n            null_mut(),\n            &mut num_devices as PUINT,\n            std::mem::size_of::<RAWINPUTDEVICELIST>() as UINT,\n        );\n\n        if result == u32::MAX {\n            println!(\"Error: Failed to get device count\");\n            return;\n        }\n\n        if num_devices == 0 {\n            println!(\"No input devices found.\");\n            return;\n        }\n\n        // Allocate buffer for device list\n        let mut devices: Vec<RAWINPUTDEVICELIST> = vec![std::mem::zeroed(); num_devices as usize];\n\n        let result = GetRawInputDeviceList(\n            devices.as_mut_ptr(),\n            &mut num_devices as PUINT,\n            std::mem::size_of::<RAWINPUTDEVICELIST>() as UINT,\n        );\n\n        if result == u32::MAX {\n            println!(\"Error: Failed to get device list\");\n            return;\n        }\n\n        // Filter for keyboards and get device info\n        let keyboards: Vec<&RAWINPUTDEVICELIST> = devices\n            .iter()\n            .filter(|device| device.dwType == RIM_TYPEKEYBOARD)\n            .collect();\n\n        if keyboards.is_empty() {\n            println!(\"No keyboard devices found.\");\n            println!(\"\\nTroubleshooting:\");\n            println!(\"  1. Ensure keyboards are connected and working\");\n            println!(\"  2. Try running as administrator if needed\");\n            return;\n        }\n\n        println!(\"Found {} keyboard device(s):\\n\", keyboards.len());\n\n        for (i, device) in keyboards.iter().enumerate() {\n            if let Some(device_info) = get_device_info(device.hDevice) {\n                println!(\"  {}. Device: {}\", i + 1, device_info.display_name);\n\n                // Show hardware ID if available\n                if let Some(hwid) = &device_info.hardware_id {\n                    println!(\"     Hardware ID: {hwid}\");\n                }\n\n                // Show raw wide string bytes for kanata configuration\n                println!(\n                    \"     Raw wide string bytes: {:?}\",\n                    device_info.raw_wide_bytes\n                );\n                println!();\n            }\n        }\n\n        if !keyboards.is_empty() {\n            println!(\"Configuration example:\");\n            println!(\"  (defcfg\");\n            println!(\"    windows-interception-keyboard-hwids (\");\n\n            for device in keyboards.iter() {\n                if let Some(device_info) = get_device_info(device.hDevice) {\n                    // Use the preserved raw wide string bytes for configuration\n                    print!(\"      {:?}\", device_info.raw_wide_bytes);\n\n                    // Add comment with hardware ID and display name for clarity\n                    if let Some(hwid) = &device_info.hardware_id {\n                        println!(\"  ; {} ({})\", hwid, device_info.display_name);\n                    } else {\n                        println!(\"  ; {}\", device_info.display_name);\n                    }\n                }\n            }\n\n            println!(\"    )\");\n            println!(\"  )\");\n        }\n    }\n}\n\n#[cfg(all(\n    target_os = \"windows\",\n    feature = \"interception_driver\",\n    not(feature = \"gui\")\n))]\nfn extract_hardware_id(device_name: &str) -> Option<String> {\n    // Windows device names typically look like:\n    // \\\\?\\HID#VID_046D&PID_C52B&MI_01#7&1234abcd&0&0000#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}\n    // We want to extract the HID#VID_046D&PID_C52B&MI_01 part\n\n    if let Some(start) = device_name.find(\"HID#\")\n        && let Some(end) = device_name[start..].find('#')\n    {\n        let hwid_part = &device_name[start..start + end];\n        return Some(hwid_part.to_string());\n    }\n\n    None\n}\n"
  },
  {
    "path": "src/main_lib/win_gui.rs",
    "content": "use super::args::Args;\nuse anyhow::{Context, Result, anyhow, bail};\nuse clap::{CommandFactory, Parser, error::ErrorKind};\nuse kanata_parser::cfg;\nuse kanata_state_machine::gui::*;\nuse kanata_state_machine::*;\nuse simplelog::{\n    ColorChoice, CombinedLogger, Config, ConfigBuilder, LevelFilter, TermLogger, TerminalMode,\n    WriteLogger, format_description,\n};\nuse std::fs::File;\n\n/// Parse CLI arguments and initialize logging.\nfn cli_init() -> Result<ValidatedArgs> {\n    let noti_lvl = LevelFilter::Error; // min lvl above which to use Win system notifications\n    let log_file_p = \"kanata_log.txt\";\n    let args = match Args::try_parse() {\n        Ok(args) => args,\n        Err(e) => {\n            if *IS_TERM {\n                // init loggers without config so '-help' \"error\" or real ones can be printed\n                let mut log_cfg = ConfigBuilder::new();\n                CombinedLogger::init(vec![\n                    TermLogger::new(\n                        LevelFilter::Debug,\n                        log_cfg.build(),\n                        TerminalMode::Mixed,\n                        ColorChoice::AlwaysAnsi,\n                    ),\n                    log_win::windbg_simple_combo(LevelFilter::Debug, noti_lvl),\n                ])\n                .expect(\"logger can init\");\n            } else {\n                CombinedLogger::init(vec![\n                    log_win::windbg_simple_combo(LevelFilter::Debug, noti_lvl),\n                    WriteLogger::new(\n                        LevelFilter::Debug,\n                        Config::default(),\n                        File::create(log_file_p).unwrap(),\n                    ),\n                ])\n                .expect(\"logger can init\");\n            }\n            match e.kind() {\n                ErrorKind::DisplayHelp => {\n                    let mut cmd = Args::command();\n                    let help = cmd.render_help();\n                    info!(\"{help}\");\n                    log::set_max_level(LevelFilter::Off);\n                    if !*IS_TERM {\n                        // detached to open log still opened for writing\n                        match open::that_detached(log_file_p) {\n                            Ok(()) => {} // on the off-chance the user looks at WinDbg logs\n                            Err(ef) => error!(\"failed to open {log_file_p} due to {ef:?}\"),\n                        }\n                    }\n                    return Err(anyhow!(\"\"));\n                }\n                _ => {\n                    if !*IS_TERM {\n                        match open::that_detached(log_file_p) {\n                            Ok(()) => {}\n                            Err(ef) => error!(\"failed to open {log_file_p} due to {ef:?}\"),\n                        }\n                    }\n                    return Err(e.into());\n                }\n            }\n        }\n    };\n\n    let cfg_paths = args.cfg.unwrap_or_else(default_cfg);\n\n    let log_lvl = match (args.debug, args.trace) {\n        (_, true) => LevelFilter::Trace,\n        (true, false) => LevelFilter::Debug,\n        (false, false) => LevelFilter::Info,\n    };\n\n    let mut log_cfg = ConfigBuilder::new();\n    if let Err(e) = log_cfg.set_time_offset_to_local() {\n        eprintln!(\"WARNING: could not set log TZ to local: {e:?}\");\n    };\n    log_cfg.set_time_format_custom(format_description!(\n        version = 2,\n        \"[hour]:[minute]:[second].[subsecond digits:4]\"\n    ));\n    if *IS_TERM {\n        CombinedLogger::init(vec![\n            TermLogger::new(\n                log_lvl,\n                log_cfg.build(),\n                TerminalMode::Mixed,\n                ColorChoice::AlwaysAnsi,\n            ),\n            log_win::windbg_simple_combo(log_lvl, noti_lvl),\n        ])\n        .expect(\"logger can init\");\n    } else {\n        CombinedLogger::init(vec![log_win::windbg_simple_combo(log_lvl, noti_lvl)])\n            .expect(\"logger can init\");\n    }\n    log::info!(\"kanata v{} starting\", env!(\"CARGO_PKG_VERSION\"));\n    #[cfg(all(not(feature = \"interception_driver\"), target_os = \"windows\"))]\n    log::info!(\"using LLHOOK+SendInput for keyboard IO\");\n    #[cfg(all(feature = \"interception_driver\", target_os = \"windows\"))]\n    log::info!(\"using the Interception driver for keyboard IO\");\n\n    if let Some(config_file) = cfg_paths.first() {\n        if !config_file.exists() {\n            bail!(\n                \"Could not find the config file ({})\\nFor more info, pass the `-h` or `--help` flags.\",\n                cfg_paths[0].to_str().unwrap_or(\"?\")\n            )\n        }\n    } else {\n        bail!(\"No config files provided\\nFor more info, pass the `-h` or `--help` flags.\");\n    }\n\n    if args.check {\n        log::info!(\"validating config only and exiting\");\n        let status = match cfg::new_from_file(&cfg_paths[0]) {\n            Ok(_) => 0,\n            Err(e) => {\n                log::error!(\"{e:?}\");\n                1\n            }\n        };\n        std::process::exit(status);\n    }\n\n    Ok(ValidatedArgs {\n        paths: cfg_paths,\n        #[cfg(feature = \"tcp_server\")]\n        tcp_server_address: args.tcp_server_address,\n        nodelay: args.nodelay,\n    })\n}\n\nfn main_impl() -> Result<()> {\n    let args = cli_init()?;\n    let kanata_arc = Kanata::new_arc(&args)?;\n\n    if CFG.set(kanata_arc.clone()).is_err() {\n        warn!(\"Someone else set our ‘CFG’\");\n    }; // store a clone of cfg so that we can ask it to reset itself\n\n    if !args.nodelay {\n        info!(\n            \"Sleeping for 2s. Please release all keys and don't press additional ones. Run kanata with --help to see how understand more and how to disable this sleep.\"\n        );\n        std::thread::sleep(std::time::Duration::from_secs(2));\n    }\n\n    // Start a processing loop in another thread and run the event loop in this thread.\n    //\n    // The reason for two different event loops is that the \"event loop\" only listens for keyboard\n    // events, which it sends to the \"processing loop\". The processing loop handles keyboard events\n    // while also maintaining `tick()` calls to keyberon.\n\n    let (tx, rx) = std::sync::mpsc::sync_channel(100);\n\n    let (server, ntx, nrx) = if let Some(address) = {\n        #[cfg(feature = \"tcp_server\")]\n        {\n            args.tcp_server_address\n        }\n        #[cfg(not(feature = \"tcp_server\"))]\n        {\n            None::<SocketAddrWrapper>\n        }\n    } {\n        let mut server = TcpServer::new(address.into_inner(), tx.clone());\n        server.start(kanata_arc.clone());\n        let (ntx, nrx) = std::sync::mpsc::sync_channel(100);\n        (Some(server), Some(ntx), Some(nrx))\n    } else {\n        (None, None, None)\n    };\n\n    native_windows_gui::init().context(\"Failed to init Native Windows GUI\")?;\n    let ui = build_tray(&kanata_arc)?;\n    let gui_tx = ui.layer_notice.sender();\n    let gui_cfg_tx = ui.cfg_notice.sender(); // allows notifying GUI on config reloads\n    let gui_err_tx = ui.err_notice.sender(); // allows notifying GUI on erorrs (from logger)\n    let gui_exit_tx = ui.exit_notice.sender(); // allows notifying GUI on app quit\n    if GUI_TX.set(gui_tx).is_err() {\n        warn!(\"Someone else set our ‘GUI_TX’\");\n    };\n    if GUI_CFG_TX.set(gui_cfg_tx).is_err() {\n        warn!(\"Someone else set our ‘GUI_CFG_TX’\");\n    };\n    if GUI_ERR_TX.set(gui_err_tx).is_err() {\n        warn!(\"Someone else set our ‘GUI_ERR_TX’\");\n    };\n    if GUI_EXIT_TX.set(gui_exit_tx).is_err() {\n        warn!(\"Someone else set our ‘GUI_EXIT_TX’\");\n    };\n    Kanata::start_processing_loop(kanata_arc.clone(), rx, ntx, args.nodelay);\n\n    if let (Some(server), Some(nrx)) = (server, nrx) {\n        #[allow(clippy::unit_arg)]\n        Kanata::start_notification_loop(nrx, server.connections);\n    }\n\n    Kanata::event_loop(kanata_arc, tx, ui)?;\n\n    Ok(())\n}\n\npub fn lib_main_gui() {\n    let _attach_console = *IS_CONSOLE;\n    let ret = main_impl();\n    if let Err(ref e) = ret {\n        log::error!(\"{e}\\n\");\n    }\n\n    unsafe {\n        FreeConsole();\n    }\n}\n"
  },
  {
    "path": "src/oskbd/linux.rs",
    "content": "//! Contains the input/output code for keyboards on Linux.\n\n#![cfg_attr(feature = \"simulated_output\", allow(dead_code, unused_imports))]\n\npub use evdev::BusType;\nuse evdev::{Device, EventType, InputEvent, KeyCode, PropType, RelativeAxisCode, uinput};\nuse inotify::{Inotify, WatchMask};\nuse mio::{Events, Interest, Poll, Token, unix::SourceFd};\nuse nix::ioctl_read_buf;\nuse rustc_hash::FxHashMap as HashMap;\nuse signal_hook::{\n    consts::{SIGINT, SIGTERM, SIGTSTP},\n    iterator::Signals,\n};\n\nuse std::convert::TryFrom;\nuse std::fs;\nuse std::io;\nuse std::os::unix::io::AsRawFd;\nuse std::path::PathBuf;\nuse std::sync::atomic::{AtomicU64, Ordering};\nuse std::thread;\n\nuse super::*;\nuse crate::{kanata::CalculatedMouseMove, oskbd::KeyEvent};\nuse kanata_parser::cfg::DeviceDetectMode;\nuse kanata_parser::cfg::UnicodeTermination;\nuse kanata_parser::custom_action::*;\nuse kanata_parser::keys::*;\n\npub struct KbdIn {\n    devices: HashMap<Token, (Device, String)>,\n    /// Some(_) if devices are explicitly listed, otherwise None.\n    missing_device_paths: Option<Vec<String>>,\n    poll: Poll,\n    events: Events,\n    token_counter: usize,\n    /// stored to prevent dropping\n    _inotify: Inotify,\n    include_names: Option<Vec<String>>,\n    exclude_names: Option<Vec<String>>,\n    device_detect_mode: DeviceDetectMode,\n}\n\nconst INOTIFY_TOKEN_VALUE: usize = 0;\nconst INOTIFY_TOKEN: Token = Token(INOTIFY_TOKEN_VALUE);\n\npub static WAIT_DEVICE_MS: AtomicU64 = AtomicU64::new(200);\n\nimpl KbdIn {\n    pub fn new(\n        dev_paths: &[String],\n        continue_if_no_devices: bool,\n        include_names: Option<Vec<String>>,\n        exclude_names: Option<Vec<String>>,\n        device_detect_mode: DeviceDetectMode,\n    ) -> Result<Self, io::Error> {\n        let poll = Poll::new()?;\n\n        let mut missing_device_paths = None;\n        let devices = if !dev_paths.is_empty() {\n            missing_device_paths = Some(vec![]);\n            devices_from_input_paths(\n                dev_paths,\n                missing_device_paths.as_mut().expect(\"initialized\"),\n            )\n        } else {\n            discover_devices(\n                include_names.as_deref(),\n                exclude_names.as_deref(),\n                device_detect_mode,\n            )\n        };\n        if devices.is_empty() {\n            if continue_if_no_devices {\n                log::warn!(\"no keyboard devices found; kanata is waiting\");\n            } else {\n                return Err(io::Error::new(\n                    io::ErrorKind::NotFound,\n                    \"No keyboard devices were found. Try:\\n\\\n                     1. Run 'kanata --list' to see available devices\\n\\\n                     2. Check permissions: sudo usermod -a -G input $USER\\n\\\n                     3. Log out and back in for group changes to take effect\\n\\\n                     4. Ensure devices are connected and working\",\n                ));\n            }\n        }\n        let _inotify = watch_devinput().map_err(|e| {\n            log::error!(\"failed to watch files: {e:?}\");\n            e\n        })?;\n        poll.registry().register(\n            &mut SourceFd(&_inotify.as_raw_fd()),\n            INOTIFY_TOKEN,\n            Interest::READABLE,\n        )?;\n\n        let mut kbdin = Self {\n            poll,\n            missing_device_paths,\n            _inotify,\n            events: Events::with_capacity(32),\n            devices: HashMap::default(),\n            token_counter: INOTIFY_TOKEN_VALUE + 1,\n            include_names,\n            exclude_names,\n            device_detect_mode,\n        };\n\n        for (device, dev_path) in devices.into_iter() {\n            if let Err(e) = kbdin.register_device(device, dev_path.clone()) {\n                log::warn!(\"found device {dev_path} but could not register it {e:?}\");\n                if let Some(ref mut missing) = kbdin.missing_device_paths {\n                    missing.push(dev_path);\n                }\n            }\n        }\n\n        Ok(kbdin)\n    }\n\n    fn register_device(&mut self, mut dev: Device, path: String) -> Result<(), io::Error> {\n        log::info!(\"registering {path}: {:?}\", dev.name().unwrap_or(\"\"));\n        wait_for_all_keys_unpressed(&dev)?;\n        // NOTE: This grab-ungrab-grab sequence magically fixes an issue with a Lenovo Yoga\n        // trackpad not working. No idea why this works.\n        dev.grab()?;\n        dev.ungrab()?;\n        dev.grab()?;\n\n        let tok = Token(self.token_counter);\n        self.token_counter += 1;\n        let fd = dev.as_raw_fd();\n        self.poll\n            .registry()\n            .register(&mut SourceFd(&fd), tok, Interest::READABLE)?;\n        self.devices.insert(tok, (dev, path));\n        Ok(())\n    }\n\n    pub fn read(&mut self) -> Result<Vec<InputEvent>, io::Error> {\n        let mut input_events = vec![];\n        loop {\n            log::trace!(\"polling\");\n\n            if let Err(e) = self.poll.poll(&mut self.events, None) {\n                log::error!(\"failed poll: {:?}\", e);\n                return Ok(vec![]);\n            }\n\n            const EVENT_LIMIT: usize = 48;\n\n            let mut do_rediscover = false;\n            for event in &self.events {\n                if let Some((device, _)) = self.devices.get_mut(&event.token()) {\n                    if let Err(e) = device.fetch_events().map(|evs| {\n                        evs.into_iter()\n                            .take(EVENT_LIMIT)\n                            .for_each(|ev| input_events.push(ev))\n                    }) {\n                        // Currently the kind() is uncategorized... not helpful, need to match\n                        // on os error. code 19 is ENODEV, \"no such device\".\n                        match e.raw_os_error() {\n                            Some(19) => {\n                                self.poll\n                                    .registry()\n                                    .deregister(&mut SourceFd(&device.as_raw_fd()))?;\n                                if let Some((_, path)) = self.devices.remove(&event.token()) {\n                                    log::warn!(\"removing kbd device: {path}\");\n                                    if let Some(ref mut missing) = self.missing_device_paths {\n                                        missing.push(path);\n                                    }\n                                }\n                            }\n                            _ => {\n                                log::error!(\"failed fetch events due to {e}, kind: {}\", e.kind());\n                                return Err(e);\n                            }\n                        };\n                    }\n                } else if event.token() == INOTIFY_TOKEN {\n                    do_rediscover = true;\n                } else {\n                    panic!(\"encountered unexpected epoll event {event:?}\");\n                }\n            }\n            if do_rediscover {\n                log::info!(\"watch found file changes, looking for new devices\");\n                self.rediscover_devices()?;\n            }\n            if !input_events.is_empty() {\n                return Ok(input_events);\n            }\n        }\n    }\n\n    fn rediscover_devices(&mut self) -> Result<(), io::Error> {\n        // This function is kinda ugly but the borrow checker doesn't like all this mutation.\n        let mut paths_registered = vec![];\n        if let Some(ref mut missing) = self.missing_device_paths {\n            if missing.is_empty() {\n                log::info!(\"no devices are missing, doing nothing\");\n                return Ok(());\n            }\n            log::info!(\"checking for {missing:?}\");\n            let discovered_devices = missing\n                .iter()\n                .filter_map(|dev_path| {\n                    for _ in 0..(WAIT_DEVICE_MS.load(Ordering::SeqCst) / 10) {\n                        // try a few times with waits in between; device might not be ready\n                        if let Ok(device) = Device::open(dev_path) {\n                            return Some((device, dev_path.clone()));\n                        }\n                        std::thread::sleep(std::time::Duration::from_millis(10));\n                    }\n                    None\n                })\n                .collect::<Vec<(_, _)>>();\n            for (device, dev_path) in discovered_devices {\n                if let Err(e) = self.register_device(device, dev_path.clone()) {\n                    log::warn!(\"found device {dev_path} but could not register it {e:?}\");\n                } else {\n                    paths_registered.push(dev_path);\n                }\n            }\n        }\n        if let Some(ref mut missing) = self.missing_device_paths {\n            missing.retain(|path| !paths_registered.contains(path));\n        } else {\n            log::info!(\"sleeping for a moment to let devices become ready\");\n            std::thread::sleep(std::time::Duration::from_millis(\n                WAIT_DEVICE_MS.load(Ordering::SeqCst),\n            ));\n            discover_devices(\n                self.include_names.as_deref(),\n                self.exclude_names.as_deref(),\n                self.device_detect_mode,\n            )\n            .into_iter()\n            .try_for_each(|(dev, path)| {\n                if !self\n                    .devices\n                    .values()\n                    .any(|(_, registered_path)| &path == registered_path)\n                {\n                    self.register_device(dev, path)\n                } else {\n                    Ok(())\n                }\n            })?;\n        }\n        Ok(())\n    }\n}\n\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\nenum DeviceType {\n    Keyboard,\n    KeyboardMouse,\n    Mouse,\n    Other,\n}\n\npub fn is_input_device(device: &Device, detect_mode: DeviceDetectMode) -> bool {\n    if device.name() == Some(\"kanata\") {\n        return false;\n    }\n    let is_keyboard = device.supported_keys().is_some_and(has_keyboard_keys);\n    let is_mouse = device\n        .supported_relative_axes()\n        .is_some_and(|axes| axes.contains(RelativeAxisCode::REL_X));\n    let device_type = match (is_keyboard, is_mouse) {\n        (true, true) => DeviceType::KeyboardMouse,\n        (true, false) => DeviceType::Keyboard,\n        (false, true) => DeviceType::Mouse,\n        (false, false) => DeviceType::Other,\n    };\n    let device_name = device.name().unwrap_or(\"unknown device name\");\n    match (detect_mode, device_type) {\n        (DeviceDetectMode::Any, _)\n        | (DeviceDetectMode::KeyboardMice, DeviceType::Keyboard | DeviceType::KeyboardMouse)\n        | (DeviceDetectMode::KeyboardOnly, DeviceType::Keyboard) => {\n            let use_input = true;\n            log::debug!(\n                \"Use for input autodetect: {use_input}. detect type {:?}; device type {:?}, device name: {}\",\n                detect_mode,\n                device_type,\n                device_name,\n            );\n            use_input\n        }\n        (_, DeviceType::Other) => {\n            log::debug!(\n                \"Use for input autodetect: false. Non-input device: {}\",\n                device_name,\n            );\n            false\n        }\n        _ => {\n            let use_input = false;\n            log::debug!(\n                \"Use for input autodetect: {use_input}. detect type {:?}; device type {:?}, device name: {}\",\n                detect_mode,\n                device_type,\n                device_name,\n            );\n            use_input\n        }\n    }\n}\n\nfn has_keyboard_keys(keys: &evdev::AttributeSetRef<KeyCode>) -> bool {\n    const SENSIBLE_KEYBOARD_SCANCODE_LOWER_BOUND: u16 = 1;\n    // The next one is power button. Some keyboards have it,\n    // but so does the power button...\n    const SENSIBLE_KEYBOARD_SCANCODE_UPPER_BOUND: u16 = 115;\n    let mut sensible_keyboard_keys = (SENSIBLE_KEYBOARD_SCANCODE_LOWER_BOUND\n        ..=SENSIBLE_KEYBOARD_SCANCODE_UPPER_BOUND)\n        .map(KeyCode::new);\n    sensible_keyboard_keys.any(|k| keys.contains(k))\n}\n\nimpl TryFrom<InputEvent> for KeyEvent {\n    type Error = ();\n    fn try_from(item: InputEvent) -> Result<Self, Self::Error> {\n        use OsCode::*;\n        match item.destructure() {\n            evdev::EventSummary::Key(_, k, _) => Ok(Self {\n                code: OsCode::from_u16(k.0).ok_or(())?,\n                value: KeyValue::from(item.value()),\n            }),\n            evdev::EventSummary::RelativeAxis(_, axis_type, _) => {\n                let dist = item.value();\n                let code: OsCode = match axis_type {\n                    RelativeAxisCode::REL_WHEEL | RelativeAxisCode::REL_WHEEL_HI_RES => {\n                        if dist > 0 {\n                            MouseWheelUp\n                        } else {\n                            MouseWheelDown\n                        }\n                    }\n                    RelativeAxisCode::REL_HWHEEL | RelativeAxisCode::REL_HWHEEL_HI_RES => {\n                        if dist > 0 {\n                            MouseWheelRight\n                        } else {\n                            MouseWheelLeft\n                        }\n                    }\n                    _ => return Err(()),\n                };\n                Ok(KeyEvent {\n                    code,\n                    value: KeyValue::Tap,\n                })\n            }\n            _ => Err(()),\n        }\n    }\n}\n\nimpl From<KeyEvent> for InputEvent {\n    fn from(item: KeyEvent) -> Self {\n        InputEvent::new(EventType::KEY.0, item.code as u16, item.value as i32)\n    }\n}\n\nuse std::cell::Cell;\n\n#[cfg(all(not(feature = \"simulated_output\"), not(feature = \"passthru_ahk\")))]\npub struct KbdOut {\n    device: uinput::VirtualDevice,\n    accumulated_scroll: u16,\n    accumulated_hscroll: u16,\n    raw_buf: Vec<InputEvent>,\n    pub unicode_termination: Cell<UnicodeTermination>,\n    pub unicode_u_code: Cell<OsCode>,\n}\n\n#[cfg(all(not(feature = \"simulated_output\"), not(feature = \"passthru_ahk\")))]\nimpl KbdOut {\n    pub fn new(\n        symlink_path: &Option<String>,\n        trackpoint: bool,\n        name: &str,\n        bus_type: BusType,\n    ) -> Result<Self, io::Error> {\n        // Support pretty much every feature of a Keyboard or a Mouse in a VirtualDevice so that no event from the original input devices gets lost\n        // TODO investigate the rare possibility that a device is e.g. a Joystick and a Keyboard or a Mouse at the same time, which could lead to lost events\n\n        // For some reason 0..0x300 (max value for a key) doesn't work, the closest that I've got to work is 560\n        let keys = evdev::AttributeSet::from_iter((0..560).map(evdev::KeyCode));\n        let relative_axes = evdev::AttributeSet::from_iter([\n            RelativeAxisCode::REL_WHEEL,\n            RelativeAxisCode::REL_HWHEEL,\n            RelativeAxisCode::REL_X,\n            RelativeAxisCode::REL_Y,\n            RelativeAxisCode::REL_Z,\n            RelativeAxisCode::REL_RX,\n            RelativeAxisCode::REL_RY,\n            RelativeAxisCode::REL_RZ,\n            RelativeAxisCode::REL_DIAL,\n            RelativeAxisCode::REL_MISC,\n            RelativeAxisCode::REL_WHEEL_HI_RES,\n            RelativeAxisCode::REL_HWHEEL_HI_RES,\n        ]);\n\n        let device = uinput::VirtualDevice::builder()?\n            .name(&name)\n            // libinput's \"disable while typing\" feature don't work when bus_type\n            // is set to BUS_USB, but appears to work when it's set to BUS_I8042.\n            .input_id(evdev::InputId::new(bus_type, 1, 1, 1))\n            .with_keys(&keys)?\n            .with_relative_axes(&relative_axes)?;\n        let device = if trackpoint {\n            device.with_properties(&evdev::AttributeSet::from_iter([PropType::POINTING_STICK]))?\n        } else {\n            device\n        };\n        let mut device = device.build()?;\n        let devnode = device\n            .enumerate_dev_nodes_blocking()?\n            .next() // Expect only one. Using fold or calling next again blocks indefinitely\n            .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, \"devnode is not found\"))??;\n        log::info!(\"Created device {:#?}\", devnode);\n        let symlink = if let Some(symlink_path) = symlink_path {\n            let dest = PathBuf::from(symlink_path);\n            let symlink = Symlink::new(devnode, dest)?;\n            Some(symlink)\n        } else {\n            None\n        };\n        handle_signals(symlink);\n\n        Ok(KbdOut {\n            device,\n            accumulated_scroll: 0,\n            accumulated_hscroll: 0,\n            raw_buf: vec![],\n\n            // historically was the only option, so make Enter the default\n            unicode_termination: Cell::new(UnicodeTermination::Enter),\n\n            // historically was the only option, so make KEY_U the default\n            unicode_u_code: Cell::new(OsCode::KEY_U),\n        })\n    }\n\n    pub fn update_unicode_termination(&self, t: UnicodeTermination) {\n        self.unicode_termination.replace(t);\n    }\n\n    pub fn update_unicode_u_code(&self, u: OsCode) {\n        self.unicode_u_code.replace(u);\n    }\n\n    pub fn write_raw(&mut self, event: InputEvent) -> Result<(), io::Error> {\n        if event.event_type() == EventType::SYNCHRONIZATION {\n            // Possible codes are:\n            //\n            // SYN_REPORT: probably the only one we'll ever receive, segments atomic reads\n            // SYN_CONFIG: unused\n            // SYN_MT_REPORT: same as SYN_REPORT above but for touch devices, which kanata almost\n            //     certainly shouldn't be dealing with.\n            // SYN_DROPPED: buffer full, events dropped. Not sure what this means or how to handle\n            //     this correctly.\n            //\n            // With this knowledge, seems fine to not bother checking.\n            self.device.emit(&self.raw_buf)?;\n            self.raw_buf.clear();\n        } else {\n            self.raw_buf.push(event);\n        }\n        Ok(())\n    }\n\n    pub fn write(&mut self, event: InputEvent) -> Result<(), io::Error> {\n        if !self.raw_buf.is_empty() {\n            self.device.emit(&self.raw_buf)?;\n            self.raw_buf.clear();\n        }\n        self.device.emit(&[event])?;\n        Ok(())\n    }\n\n    pub fn write_many(&mut self, events: &[InputEvent]) -> Result<(), io::Error> {\n        if !self.raw_buf.is_empty() {\n            self.device.emit(&self.raw_buf)?;\n            self.raw_buf.clear();\n        }\n        self.device.emit(events)?;\n        Ok(())\n    }\n\n    pub fn write_key(&mut self, key: OsCode, value: KeyValue) -> Result<(), io::Error> {\n        let key_ev = KeyEvent::new(key, value);\n        let input_ev = key_ev.into();\n        log::debug!(\"send to uinput: {:?}\", input_ev);\n        self.device.emit(&[input_ev])?;\n        Ok(())\n    }\n\n    pub fn write_code(&mut self, code: u32, value: KeyValue) -> Result<(), io::Error> {\n        let event = InputEvent::new(EventType::KEY.0, code as u16, value as i32);\n        self.device.emit(&[event])?;\n        Ok(())\n    }\n\n    pub fn press_key(&mut self, key: OsCode) -> Result<(), io::Error> {\n        self.write_key(key, KeyValue::Press)\n    }\n\n    pub fn release_key(&mut self, key: OsCode) -> Result<(), io::Error> {\n        self.write_key(key, KeyValue::Release)\n    }\n\n    /// Send using C-S-u + <unicode hex number> + spc\n    pub fn send_unicode(&mut self, c: char) -> Result<(), io::Error> {\n        log::debug!(\"sending unicode {c}\");\n        let hex = format!(\"{:x}\", c as u32);\n        self.press_key(OsCode::KEY_LEFTCTRL)?;\n        self.press_key(OsCode::KEY_LEFTSHIFT)?;\n        self.press_key(self.unicode_u_code.get())?;\n        self.release_key(self.unicode_u_code.get())?;\n        self.release_key(OsCode::KEY_LEFTSHIFT)?;\n        self.release_key(OsCode::KEY_LEFTCTRL)?;\n        let mut s = String::new();\n        for c in hex.chars() {\n            s.push(c);\n            let osc = str_to_oscode(&s).expect(\"valid keycodes for unicode\");\n            s.clear();\n            self.press_key(osc)?;\n            self.release_key(osc)?;\n        }\n        match self.unicode_termination.get() {\n            UnicodeTermination::Enter => {\n                self.press_key(OsCode::KEY_ENTER)?;\n                self.release_key(OsCode::KEY_ENTER)?;\n            }\n            UnicodeTermination::Space => {\n                self.press_key(OsCode::KEY_SPACE)?;\n                self.release_key(OsCode::KEY_SPACE)?;\n            }\n            UnicodeTermination::SpaceEnter => {\n                self.press_key(OsCode::KEY_SPACE)?;\n                self.release_key(OsCode::KEY_SPACE)?;\n                self.press_key(OsCode::KEY_ENTER)?;\n                self.release_key(OsCode::KEY_ENTER)?;\n            }\n            UnicodeTermination::EnterSpace => {\n                self.press_key(OsCode::KEY_ENTER)?;\n                self.release_key(OsCode::KEY_ENTER)?;\n                self.press_key(OsCode::KEY_SPACE)?;\n                self.release_key(OsCode::KEY_SPACE)?;\n            }\n        }\n        Ok(())\n    }\n\n    pub fn click_btn(&mut self, btn: Btn) -> Result<(), io::Error> {\n        self.press_key(btn.into())\n    }\n\n    pub fn release_btn(&mut self, btn: Btn) -> Result<(), io::Error> {\n        self.release_key(btn.into())\n    }\n\n    pub fn scroll(\n        &mut self,\n        direction: MWheelDirection,\n        hi_res_distance: u16,\n    ) -> Result<(), io::Error> {\n        log::debug!(\"scroll: {direction:?} {hi_res_distance:?}\");\n\n        let mut lo_res_distance = hi_res_distance / HI_RES_SCROLL_UNITS_IN_LO_RES;\n        let leftover_hi_res_distance = hi_res_distance % HI_RES_SCROLL_UNITS_IN_LO_RES;\n\n        match direction {\n            MWheelDirection::Up | MWheelDirection::Down => {\n                self.accumulated_scroll += leftover_hi_res_distance;\n                lo_res_distance += self.accumulated_scroll / HI_RES_SCROLL_UNITS_IN_LO_RES;\n                self.accumulated_scroll %= HI_RES_SCROLL_UNITS_IN_LO_RES;\n            }\n            MWheelDirection::Left | MWheelDirection::Right => {\n                self.accumulated_hscroll += leftover_hi_res_distance;\n                lo_res_distance += self.accumulated_hscroll / HI_RES_SCROLL_UNITS_IN_LO_RES;\n                self.accumulated_hscroll %= HI_RES_SCROLL_UNITS_IN_LO_RES;\n            }\n        }\n\n        let hi_res_scroll_event = InputEvent::new(\n            EventType::RELATIVE.0,\n            match direction {\n                MWheelDirection::Up | MWheelDirection::Down => RelativeAxisCode::REL_WHEEL_HI_RES.0,\n                MWheelDirection::Left | MWheelDirection::Right => {\n                    RelativeAxisCode::REL_HWHEEL_HI_RES.0\n                }\n            },\n            match direction {\n                MWheelDirection::Up | MWheelDirection::Right => i32::from(hi_res_distance),\n                MWheelDirection::Down | MWheelDirection::Left => -i32::from(hi_res_distance),\n            },\n        );\n\n        if lo_res_distance > 0 {\n            self.write_many(&[\n                hi_res_scroll_event,\n                InputEvent::new(\n                    EventType::RELATIVE.0,\n                    match direction {\n                        MWheelDirection::Up | MWheelDirection::Down => {\n                            RelativeAxisCode::REL_WHEEL.0\n                        }\n                        MWheelDirection::Left | MWheelDirection::Right => {\n                            RelativeAxisCode::REL_HWHEEL.0\n                        }\n                    },\n                    match direction {\n                        MWheelDirection::Up | MWheelDirection::Right => i32::from(lo_res_distance),\n                        MWheelDirection::Down | MWheelDirection::Left => {\n                            -i32::from(lo_res_distance)\n                        }\n                    },\n                ),\n            ])\n        } else {\n            self.write(hi_res_scroll_event)\n        }\n    }\n\n    pub fn move_mouse(&mut self, mv: CalculatedMouseMove) -> Result<(), io::Error> {\n        let (axis, distance) = match mv.direction {\n            MoveDirection::Up => (RelativeAxisCode::REL_Y, -i32::from(mv.distance)),\n            MoveDirection::Down => (RelativeAxisCode::REL_Y, i32::from(mv.distance)),\n            MoveDirection::Left => (RelativeAxisCode::REL_X, -i32::from(mv.distance)),\n            MoveDirection::Right => (RelativeAxisCode::REL_X, i32::from(mv.distance)),\n        };\n        self.write(InputEvent::new(EventType::RELATIVE.0, axis.0, distance))\n    }\n\n    pub fn move_mouse_many(&mut self, moves: &[CalculatedMouseMove]) -> Result<(), io::Error> {\n        let mut events = vec![];\n        for mv in moves {\n            let (axis, distance) = match mv.direction {\n                MoveDirection::Up => (RelativeAxisCode::REL_Y, -i32::from(mv.distance)),\n                MoveDirection::Down => (RelativeAxisCode::REL_Y, i32::from(mv.distance)),\n                MoveDirection::Left => (RelativeAxisCode::REL_X, -i32::from(mv.distance)),\n                MoveDirection::Right => (RelativeAxisCode::REL_X, i32::from(mv.distance)),\n            };\n            events.push(InputEvent::new(EventType::RELATIVE.0, axis.0, distance));\n        }\n        self.write_many(&events)\n    }\n\n    pub fn set_mouse(&mut self, _x: u16, _y: u16) -> Result<(), io::Error> {\n        log::warn!(\n            \"setmouse does not work in Linux yet. Maybe try out warpd:\\n\\thttps://github.com/rvaiya/warpd\"\n        );\n        Ok(())\n    }\n}\n\nfn devices_from_input_paths(\n    dev_paths: &[String],\n    missing_device_paths: &mut Vec<String>,\n) -> Vec<(Device, String)> {\n    dev_paths\n        .iter()\n        .map(|dev_path| (dev_path, Device::open(dev_path)))\n        .filter_map(|(dev_path, open_result)| match open_result {\n            Ok(d) => Some((d, dev_path.clone())),\n            Err(e) => {\n                log::warn!(\"failed to open device '{dev_path}': {e:?}\");\n                missing_device_paths.push(dev_path.clone());\n                None\n            }\n        })\n        .collect()\n}\n\npub fn discover_devices(\n    include_names: Option<&[String]>,\n    exclude_names: Option<&[String]>,\n    device_detect_mode: DeviceDetectMode,\n) -> Vec<(Device, String)> {\n    log::info!(\"looking for devices in /dev/input\");\n    let devices: Vec<_> = evdev::enumerate()\n        .map(|(path, device)| {\n            (\n                device,\n                path.to_str()\n                    .expect(\"non-utf8 path found for device\")\n                    .to_owned(),\n            )\n        })\n        .filter(|pd| {\n            let is_input = is_input_device(&pd.0, device_detect_mode);\n            (match include_names {\n                None => is_input,\n                Some(include_names) => {\n                    let name = pd.0.name().unwrap_or(\"\");\n                    if include_names.iter().any(|include| name == include) {\n                        log::info!(\"device [{}:{name}] is included\", &pd.1);\n                        true\n                    } else {\n                        log::info!(\"device [{}:{name}] is ignored\", &pd.1);\n                        false\n                    }\n                }\n            }) && (match exclude_names {\n                None => true,\n                Some(exclude_names) => {\n                    let name = pd.0.name().unwrap_or(\"\");\n                    if exclude_names.iter().any(|exclude| name == exclude) {\n                        log::info!(\"device [{}:{name}] is excluded\", &pd.1);\n                        false\n                    } else {\n                        true\n                    }\n                }\n            })\n        })\n        .collect();\n    devices\n}\n\nfn watch_devinput() -> Result<Inotify, io::Error> {\n    let inotify = Inotify::init().expect(\"Failed to initialize inotify\");\n    inotify.watches().add(\"/dev/input\", WatchMask::CREATE)?;\n    Ok(inotify)\n}\n\n#[derive(Clone)]\nstruct Symlink {\n    dest: PathBuf,\n}\n\nimpl Symlink {\n    fn new(source: PathBuf, dest: PathBuf) -> Result<Self, io::Error> {\n        if let Ok(metadata) = fs::symlink_metadata(&dest) {\n            if metadata.file_type().is_symlink() {\n                fs::remove_file(&dest)?;\n            } else {\n                return Err(io::Error::new(\n                    io::ErrorKind::AlreadyExists,\n                    format!(\n                        \"Cannot create a symlink at \\\"{}\\\": path already exists.\",\n                        dest.to_string_lossy()\n                    ),\n                ));\n            }\n        }\n        std::os::unix::fs::symlink(&source, &dest)?;\n        log::info!(\"Created symlink {:#?} -> {:#?}\", dest, source);\n        Ok(Self { dest })\n    }\n}\n\nfn handle_signals(symlink: Option<Symlink>) {\n    thread::spawn(|| {\n        let mut signals = Signals::new([SIGINT, SIGTERM, SIGTSTP]).expect(\"signals register\");\n        if let Some(signal) = (&mut signals).into_iter().next() {\n            match signal {\n                SIGINT | SIGTERM => {\n                    drop(symlink);\n                    signal_hook::low_level::emulate_default_handler(signal)\n                        .expect(\"run original sighandlers\");\n                    unreachable!();\n                }\n                SIGTSTP => {\n                    drop(symlink);\n                    log::warn!(\"got SIGTSTP, exiting instead of pausing so keyboards don't hang\");\n                    std::process::exit(SIGTSTP);\n                }\n                _ => unreachable!(),\n            }\n        }\n    });\n}\n\n// Note for allow: the ioctl_read_buf triggers this clippy lint.\n// Note: CI does not yet support this lint, so also allowing unknown lints.\n#[allow(unknown_lints)]\n#[allow(clippy::manual_slice_size_calculation)]\nfn wait_for_all_keys_unpressed(dev: &Device) -> Result<(), io::Error> {\n    let mut pending_release = false;\n    const KEY_MAX: usize = OsCode::KEY_MAX as usize;\n    let mut keystate = [0u8; KEY_MAX / 8 + 1];\n    let mut loop_attempts: usize = 0;\n    loop {\n        let mut n_pressed_keys = 0;\n        ioctl_read_buf!(read_keystates, 'E', 0x18, u8);\n        unsafe { read_keystates(dev.as_raw_fd(), &mut keystate) }\n            .map_err(|_| io::Error::last_os_error())?;\n        for i in 0..KEY_MAX {\n            if (keystate[i / 8] >> (i % 8)) & 0x1 > 0 {\n                n_pressed_keys += 1;\n            }\n        }\n        match n_pressed_keys {\n            0 => break,\n            _ => pending_release = true,\n        }\n        if loop_attempts > 1250 {\n            let dev_str = if let Some(dev_name) = dev.name() {\n                format!(\"\\\"{}\\\"\", dev_name)\n            } else if let Some(dev_path) = dev.physical_path() {\n                format!(\"\\\"{}\\\"\", dev_path)\n            } else {\n                let dev_input_id = dev.input_id();\n                let vendor = dev_input_id.vendor();\n                let product = dev_input_id.product();\n                let version = dev_input_id.version();\n                format!(\n                    \"{{ Vendor:{:#04x}, Product:{:#04x}, Version:{:#04x} }}\",\n                    vendor, product, version\n                )\n            };\n            log::warn!(\n                \"timed out waiting for {} to release its keys ({} pressed)\",\n                dev_str,\n                n_pressed_keys\n            );\n            break;\n        }\n        loop_attempts += 1;\n        std::thread::sleep(std::time::Duration::from_millis(4));\n    }\n    if pending_release {\n        std::thread::sleep(std::time::Duration::from_micros(100));\n    }\n    Ok(())\n}\n\nimpl Drop for Symlink {\n    fn drop(&mut self) {\n        let _ = fs::remove_file(&self.dest);\n        log::info!(\"Deleted symlink {:#?}\", self.dest);\n    }\n}\n"
  },
  {
    "path": "src/oskbd/macos.rs",
    "content": "//! Contains the input/output code for keyboards on Macos.\n\n// Caused by unmaintained objc crate triggering warnings.\n#![allow(unexpected_cfgs)]\n#![cfg_attr(\n    feature = \"simulated_output\",\n    allow(dead_code, unused_imports, unused_variables, unused_mut)\n)]\n\nuse super::*;\nuse crate::kanata::CalculatedMouseMove;\nuse crate::oskbd::KeyEvent;\nuse anyhow::anyhow;\nuse core_graphics::base::CGFloat;\nuse core_graphics::display::{CGDisplay, CGPoint};\nuse core_graphics::event::{CGEvent, CGEventTapLocation, CGEventType, CGMouseButton, EventField};\nuse core_graphics::event_source::{CGEventSource, CGEventSourceStateID};\nuse kanata_parser::custom_action::*;\nuse kanata_parser::keys::*;\nuse karabiner_driverkit::*;\nuse objc::runtime::Class;\nuse objc::{msg_send, sel, sel_impl};\nuse std::collections::HashMap;\nuse std::convert::TryFrom;\nuse std::fmt;\nuse std::io;\nuse std::io::Error;\nuse std::time::{Duration, Instant};\n\n#[derive(Debug, Clone, Copy)]\npub struct InputEvent {\n    pub value: u64,\n    pub page: u32,\n    pub code: u32,\n}\n\nimpl InputEvent {\n    pub fn new(event: DKEvent) -> Self {\n        InputEvent {\n            value: event.value,\n            page: event.page,\n            code: event.code,\n        }\n    }\n}\n\nimpl From<InputEvent> for DKEvent {\n    fn from(event: InputEvent) -> Self {\n        Self {\n            value: event.value,\n            page: event.page,\n            code: event.code,\n        }\n    }\n}\n\npub struct KbdIn {\n    grabbed: bool,\n}\n\nimpl Drop for KbdIn {\n    fn drop(&mut self) {\n        if self.grabbed {\n            release();\n        }\n    }\n}\n\nimpl KbdIn {\n    pub fn new(\n        include_names: Option<Vec<String>>,\n        exclude_names: Option<Vec<String>>,\n    ) -> Result<Self, anyhow::Error> {\n        if !driver_activated() {\n            return Err(anyhow!(\n                \"Karabiner-VirtualHIDDevice driver is not activated.\"\n            ));\n        }\n\n        // Based on the definition of include and exclude names, they should never be used together.\n        // Kanata config parser should probably enforce this.\n        let device_names = if let Some(included_names) = include_names {\n            validate_and_register_devices(included_names)\n        } else if let Some(excluded_names) = exclude_names {\n            // get all devices\n            let kb_list = fetch_devices();\n\n            // filter out excluded devices\n            let devices_to_include = kb_list\n                .iter()\n                .filter(|k| !excluded_names.iter().any(|n| *k == n.as_str()))\n                .map(|k| {\n                    if k.product_key.trim().is_empty() {\n                        format!(\"{:x}\", k.hash)\n                    } else {\n                        k.product_key.clone()\n                    }\n                })\n                .collect::<Vec<String>>();\n\n            // register the remeining devices\n            validate_and_register_devices(devices_to_include)\n        } else {\n            vec![]\n        };\n\n        if !device_names.is_empty() || register_device(\"\") {\n            if grab() {\n                Ok(Self { grabbed: true })\n            } else {\n                Err(anyhow!(\"grab failed\"))\n            }\n        } else {\n            Err(anyhow!(\n                \"Couldn't register any device. Use 'kanata --list' to see available devices. \\\n                 Note: devices with empty names are automatically skipped to prevent crashes.\"\n            ))\n        }\n    }\n\n    pub fn read(&mut self) -> Result<InputEvent, io::Error> {\n        let mut event = DKEvent {\n            value: 0,\n            page: 0,\n            code: 0,\n        };\n\n        let got_event = wait_key(&mut event);\n        if got_event == 0 {\n            // Pipe returned EOF — input was released via release_input_only()\n            return Err(io::Error::new(\n                io::ErrorKind::UnexpectedEof,\n                \"input pipe closed (devices released)\",\n            ));\n        }\n\n        Ok(InputEvent::new(event))\n    }\n\n    /// Release seized input devices without tearing down the output connection.\n    /// After this call, `read()` will return `UnexpectedEof`.\n    pub fn release_input(&mut self) {\n        if self.grabbed {\n            release_input_only();\n            self.grabbed = false;\n        }\n    }\n\n    /// Re-seize input devices after a previous `release_input()`.\n    /// Returns true if at least one device was seized.\n    pub fn regrab_input(&mut self) -> bool {\n        if !self.grabbed {\n            let ok = karabiner_driverkit::regrab_input();\n            self.grabbed = ok;\n            ok\n        } else {\n            true\n        }\n    }\n\n    pub fn is_grabbed(&self) -> bool {\n        self.grabbed\n    }\n}\n\nfn validate_and_register_devices(include_names: Vec<String>) -> Vec<String> {\n    include_names\n        .iter()\n        .filter_map(|dev| {\n            // Defensive check: skip empty device names that could cause crashes\n            if dev.trim().is_empty() {\n                log::warn!(\"Skipping empty device name (likely old keyboard without proper identification)\");\n                return None;\n            }\n\n            // Also skip the Karabiner device\n            // driverkit already prevents registering it, but this avoids unnecessary warnings\n            if dev.to_lowercase().contains(\"karabiner\") {\n                return None;\n            }\n\n            match device_matches(dev) {\n                true => Some(dev.to_string()),\n                false => {\n                    log::warn!(\"'{dev}' doesn't match any connected device\");\n                    None\n                }\n            }\n        })\n        .filter_map(|dev| {\n            if register_device(&dev) {\n                Some(dev.to_string())\n            } else {\n                log::warn!(\"Couldn't register device '{}' - device may be in use by another application or disconnected\", dev);\n                None\n            }\n        })\n        .collect()\n}\n\nimpl fmt::Display for InputEvent {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        use kanata_keyberon::key_code::KeyCode;\n        let ke = KeyEvent::try_from(*self).unwrap();\n        let direction = match ke.value {\n            KeyValue::Press => \"↓\",\n            KeyValue::Release => \"↑\",\n            KeyValue::Repeat => \"⟳\",\n            KeyValue::Tap => \"↕\",\n            KeyValue::WakeUp => \"!\",\n        };\n        let key_name = KeyCode::from(ke.code);\n        write!(f, \"{direction}{key_name:?}\")\n    }\n}\n\nimpl TryFrom<InputEvent> for KeyEvent {\n    type Error = ();\n\n    fn try_from(item: InputEvent) -> Result<Self, Self::Error> {\n        if let Ok(oscode) = OsCode::try_from(PageCode {\n            page: item.page,\n            code: item.code,\n        }) {\n            Ok(KeyEvent {\n                code: oscode,\n                value: if item.value == 1 {\n                    KeyValue::Press\n                } else {\n                    KeyValue::Release\n                },\n            })\n        } else {\n            Err(())\n        }\n    }\n}\n\nimpl TryFrom<KeyEvent> for InputEvent {\n    type Error = ();\n\n    fn try_from(item: KeyEvent) -> Result<Self, Self::Error> {\n        if let Ok(pagecode) = PageCode::try_from(item.code) {\n            let val = match item.value {\n                KeyValue::Press | KeyValue::Repeat => 1,\n                _ => 0,\n            };\n            Ok(InputEvent {\n                value: val,\n                page: pagecode.page,\n                code: pagecode.code,\n            })\n        } else {\n            Err(())\n        }\n    }\n}\n\n#[cfg(all(not(feature = \"simulated_output\"), not(feature = \"passthru_ahk\")))]\npub struct KbdOut {\n    output_pressed_since: HashMap<OsCode, Instant>,\n}\n\n#[cfg(all(not(feature = \"simulated_output\"), not(feature = \"passthru_ahk\")))]\nimpl KbdOut {\n    pub fn new() -> Result<Self, io::Error> {\n        Ok(KbdOut {\n            output_pressed_since: HashMap::default(),\n        })\n    }\n\n    pub fn write(&mut self, event: InputEvent) -> Result<(), io::Error> {\n        let mut devent = event.into();\n        log::debug!(\"Attempting to write {event:?} {devent:?}\");\n        let rc = send_key(&mut devent);\n        if rc == 2 {\n            return Err(io::Error::new(\n                io::ErrorKind::NotConnected,\n                \"DriverKit virtual keyboard not ready (sink disconnected)\",\n            ));\n        }\n        Ok(())\n    }\n\n    pub fn output_ready(&self) -> bool {\n        is_sink_ready()\n    }\n\n    pub fn wait_until_ready(&self, timeout: Option<Duration>) -> bool {\n        let start = Instant::now();\n        let mut attempt = 0u32;\n\n        loop {\n            if self.output_ready() {\n                return true;\n            }\n\n            if let Some(timeout) = timeout\n                && start.elapsed() >= timeout\n            {\n                return false;\n            }\n\n            attempt += 1;\n            if attempt % 10 == 0 {\n                if let Some(timeout) = timeout {\n                    log::info!(\n                        \"Waiting for DriverKit virtual keyboard... ({:.1}s/{:.1}s)\",\n                        start.elapsed().as_secs_f64(),\n                        timeout.as_secs_f64()\n                    );\n                } else {\n                    log::info!(\n                        \"Waiting for DriverKit virtual keyboard... ({:.1}s)\",\n                        start.elapsed().as_secs_f64()\n                    );\n                }\n            }\n\n            std::thread::sleep(Duration::from_millis(100));\n        }\n    }\n\n    pub fn write_key(&mut self, key: OsCode, value: KeyValue) -> Result<(), io::Error> {\n        if let Ok(event) = InputEvent::try_from(KeyEvent { value, code: key }) {\n            let result = self.write(event);\n            if result.is_ok() {\n                self.record_output_transition_after_write(key, value);\n            }\n            result\n        } else {\n            log::debug!(\"couldn't write unrecognized {key:?}\");\n            Err(io::Error::other(\"OsCode not recognized!\"))\n        }\n    }\n\n    pub fn write_code(&mut self, code: u32, value: KeyValue) -> Result<(), io::Error> {\n        if let Ok(event) = InputEvent::try_from(KeyEvent {\n            value,\n            code: OsCode::from_u16(code as u16).unwrap(),\n        }) {\n            self.write(event)\n        } else {\n            log::debug!(\"couldn't write unrecognized OsCode {code}\");\n            Err(io::Error::other(\"OsCode not recognized!\"))\n        }\n    }\n\n    pub fn press_key(&mut self, key: OsCode) -> Result<(), io::Error> {\n        self.write_key(key, KeyValue::Press)\n    }\n\n    pub fn release_key(&mut self, key: OsCode) -> Result<(), io::Error> {\n        self.write_key(key, KeyValue::Release)\n    }\n\n    pub fn release_tracked_output_keys(&mut self, reason: &str) {\n        let tracked_keys: Vec<OsCode> = self.output_pressed_since.keys().copied().collect();\n        if tracked_keys.is_empty() {\n            return;\n        }\n\n        for key in tracked_keys {\n            if let Err(error) = self.write_key(key, KeyValue::Release) {\n                log::warn!(\n                    \"failed to release tracked output key during {} recovery: key={key:?} error={error}\",\n                    reason\n                );\n            }\n        }\n\n        self.output_pressed_since.clear();\n    }\n\n    pub fn send_unicode(&mut self, c: char) -> Result<(), io::Error> {\n        let event = Self::make_event()?;\n        let mut arr = [0u16; 2];\n        // Capture the slice containing the encoded UTF-16 code units.\n        let encoded = c.encode_utf16(&mut arr);\n        // Pass only the part of the array that was populated.\n        event.set_string_from_utf16_unchecked(encoded);\n        event.set_type(CGEventType::KeyDown);\n        event.post(CGEventTapLocation::AnnotatedSession);\n        event.set_type(CGEventType::KeyUp);\n        event.post(CGEventTapLocation::AnnotatedSession);\n        Ok(())\n    }\n    pub fn scroll(&mut self, _direction: MWheelDirection, _distance: u16) -> Result<(), io::Error> {\n        let event = Self::make_event()?;\n        event.set_type(CGEventType::ScrollWheel);\n        match _direction {\n            MWheelDirection::Down => event.set_integer_value_field(\n                EventField::SCROLL_WHEEL_EVENT_DELTA_AXIS_1,\n                _distance as i64,\n            ),\n            MWheelDirection::Up => event.set_integer_value_field(\n                EventField::SCROLL_WHEEL_EVENT_DELTA_AXIS_1,\n                -(_distance as i64),\n            ),\n            MWheelDirection::Left => event.set_integer_value_field(\n                EventField::SCROLL_WHEEL_EVENT_DELTA_AXIS_2,\n                _distance as i64,\n            ),\n            MWheelDirection::Right => event.set_integer_value_field(\n                EventField::SCROLL_WHEEL_EVENT_DELTA_AXIS_2,\n                -(_distance as i64),\n            ),\n        }\n        // Mouse control only seems to work with CGEventTapLocation::HID.\n        event.post(CGEventTapLocation::HID);\n        Ok(())\n    }\n    fn button_action(&mut self, _btn: Btn, is_click: bool) -> Result<(), io::Error> {\n        let (event_type, button) = match _btn {\n            Btn::Left => (\n                if is_click {\n                    CGEventType::LeftMouseDown\n                } else {\n                    CGEventType::LeftMouseUp\n                },\n                Some(CGMouseButton::Left),\n            ),\n            Btn::Right => (\n                if is_click {\n                    CGEventType::RightMouseDown\n                } else {\n                    CGEventType::RightMouseUp\n                },\n                Some(CGMouseButton::Right),\n            ),\n            Btn::Mid => (\n                if is_click {\n                    CGEventType::OtherMouseDown\n                } else {\n                    CGEventType::OtherMouseUp\n                },\n                Some(CGMouseButton::Center),\n            ),\n            // It's unclear to me which event type to use here, hence unsupported for now\n            Btn::Forward => (CGEventType::Null, None),\n            Btn::Backward => (CGEventType::Null, None),\n        };\n        // CGEventType doesn't implement Eq, therefore the casting to u8\n        if event_type as u8 == CGEventType::Null as u8 {\n            panic!(\"mouse buttons other than left, right, and middle aren't currently supported\")\n        }\n\n        let event_source = Self::make_event_source()?;\n        let event = Self::make_event()?;\n        let mouse_position = event.location();\n        let event =\n            CGEvent::new_mouse_event(event_source, event_type, mouse_position, button.unwrap())\n                .map_err(|_| std::io::Error::other(\"Failed to create mouse event\"))?;\n\n        // Mouse control only seems to work with CGEventTapLocation::HID.\n        event.post(CGEventTapLocation::HID);\n        Ok(())\n    }\n\n    pub fn click_btn(&mut self, _btn: Btn) -> Result<(), io::Error> {\n        Self::button_action(self, _btn, true)\n    }\n\n    pub fn release_btn(&mut self, _btn: Btn) -> Result<(), io::Error> {\n        Self::button_action(self, _btn, false)\n    }\n\n    pub fn move_mouse(&mut self, _mv: CalculatedMouseMove) -> Result<(), io::Error> {\n        let pressed = Self::pressed_buttons();\n\n        let event_type = if pressed & 1 > 0 {\n            CGEventType::LeftMouseDragged\n        } else if pressed & 2 > 0 {\n            CGEventType::RightMouseDragged\n        } else {\n            CGEventType::MouseMoved\n        };\n\n        let event = Self::make_event()?;\n        let mut mouse_position = event.location();\n        Self::apply_calculated_move(&_mv, &mut mouse_position);\n        if let Ok(event) = CGEvent::new_mouse_event(\n            Self::make_event_source()?,\n            event_type,\n            mouse_position,\n            CGMouseButton::Left,\n        ) {\n            event.post(CGEventTapLocation::HID);\n        }\n        Ok(())\n    }\n\n    fn pressed_buttons() -> usize {\n        if let Some(ns_event) = Class::get(\"NSEvent\") {\n            unsafe { msg_send![ns_event, pressedMouseButtons] }\n        } else {\n            0\n        }\n    }\n\n    pub fn move_mouse_many(&mut self, _moves: &[CalculatedMouseMove]) -> Result<(), io::Error> {\n        let event = Self::make_event()?;\n        let mut mouse_position = event.location();\n        let display = CGDisplay::main();\n        for current_move in _moves.iter() {\n            Self::apply_calculated_move(current_move, &mut mouse_position);\n        }\n        display\n            .move_cursor_to_point(mouse_position)\n            .map_err(|_| io::Error::other(\"failed to move mouse\"))?;\n        Ok(())\n    }\n\n    pub fn set_mouse(&mut self, _x: u16, _y: u16) -> Result<(), io::Error> {\n        let display = CGDisplay::main();\n        let point = CGPoint::new(_x as CGFloat, _y as CGFloat);\n        display\n            .move_cursor_to_point(point)\n            .map_err(|_| io::Error::other(\"failed to move cursor to point\"))?;\n        Ok(())\n    }\n\n    fn make_event_source() -> Result<CGEventSource, Error> {\n        CGEventSource::new(CGEventSourceStateID::CombinedSessionState)\n            .map_err(|_| Error::other(\"failed to create core graphics event source\"))\n    }\n    /// Creates a core graphics event.\n    /// The CGEventSourceStateID is a guess at this point - all functionality works using this but\n    /// I have not verified that this is the correct parameter.\n    /// Note that the CFRelease function mentioned in the docs is automatically called when the\n    /// event is dropped, therefore we don't need to care about this ourselves.\n    fn make_event() -> Result<CGEvent, Error> {\n        let event_source = Self::make_event_source()?;\n        let event = CGEvent::new(event_source)\n            .map_err(|_| Error::other(\"failed to create core graphics event\"))?;\n        Ok(event)\n    }\n\n    fn record_output_transition_after_write(&mut self, key: OsCode, value: KeyValue) {\n        match value {\n            KeyValue::Press | KeyValue::Repeat => {\n                self.output_pressed_since\n                    .entry(key)\n                    .or_insert_with(Instant::now);\n            }\n            KeyValue::Release => {\n                self.output_pressed_since.remove(&key);\n            }\n            KeyValue::Tap | KeyValue::WakeUp => {}\n        }\n    }\n\n    /// Applies a calculated mouse move to a CGPoint.\n    ///\n    /// This does _not_ move the mouse, it just mutates the point.\n    fn apply_calculated_move(_mv: &CalculatedMouseMove, mouse_position: &mut CGPoint) {\n        match _mv.direction {\n            MoveDirection::Up => mouse_position.y -= _mv.distance as CGFloat,\n            MoveDirection::Down => mouse_position.y += _mv.distance as CGFloat,\n            MoveDirection::Left => mouse_position.x -= _mv.distance as CGFloat,\n            MoveDirection::Right => mouse_position.x += _mv.distance as CGFloat,\n        }\n    }\n}\n"
  },
  {
    "path": "src/oskbd/mod.rs",
    "content": "//! Platform specific code for low level keyboard read/write.\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\nmod linux;\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\npub use linux::*;\n\n#[cfg(target_os = \"windows\")]\nmod windows;\n#[cfg(target_os = \"windows\")]\npub use windows::*;\n\n#[cfg(target_os = \"macos\")]\nmod macos;\n#[cfg(target_os = \"macos\")]\npub use macos::*;\n\n#[cfg(any(\n    all(\n        not(feature = \"simulated_input\"),\n        feature = \"simulated_output\",\n        not(feature = \"passthru_ahk\")\n    ),\n    all(\n        feature = \"simulated_input\",\n        not(feature = \"simulated_output\"),\n        not(feature = \"passthru_ahk\")\n    )\n))]\nmod simulated; // has KbdOut\n#[cfg(any(\n    all(\n        not(feature = \"simulated_input\"),\n        feature = \"simulated_output\",\n        not(feature = \"passthru_ahk\")\n    ),\n    all(\n        feature = \"simulated_input\",\n        not(feature = \"simulated_output\"),\n        not(feature = \"passthru_ahk\")\n    )\n))]\npub use simulated::*;\n#[cfg(any(\n    all(feature = \"simulated_input\", feature = \"simulated_output\"),\n    all(\n        feature = \"simulated_input\",\n        feature = \"simulated_output\",\n        feature = \"passthru_ahk\"\n    ),\n))]\nmod sim_passthru; // has KbdOut\n#[cfg(any(\n    all(feature = \"simulated_input\", feature = \"simulated_output\"),\n    all(\n        feature = \"simulated_input\",\n        feature = \"simulated_output\",\n        feature = \"passthru_ahk\"\n    ),\n))]\npub use sim_passthru::*;\n\npub const HI_RES_SCROLL_UNITS_IN_LO_RES: u16 = 120;\n\n// ------------------ KeyValue --------------------\n\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\npub enum KeyValue {\n    Release = 0,\n    Press = 1,\n    Repeat = 2,\n    Tap,\n    WakeUp,\n}\n\nimpl From<i32> for KeyValue {\n    fn from(item: i32) -> Self {\n        match item {\n            0 => Self::Release,\n            1 => Self::Press,\n            2 => Self::Repeat,\n            _ => unreachable!(),\n        }\n    }\n}\n\nimpl From<bool> for KeyValue {\n    fn from(up: bool) -> Self {\n        match up {\n            true => Self::Release,\n            false => Self::Press,\n        }\n    }\n}\n\nimpl From<KeyValue> for bool {\n    fn from(val: KeyValue) -> Self {\n        matches!(val, KeyValue::Release)\n    }\n}\n\nuse kanata_parser::keys::OsCode;\n\n#[derive(Clone, Copy)]\npub struct KeyEvent {\n    pub code: OsCode,\n    pub value: KeyValue,\n}\n\n#[allow(dead_code, unused)]\nimpl KeyEvent {\n    pub fn new(code: OsCode, value: KeyValue) -> Self {\n        Self { code, value }\n    }\n}\n\nuse core::fmt;\nimpl fmt::Display for KeyEvent {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        use kanata_keyberon::key_code::KeyCode;\n        let direction = match self.value {\n            KeyValue::Press => \"↓\",\n            KeyValue::Release => \"↑\",\n            KeyValue::Repeat => \"⟳\",\n            KeyValue::Tap => \"↕\",\n            KeyValue::WakeUp => \"!\",\n        };\n        let key_name = KeyCode::from(self.code);\n        write!(f, \"{direction}{key_name:?}\")\n    }\n}\n\nimpl fmt::Debug for KeyEvent {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        f.debug_struct(\"KeyEvent\")\n            .field(\n                \"code\",\n                &format_args!(\"{:?} ({})\", self.code, self.code.as_u16()),\n            )\n            .field(\"value\", &self.value)\n            .finish()\n    }\n}\n"
  },
  {
    "path": "src/oskbd/sim_passthru.rs",
    "content": "//! Redirects output to the function provided by the entity supplying simulated input (e.g., AHK)\n// todo: allow sharing numpad status to differentiate between vk enter and vk numpad enter\n// todo: only press/release_key is implemented\nuse super::*;\nuse anyhow::Result;\nuse log::*;\n\nuse crate::kanata::CalculatedMouseMove;\nuse kanata_parser::custom_action::*;\n\nuse std::io;\n\n#[cfg(not(any(target_os = \"windows\", target_os = \"macos\")))]\nuse std::fmt;\n\nuse std::sync::Arc;\nuse std::sync::OnceLock;\ntype CbOutEvFn = dyn Fn(i64, i64, i64) -> i64 + Send + Sync + 'static; // Rust wrapper func around external callback (transmuted into this) Ahk accept only i64 arguments (vk,sc,up)\npub struct FnOutEvWrapper {\n    pub cb: Arc<CbOutEvFn>,\n} // wrapper struct to store our callback in a thread-shareable manner\npub static OUTEVWRAP: OnceLock<FnOutEvWrapper> = OnceLock::new(); // ensure that our wrapper struct is created once (thread-safe)\n\nuse std::sync::mpsc::{SendError, Sender as ASender};\n/// Handle for writing keys to the simulated input provider.\npub struct KbdOut {\n    pub tx_kout: Option<ASender<InputEvent>>,\n}\n\nuse std::io::{Error as IoErr, ErrorKind::NotConnected};\nimpl KbdOut {\n    #[cfg(not(any(target_os = \"linux\", target_os = \"android\")))]\n    pub fn new() -> Result<Self, io::Error> {\n        Ok(Self { tx_kout: None })\n    }\n    #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n    pub fn new(\n        _s: &Option<String>,\n        _tp: bool,\n        _name: &str,\n        _bustype: evdev::BusType,\n    ) -> Result<Self, io::Error> {\n        Ok(Self { tx_kout: None })\n    }\n    #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n    pub fn write_raw(&mut self, event: InputEvent) -> Result<(), io::Error> {\n        trace!(\"out-raw:{event:?}\");\n        Ok(())\n    }\n    pub fn write(&mut self, event: InputEvent) -> Result<(), io::Error> {\n        trace!(\"out:{event}\");\n        if let Some(tx_kout) = &self.tx_kout {\n            // Send key event msg → main thread so it can be polled to try receiving it after processing external input events\n            match tx_kout.send(event) {\n                // send won't block for an async channel\n                Ok(res) => {\n                    debug!(\n                        \"✓ tx_kout → rx_kout@key_out(dll) ‘{event}’ from send_out_ev_msg@sim_passthru(oskbd)\"\n                    );\n                    return Ok(res);\n                }\n                Err(SendError(event)) => {\n                    error!(\n                        \"✗ tx_kout → rx_kout@key_out(dll) ‘{event}’ from send_out_ev_msg@sim_passthru(oskbd)\"\n                    );\n                    return Err(IoErr::new(\n                        NotConnected,\n                        format!(\"Failed sending sending {event}\"),\n                    ));\n                }\n            }\n        } else {\n            debug!(\"✗ tx_kout doesn't exist\");\n        }\n        Ok(())\n    }\n    pub fn output_ready(&self) -> bool {\n        true\n    }\n    pub fn wait_until_ready(&self, _timeout: Option<std::time::Duration>) -> bool {\n        true\n    }\n    pub fn write_key(&mut self, key: OsCode, value: KeyValue) -> Result<(), io::Error> {\n        let key_ev = KeyEvent::new(key, value);\n        let event = {\n            #[cfg(target_os = \"macos\")]\n            {\n                key_ev.try_into().unwrap()\n            }\n            #[cfg(not(target_os = \"macos\"))]\n            {\n                key_ev.into()\n            }\n        };\n        self.write(event)\n    }\n    pub fn write_code(&mut self, code: u32, value: KeyValue) -> Result<(), io::Error> {\n        trace!(\"out-code:{code};{value:?}\");\n        Ok(())\n    }\n    pub fn press_key(&mut self, key: OsCode) -> Result<(), io::Error> {\n        self.write_key(key, KeyValue::Press)\n    }\n    pub fn release_key(&mut self, key: OsCode) -> Result<(), io::Error> {\n        self.write_key(key, KeyValue::Release)\n    }\n    pub fn release_tracked_output_keys(&mut self, _reason: &str) {}\n    pub fn send_unicode(&mut self, c: char) -> Result<(), io::Error> {\n        trace!(\"outU:{c}\");\n        Ok(())\n    }\n    pub fn click_btn(&mut self, btn: Btn) -> Result<(), io::Error> {\n        trace!(\"out🖰:↓{btn:?}\");\n        Ok(())\n    }\n    pub fn release_btn(&mut self, btn: Btn) -> Result<(), io::Error> {\n        trace!(\"out🖰:↑{btn:?}\");\n        Ok(())\n    }\n    pub fn scroll(&mut self, direction: MWheelDirection, distance: u16) -> Result<(), io::Error> {\n        trace!(\"scroll:{direction:?},{distance:?}\");\n        Ok(())\n    }\n    pub fn move_mouse(&mut self, mv: CalculatedMouseMove) -> Result<(), io::Error> {\n        let (direction, distance) = (mv.direction, mv.distance);\n        trace!(\"out🖰:move {direction:?},{distance:?}\");\n        Ok(())\n    }\n    pub fn move_mouse_many(&mut self, moves: &[CalculatedMouseMove]) -> Result<(), io::Error> {\n        for mv in moves {\n            let (direction, distance) = (&mv.direction, &mv.distance);\n            trace!(\"out🖰:move {direction:?},{distance:?}\");\n        }\n        Ok(())\n    }\n    pub fn set_mouse(&mut self, x: u16, y: u16) -> Result<(), io::Error> {\n        log::info!(\"out🖰:@{x},{y}\");\n        Ok(())\n    }\n    pub fn tick(&mut self) {}\n}\n\n#[cfg(not(any(target_os = \"windows\", target_os = \"macos\")))]\n#[derive(Debug, Clone, Copy)]\npub struct InputEvent {\n    pub code: u32,\n\n    /// Key was released\n    pub up: bool,\n}\n#[cfg(not(any(target_os = \"windows\", target_os = \"macos\")))]\nimpl fmt::Display for InputEvent {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        use kanata_keyberon::key_code::KeyCode;\n        let direction = if self.up { \"↑\" } else { \"↓\" };\n        let key_name = KeyCode::from(OsCode::from(self.code));\n        write!(f, \"{}{:?}\", direction, key_name)\n    }\n}\n\n#[cfg(not(any(target_os = \"windows\", target_os = \"macos\")))]\nimpl InputEvent {\n    pub fn from_oscode(code: OsCode, val: KeyValue) -> Self {\n        Self {\n            code: code.into(),\n            up: val.into(),\n        }\n    }\n}\n\n#[cfg(not(any(target_os = \"windows\", target_os = \"macos\")))]\nimpl TryFrom<InputEvent> for KeyEvent {\n    type Error = ();\n    fn try_from(item: InputEvent) -> Result<Self, Self::Error> {\n        Ok(Self {\n            code: OsCode::from_u16(item.code as u16).ok_or(())?,\n            value: match item.up {\n                true => KeyValue::Release,\n                false => KeyValue::Press,\n            },\n        })\n    }\n}\n\n#[cfg(not(any(target_os = \"windows\", target_os = \"macos\")))]\nimpl From<KeyEvent> for InputEvent {\n    fn from(item: KeyEvent) -> Self {\n        Self {\n            code: item.code.into(),\n            up: item.value.into(),\n        }\n    }\n}\n"
  },
  {
    "path": "src/oskbd/simulated.rs",
    "content": "//! Output that just prints text to stdout instead of actually doing anything OS-related.\n//! See <../../docs/simulated_output/sim_out.txt> for an example output.\nuse indoc::formatdoc;\nuse std::ffi::{OsStr, OsString};\nuse std::fs::File;\nuse std::io::Write;\nuse std::path::{Path, PathBuf};\n\npub fn concat_os_str2(a: &OsStr, b: &OsStr) -> OsString {\n    let mut ret = OsString::with_capacity(a.len() + b.len()); // allocate once\n    ret.push(a);\n    ret.push(b); // doesn't allocate\n    ret\n}\nfn append_file_name(path: impl AsRef<Path>, appendix: impl AsRef<OsStr>) -> PathBuf {\n    let path = path.as_ref();\n    let mut result = path.to_owned();\n    let stem_in = path.file_stem().unwrap_or(OsStr::new(\"\"));\n    let stem_out = concat_os_str2(stem_in, OsStr::new(&appendix));\n    result.set_file_name(stem_out);\n    if let Some(ext) = path.extension() {\n        result.set_extension(ext);\n    }\n    result\n}\n\n#[derive(Debug, Copy, Clone, PartialEq)]\npub enum LogFmtT {\n    InKeyUp,\n    InKeyDown,\n    InKeyRep,\n    InTick,\n    KeyUp,\n    KeyDown,\n    Tick,\n    MouseUp,\n    MouseDown,\n    MouseMove,\n    Unicode,\n    Code,\n    RawUp,\n    RawDown,\n}\n\npub struct LogFmt {\n    ticks: u64,\n    //In       \t//\n    in_time: String,\n    in_key_up: String,\n    in_key_down: String,\n    in_key_rep: String,\n    in_combo: String,\n    //Out      \t//\n    time: String,\n    key_up: String,\n    key_down: String,\n    raw_up: String,\n    raw_down: String,\n    combo: String,\n    mouse_up: String,\n    mouse_down: String,\n    mouse_move: String,\n    unicode: String,\n    code: String,\n}\nimpl Default for LogFmt {\n    fn default() -> Self {\n        Self::new()\n    }\n}\nimpl LogFmt {\n    pub fn new() -> Self {\n        Self {\n            ticks: 0,\n            //In       \t//\n            in_time: String::new(),\n            in_key_up: String::new(),\n            in_key_down: String::new(),\n            in_key_rep: String::new(),\n            in_combo: String::new(),\n            //Out      \t//\n            time: String::new(),\n            key_up: String::new(),\n            key_down: String::new(),\n            raw_up: String::new(),\n            raw_down: String::new(),\n            mouse_up: String::new(),\n            mouse_down: String::new(),\n            mouse_move: String::new(),\n            unicode: String::new(),\n            code: String::new(),\n            combo: String::new(),\n        }\n    }\n    pub fn fmt(&mut self, key: LogFmtT, value: String) {\n        let mut pad = value.len();\n        let mut time = \"\".to_string();\n        if self.ticks > 0 {\n            pad = std::cmp::max(value.len(), self.ticks.to_string().len()); // add extra padding if\n            // event tick is wider\n            time = format!(\"  {: <pad$}\", self.ticks);\n            self.ticks = 0;\n        }\n        let blank = format!(\"  {: <pad$}\", \"\"); //+␠ to allow combo log indicator\n        let val = format!(\"  {value: <pad$}\");\n        self.in_time += if key == LogFmtT::InTick {\n            self.combo += &blank;\n            self.in_combo += &format!(\" 🕐{value: <pad$}\");\n            &val\n        } else {\n            &blank\n        };\n        self.in_key_up += if key == LogFmtT::InKeyUp {\n            self.combo += &blank;\n            self.in_combo += &format!(\" ↑{value: <pad$}\");\n            &val\n        } else {\n            &blank\n        };\n        self.in_key_down += if key == LogFmtT::InKeyDown {\n            self.combo += &blank;\n            self.in_combo += &format!(\" ↓{value: <pad$}\");\n            &val\n        } else {\n            &blank\n        };\n        self.in_key_rep += if key == LogFmtT::InKeyRep {\n            self.combo += &blank;\n            self.in_combo += &format!(\" ⟳{value: <pad$}\");\n            &val\n        } else {\n            &blank\n        };\n        self.time += if !time.is_empty() { &time } else { &blank };\n        self.key_up += if key == LogFmtT::KeyUp {\n            self.in_combo += &blank;\n            self.combo += &format!(\" ↑{value: <pad$}\");\n            &val\n        } else {\n            &blank\n        };\n        self.key_down += if key == LogFmtT::KeyDown {\n            self.in_combo += &blank;\n            self.combo += &format!(\" ↓{value: <pad$}\");\n            &val\n        } else {\n            &blank\n        };\n        self.mouse_up += if key == LogFmtT::MouseUp {\n            self.in_combo += &blank;\n            self.combo += &format!(\" ↑{value: <pad$}\");\n            &val\n        } else {\n            &blank\n        };\n        self.mouse_down += if key == LogFmtT::MouseDown {\n            self.in_combo += &blank;\n            self.combo += &format!(\" ↓{value: <pad$}\");\n            &val\n        } else {\n            &blank\n        };\n        self.mouse_move += if key == LogFmtT::MouseMove {\n            self.in_combo += &blank;\n            self.combo += &val;\n            &val\n        } else {\n            &blank\n        };\n        self.raw_up += if key == LogFmtT::RawUp {\n            self.in_combo += &blank;\n            self.combo += &format!(\" ↑{value: <pad$}\");\n            &val\n        } else {\n            &blank\n        };\n        self.raw_down += if key == LogFmtT::RawDown {\n            self.in_combo += &blank;\n            self.combo += &format!(\" ↓{value: <pad$}\");\n            &val\n        } else {\n            &blank\n        };\n        self.unicode += if key == LogFmtT::Unicode {\n            self.in_combo += &blank;\n            self.combo += &val;\n            &val\n        } else {\n            &blank\n        };\n        self.code += if key == LogFmtT::Code {\n            self.in_combo += &blank;\n            self.combo += &val;\n            &val\n        } else {\n            &blank\n        };\n    }\n\n    #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n    pub fn write_raw(&mut self, event: InputEvent) {\n        let key_name = KeyCode::from(OsCode::from(event.code));\n        if event.up {\n            self.fmt(LogFmtT::RawUp, key_name.to_string())\n        } else {\n            self.fmt(LogFmtT::RawDown, key_name.to_string())\n        }\n    }\n    pub fn in_tick(&mut self, t: u128) {\n        self.fmt(LogFmtT::InTick, t.to_string())\n    }\n    pub fn in_press_key(&mut self, key: OsCode) {\n        self.fmt(LogFmtT::InKeyDown, KeyCode::from(key).to_string())\n    }\n    pub fn in_release_key(&mut self, key: OsCode) {\n        self.fmt(LogFmtT::InKeyUp, KeyCode::from(key).to_string())\n    }\n    pub fn in_repeat_key(&mut self, key: OsCode) {\n        self.fmt(LogFmtT::InKeyRep, KeyCode::from(key).to_string())\n    }\n    pub fn press_key(&mut self, key: OsCode) {\n        self.fmt(LogFmtT::KeyDown, KeyCode::from(key).to_string())\n    }\n    pub fn release_key(&mut self, key: OsCode) {\n        self.fmt(LogFmtT::KeyUp, KeyCode::from(key).to_string())\n    }\n    pub fn send_unicode(&mut self, c: char) {\n        self.fmt(LogFmtT::Unicode, c.to_string())\n    }\n    pub fn click_btn(&mut self, btn: Btn) {\n        self.fmt(LogFmtT::MouseDown, btn.to_string())\n    }\n    pub fn release_btn(&mut self, btn: Btn) {\n        self.fmt(LogFmtT::MouseUp, btn.to_string())\n    }\n    pub fn set_mouse(&mut self, x: u16, y: u16) {\n        self.fmt(LogFmtT::MouseMove, format!(\"@{x},{y}\"))\n    }\n    pub fn scroll(&mut self, dir: MWheelDirection, dist: u16) {\n        self.fmt(LogFmtT::MouseMove, format!(\"{dir}{dist}\"))\n    }\n    pub fn move_mouse(&mut self, dir: MoveDirection, dist: u16) {\n        self.fmt(LogFmtT::MouseMove, format!(\"{dir}{dist}\"))\n    }\n    pub fn write_code(&mut self, code: u32, value: KeyValue) {\n        self.fmt(LogFmtT::Code, format!(\"{code};{value:?}\"))\n    }\n\n    pub fn end(&self, in_path: &PathBuf, appendix: Option<String>) {\n        let pad = self.combo.len().saturating_sub(3);\n        let table_out = formatdoc!(\n            \"🕐Δms│{}\n          In───┼{:─<pad$}\n           k↑  │{}\n           k↓  │{}\n           k⟳  │{}\n          Σin  │{}\n          Out──┼{:─<pad$}\n           k↑  │{}\n           k↓  │{}\n           🖰↑  │{}\n           🖰↓  │{}\n           🖰   │{}\n           🔣  │{}\n           code│{}\n           raw↑│{}\n           raw↓│{}\n          Σout │{}\n          \",\n            self.time,\n            \"\",\n            self.in_key_up,\n            self.in_key_down,\n            self.in_key_rep,\n            self.in_combo,\n            \"\",\n            self.key_up,\n            self.key_down,\n            self.mouse_up,\n            self.mouse_down,\n            self.mouse_move,\n            self.unicode,\n            self.code,\n            self.raw_up,\n            self.raw_down,\n            self.combo\n        );\n        eprintln!(\"{table_out}\");\n        if let Some(appendix_s) = appendix {\n            let out_path = append_file_name(in_path, appendix_s);\n            let out_path_s = out_path.display();\n            let mut out_file = match File::create(&out_path) {\n                Err(e) => panic!(\"✗ Couldn't create {out_path_s}: {e}\"),\n                Ok(out_file) => out_file,\n            };\n            match out_file.write_all(table_out.as_bytes()) {\n                Err(e) => panic!(\"✗ Couldn't write to {out_path_s}: {e}\"),\n                Ok(_) => eprintln!(\"Saved output → {out_path_s}\"),\n            }\n        }\n    }\n}\n\nuse super::*;\n\nuse crate::kanata::CalculatedMouseMove;\nuse kanata_parser::custom_action::*;\n\nuse std::io;\n\nuse kanata_keyberon::key_code::KeyCode;\n#[cfg(not(any(target_os = \"windows\", target_os = \"macos\")))]\nuse std::fmt;\n\npub struct Outputs {\n    pub events: Vec<String>,\n    ticks: u64,\n}\n\nimpl Outputs {\n    fn new() -> Self {\n        Self {\n            events: vec![],\n            ticks: 0,\n        }\n    }\n\n    fn push(&mut self, event: impl AsRef<str>) {\n        if self.ticks > 0 {\n            self.events.push(format!(\"t:{}ms\", self.ticks));\n        }\n        self.events.push(event.as_ref().to_string());\n        self.ticks = 0;\n    }\n}\n\n/// Handle for writing keys to the OS.\npub struct KbdOut {\n    pub log: LogFmt,\n    pub outputs: Outputs,\n}\n\nimpl KbdOut {\n    fn new_actual() -> Result<Self, io::Error> {\n        Ok(Self {\n            log: LogFmt::new(),\n            outputs: Outputs::new(),\n        })\n    }\n\n    #[cfg(not(any(target_os = \"linux\", target_os = \"android\")))]\n    pub fn new() -> Result<Self, io::Error> {\n        Self::new_actual()\n    }\n    #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n    pub fn new(\n        _s: &Option<String>,\n        _tp: bool,\n        _name: &str,\n        _bustype: evdev::BusType,\n    ) -> Result<Self, io::Error> {\n        Self::new_actual()\n    }\n    #[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n    pub fn write_raw(&mut self, event: InputEvent) -> Result<(), io::Error> {\n        self.log.write_raw(event);\n        self.outputs.push(format!(\"out-raw:{event:?}\"));\n        Ok(())\n    }\n    pub fn write(&mut self, event: InputEvent) -> Result<(), io::Error> {\n        self.outputs.push(format!(\"out:{event}\"));\n        Ok(())\n    }\n    pub fn output_ready(&self) -> bool {\n        true\n    }\n    pub fn wait_until_ready(&self, _timeout: Option<std::time::Duration>) -> bool {\n        true\n    }\n    pub fn write_key(&mut self, key: OsCode, value: KeyValue) -> Result<(), io::Error> {\n        let key_ev = KeyEvent::new(key, value);\n        let event = {\n            #[cfg(target_os = \"macos\")]\n            {\n                key_ev.try_into().unwrap()\n            }\n            #[cfg(not(target_os = \"macos\"))]\n            {\n                key_ev.into()\n            }\n        };\n        self.write(event)\n    }\n    pub fn write_code(&mut self, code: u32, value: KeyValue) -> Result<(), io::Error> {\n        self.log.write_code(code, value);\n        self.outputs.push(format!(\"out-code:{code};{value:?}\"));\n        Ok(())\n    }\n    pub fn press_key(&mut self, key: OsCode) -> Result<(), io::Error> {\n        self.log.press_key(key);\n        self.write_key(key, KeyValue::Press)\n    }\n    pub fn release_key(&mut self, key: OsCode) -> Result<(), io::Error> {\n        self.log.release_key(key);\n        self.write_key(key, KeyValue::Release)\n    }\n    pub fn release_tracked_output_keys(&mut self, _reason: &str) {}\n    pub fn send_unicode(&mut self, c: char) -> Result<(), io::Error> {\n        self.log.send_unicode(c);\n        self.outputs.push(format!(\"outU:{c}\"));\n        Ok(())\n    }\n    pub fn click_btn(&mut self, btn: Btn) -> Result<(), io::Error> {\n        self.log.click_btn(btn);\n        self.outputs.push(format!(\"out🖰:↓{btn:?}\"));\n        Ok(())\n    }\n    pub fn release_btn(&mut self, btn: Btn) -> Result<(), io::Error> {\n        self.log.release_btn(btn);\n        self.outputs.push(format!(\"out🖰:↑{btn:?}\"));\n        Ok(())\n    }\n    pub fn scroll(&mut self, direction: MWheelDirection, distance: u16) -> Result<(), io::Error> {\n        self.log.scroll(direction, distance);\n        self.outputs\n            .push(format!(\"scroll:{direction:?},{distance:?}\"));\n        Ok(())\n    }\n    pub fn move_mouse(&mut self, mv: CalculatedMouseMove) -> Result<(), io::Error> {\n        let (direction, distance) = (mv.direction, mv.distance);\n        self.log.move_mouse(direction, distance);\n        self.outputs\n            .push(format!(\"out🖰:move {direction:?},{distance:?}\"));\n        Ok(())\n    }\n    pub fn move_mouse_many(&mut self, moves: &[CalculatedMouseMove]) -> Result<(), io::Error> {\n        for mv in moves {\n            let (direction, distance) = (&mv.direction, &mv.distance);\n            self.log.move_mouse(*direction, *distance);\n            self.outputs\n                .push(format!(\"out🖰:move {direction:?},{distance:?}\"));\n        }\n        Ok(())\n    }\n    pub fn set_mouse(&mut self, x: u16, y: u16) -> Result<(), io::Error> {\n        self.log.set_mouse(x, y);\n        log::info!(\"out🖰:@{x},{y}\");\n        Ok(())\n    }\n    pub fn tick(&mut self) {\n        self.outputs.ticks += 1;\n        self.log.ticks += 1;\n    }\n}\n\n#[cfg(not(any(target_os = \"windows\", target_os = \"macos\")))]\n#[derive(Debug, Clone, Copy)]\npub struct InputEvent {\n    pub code: u32,\n    /// Key was released\n    pub up: bool,\n}\n\n#[cfg(not(any(target_os = \"windows\", target_os = \"macos\")))]\nimpl fmt::Display for InputEvent {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        let direction = if self.up { \"↑\" } else { \"↓\" };\n        let key_name = KeyCode::from(OsCode::from(self.code));\n        write!(f, \"{direction}{key_name:?}\")\n    }\n}\n\n#[cfg(not(any(target_os = \"windows\", target_os = \"macos\")))]\nimpl InputEvent {\n    pub fn from_oscode(code: OsCode, val: KeyValue) -> Self {\n        Self {\n            code: code.into(),\n            up: val.into(),\n        }\n    }\n}\n\n#[cfg(not(any(target_os = \"windows\", target_os = \"macos\")))]\nimpl TryFrom<InputEvent> for KeyEvent {\n    type Error = ();\n    fn try_from(item: InputEvent) -> Result<Self, Self::Error> {\n        Ok(Self {\n            code: OsCode::from_u16(item.code as u16).ok_or(())?,\n            value: match item.up {\n                true => KeyValue::Release,\n                false => KeyValue::Press,\n            },\n        })\n    }\n}\n\n#[cfg(not(any(target_os = \"windows\", target_os = \"macos\")))]\nimpl From<KeyEvent> for InputEvent {\n    fn from(item: KeyEvent) -> Self {\n        Self {\n            code: item.code.into(),\n            up: item.value.into(),\n        }\n    }\n}\n\n#[cfg(all(target_os = \"windows\", feature = \"interception_driver\"))]\nimpl From<KeyEvent> for InputEvent {\n    fn from(_item: KeyEvent) -> Self {\n        unimplemented!()\n    }\n}\n"
  },
  {
    "path": "src/oskbd/windows/exthook_os.rs",
    "content": "//! A function listener for keyboard input events replacing Windows keyboard hook API\n\nuse core::fmt;\nuse once_cell::sync::Lazy;\nuse parking_lot::Mutex;\n\nuse winapi::ctypes::*;\nuse winapi::um::winuser::*;\n\nuse crate::oskbd::{KeyEvent, KeyValue};\nuse kanata_keyberon::key_code::KeyCode;\n\nuse kanata_parser::keys::*;\n\npub const LLHOOK_IDLE_TIME_SECS_CLEAR_INPUTS: u64 = 60;\n\ntype HookFn = dyn FnMut(InputEvent) -> bool + Send + Sync + 'static;\n\npub 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)\n\npub struct KeyboardHook {} // reusing hook type for our listener\nimpl KeyboardHook {\n    /// Sets input callback (panics if already registered)\n    pub fn set_input_cb(\n        callback: impl FnMut(InputEvent) -> bool + Send + Sync + 'static,\n    ) -> KeyboardHook {\n        let mut cb_opt = HOOK_CB.lock();\n        assert!(\n            cb_opt.take().is_none(),\n            \"Only 1 external listener is allowed!\"\n        );\n        *cb_opt = Some(Box::new(callback));\n        KeyboardHook {}\n    }\n}\n#[cfg(not(feature = \"passthru_ahk\"))] // unused KeyboardHook will be dropped, breaking our hook, disable it\nimpl Drop for KeyboardHook {\n    fn drop(&mut self) {\n        let mut cb_opt = HOOK_CB.lock();\n        cb_opt.take();\n    }\n}\n\n#[derive(Debug, Clone, Copy)]\npub struct InputEvent {\n    // Key event received by the low level keyboard hook.\n    pub code: u32,\n    pub up: bool, /*Key was released*/\n}\nimpl fmt::Display for InputEvent {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        let direction = if self.up { \"↑\" } else { \"↓\" };\n        let key_name = KeyCode::from(OsCode::from(self.code));\n        write!(f, \"{direction}{key_name:?}\")\n    }\n}\nimpl InputEvent {\n    pub fn from_vk_sc(vk: c_uint, sc: c_uint, up: c_int) -> Self {\n        let code = if vk == (VK_RETURN as u32) {\n            // todo: do a proper check for numpad enter, maybe 0x11c isn't universal\n            match sc {\n                0x11C => u32::from(VK_KPENTER_FAKE),\n                _ => VK_RETURN as u32,\n            }\n        } else {\n            vk\n        };\n        Self {\n            code,\n            up: (up != 0),\n        }\n    }\n    pub fn from_oscode(code: OsCode, val: KeyValue) -> Self {\n        Self {\n            code: code.into(),\n            up: val.into(),\n        }\n    }\n}\nimpl TryFrom<InputEvent> for KeyEvent {\n    type Error = ();\n    fn try_from(item: InputEvent) -> Result<Self, Self::Error> {\n        Ok(Self {\n            code: OsCode::from_u16(item.code as u16).ok_or(())?,\n            value: match item.up {\n                true => KeyValue::Release,\n                false => KeyValue::Press,\n            },\n        })\n    }\n}\nimpl From<KeyEvent> for InputEvent {\n    fn from(item: KeyEvent) -> Self {\n        Self {\n            code: item.code.into(),\n            up: item.value.into(),\n        }\n    }\n}\n"
  },
  {
    "path": "src/oskbd/windows/interception.rs",
    "content": "//! Windows interception-based mechanism for reading/writing input events.\n\nuse std::io;\n\nuse kanata_interception::{Interception, KeyState, MouseFlags, MouseState, ScanCode, Stroke};\n\nuse super::OsCodeWrapper;\nuse crate::kanata::CalculatedMouseMove;\nuse crate::oskbd::KeyValue;\nuse kanata_parser::custom_action::*;\nuse kanata_parser::keys::*;\n\n/// Key event received by the low level keyboard hook.\n#[derive(Debug, Clone, Copy)]\npub struct InputEvent(pub Stroke);\n\nuse std::fmt;\nimpl fmt::Display for InputEvent {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{self:?}\")\n    }\n}\n\nimpl InputEvent {\n    fn from_oscode(code: OsCode, val: KeyValue) -> Self {\n        let mut stroke = Stroke::try_from(OsCodeWrapper(code)).unwrap_or_else(|_| {\n            log::error!(\"Trying to send unmapped oscode '{code:?}', sending esc instead\");\n            Stroke::Keyboard {\n                code: ScanCode::Esc,\n                state: KeyState::empty(),\n                information: 0,\n            }\n        });\n        match &mut stroke {\n            Stroke::Keyboard { state, .. } => {\n                state.set(\n                    match val {\n                        KeyValue::Press | KeyValue::Repeat => KeyState::DOWN,\n                        KeyValue::Release => KeyState::UP,\n                        KeyValue::Tap => panic!(\"invalid value attempted to be sent\"),\n                        KeyValue::WakeUp => panic!(\"invalid value attempted to be sent\"),\n                    },\n                    true,\n                );\n            }\n            _ => panic!(\"expected keyboard stroke\"),\n        }\n        Self(stroke)\n    }\n\n    fn from_mouse_btn(btn: Btn, is_up: bool) -> Self {\n        Self(Stroke::Mouse {\n            state: match (btn, is_up) {\n                (Btn::Left, true) => MouseState::LEFT_BUTTON_UP,\n                (Btn::Left, false) => MouseState::LEFT_BUTTON_DOWN,\n                (Btn::Right, true) => MouseState::RIGHT_BUTTON_UP,\n                (Btn::Right, false) => MouseState::RIGHT_BUTTON_DOWN,\n                (Btn::Mid, true) => MouseState::MIDDLE_BUTTON_UP,\n                (Btn::Mid, false) => MouseState::MIDDLE_BUTTON_DOWN,\n                (Btn::Backward, true) => MouseState::BUTTON_4_UP,\n                (Btn::Backward, false) => MouseState::BUTTON_4_DOWN,\n                (Btn::Forward, true) => MouseState::BUTTON_5_UP,\n                (Btn::Forward, false) => MouseState::BUTTON_5_DOWN,\n            },\n            flags: MouseFlags::empty(),\n            rolling: 0,\n            x: 0,\n            y: 0,\n            information: 0,\n        })\n    }\n\n    fn from_mouse_scroll(direction: MWheelDirection, distance: u16) -> Self {\n        Self(Stroke::Mouse {\n            state: match direction {\n                MWheelDirection::Up | MWheelDirection::Down => MouseState::WHEEL,\n                MWheelDirection::Left | MWheelDirection::Right => MouseState::HWHEEL,\n            },\n            flags: MouseFlags::empty(),\n            rolling: match direction {\n                MWheelDirection::Up | MWheelDirection::Right => {\n                    distance.try_into().expect(\"checked bound of 30000 in cfg\")\n                }\n                MWheelDirection::Down | MWheelDirection::Left => {\n                    -(i16::try_from(distance).expect(\"checked bound of 30000 in cfg\"))\n                }\n            },\n            x: 0,\n            y: 0,\n            information: 0,\n        })\n    }\n\n    fn from_mouse_move(direction: MoveDirection, distance: u16) -> Self {\n        Self(Stroke::Mouse {\n            state: MouseState::MOVE,\n            flags: MouseFlags::empty(),\n            rolling: 0,\n            x: match direction {\n                MoveDirection::Left => -i32::from(distance),\n                MoveDirection::Right => i32::from(distance),\n                _ => 0,\n            },\n            y: match direction {\n                MoveDirection::Up => -i32::from(distance),\n                MoveDirection::Down => i32::from(distance),\n                _ => 0,\n            },\n            information: 0,\n        })\n    }\n\n    fn from_mouse_move_many(moves: &[CalculatedMouseMove]) -> Self {\n        let mut x_acc = 0;\n        let mut y_acc = 0;\n        for mov in moves {\n            let acc_change = match mov.direction {\n                MoveDirection::Up => (0, -i32::from(mov.distance)),\n                MoveDirection::Down => (0, i32::from(mov.distance)),\n                MoveDirection::Left => (-i32::from(mov.distance), 0),\n                MoveDirection::Right => (i32::from(mov.distance), 0),\n            };\n            x_acc += acc_change.0;\n            y_acc += acc_change.1;\n        }\n        Self(Stroke::Mouse {\n            state: MouseState::MOVE,\n            flags: MouseFlags::empty(),\n            rolling: 0,\n            x: x_acc,\n            y: y_acc,\n            information: 0,\n        })\n    }\n\n    fn from_mouse_set(x: u16, y: u16) -> Self {\n        Self(Stroke::Mouse {\n            state: MouseState::MOVE,\n            flags: MouseFlags::MOVE_ABSOLUTE | MouseFlags::VIRTUAL_DESKTOP,\n            rolling: 0,\n            x: i32::from(x),\n            y: i32::from(y),\n            information: 0,\n        })\n    }\n}\n\nthread_local! {\n    static INTRCPTN: Interception = Interception::new().expect(\"interception driver should init: have you completed the interception driver installation?\");\n}\n\n#[cfg(all(not(feature = \"simulated_output\"), not(feature = \"passthru_ahk\")))]\n/// Handle for writing keys to the OS.\npub struct KbdOut {}\n\nfn write_interception(event: InputEvent) {\n    let strokes = [event.0];\n    log::debug!(\"kanata sending {:?} to driver\", strokes[0]);\n    INTRCPTN.with(|ic| {\n        match strokes[0] {\n            // Note regarding device numbers:\n            // Keyboard devices are 1-10 and mouse devices are 11-20. Source:\n            // https://github.com/oblitum/Interception/blob/39eecbbc46a52e0402f783b872ef62b0254a896a/library/interception.h#L34\n            Stroke::Keyboard { .. } => {\n                ic.send(1, &strokes[0..1]);\n            }\n            Stroke::Mouse { .. } => {\n                ic.send(11, &strokes[0..1]);\n            }\n        }\n    })\n}\n\n#[cfg(all(not(feature = \"simulated_output\"), not(feature = \"passthru_ahk\")))]\nimpl KbdOut {\n    pub fn new() -> Result<Self, io::Error> {\n        Ok(Self {})\n    }\n\n    pub fn write(&mut self, event: InputEvent) -> Result<(), io::Error> {\n        write_interception(event);\n        Ok(())\n    }\n\n    pub fn write_code_raw(&mut self, code: u16, value: KeyValue) -> Result<(), io::Error> {\n        super::write_code_raw(code, value)\n    }\n\n    pub fn write_code(&mut self, code: u32, value: KeyValue) -> Result<(), io::Error> {\n        super::write_code(code as u16, value)\n    }\n\n    pub fn write_key(&mut self, key: OsCode, value: KeyValue) -> Result<(), io::Error> {\n        self.write(InputEvent::from_oscode(key, value))\n    }\n\n    pub fn press_key(&mut self, key: OsCode) -> Result<(), io::Error> {\n        self.write_key(key, KeyValue::Press)\n    }\n\n    pub fn release_key(&mut self, key: OsCode) -> Result<(), io::Error> {\n        self.write_key(key, KeyValue::Release)\n    }\n\n    pub fn click_btn(&mut self, btn: Btn) -> Result<(), io::Error> {\n        log::debug!(\"click btn: {:?}\", btn);\n        write_interception(InputEvent::from_mouse_btn(btn, false));\n        Ok(())\n    }\n\n    pub fn release_btn(&mut self, btn: Btn) -> Result<(), io::Error> {\n        log::debug!(\"release btn: {:?}\", btn);\n        let event = InputEvent::from_mouse_btn(btn, true);\n        write_interception(event);\n        Ok(())\n    }\n\n    pub fn scroll(&mut self, direction: MWheelDirection, distance: u16) -> Result<(), io::Error> {\n        log::debug!(\"scroll: {direction:?} {distance:?}\");\n        write_interception(InputEvent::from_mouse_scroll(direction, distance));\n        Ok(())\n    }\n\n    /// Send using VK_PACKET\n    pub fn send_unicode(&mut self, c: char) -> Result<(), io::Error> {\n        super::send_uc(c, false);\n        super::send_uc(c, true);\n        Ok(())\n    }\n\n    pub fn move_mouse(&mut self, mv: CalculatedMouseMove) -> Result<(), io::Error> {\n        write_interception(InputEvent::from_mouse_move(mv.direction, mv.distance));\n        Ok(())\n    }\n\n    pub fn move_mouse_many(&mut self, moves: &[CalculatedMouseMove]) -> Result<(), io::Error> {\n        write_interception(InputEvent::from_mouse_move_many(moves));\n        Ok(())\n    }\n\n    pub fn set_mouse(&mut self, x: u16, y: u16) -> Result<(), io::Error> {\n        write_interception(InputEvent::from_mouse_set(x, y));\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/oskbd/windows/interception_convert.rs",
    "content": "//! `Interception::Stroke` conversion functions\n//!\n//! The keyboard scancode values come from this website:\n//! https://handmade.network/forums/articles/t/2823-keyboard_inputs_-_scancodes%252C_raw_input%252C_text_input%252C_key_names\n//!\n//! Which states that it got these values from:\n//! - http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/scancode.doc (March 16, 2000).\n//! - http://www.computer-engineering.org/ps2keyboard/scancodes1.html\n//! - using MapVirtualKeyEx( VK_*, MAPVK_VK_TO_VSC_EX, 0 ) with the english us keyboard layout\n//! - reading win32 WM_INPUT keyboard messages.\n\n/*\nenum Scancode {\n    sc_escape = 0x01,\n    sc_1 = 0x02,\n    sc_2 = 0x03,\n    sc_3 = 0x04,\n    sc_4 = 0x05,\n    sc_5 = 0x06,\n    sc_6 = 0x07,\n    sc_7 = 0x08,\n    sc_8 = 0x09,\n    sc_9 = 0x0A,\n    sc_0 = 0x0B,\n    sc_minus = 0x0C,\n    sc_equals = 0x0D,\n    sc_backspace = 0x0E,\n    sc_tab = 0x0F,\n    sc_q = 0x10,\n    sc_w = 0x11,\n    sc_e = 0x12,\n    sc_r = 0x13,\n    sc_t = 0x14,\n    sc_y = 0x15,\n    sc_u = 0x16,\n    sc_i = 0x17,\n    sc_o = 0x18,\n    sc_p = 0x19,\n    sc_bracketLeft = 0x1A,\n    sc_bracketRight = 0x1B,\n    sc_enter = 0x1C,\n    sc_controlLeft = 0x1D,\n    sc_a = 0x1E,\n    sc_s =0x1F,\n    sc_d = 0x20,\n    sc_f = 0x21,\n    sc_g = 0x22,\n    sc_h = 0x23,\n    sc_j = 0x24,\n    sc_k = 0x25,\n    sc_l = 0x26,\n    sc_semicolon = 0x27,\n    sc_apostrophe = 0x28,\n    sc_grave = 0x29,\n    sc_shiftLeft = 0x2A,\n    sc_backslash = 0x2B,\n    sc_z = 0x2C,\n    sc_x = 0x2D,\n    sc_c = 0x2E,\n    sc_v = 0x2F,\n    sc_b = 0x30,\n    sc_n = 0x31,\n    sc_m = 0x32,\n    sc_comma = 0x33,\n    sc_preiod = 0x34,\n    sc_slash = 0x35,\n    sc_shiftRight = 0x36,\n    sc_numpad_multiply = 0x37,\n    sc_altLeft = 0x38,\n    sc_space = 0x39,\n    sc_capsLock = 0x3A,\n    sc_f1 = 0x3B,\n    sc_f2 = 0x3C,\n    sc_f3 = 0x3D,\n    sc_f4 = 0x3E,\n    sc_f5 = 0x3F,\n    sc_f6 = 0x40,\n    sc_f7 = 0x41,\n    sc_f8 = 0x42,\n    sc_f9 = 0x43,\n    sc_f10 = 0x44,\n    sc_numLock = 0x45,\n    sc_scrollLock = 0x46,\n    sc_numpad_7 = 0x47,\n    sc_numpad_8 = 0x48,\n    sc_numpad_9 = 0x49,\n    sc_numpad_minus = 0x4A,\n    sc_numpad_4 = 0x4B,\n    sc_numpad_5 = 0x4C,\n    sc_numpad_6 = 0x4D,\n    sc_numpad_plus = 0x4E,\n    sc_numpad_1 = 0x4F,\n    sc_numpad_2 = 0x50,\n    sc_numpad_3 = 0x51,\n    sc_numpad_0 = 0x52,\n    sc_numpad_period = 0x53,\n    sc_alt_printScreen = 0x54, /* Alt + print screen. MapVirtualKeyEx( VK_SNAPSHOT, MAPVK_VK_TO_VSC_EX, 0 ) returns scancode 0x54. */\n    sc_bracketAngle = 0x56, /* Key between the left shift and Z. */\n    sc_f11 = 0x57,\n    sc_f12 = 0x58,\n    sc_oem_1 = 0x5a, /* VK_OEM_WSCTRL */\n    sc_oem_2 = 0x5b, /* VK_OEM_FINISH */\n    sc_oem_3 = 0x5c, /* VK_OEM_JUMP */\n    sc_eraseEOF = 0x5d,\n    sc_oem_4 = 0x5e, /* VK_OEM_BACKTAB */\n    sc_oem_5 = 0x5f, /* VK_OEM_AUTO */\n    sc_zoom = 0x62,\n    sc_help = 0x63,\n    sc_f13 = 0x64,\n    sc_f14 = 0x65,\n    sc_f15 = 0x66,\n    sc_f16 = 0x67,\n    sc_f17 = 0x68,\n    sc_f18 = 0x69,\n    sc_f19 = 0x6a,\n    sc_f20 = 0x6b,\n    sc_f21 = 0x6c,\n    sc_f22 = 0x6d,\n    sc_f23 = 0x6e,\n    sc_oem_6 = 0x6f, /* VK_OEM_PA3 */\n    sc_katakana = 0x70,\n    sc_oem_7 = 0x71, /* VK_OEM_RESET */\n    sc_f24 = 0x76,\n    sc_sbcschar = 0x77,\n    sc_convert = 0x79,\n    sc_nonconvert = 0x7B, /* VK_OEM_PA1 */\n    sc_media_previous = 0xE010,\n    sc_media_next = 0xE019,\n    sc_numpad_enter = 0xE01C,\n    sc_controlRight = 0xE01D,\n    sc_volume_mute = 0xE020,\n    sc_launch_app2 = 0xE021,\n    sc_media_play = 0xE022,\n    sc_media_stop = 0xE024,\n    sc_volume_down = 0xE02E,\n    sc_volume_up = 0xE030,\n    sc_browser_home = 0xE032,\n    sc_numpad_divide = 0xE035,\n    sc_printScreen = 0xE037,\n    /*\n    sc_printScreen:\n    - make: 0xE02A 0xE037\n    - break: 0xE0B7 0xE0AA\n    - MapVirtualKeyEx( VK_SNAPSHOT, MAPVK_VK_TO_VSC_EX, 0 ) returns scancode 0x54;\n    - There is no VK_KEYDOWN with VK_SNAPSHOT.\n    */\n    sc_altRight = 0xE038,\n    sc_cancel = 0xE046, /* CTRL + Pause */\n    sc_home = 0xE047,\n    sc_arrowUp = 0xE048,\n    sc_pageUp = 0xE049,\n    sc_arrowLeft = 0xE04B,\n    sc_arrowRight = 0xE04D,\n    sc_end = 0xE04F,\n    sc_arrowDown = 0xE050,\n    sc_pageDown = 0xE051,\n    sc_insert = 0xE052,\n    sc_delete = 0xE053,\n    sc_metaLeft = 0xE05B,\n    sc_metaRight = 0xE05C,\n    sc_application = 0xE05D,\n    sc_power = 0xE05E,\n    sc_sleep = 0xE05F,\n    sc_wake = 0xE063,\n    sc_browser_search = 0xE065,\n    sc_browser_favorites = 0xE066,\n    sc_browser_refresh = 0xE067,\n    sc_browser_stop = 0xE068,\n    sc_browser_forward = 0xE069,\n    sc_browser_back = 0xE06A,\n    sc_launch_app1 = 0xE06B,\n    sc_launch_email = 0xE06C,\n    sc_launch_media = 0xE06D,\n    sc_pause = 0xE11D45,\n    /*\n    sc_pause:\n    - make: 0xE11D 45 0xE19D C5\n    - make in raw input: 0xE11D 0x45\n    - break: none\n    - No repeat when you hold the key down\n    - There are no break so I don't know how the key down/up is expected to work. Raw input sends \"keydown\" and \"keyup\" messages, and it appears that the keyup message is sent directly after the keydown message (you can't hold the key down) so depending on when GetMessage or PeekMessage will return messages, you may get both a keydown and keyup message \"at the same time\". If you use VK messages most of the time you only get keydown messages, but some times you get keyup messages too.\n    - when pressed at the same time as one or both control keys, generates a 0xE046 (sc_cancel) and the string for that scancode is \"break\".\n    */\n}\n*/\n\nuse kanata_interception::*;\nuse kanata_parser::keys::OsCode;\n\n// We need to wrap OsCode to impl TryFrom<..> for it, because it's in external crate.\npub struct OsCodeWrapper(pub OsCode);\n\nimpl TryFrom<Stroke> for OsCodeWrapper {\n    type Error = ();\n\n    fn try_from(item: Stroke) -> Result<Self, Self::Error> {\n        Ok(match item {\n            Stroke::Keyboard { code, state, .. } => {\n                let code = match (state.contains(KeyState::E0), state.contains(KeyState::E1)) {\n                    (false, false) => crate::oskbd::u16_to_osc(code as u16).ok_or(())?,\n                    (true, _) => crate::oskbd::u16_to_osc((code as u16) | 0xE000).ok_or(())?,\n                    _ => return Err(()),\n                };\n                OsCodeWrapper(code)\n            }\n            _ => return Err(()),\n        })\n    }\n}\n\nimpl TryFrom<OsCodeWrapper> for Stroke {\n    type Error = ();\n\n    fn try_from(item: OsCodeWrapper) -> Result<Self, Self::Error> {\n        let (code, state) = match item.0 {\n            OsCode::KEY_ESC => (ScanCode::Esc, KeyState::empty()),\n            OsCode::KEY_1 => (ScanCode::Num1, KeyState::empty()),\n            OsCode::KEY_2 => (ScanCode::Num2, KeyState::empty()),\n            OsCode::KEY_3 => (ScanCode::Num3, KeyState::empty()),\n            OsCode::KEY_4 => (ScanCode::Num4, KeyState::empty()),\n            OsCode::KEY_5 => (ScanCode::Num5, KeyState::empty()),\n            OsCode::KEY_6 => (ScanCode::Num6, KeyState::empty()),\n            OsCode::KEY_7 => (ScanCode::Num7, KeyState::empty()),\n            OsCode::KEY_8 => (ScanCode::Num8, KeyState::empty()),\n            OsCode::KEY_9 => (ScanCode::Num9, KeyState::empty()),\n            OsCode::KEY_0 => (ScanCode::Num0, KeyState::empty()),\n            OsCode::KEY_MINUS => (ScanCode::Minus, KeyState::empty()),\n            OsCode::KEY_EQUAL => (ScanCode::Equals, KeyState::empty()),\n            OsCode::KEY_BACKSPACE => (ScanCode::Backspace, KeyState::empty()),\n            OsCode::KEY_TAB => (ScanCode::Tab, KeyState::empty()),\n            OsCode::KEY_Q => (ScanCode::Q, KeyState::empty()),\n            OsCode::KEY_W => (ScanCode::W, KeyState::empty()),\n            OsCode::KEY_E => (ScanCode::E, KeyState::empty()),\n            OsCode::KEY_R => (ScanCode::R, KeyState::empty()),\n            OsCode::KEY_T => (ScanCode::T, KeyState::empty()),\n            OsCode::KEY_Y => (ScanCode::Y, KeyState::empty()),\n            OsCode::KEY_U => (ScanCode::U, KeyState::empty()),\n            OsCode::KEY_I => (ScanCode::I, KeyState::empty()),\n            OsCode::KEY_O => (ScanCode::O, KeyState::empty()),\n            OsCode::KEY_P => (ScanCode::P, KeyState::empty()),\n            OsCode::KEY_LEFTBRACE => (ScanCode::LeftBracket, KeyState::empty()),\n            OsCode::KEY_RIGHTBRACE => (ScanCode::RightBracket, KeyState::empty()),\n            OsCode::KEY_ENTER => (ScanCode::Enter, KeyState::empty()),\n            OsCode::KEY_LEFTCTRL => (ScanCode::LeftControl, KeyState::empty()),\n            OsCode::KEY_A => (ScanCode::A, KeyState::empty()),\n            OsCode::KEY_S => (ScanCode::S, KeyState::empty()),\n            OsCode::KEY_D => (ScanCode::D, KeyState::empty()),\n            OsCode::KEY_F => (ScanCode::F, KeyState::empty()),\n            OsCode::KEY_G => (ScanCode::G, KeyState::empty()),\n            OsCode::KEY_H => (ScanCode::H, KeyState::empty()),\n            OsCode::KEY_J => (ScanCode::J, KeyState::empty()),\n            OsCode::KEY_K => (ScanCode::K, KeyState::empty()),\n            OsCode::KEY_L => (ScanCode::L, KeyState::empty()),\n            OsCode::KEY_SEMICOLON => (ScanCode::SemiColon, KeyState::empty()),\n            OsCode::KEY_APOSTROPHE => (ScanCode::Apostrophe, KeyState::empty()),\n            OsCode::KEY_GRAVE => (ScanCode::Grave, KeyState::empty()),\n            OsCode::KEY_LEFTSHIFT => (ScanCode::LeftShift, KeyState::empty()),\n            OsCode::KEY_BACKSLASH => (ScanCode::BackSlash, KeyState::empty()),\n            OsCode::KEY_Z => (ScanCode::Z, KeyState::empty()),\n            OsCode::KEY_X => (ScanCode::X, KeyState::empty()),\n            OsCode::KEY_C => (ScanCode::C, KeyState::empty()),\n            OsCode::KEY_V => (ScanCode::V, KeyState::empty()),\n            OsCode::KEY_B => (ScanCode::B, KeyState::empty()),\n            OsCode::KEY_N => (ScanCode::N, KeyState::empty()),\n            OsCode::KEY_M => (ScanCode::M, KeyState::empty()),\n            OsCode::KEY_COMMA => (ScanCode::Comma, KeyState::empty()),\n            OsCode::KEY_DOT => (ScanCode::Period, KeyState::empty()),\n            OsCode::KEY_SLASH => (ScanCode::Slash, KeyState::empty()),\n            OsCode::KEY_RIGHTSHIFT => (ScanCode::RightShift, KeyState::empty()),\n            OsCode::KEY_KPASTERISK => (ScanCode::NumpadMultiply, KeyState::empty()),\n            OsCode::KEY_LEFTALT => (ScanCode::LeftAlt, KeyState::empty()),\n            OsCode::KEY_SPACE => (ScanCode::Space, KeyState::empty()),\n            OsCode::KEY_CAPSLOCK => (ScanCode::CapsLock, KeyState::empty()),\n            OsCode::KEY_F1 => (ScanCode::F1, KeyState::empty()),\n            OsCode::KEY_F2 => (ScanCode::F2, KeyState::empty()),\n            OsCode::KEY_F3 => (ScanCode::F3, KeyState::empty()),\n            OsCode::KEY_F4 => (ScanCode::F4, KeyState::empty()),\n            OsCode::KEY_F5 => (ScanCode::F5, KeyState::empty()),\n            OsCode::KEY_F6 => (ScanCode::F6, KeyState::empty()),\n            OsCode::KEY_F7 => (ScanCode::F7, KeyState::empty()),\n            OsCode::KEY_F8 => (ScanCode::F8, KeyState::empty()),\n            OsCode::KEY_F9 => (ScanCode::F9, KeyState::empty()),\n            OsCode::KEY_F10 => (ScanCode::F10, KeyState::empty()),\n            OsCode::KEY_NUMLOCK => (ScanCode::NumLock, KeyState::empty()),\n            OsCode::KEY_SCROLLLOCK => (ScanCode::ScrollLock, KeyState::empty()),\n            OsCode::KEY_KP7 => (ScanCode::Numpad7, KeyState::empty()),\n            OsCode::KEY_KP8 => (ScanCode::Numpad8, KeyState::empty()),\n            OsCode::KEY_KP9 => (ScanCode::Numpad9, KeyState::empty()),\n            OsCode::KEY_KPMINUS => (ScanCode::NumpadMinus, KeyState::empty()),\n            OsCode::KEY_KP4 => (ScanCode::Numpad4, KeyState::empty()),\n            OsCode::KEY_KP5 => (ScanCode::Numpad5, KeyState::empty()),\n            OsCode::KEY_KP6 => (ScanCode::Numpad6, KeyState::empty()),\n            OsCode::KEY_KPPLUS => (ScanCode::NumpadPlus, KeyState::empty()),\n            OsCode::KEY_KP1 => (ScanCode::Numpad1, KeyState::empty()),\n            OsCode::KEY_KP2 => (ScanCode::Numpad2, KeyState::empty()),\n            OsCode::KEY_KP3 => (ScanCode::Numpad3, KeyState::empty()),\n            OsCode::KEY_KP0 => (ScanCode::Numpad0, KeyState::empty()),\n            OsCode::KEY_KPDOT => (ScanCode::NumpadPeriod, KeyState::empty()),\n            OsCode::KEY_102ND => (ScanCode::Int1, KeyState::empty()), /* Key between the left shift and Z. */\n            OsCode::KEY_F11 => (ScanCode::F11, KeyState::empty()),\n            OsCode::KEY_F12 => (ScanCode::F12, KeyState::empty()),\n            OsCode::KEY_F13 => (ScanCode::F13, KeyState::empty()),\n            OsCode::KEY_F14 => (ScanCode::F14, KeyState::empty()),\n            OsCode::KEY_F15 => (ScanCode::F15, KeyState::empty()),\n            OsCode::KEY_F16 => (ScanCode::F16, KeyState::empty()),\n            OsCode::KEY_F17 => (ScanCode::F17, KeyState::empty()),\n            OsCode::KEY_F18 => (ScanCode::F18, KeyState::empty()),\n            OsCode::KEY_F19 => (ScanCode::F19, KeyState::empty()),\n            OsCode::KEY_F20 => (ScanCode::F20, KeyState::empty()),\n            OsCode::KEY_F21 => (ScanCode::F21, KeyState::empty()),\n            OsCode::KEY_F22 => (ScanCode::F22, KeyState::empty()),\n            OsCode::KEY_F23 => (ScanCode::F23, KeyState::empty()),\n            OsCode::KEY_F24 => (ScanCode::F24, KeyState::empty()),\n            OsCode::KEY_HANGEUL => (ScanCode::Katakana, KeyState::empty()),\n            // Note: the OEM keys below don't seem to correspond to the same VK OEM\n            // mappings as the LLHOOK codes.\n            // ScanCode::Oem1 = 0x5A, /* VK_OEM_WSCTRL */\n            // ScanCode::Oem2 = 0x5B, /* VK_OEM_FINISH */\n            // ScanCode::Oem3 = 0x5C, /* VK_OEM_JUMP */\n            // ScanCode::Oem4 = 0x5E, /* VK_OEM_BACKTAB */\n            // ScanCode::Oem5 = 0x5F, /* VK_OEM_AUTO */\n            // ScanCode::Oem6 = 0x6F, /* VK_OEM_PA3 */\n            // ScanCode::Oem7 = 0x71, /* VK_OEM_RESET */\n            // ScanCode::EraseEOF = 0x5D,\n            // ScanCode::Zoom => 0x62,\n            // ScanCode::Help => 0x63,\n            // ScanCode::AltPrintScreen = 0x55, /* Alt + print screen. */\n            // ScanCode::SBCSChar = 0x77,\n            OsCode::KEY_HENKAN => (ScanCode::Convert, KeyState::empty()),\n            OsCode::KEY_MUHENKAN => (ScanCode::NonConvert, KeyState::empty()),\n            OsCode::KEY_PREVIOUSSONG => (ScanCode::Q, KeyState::E0),\n            OsCode::KEY_NEXTSONG => (ScanCode::P, KeyState::E0), // 0x19\n            OsCode::KEY_KPENTER => (ScanCode::Enter, KeyState::E0), // 0x1C\n            OsCode::KEY_RIGHTCTRL => (ScanCode::LeftControl, KeyState::E0), // 0x1D\n            OsCode::KEY_MUTE => (ScanCode::D, KeyState::E0),     // 0x20\n            OsCode::KEY_PLAYPAUSE => (ScanCode::G, KeyState::E0), // 0x22 // sc_media_play\n            OsCode::KEY_VOLUMEDOWN => (ScanCode::C, KeyState::E0), // 0x2E // sc_volume_down\n            OsCode::KEY_VOLUMEUP => (ScanCode::B, KeyState::E0), // 0x30   // sc_volume_up\n            OsCode::KEY_KPSLASH => (ScanCode::Slash, KeyState::E0), // 0x35 // sc_numpad_divide\n            OsCode::KEY_PRINT => (ScanCode::NumpadMultiply, KeyState::E0), // 0x37   // sc_printScreen\n            OsCode::KEY_RIGHTALT => (ScanCode::LeftAlt, KeyState::E0),     // 0x38 // sc_altRight\n            OsCode::KEY_HOME => (ScanCode::Numpad7, KeyState::E0),         // 0x47     // sc_home\n            OsCode::KEY_UP => (ScanCode::Numpad8, KeyState::E0), // 0x48       // sc_arrowUp\n            OsCode::KEY_PAGEUP => (ScanCode::Numpad9, KeyState::E0), // 0x49   // sc_pageUp\n            OsCode::KEY_LEFT => (ScanCode::Numpad4, KeyState::E0), // 0x4B     // sc_arrowLeft\n            OsCode::KEY_RIGHT => (ScanCode::Numpad6, KeyState::E0), // 0x4D    // sc_arrowRight\n            OsCode::KEY_END => (ScanCode::Numpad1, KeyState::E0), // 0x4F      // sc_end\n            OsCode::KEY_DOWN => (ScanCode::Numpad2, KeyState::E0), // 0x50     // sc_arrowDown\n            OsCode::KEY_PAGEDOWN => (ScanCode::Numpad3, KeyState::E0), // 0x51 // sc_pageDown\n            OsCode::KEY_INSERT => (ScanCode::Numpad0, KeyState::E0), // 0x52   // sc_insert\n            OsCode::KEY_DELETE => (ScanCode::NumpadPeriod, KeyState::E0), // 0x53   // sc_delete\n            OsCode::KEY_LEFTMETA => (ScanCode::Oem2, KeyState::E0), // 0x5B // sc_metaLeft\n            OsCode::KEY_RIGHTMETA => (ScanCode::Oem3, KeyState::E0), // 0x5C // sc_metaRight\n            OsCode::KEY_FORWARD => (ScanCode::F18, KeyState::E0), // 0x69 // sc_browser_forward\n            OsCode::KEY_BACK => (ScanCode::F19, KeyState::E0),   // 0x6A    // sc_browser_back\n            OsCode::KEY_COMPOSE => (ScanCode::EraseEOF, KeyState::E0),\n            // OsCode::KEY_TODO => 0x24 as ScanCode, // sc_media_stop\n            // OsCode::KEY_TODO => 0x32 as ScanCode, // sc_browser_home\n            // OsCode::KEY_TODO => 0x46 as ScanCode, // sc_cancel\n            // OsCode::KEY_TODO => 0x5D as ScanCode, // sc_application\n            // OsCode::KEY_TODO => 0x5E as ScanCode, // sc_power\n            // OsCode::KEY_TODO => 0x5F as ScanCode, // sc_sleep\n            // OsCode::KEY_TODO => 0x63 as ScanCode, // sc_wake\n            // OsCode::KEY_TODO => 0x65 as ScanCode, // sc_browser_search\n            // OsCode::KEY_TODO => 0x66 as ScanCode, // sc_browser_favorites\n            // OsCode::KEY_TODO => 0x67 as ScanCode, // sc_browser_refresh\n            // OsCode::KEY_TODO => 0x68 as ScanCode, // sc_browser_stop\n            // 0x6B => OsCode::KEY_TODO, // sc_launch_app1\n            // 0x6C => OsCode::KEY_TODO, // sc_launch_email\n            // 0x6D => OsCode::KEY_TODO, // sc_launch_media\n            _ => return Err(()),\n        };\n        Ok(Stroke::Keyboard {\n            code,\n            state,\n            information: 0,\n        })\n    }\n}\n"
  },
  {
    "path": "src/oskbd/windows/llhook/mouse.rs",
    "content": "/// Mouse functionality for windows hooks.\n///\n/// Code taken and adapted from:\n/// https://github.com/myood/willhook-rs/tree/f4ccbc897504834d0c01fa449a2660b9989290e5\n/// License: MIT\nuse super::*;\n\nuse winapi::shared::windef::HHOOK;\nuse winapi::um::winuser::UnhookWindowsHookEx;\n\ntype MHookFn = dyn FnMut(MouseEventType) -> bool;\nthread_local! {\n    /// Stores the hook callback for the current thread.\n    static MHOOK: Cell<Option<Box<MHookFn>>> = Cell::default();\n}\n\n/// Wrapper for the low-level keyboard hook API.\n/// Automatically unregisters the hook when dropped.\npub struct MouseHook {\n    handle: HHOOK,\n}\n\nimpl MouseHook {\n    pub fn set_input_cb(callback: impl FnMut(MouseEventType) -> bool + 'static) -> MouseHook {\n        MHOOK.with(|state| {\n            assert!(\n                state.take().is_none(),\n                \"Only one mouse hook can be registered per thread.\"\n            );\n\n            state.set(Some(Box::new(callback)));\n\n            MouseHook {\n                handle: unsafe {\n                    SetWindowsHookExW(WH_MOUSE_LL, Some(mhook_proc), ptr::null_mut(), 0)\n                        .as_mut()\n                        .expect(\"install low-level keyboard hook successfully\")\n                },\n            }\n        })\n    }\n}\n\nimpl Drop for MouseHook {\n    fn drop(&mut self) {\n        unsafe { UnhookWindowsHookEx(self.handle) };\n        MHOOK.with(|state| state.take());\n    }\n}\n\nunsafe extern \"system\" fn mhook_proc(code: c_int, wparam: WPARAM, lparam: LPARAM) -> LRESULT {\n    let mouse_lparam = unsafe { &*(lparam as *const MSLLHOOKSTRUCT) };\n    let is_injected = mouse_lparam.flags & (LLMHF_INJECTED | LLMHF_LOWER_IL_INJECTED) != 0;\n    log::trace!(\"{code} {wparam} {is_injected}\");\n\n    // Regarding is_injected check:\n    // `SendInput()` internally calls the hook function.\n    // Filter out injected events to prevent infinite recursion.\n    if is_injected {\n        return unsafe { CallNextHookEx(ptr::null_mut(), code, wparam, lparam) };\n    }\n\n    let mut handled = false;\n    let mouse_event = unsafe { classify_mouse_event(wparam, mouse_lparam) };\n    MHOOK.with(|state| {\n        // The unwrap cannot fail, because we have initialized [`HOOK`] with a\n        // valid closure before registering the hook (this function).\n        // To access the closure we move it out of the cell and put it back\n        // after it returned. For this to work we need to prevent recursion by\n        // dropping injected events. Otherwise we would try to take the closure\n        // twice and the call would fail the second time.\n        let mut hook = state.take().expect(\"no recurse\");\n        handled = hook(mouse_event);\n        state.set(Some(hook));\n    });\n\n    if handled {\n        1\n    } else {\n        unsafe { CallNextHookEx(ptr::null_mut(), code, wparam, lparam) }\n    }\n}\n\n/// The type of the mouse event with it's specific data\n#[derive(Copy, Clone, Ord, PartialOrd, Hash, Eq, PartialEq, Debug)]\npub enum MouseEventType {\n    /// Button on the mouse was pressed\n    Press(MousePressEvent),\n    /// Mouse was moved\n    Move(MouseMoveEvent),\n    /// Wheel on the mouse was, well, spinning.\n    Wheel(MouseWheelEvent),\n    /// Received unrecognized mouse event type, the code is stored for reference.\n    Other(usize),\n}\n\nuse MouseEventType::*;\n\n/// Holds information which button was pressed or released\n#[derive(Copy, Clone, Ord, PartialOrd, Hash, Eq, PartialEq, Debug)]\npub struct MousePressEvent {\n    pub pressed: MouseButtonPress,\n    pub button: MouseButton,\n}\n\n/// Holds information which mouse wheel triggered the event\n#[derive(Copy, Clone, Ord, PartialOrd, Hash, Eq, PartialEq, Debug)]\npub enum MouseWheel {\n    Horizontal,\n    Vertical,\n    Unknown(usize),\n}\n\n/// Indicates the direction of the mouse wheel spin\n#[derive(Copy, Clone, Ord, PartialOrd, Hash, Eq, PartialEq, Debug)]\npub enum MouseWheelDirection {\n    Forward,\n    Backward,\n    Unknown(u32),\n}\n\n/// The mouse wheel event with information which wheel triggered an event and the direction of the spin\n#[derive(Copy, Clone, Ord, PartialOrd, Hash, Eq, PartialEq, Debug)]\npub struct MouseWheelEvent {\n    pub wheel: MouseWheel,\n    pub direction: Option<MouseWheelDirection>,\n}\n\n/// Point in per-monitor aware coordinates, see\n/// [MSDN](https://learn.microsoft.com/en-us/windows/desktop/api/shellscalingapi/ne-shellscalingapi-process_dpi_awareness)\n#[derive(Copy, Clone, Ord, PartialOrd, Hash, Eq, PartialEq, Debug)]\npub struct Point {\n    pub x: i32,\n    pub y: i32,\n}\n\n/// Holds the new cursor position after mouse move\n#[derive(Copy, Clone, Ord, PartialOrd, Hash, Eq, PartialEq, Debug)]\npub struct MouseMoveEvent {\n    pub point: Option<Point>,\n}\n\n/// Indicates if button was pressed or released\n#[derive(Copy, Clone, Ord, PartialOrd, Hash, Eq, PartialEq, Debug)]\npub enum MouseButtonPress {\n    Down,\n    Up,\n    Other(usize),\n}\n\n/// Indicates if mouse button press is single or double click\n#[derive(Copy, Clone, Ord, PartialOrd, Hash, Eq, PartialEq, Debug)]\npub enum MouseClick {\n    SingleClick,\n    DoubleClick,\n    Other(u32),\n}\n\n/// Identifies which mouse button triggered an event\n#[derive(Copy, Clone, Ord, PartialOrd, Hash, Eq, PartialEq, Debug)]\npub enum MouseButton {\n    Left(MouseClick),\n    Right(MouseClick),\n    Middle(MouseClick),\n    /// XBUTTON1\n    X1(MouseClick),\n    /// XBUTTON2\n    X2(MouseClick),\n    /// Either XBUTTON1 or XBUTTON2\n    UnkownX(MouseClick),\n    /// Unexpected mouse button. Raw code stored for reference, see MSDN documentation about low-level hooks.\n    Other(usize),\n}\n\n/// # Safety\n/// Pointers must be valid from winapi.\npub unsafe fn classify_mouse_event(\n    wm_mouse_param: WPARAM,\n    ms_ll_hook_struct: *const MSLLHOOKSTRUCT,\n) -> MouseEventType {\n    match wm_mouse_param as u32 {\n        // Mouse press\n        WM_LBUTTONDOWN | WM_LBUTTONUP | WM_LBUTTONDBLCLK => {\n            Press(unsafe { MousePressEvent::new(wm_mouse_param, ms_ll_hook_struct) })\n        }\n        WM_RBUTTONDOWN | WM_RBUTTONUP | WM_RBUTTONDBLCLK => {\n            Press(unsafe { MousePressEvent::new(wm_mouse_param, ms_ll_hook_struct) })\n        }\n        WM_MBUTTONDOWN | WM_MBUTTONUP | WM_MBUTTONDBLCLK => {\n            Press(unsafe { MousePressEvent::new(wm_mouse_param, ms_ll_hook_struct) })\n        }\n        WM_XBUTTONDOWN | WM_XBUTTONUP | WM_XBUTTONDBLCLK => {\n            Press(unsafe { MousePressEvent::new(wm_mouse_param, ms_ll_hook_struct) })\n        }\n\n        // Mouse move\n        WM_MOUSEMOVE => Move(unsafe { MouseMoveEvent::new(ms_ll_hook_struct) }),\n\n        // Wheel move\n        WM_MOUSEWHEEL | WM_MOUSEHWHEEL => {\n            Wheel(unsafe { MouseWheelEvent::new(wm_mouse_param, ms_ll_hook_struct) })\n        }\n\n        _ => Other(wm_mouse_param),\n    }\n}\n\nimpl MousePressEvent {\n    /// # Safety\n    /// Pointers must be valid from winapi.\n    pub unsafe fn new(\n        wm_mouse_param: WPARAM,\n        ms_ll_hook_struct: *const MSLLHOOKSTRUCT,\n    ) -> MousePressEvent {\n        MousePressEvent {\n            pressed: MouseButtonPress::from(wm_mouse_param),\n            button: unsafe { MouseButton::from(wm_mouse_param, ms_ll_hook_struct) },\n        }\n    }\n}\n\nimpl From<POINT> for Point {\n    fn from(value: POINT) -> Self {\n        Point {\n            x: value.x,\n            y: value.y,\n        }\n    }\n}\n\nimpl MouseWheel {\n    pub fn new(wm_mouse_param: WPARAM) -> MouseWheel {\n        use MouseWheel::*;\n        match wm_mouse_param.try_into() {\n            Ok(param_u32) => match param_u32 {\n                WM_MOUSEWHEEL => Vertical,\n                WM_MOUSEHWHEEL => Horizontal,\n                _ => Unknown(wm_mouse_param),\n            },\n            _ => Unknown(wm_mouse_param),\n        }\n    }\n}\n\nimpl MouseWheelEvent {\n    /// # Safety\n    /// Pointers must be valid from winapi.\n    pub unsafe fn new(\n        wm_mouse_param: WPARAM,\n        ms_ll_hook_struct: *const MSLLHOOKSTRUCT,\n    ) -> MouseWheelEvent {\n        MouseWheelEvent {\n            wheel: MouseWheel::new(wm_mouse_param),\n            direction: unsafe { MouseWheelDirection::optionally_from(ms_ll_hook_struct) },\n        }\n    }\n}\n\nimpl MouseWheelDirection {\n    /// # Safety\n    /// Pointers must be valid from winapi.\n    pub unsafe fn optionally_from(\n        ms_ll_hook_struct: *const MSLLHOOKSTRUCT,\n    ) -> Option<MouseWheelDirection> {\n        if ms_ll_hook_struct.is_null() {\n            None\n        } else {\n            Some(unsafe { MouseWheelDirection::new(&*ms_ll_hook_struct) })\n        }\n    }\n\n    fn new(ms_ll_hook_struct: &MSLLHOOKSTRUCT) -> MouseWheelDirection {\n        use MouseWheelDirection::*;\n        let delta = GET_WHEEL_DELTA_WPARAM(ms_ll_hook_struct.mouseData as WPARAM);\n        match delta {\n            _ if delta > 0 => Forward,\n            _ if delta < 0 => Backward,\n            _ => Unknown(ms_ll_hook_struct.mouseData),\n        }\n    }\n}\n\nimpl MouseMoveEvent {\n    /// # Safety\n    /// Pointers must be valid from winapi.\n    pub unsafe fn new(ms_ll_hook_struct: *const MSLLHOOKSTRUCT) -> MouseMoveEvent {\n        if ms_ll_hook_struct.is_null() {\n            MouseMoveEvent { point: None }\n        } else {\n            let msll = unsafe { &*ms_ll_hook_struct };\n            let pt = msll.pt;\n            MouseMoveEvent {\n                point: Some(pt.into()),\n            }\n        }\n    }\n}\n\nimpl From<WPARAM> for MouseButtonPress {\n    fn from(value: WPARAM) -> Self {\n        use MouseButtonPress::*;\n        match value.try_into() {\n            Ok(uv) => match uv {\n                WM_LBUTTONDOWN | WM_RBUTTONDOWN | WM_MBUTTONDOWN | WM_XBUTTONDOWN => Down,\n                WM_RBUTTONUP | WM_LBUTTONUP | WM_MBUTTONUP | WM_XBUTTONUP => Up,\n                _ => Other(value),\n            },\n            Err(_) => Other(value),\n        }\n    }\n}\n\nimpl From<WPARAM> for MouseClick {\n    fn from(value: WPARAM) -> Self {\n        use MouseClick::*;\n        match value.try_into() {\n            Ok(uv) => match uv {\n                WM_LBUTTONDOWN | WM_RBUTTONDOWN | WM_MBUTTONDOWN | WM_XBUTTONDOWN => SingleClick,\n                WM_LBUTTONUP | WM_RBUTTONUP | WM_MBUTTONUP | WM_XBUTTONUP => SingleClick,\n                WM_LBUTTONDBLCLK | WM_RBUTTONDBLCLK | WM_MBUTTONDBLCLK | WM_XBUTTONDBLCLK => {\n                    DoubleClick\n                }\n                _ => Other(value as u32),\n            },\n            Err(_) => Other(value as u32),\n        }\n    }\n}\n\nimpl MouseButton {\n    /// # Safety\n    /// Pointers must be valid from winapi.\n    pub unsafe fn from(wm_mouse_param: WPARAM, ms_ll_hook_struct: *const MSLLHOOKSTRUCT) -> Self {\n        let click = MouseClick::from(wm_mouse_param);\n\n        use MouseButton::*;\n        match wm_mouse_param.try_into() {\n            Ok(param) => {\n                match param {\n                    WM_LBUTTONDOWN | WM_LBUTTONUP | WM_LBUTTONDBLCLK => Left(click),\n                    WM_RBUTTONDOWN | WM_RBUTTONUP | WM_RBUTTONDBLCLK => Right(click),\n                    WM_MBUTTONDOWN | WM_MBUTTONUP | WM_MBUTTONDBLCLK => Middle(click),\n                    WM_XBUTTONDOWN | WM_XBUTTONUP | WM_XBUTTONDBLCLK => {\n                        if ms_ll_hook_struct.is_null() {\n                            UnkownX(click)\n                        } else {\n                            Self::into_extra(click, unsafe { &*ms_ll_hook_struct })\n                        }\n                    }\n                    // Value out of expected set\n                    _ => Other(wm_mouse_param),\n                }\n            }\n            // Conversion error\n            Err(_) => Other(wm_mouse_param),\n        }\n    }\n\n    fn into_extra(click: MouseClick, ms_ll_hook_struct: &MSLLHOOKSTRUCT) -> Self {\n        use MouseButton::*;\n        match GET_XBUTTON_WPARAM(ms_ll_hook_struct.mouseData.try_into().expect(\"\")) {\n            XBUTTON1 => X1(click),\n            XBUTTON2 => X2(click),\n            _ => UnkownX(click),\n        }\n    }\n}\n\nimpl TryFrom<MouseEventType> for KeyEvent {\n    type Error = ();\n    fn try_from(mevt: MouseEventType) -> Result<Self, ()> {\n        use OsCode::*;\n        match mevt {\n            Move(..) | Other(..) => Err(()),\n            Press(MousePressEvent { pressed, button }) => {\n                let value = match pressed {\n                    MouseButtonPress::Up => KeyValue::Release,\n                    MouseButtonPress::Down => KeyValue::Press,\n                    MouseButtonPress::Other(..) => return Err(()),\n                };\n                let code = match button {\n                    // TODO:\n                    // The inner type is MouseClick.\n                    // - DoubleClick might need to actually send two events.. but Kanata doesn't\n                    //   have a good way to signal multiple events into the event queue for a\n                    //   single oskbd source event.\n                    // - Other might be better off as Err(())\n                    // This applies to all variants.\n                    MouseButton::Left(..) => BTN_LEFT,\n                    MouseButton::Right(..) => BTN_RIGHT,\n                    MouseButton::Middle(..) => BTN_MIDDLE,\n                    MouseButton::X1(..) => BTN_SIDE,\n                    MouseButton::X2(..) => BTN_EXTRA,\n                    MouseButton::UnkownX(..) | MouseButton::Other(..) => return Err(()),\n                };\n                Ok(KeyEvent { code, value })\n            }\n            Wheel(MouseWheelEvent { wheel, direction }) => {\n                use MouseWheel::*;\n                use MouseWheelDirection::*;\n                let Some(direction) = direction else {\n                    return Err(());\n                };\n                let code = match (wheel, direction) {\n                    (Vertical, Forward) => MouseWheelUp,\n                    (Vertical, Backward) => MouseWheelDown,\n                    (Horizontal, Forward) => MouseWheelRight,\n                    (Horizontal, Backward) => MouseWheelLeft,\n                    (MouseWheel::Unknown(..), _) | (_, MouseWheelDirection::Unknown(..)) => {\n                        return Err(());\n                    }\n                };\n                Ok(KeyEvent {\n                    code,\n                    value: KeyValue::Tap,\n                })\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/oskbd/windows/llhook.rs",
    "content": "//! Safe abstraction over the low-level windows keyboard hook API.\n\n// This file is taken from kbremap with minor modifications.\n// https://github.com/timokroeger/kbremap\n\n#![cfg_attr(\n    feature = \"simulated_output\",\n    allow(dead_code, unused_imports, unused_variables, unused_mut)\n)]\n\nmod mouse;\npub use mouse::*;\n\nuse core::fmt;\nuse std::cell::Cell;\nuse std::io;\nuse std::{mem, ptr};\n\nuse winapi::ctypes::*;\nuse winapi::shared::minwindef::*;\nuse winapi::shared::windef::*;\nuse winapi::um::winuser::*;\n\nuse crate::kanata::CalculatedMouseMove;\nuse crate::oskbd::{KeyEvent, KeyValue};\nuse kanata_keyberon::key_code::KeyCode;\nuse kanata_parser::custom_action::*;\nuse kanata_parser::keys::*;\n\npub const LLHOOK_IDLE_TIME_SECS_CLEAR_INPUTS: u64 = 60;\n\ntype HookFn = dyn FnMut(InputEvent) -> bool;\n\nthread_local! {\n    /// Stores the hook callback for the current thread.\n    static HOOK: Cell<Option<Box<HookFn>>> = Cell::default();\n}\n\n/// Wrapper for the low-level keyboard hook API.\n/// Automatically unregisters the hook when dropped.\npub struct KeyboardHook {\n    handle: HHOOK,\n}\n\nimpl KeyboardHook {\n    /// Sets the low-level keyboard hook for this thread.\n    ///\n    /// Panics when a hook is already registered from the same thread.\n    #[must_use = \"The hook will immediatelly be unregistered and not work.\"]\n    pub fn set_input_cb(callback: impl FnMut(InputEvent) -> bool + 'static) -> KeyboardHook {\n        HOOK.with(|state| {\n            assert!(\n                state.take().is_none(),\n                \"Only one keyboard hook can be registered per thread.\"\n            );\n\n            state.set(Some(Box::new(callback)));\n\n            KeyboardHook {\n                handle: unsafe {\n                    SetWindowsHookExW(WH_KEYBOARD_LL, Some(hook_proc), ptr::null_mut(), 0)\n                        .as_mut()\n                        .expect(\"install low-level keyboard hook successfully\")\n                },\n            }\n        })\n    }\n}\n\nimpl Drop for KeyboardHook {\n    fn drop(&mut self) {\n        unsafe { UnhookWindowsHookEx(self.handle) };\n        HOOK.with(|state| state.take());\n    }\n}\n\n/// Key event received by the low level keyboard hook.\n#[derive(Debug, Clone, Copy)]\npub struct InputEvent {\n    pub code: u32,\n\n    /// Key was released\n    pub up: bool,\n}\n\nimpl fmt::Display for InputEvent {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        let direction = if self.up { \"↑\" } else { \"↓\" };\n        let key_name = KeyCode::from(OsCode::from(self.code));\n        write!(f, \"{direction}{key_name:?}\")\n    }\n}\n\nimpl InputEvent {\n    #[rustfmt::skip]\n    fn from_hook_lparam(lparam: &KBDLLHOOKSTRUCT) -> Self {\n        let code = if lparam.vkCode == (VK_RETURN as u32) {\n            match lparam.flags & 0x1 {\n                0 => VK_RETURN as u32,\n                _ => u32::from(VK_KPENTER_FAKE),\n            }\n        } else {\n            #[cfg(not(feature = \"win_llhook_read_scancodes\"))]\n            {\n                lparam.vkCode\n            }\n            #[cfg(feature = \"win_llhook_read_scancodes\")]\n            {\n                let extended = if lparam.flags & 0x1 == 0x1 {\n                    0xE000\n                } else {\n                    0\n                };\n                let sc_with_ext = (lparam.scanCode as u16) | extended;\n                log::debug!(\"converting {sc_with_ext}\");\n                crate::oskbd::u16_to_osc(sc_with_ext)\n                    .map(Into::into)\n                    .unwrap_or(lparam.vkCode)\n            }\n        };\n        Self {\n            code,\n            up: lparam.flags & LLKHF_UP != 0,\n        }\n    }\n\n    pub fn from_oscode(code: OsCode, val: KeyValue) -> Self {\n        Self {\n            code: code.into(),\n            up: val.into(),\n        }\n    }\n}\n\nimpl TryFrom<InputEvent> for KeyEvent {\n    type Error = ();\n    fn try_from(item: InputEvent) -> Result<Self, Self::Error> {\n        Ok(Self {\n            code: OsCode::from_u16(item.code as u16).ok_or(())?,\n            value: match item.up {\n                true => KeyValue::Release,\n                false => KeyValue::Press,\n            },\n        })\n    }\n}\n\nimpl From<KeyEvent> for InputEvent {\n    fn from(item: KeyEvent) -> Self {\n        Self {\n            code: item.code.into(),\n            up: item.value.into(),\n        }\n    }\n}\n\n/// The actual WinAPI compatible callback.\n/// code: determines how to process the message\n/// source: https://learn.microsoft.com/windows/win32/winmsg/lowlevelkeyboardproc\n///   <0 : must pass the message to CallNextHookEx without further processing\n///    and should return the value returned by CallNextHookEx\n///   HC_ACTION (=0) : wParam and lParam parameters contain information about the message\n///\n/// wparam: ID keyboard message\n/// source: https://learn.microsoft.com/windows/win32/winmsg/lowlevelkeyboardproc\n///   WM_KEY(DOWN|UP) Posted to kb-focused window when a nonsystem key is pressed\n///   WM_SYSKEYDOWN¦UP Posted to kb-focused window when a F10 (activate menu bar)\n///     or ⎇X⃣ or posted to active window if no win has kb focus (check context code in lParam)\n///\n/// lparam: pointer to a KBDLLHOOKSTRUCT struct\n/// source: https://learn.microsoft.com/windows/win32/api/winuser/ns-winuser-kbdllhookstruct\n///   vkCode     :DWORD key's virtual code (1–254)\n///   scanCode   :DWORD key's hardware scan code\n///   flags      :DWORD flags (extended-key, event-injected, transition-state), context code\n///     Bits (2-3 6 reserved)                        Description\n///     7 KF_UP       >> 8 LLKHF_UP                  transition state: 0=key↓  1=key↑\n///                                                           (being pressed)  (being released)\n///     5 KF_ALTDOWN  >> 8 LLKHF_ALTDOWN             context code    : 1=alt↓  0=alt↑\n///     4 0x10             LLKHF_INJECTED            event was injected: 1=yes, 0=no\n///     1 0x02             LLKHF_LOWER_IL_INJECTED   injected by proc with lower integrity level\n//                                                   1=yes 0=no (bit 4 will also set)\n///     0 KF_EXTENDED >> 8 LLKHF_EXTENDED            extended key (Fn, numpad): 1=yes, 0=no\n///   time       :DWORD time stamp = GetMessageTime\n///   dwExtraInfo:ULONG_PTR Additional info\nunsafe extern \"system\" fn hook_proc(code: c_int, wparam: WPARAM, lparam: LPARAM) -> LRESULT {\n    unsafe {\n        let hook_lparam = &*(lparam as *const KBDLLHOOKSTRUCT);\n        let is_injected = hook_lparam.flags & LLKHF_INJECTED != 0;\n        log::trace!(\"{code} {}{wparam} {is_injected}\", {\n            match wparam as u32 {\n                WM_KEYDOWN => \"↓\",\n                WM_KEYUP => \"↑\",\n                WM_SYSKEYDOWN => \"sys↓\",\n                WM_SYSKEYUP => \"sys↑\",\n                _ => \"?\",\n            }\n        });\n\n        // Regarding code check:\n        // If code is non-zero (technically <0, but 0 is the only valid value anyway),\n        // then it must be forwarded.\n        // Source: https://learn.microsoft.com/windows/win32/winmsg/lowlevelkeyboardproc\n        //\n        // Regarding in_injected check:\n        // `SendInput()` internally calls the hook function.\n        // Filter out injected events to prevent infinite recursion.\n        if code != HC_ACTION || is_injected {\n            return CallNextHookEx(ptr::null_mut(), code, wparam, lparam);\n        }\n\n        let key_event = InputEvent::from_hook_lparam(hook_lparam);\n\n        let mut handled = false;\n        HOOK.with(|state| {\n            // The unwrap cannot fail, because we have initialized [`HOOK`] with a\n            // valid closure before registering the hook (this function).\n            // To access the closure we move it out of the cell and put it back\n            // after it returned. For this to work we need to prevent recursion by\n            // dropping injected events. Otherwise we would try to take the closure\n            // twice and the call would fail the second time.\n            let mut hook = state.take().expect(\"no recurse\");\n            handled = hook(key_event);\n            state.set(Some(hook));\n        });\n\n        if handled {\n            1\n        } else {\n            CallNextHookEx(ptr::null_mut(), code, wparam, lparam)\n        }\n    }\n}\n\n#[cfg(all(not(feature = \"simulated_output\"), not(feature = \"passthru_ahk\")))]\n/// Handle for writing keys to the OS.\npub struct KbdOut {}\n\n#[cfg(all(not(feature = \"simulated_output\"), not(feature = \"passthru_ahk\")))]\nimpl KbdOut {\n    pub fn new() -> Result<Self, io::Error> {\n        Ok(Self {})\n    }\n\n    pub fn write(&mut self, event: InputEvent) -> Result<(), io::Error> {\n        super::send_key_sendinput(event.code as u16, event.up);\n        Ok(())\n    }\n\n    pub fn write_key(&mut self, key: OsCode, value: KeyValue) -> Result<(), io::Error> {\n        let event = InputEvent::from_oscode(key, value);\n        self.write(event)\n    }\n\n    pub fn write_code(&mut self, code: u32, value: KeyValue) -> Result<(), io::Error> {\n        super::write_code(code as u16, value)\n    }\n\n    pub fn write_code_raw(&mut self, code: u16, value: KeyValue) -> Result<(), io::Error> {\n        super::write_code_raw(code, value)\n    }\n\n    pub fn press_key(&mut self, key: OsCode) -> Result<(), io::Error> {\n        self.write_key(key, KeyValue::Press)\n    }\n\n    pub fn release_key(&mut self, key: OsCode) -> Result<(), io::Error> {\n        self.write_key(key, KeyValue::Release)\n    }\n\n    /// Send using VK_PACKET\n    pub fn send_unicode(&mut self, c: char) -> Result<(), io::Error> {\n        super::send_uc(c, false);\n        super::send_uc(c, true);\n        Ok(())\n    }\n\n    pub fn click_btn(&mut self, btn: Btn) -> Result<(), io::Error> {\n        log::debug!(\"click btn: {:?}\", btn);\n        match btn {\n            Btn::Left => send_btn(MOUSEEVENTF_LEFTDOWN),\n            Btn::Right => send_btn(MOUSEEVENTF_RIGHTDOWN),\n            Btn::Mid => send_btn(MOUSEEVENTF_MIDDLEDOWN),\n            Btn::Backward => send_xbtn(MOUSEEVENTF_XDOWN, XBUTTON1),\n            Btn::Forward => send_xbtn(MOUSEEVENTF_XDOWN, XBUTTON2),\n        };\n        Ok(())\n    }\n\n    pub fn release_btn(&mut self, btn: Btn) -> Result<(), io::Error> {\n        log::debug!(\"release btn: {:?}\", btn);\n        match btn {\n            Btn::Left => send_btn(MOUSEEVENTF_LEFTUP),\n            Btn::Right => send_btn(MOUSEEVENTF_RIGHTUP),\n            Btn::Mid => send_btn(MOUSEEVENTF_MIDDLEUP),\n            Btn::Backward => send_xbtn(MOUSEEVENTF_XUP, XBUTTON1),\n            Btn::Forward => send_xbtn(MOUSEEVENTF_XUP, XBUTTON2),\n        };\n        Ok(())\n    }\n\n    pub fn scroll(&mut self, direction: MWheelDirection, distance: u16) -> Result<(), io::Error> {\n        log::debug!(\"scroll: {direction:?} {distance:?}\");\n        match direction {\n            MWheelDirection::Up | MWheelDirection::Down => scroll(direction, distance),\n            MWheelDirection::Left | MWheelDirection::Right => hscroll(direction, distance),\n        }\n        Ok(())\n    }\n\n    pub fn move_mouse(&mut self, mv: CalculatedMouseMove) -> Result<(), io::Error> {\n        move_mouse(mv.direction, mv.distance);\n        Ok(())\n    }\n\n    pub fn move_mouse_many(&mut self, moves: &[CalculatedMouseMove]) -> Result<(), io::Error> {\n        move_mouse_many(moves);\n        Ok(())\n    }\n\n    pub fn set_mouse(&mut self, x: u16, y: u16) -> Result<(), io::Error> {\n        log::info!(\"setting mouse {x} {y}\");\n        set_mouse_xy(i32::from(x), i32::from(y));\n        Ok(())\n    }\n}\n\nfn send_btn(flag: u32) {\n    unsafe {\n        let mut inputs: [INPUT; 1] = mem::zeroed();\n        inputs[0].type_ = INPUT_MOUSE;\n\n        // set button\n        let mut m_input: MOUSEINPUT = mem::zeroed();\n        m_input.dwFlags |= flag;\n\n        *inputs[0].u.mi_mut() = m_input;\n        SendInput(1, inputs.as_mut_ptr(), mem::size_of::<INPUT>() as _);\n    }\n}\n\nfn send_xbtn(flag: u32, xbtn: u16) {\n    unsafe {\n        let mut inputs: [INPUT; 1] = mem::zeroed();\n        inputs[0].type_ = INPUT_MOUSE;\n\n        // set button\n        let mut m_input: MOUSEINPUT = mem::zeroed();\n        m_input.dwFlags |= flag;\n        m_input.mouseData = xbtn.into();\n\n        *inputs[0].u.mi_mut() = m_input;\n        SendInput(1, inputs.as_mut_ptr(), mem::size_of::<INPUT>() as _);\n    }\n}\n\nfn scroll(direction: MWheelDirection, distance: u16) {\n    unsafe {\n        let mut inputs: [INPUT; 1] = mem::zeroed();\n        inputs[0].type_ = INPUT_MOUSE;\n\n        let mut m_input: MOUSEINPUT = mem::zeroed();\n        m_input.dwFlags |= MOUSEEVENTF_WHEEL;\n        m_input.mouseData = match direction {\n            MWheelDirection::Up => distance.into(),\n            MWheelDirection::Down => (-i32::from(distance)) as u32,\n            _ => unreachable!(), // unreachable based on pub fn scroll\n        };\n\n        *inputs[0].u.mi_mut() = m_input;\n        SendInput(1, inputs.as_mut_ptr(), mem::size_of::<INPUT>() as _);\n    }\n}\n\nfn hscroll(direction: MWheelDirection, distance: u16) {\n    unsafe {\n        let mut inputs: [INPUT; 1] = mem::zeroed();\n        inputs[0].type_ = INPUT_MOUSE;\n\n        let mut m_input: MOUSEINPUT = mem::zeroed();\n        m_input.dwFlags |= MOUSEEVENTF_HWHEEL;\n        m_input.mouseData = match direction {\n            MWheelDirection::Right => distance.into(),\n            MWheelDirection::Left => (-i32::from(distance)) as u32,\n            _ => unreachable!(), // unreachable based on pub fn scroll\n        };\n\n        *inputs[0].u.mi_mut() = m_input;\n        SendInput(1, inputs.as_mut_ptr(), mem::size_of::<INPUT>() as _);\n    }\n}\n\nfn move_mouse(direction: MoveDirection, distance: u16) {\n    log::debug!(\"move mouse: {direction:?} {distance:?}\");\n    match direction {\n        MoveDirection::Up => move_mouse_xy(0, -i32::from(distance)),\n        MoveDirection::Down => move_mouse_xy(0, i32::from(distance)),\n        MoveDirection::Left => move_mouse_xy(-i32::from(distance), 0),\n        MoveDirection::Right => move_mouse_xy(i32::from(distance), 0),\n    }\n}\n\nfn move_mouse_many(moves: &[CalculatedMouseMove]) {\n    let mut x_acc = 0;\n    let mut y_acc = 0;\n    for mov in moves {\n        let acc_change = match mov.direction {\n            MoveDirection::Up => (0, -i32::from(mov.distance)),\n            MoveDirection::Down => (0, i32::from(mov.distance)),\n            MoveDirection::Left => (-i32::from(mov.distance), 0),\n            MoveDirection::Right => (i32::from(mov.distance), 0),\n        };\n        x_acc += acc_change.0;\n        y_acc += acc_change.1;\n    }\n    move_mouse_xy(x_acc, y_acc);\n}\n\nfn move_mouse_xy(x: i32, y: i32) {\n    mouse_event(MOUSEEVENTF_MOVE, 0, x, y);\n}\n\nfn set_mouse_xy(x: i32, y: i32) {\n    mouse_event(\n        MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE | MOUSEEVENTF_VIRTUALDESK,\n        0,\n        x,\n        y,\n    );\n}\n\n// Taken from Enigo: https://github.com/enigo-rs/enigo\nfn mouse_event(flags: u32, data: u32, dx: i32, dy: i32) {\n    let mut input = INPUT {\n        type_: INPUT_MOUSE,\n        u: unsafe {\n            mem::transmute::<winapi::um::winuser::MOUSEINPUT, winapi::um::winuser::INPUT_u>(\n                MOUSEINPUT {\n                    dx,\n                    dy,\n                    mouseData: data,\n                    dwFlags: flags,\n                    time: 0,\n                    dwExtraInfo: 0,\n                },\n            )\n        },\n    };\n    unsafe { SendInput(1, &mut input as LPINPUT, mem::size_of::<INPUT>() as c_int) };\n}\n"
  },
  {
    "path": "src/oskbd/windows/mod.rs",
    "content": "#![cfg_attr(\n    feature = \"simulated_output\",\n    allow(dead_code, unused_imports, unused_variables, unused_mut)\n)]\n\n#[cfg(not(feature = \"simulated_input\"))]\nuse std::mem;\n\n#[cfg(not(feature = \"simulated_input\"))]\nuse winapi::um::winuser::*;\n\n#[cfg(not(feature = \"simulated_input\"))]\nuse encode_unicode::CharExt;\n\n#[cfg(not(feature = \"simulated_input\"))]\nuse crate::oskbd::KeyValue;\n\n#[cfg(all(not(feature = \"interception_driver\"), not(feature = \"simulated_input\")))]\nmod llhook; // contains KbdOut any(not(feature = \"simulated_output\"), not(feature = \"passthru_ahk\"))\n#[cfg(all(not(feature = \"interception_driver\"), not(feature = \"simulated_input\")))]\npub use llhook::*;\n\n#[cfg(all(not(feature = \"interception_driver\"), feature = \"simulated_input\"))]\nmod exthook_os;\n#[cfg(all(not(feature = \"interception_driver\"), feature = \"simulated_input\"))]\npub use exthook_os::*;\n\nmod scancode_to_usvk;\n#[allow(unused)]\npub use scancode_to_usvk::*;\n\n#[cfg(feature = \"interception_driver\")]\nmod interception;\n#[cfg(feature = \"interception_driver\")]\nmod interception_convert;\n#[cfg(feature = \"interception_driver\")]\npub use self::interception::*;\n#[cfg(feature = \"interception_driver\")]\npub use interception_convert::*;\n\n#[cfg(not(feature = \"simulated_input\"))]\nfn send_uc(c: char, up: bool) {\n    log::debug!(\"sending unicode {c}\");\n    let mut inputs: [INPUT; 2] = unsafe { mem::zeroed() };\n\n    let n_inputs = inputs\n        .iter_mut()\n        .zip(c.to_utf16())\n        .map(|(input, c)| {\n            let mut kb_input: KEYBDINPUT = unsafe { mem::zeroed() };\n            kb_input.wScan = c;\n            kb_input.dwFlags |= KEYEVENTF_UNICODE;\n            if up {\n                kb_input.dwFlags |= KEYEVENTF_KEYUP;\n            }\n            input.type_ = INPUT_KEYBOARD;\n            unsafe { *input.u.ki_mut() = kb_input };\n        })\n        .count();\n\n    unsafe {\n        SendInput(\n            n_inputs as _,\n            inputs.as_mut_ptr(),\n            mem::size_of::<INPUT>() as _,\n        );\n    }\n}\n\n#[cfg(not(feature = \"simulated_output\"))]\nfn write_code_raw(code: u16, value: KeyValue) -> Result<(), std::io::Error> {\n    let is_key_up = match value {\n        KeyValue::Press | KeyValue::Repeat => false,\n        KeyValue::Release => true,\n        KeyValue::Tap => panic!(\"invalid value attempted to be sent\"),\n        KeyValue::WakeUp => panic!(\"invalid value attempted to be sent\"),\n    };\n    unsafe {\n        let mut kb_input: KEYBDINPUT = mem::zeroed();\n        if is_key_up {\n            kb_input.dwFlags |= KEYEVENTF_KEYUP;\n        }\n        kb_input.wVk = code;\n        let mut inputs: [INPUT; 1] = mem::zeroed();\n        inputs[0].type_ = INPUT_KEYBOARD;\n        *inputs[0].u.ki_mut() = kb_input;\n        SendInput(1, inputs.as_mut_ptr(), mem::size_of::<INPUT>() as _);\n    }\n    Ok(())\n}\n\n#[cfg(not(feature = \"simulated_input\"))]\nfn write_code(code: u16, value: KeyValue) -> Result<(), std::io::Error> {\n    send_key_sendinput(\n        code,\n        match value {\n            KeyValue::Press | KeyValue::Repeat => false,\n            KeyValue::Release => true,\n            KeyValue::Tap => panic!(\"invalid value attempted to be sent\"),\n            KeyValue::WakeUp => panic!(\"invalid value attempted to be sent\"),\n        },\n    );\n    Ok(())\n}\n\n#[cfg(not(feature = \"simulated_input\"))]\nfn send_key_sendinput(code: u16, is_key_up: bool) {\n    unsafe {\n        let mut kb_input: KEYBDINPUT = mem::zeroed();\n        if is_key_up {\n            kb_input.dwFlags |= KEYEVENTF_KEYUP;\n        }\n\n        #[cfg(feature = \"win_sendinput_send_scancodes\")]\n        {\n            /*\n            Credit to @VictorLemosR from GitHub for the code here 🙂:\n\n            All the keys that are extended are on font 1, inside the table on column 'Scan 1 Make' and start with '0xE0'.\n            To obtain the scancode, one could just print 'kb_input.wScan' from the function below.\n            Font 1: https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#scan-codes\n            To obtain a virtual key code, one could just print 'code' from the function below for a key or see font 2\n            Font 2: https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes\n\n            For example, left arrow and 4 from numpad. For the scancode, they have the same low byte,\n            but not the same high byte, which is 0xE0. LeftArrow = 0xE04B, keypad 4 = 0x004B. For the virtual code,\n            left arrow is 0x25 and 4 from numpad is 0x64.\n            There is a windows function called 'MapVirtualKeyA' that can be used to convert a virtual key code to a scancode.\n\n            IMPORTANT: these numbers are the VK numbers, e.g. VK_Q, VK_LSHIFT\n            */\n            const EXTENDED_KEYS: [u8; 48] = if cfg!(not(feature = \"win_llhook_read_scancodes\")) {\n                // BUG NOTES:\n                // The difference between the two variants is the handling of VK_SNAPSHOT.\n                //\n                // It seems the winapi MapVirtualKeyA does an different mapping when passing in\n                // VK_SNAPSHOT than the rest of the code used to expect. It gets mapped to 88, or\n                // 0x54, and this should be non-extended, i.e. it remains 0x54 and not 0xE037. With\n                // winiov2/gui/interception variants, MapVirtualKeyA is not used by default and\n                // instead it's a custom mapping, which maps it to 0xE037, and this value seems to\n                // have the correct effect.\n                //\n                // According to readings, MapVirtualKeyA does not do extended flag properly, so\n                // since the VK_SNAPSHOT mapping was believed to be an extended key, the 0xE000 was\n                // added and 0x54 became 0xE054 which doesn't do anything. Avoiding adding of the\n                // 0xE000 fixes the issue.\n                [\n                    0xb1, 0xb0, 0xa3, 0xad, 0x8c, 0xb3, 0xb2, 0xae, 0xaf, 0xac, 0x6f, 0x13, 0xa5,\n                    0x24, 0x26, 0x21, 0x25, 0x27, 0x23, 0x28, 0x22, 0x2d, 0x2e, 0x5b, 0x5c, 0x5d,\n                    0x5f, 0xaa, 0xa8, 0xa9, 0xa7, 0xa6, 0xac, 0xb4, 0x13,\n                    /*\n                    The 0x13 here is repeated. Why? Maybe it will generate better comparison code 😅.\n                    Probably should test+measure when making changes like this (but I didn't).\n                    The theory is that comparing on a 16-byte boundary seems good.\n                    Below taken from Rust source:\n\n                    const fn memchr_aligned(x: u8, text: &[u8]) -> Option<usize> {\n                        // Scan for a single byte value by reading two `usize` words at a time.\n                        //\n                        // Split `text` in three parts\n                        // - unaligned initial part, before the first word aligned address in text\n                        // - body, scan by 2 words at a time\n                        // - the last remaining part, < 2 word size\n                    */\n                    0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,\n                ]\n            } else {\n                [\n                    0xb1, 0xb0, 0xa3, 0xad, 0x8c, 0xb3, 0xb2, 0xae, 0xaf, 0xac, 0x6f, 0x2c, 0xa5,\n                    0x24, 0x26, 0x21, 0x25, 0x27, 0x23, 0x28, 0x22, 0x2d, 0x2e, 0x5b, 0x5c, 0x5d,\n                    0x5f, 0xaa, 0xa8, 0xa9, 0xa7, 0xa6, 0xac, 0xb4, 0x13, 0x13, 0x13, 0x13, 0x13,\n                    0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,\n                ]\n            };\n\n            let code_u32 = code as u32;\n            kb_input.dwFlags |= KEYEVENTF_SCANCODE;\n            #[cfg(not(feature = \"win_llhook_read_scancodes\"))]\n            {\n                // This MapVirtualKeyA is needed to translate back to the proper scancode\n                // associated with with the virtual key.\n                // E.g. take this example:\n                //\n                // - KEY_A is the code here\n                // - OS layout is AZERTY\n                // - No remapping, e.g. active layer is:\n                //   - (deflayermap (active-layer) a a)\n                //\n                // This means kanata received a key press at US-layout position Q. However,\n                // translating KEY_A via osc_to_u16 will result in the scancode assocated with\n                // US-layout position A, but we want to output the position Q scancode. It is\n                // MapVirtualKeyA that does the correct translation for this based on the user's OS\n                // layout.\n                kb_input.wScan = MapVirtualKeyA(code_u32, 0) as u16;\n                if kb_input.wScan == 0 {\n                    // The known scenario for this is VK_KPENTER_FAKE which isn't a real VK so\n                    // MapVirtualKeyA is expected to return 0. This fake VK is used to\n                    // distinguish the key within kanata since in Windows there is no output VK for\n                    // the enter key at the numpad.\n                    //\n                    // The osc_to_u16 function knows the scancode for keypad enter though, and it\n                    // isn't known to change based on language layout so this seems fine to do.\n                    kb_input.wScan = osc_to_u16(code.into()).unwrap_or(0);\n                }\n            }\n            #[cfg(feature = \"win_llhook_read_scancodes\")]\n            {\n                kb_input.wScan =\n                    osc_to_u16(code.into()).unwrap_or_else(|| MapVirtualKeyA(code_u32, 0) as u16);\n            }\n\n            if kb_input.wScan == 0 {\n                kb_input.dwFlags &= !KEYEVENTF_SCANCODE;\n                kb_input.wVk = code;\n            }\n\n            let is_extended_key: bool = code < 0xff && EXTENDED_KEYS.contains(&(code as u8));\n            if is_extended_key {\n                kb_input.wScan |= 0xE0 << 8;\n                kb_input.dwFlags |= KEYEVENTF_EXTENDEDKEY;\n            }\n        }\n        #[cfg(not(feature = \"win_sendinput_send_scancodes\"))]\n        {\n            use kanata_parser::keys::*;\n            kb_input.wVk = match code {\n                VK_KPENTER_FAKE => VK_RETURN as u16,\n                _ => code,\n            };\n        }\n\n        let mut inputs: [INPUT; 1] = mem::zeroed();\n        inputs[0].type_ = INPUT_KEYBOARD;\n        *inputs[0].u.ki_mut() = kb_input;\n        SendInput(1, inputs.as_mut_ptr(), mem::size_of::<INPUT>() as _);\n    }\n}\n"
  },
  {
    "path": "src/oskbd/windows/scancode_to_usvk.rs",
    "content": "use kanata_parser::keys::OsCode;\n\n#[rustfmt::skip]\n#[allow(unused)]\npub fn u16_to_osc(input: u16) -> Option<OsCode> {\n    Some(if input < 0xE000 {\n        match input {\n            0x01 => OsCode::KEY_ESC,\n            0x02 => OsCode::KEY_1,\n            0x03 => OsCode::KEY_2,\n            0x04 => OsCode::KEY_3,\n            0x05 => OsCode::KEY_4,\n            0x06 => OsCode::KEY_5,\n            0x07 => OsCode::KEY_6,\n            0x08 => OsCode::KEY_7,\n            0x09 => OsCode::KEY_8,\n            0x0A => OsCode::KEY_9,\n            0x0B => OsCode::KEY_0,\n            0x0C => OsCode::KEY_MINUS,\n            0x0D => OsCode::KEY_EQUAL,\n            0x0E => OsCode::KEY_BACKSPACE,\n            0x0F => OsCode::KEY_TAB,\n            0x10 => OsCode::KEY_Q,\n            0x11 => OsCode::KEY_W,\n            0x12 => OsCode::KEY_E,\n            0x13 => OsCode::KEY_R,\n            0x14 => OsCode::KEY_T,\n            0x15 => OsCode::KEY_Y,\n            0x16 => OsCode::KEY_U,\n            0x17 => OsCode::KEY_I,\n            0x18 => OsCode::KEY_O,\n            0x19 => OsCode::KEY_P,\n            0x1A => OsCode::KEY_LEFTBRACE,\n            0x1B => OsCode::KEY_RIGHTBRACE,\n            0x1C => OsCode::KEY_ENTER,\n            0x1D => OsCode::KEY_LEFTCTRL,\n            0x1E => OsCode::KEY_A,\n            0x1F => OsCode::KEY_S,\n            0x20 => OsCode::KEY_D,\n            0x21 => OsCode::KEY_F,\n            0x22 => OsCode::KEY_G,\n            0x23 => OsCode::KEY_H,\n            0x24 => OsCode::KEY_J,\n            0x25 => OsCode::KEY_K,\n            0x26 => OsCode::KEY_L,\n            0x27 => OsCode::KEY_SEMICOLON,\n            0x28 => OsCode::KEY_APOSTROPHE,\n            0x29 => OsCode::KEY_GRAVE,\n            0x2A => OsCode::KEY_LEFTSHIFT,\n            0x2B => OsCode::KEY_BACKSLASH,\n            0x2C => OsCode::KEY_Z,\n            0x2D => OsCode::KEY_X,\n            0x2E => OsCode::KEY_C,\n            0x2F => OsCode::KEY_V,\n            0x30 => OsCode::KEY_B,\n            0x31 => OsCode::KEY_N,\n            0x32 => OsCode::KEY_M,\n            0x33 => OsCode::KEY_COMMA,\n            0x34 => OsCode::KEY_DOT,\n            0x35 => OsCode::KEY_SLASH,\n            0x36 => OsCode::KEY_RIGHTSHIFT,\n            0x37 => OsCode::KEY_KPASTERISK,\n            0x38 => OsCode::KEY_LEFTALT,\n            0x39 => OsCode::KEY_SPACE,\n            0x3A => OsCode::KEY_CAPSLOCK,\n            0x3B => OsCode::KEY_F1,\n            0x3C => OsCode::KEY_F2,\n            0x3D => OsCode::KEY_F3,\n            0x3E => OsCode::KEY_F4,\n            0x3F => OsCode::KEY_F5,\n            0x40 => OsCode::KEY_F6,\n            0x41 => OsCode::KEY_F7,\n            0x42 => OsCode::KEY_F8,\n            0x43 => OsCode::KEY_F9,\n            0x44 => OsCode::KEY_F10,\n            0x45 => OsCode::KEY_NUMLOCK,\n            0x46 => OsCode::KEY_SCROLLLOCK,\n            0x47 => OsCode::KEY_KP7,\n            0x48 => OsCode::KEY_KP8,\n            0x49 => OsCode::KEY_KP9,\n            0x4A => OsCode::KEY_KPMINUS,\n            0x4B => OsCode::KEY_KP4,\n            0x4C => OsCode::KEY_KP5,\n            0x4D => OsCode::KEY_KP6,\n            0x4E => OsCode::KEY_KPPLUS,\n            0x4F => OsCode::KEY_KP1,\n            0x50 => OsCode::KEY_KP2,\n            0x51 => OsCode::KEY_KP3,\n            0x52 => OsCode::KEY_KP0,\n            0x53 => OsCode::KEY_KPDOT,\n            0x56 => OsCode::KEY_102ND, /* Key between the left shift and Z. */\n            0x57 => OsCode::KEY_F11,\n            0x58 => OsCode::KEY_F12,\n            0x64 => OsCode::KEY_F13,\n            0x65 => OsCode::KEY_F14,\n            0x66 => OsCode::KEY_F15,\n            0x67 => OsCode::KEY_F16,\n            0x68 => OsCode::KEY_F17,\n            0x69 => OsCode::KEY_F18,\n            0x6A => OsCode::KEY_F19,\n            0x6B => OsCode::KEY_F20,\n            0x6C => OsCode::KEY_F21,\n            0x6D => OsCode::KEY_F22,\n            0x6E => OsCode::KEY_F23,\n            0x73 => OsCode::KEY_RO,\n            0x76 => OsCode::KEY_F24,\n            0x70 => OsCode::KEY_KATAKANA,\n            0x79 => OsCode::KEY_HENKAN,   // Convert\n            0x7B => OsCode::KEY_MUHENKAN, // Noconvert\n            0x7D => OsCode::KEY_YEN,      // Yen / Pipe\n            // Note: the OEM keys below don't seem to correspond to the same VK OEM\n            // mappings as the LLHOOK codes.\n            // ScanCode::Oem1 = 0x5A, /* VK_OEM_WSCTRL */\n            // ScanCode::Oem2 = 0x5B, /* VK_OEM_FINISH */\n            // ScanCode::Oem3 = 0x5C, /* VK_OEM_JUMP */\n            // ScanCode::Oem4 = 0x5E, /* VK_OEM_BACKTAB */\n            // ScanCode::Oem5 = 0x5F, /* VK_OEM_AUTO */\n            // ScanCode::Oem6 = 0x6F, /* VK_OEM_PA3 */\n            // ScanCode::Oem7 = 0x71, /* VK_OEM_RESET */\n            // ScanCode::EraseEOF = 0x5D,\n            // ScanCode::Zoom => 0x62,\n            // ScanCode::Help => 0x63,\n            // ScanCode::AltPrintScreen = 0x55, /* Alt + print screen. */\n            // ScanCode::SBCSChar = 0x77,\n            _ => return None,\n        }\n    } else {\n        match input & 0xFF {\n            0x10 => OsCode::KEY_PREVIOUSSONG,\n            0x19 => OsCode::KEY_NEXTSONG,\n            0x1C => OsCode::KEY_KPENTER,\n            0x1D => OsCode::KEY_RIGHTCTRL,\n            0x20 => OsCode::KEY_MUTE,\n            0x22 => OsCode::KEY_PLAYPAUSE, // sc_media_play\n            // 0x24 => OsCode::KEY_TODO, // sc_media_stop\n            0x2E => OsCode::KEY_VOLUMEDOWN, // sc_volume_down\n            0x30 => OsCode::KEY_VOLUMEUP,   // sc_volume_up\n            // 0x32 => OsCode::KEY_TODO, // sc_browser_home\n            0x35 => OsCode::KEY_KPSLASH,  // sc_numpad_divide\n            0x37 => OsCode::KEY_PRINT,    // sc_printScreen\n            0x38 => OsCode::KEY_RIGHTALT, // sc_altRight\n            // 0x46 => OsCode::KEY_TODO, // sc_cancel\n            0x47 => OsCode::KEY_HOME,      // sc_home\n            0x48 => OsCode::KEY_UP,        // sc_arrowUp\n            0x49 => OsCode::KEY_PAGEUP,    // sc_pageUp\n            0x4B => OsCode::KEY_LEFT,      // sc_arrowLeft\n            0x4D => OsCode::KEY_RIGHT,     // sc_arrowRight\n            0x4F => OsCode::KEY_END,       // sc_end\n            0x50 => OsCode::KEY_DOWN,      // sc_arrowDown\n            0x51 => OsCode::KEY_PAGEDOWN,  // sc_pageDown\n            0x52 => OsCode::KEY_INSERT,    // sc_insert\n            0x53 => OsCode::KEY_DELETE,    // sc_delete\n            0x5B => OsCode::KEY_LEFTMETA,  // sc_metaLeft\n            0x5C => OsCode::KEY_RIGHTMETA, // sc_metaRight\n            0x5D => OsCode::KEY_COMPOSE,   // sc_application / compose\n            // 0x5E => OsCode::KEY_TODO, // sc_power\n            // 0x5F => OsCode::KEY_TODO, // sc_sleep\n            // 0x63 => OsCode::KEY_TODO, // sc_wake\n            // 0x65 => OsCode::KEY_TODO, // sc_browser_search\n            // 0x66 => OsCode::KEY_TODO, // sc_browser_favorites\n            // 0x67 => OsCode::KEY_TODO, // sc_browser_refresh\n            // 0x68 => OsCode::KEY_TODO, // sc_browser_stop\n            0x69 => OsCode::KEY_FORWARD, // sc_browser_forward\n            0x6A => OsCode::KEY_BACK,    // sc_browser_back\n            // 0x6B => OsCode::KEY_TODO, // sc_launch_app1\n            // 0x6C => OsCode::KEY_TODO, // sc_launch_email\n            // 0x6D => OsCode::KEY_TODO, // sc_launch_media\n            _ => return None,\n        }\n    })\n}\n\n#[allow(unused)]\npub(crate) fn osc_to_u16(osc: OsCode) -> Option<u16> {\n    Some(match osc {\n        OsCode::KEY_ESC => 0x01,\n        OsCode::KEY_1 => 0x02,\n        OsCode::KEY_2 => 0x03,\n        OsCode::KEY_3 => 0x04,\n        OsCode::KEY_4 => 0x05,\n        OsCode::KEY_5 => 0x06,\n        OsCode::KEY_6 => 0x07,\n        OsCode::KEY_7 => 0x08,\n        OsCode::KEY_8 => 0x09,\n        OsCode::KEY_9 => 0x0A,\n        OsCode::KEY_0 => 0x0B,\n        OsCode::KEY_MINUS => 0x0C,\n        OsCode::KEY_EQUAL => 0x0D,\n        OsCode::KEY_BACKSPACE => 0x0E,\n        OsCode::KEY_TAB => 0x0F,\n        OsCode::KEY_Q => 0x10,\n        OsCode::KEY_W => 0x11,\n        OsCode::KEY_E => 0x12,\n        OsCode::KEY_R => 0x13,\n        OsCode::KEY_T => 0x14,\n        OsCode::KEY_Y => 0x15,\n        OsCode::KEY_U => 0x16,\n        OsCode::KEY_I => 0x17,\n        OsCode::KEY_O => 0x18,\n        OsCode::KEY_P => 0x19,\n        OsCode::KEY_LEFTBRACE => 0x1A,\n        OsCode::KEY_RIGHTBRACE => 0x1B,\n        OsCode::KEY_ENTER => 0x1C,\n        OsCode::KEY_LEFTCTRL => 0x1D,\n        OsCode::KEY_A => 0x1E,\n        OsCode::KEY_S => 0x1F,\n        OsCode::KEY_D => 0x20,\n        OsCode::KEY_F => 0x21,\n        OsCode::KEY_G => 0x22,\n        OsCode::KEY_H => 0x23,\n        OsCode::KEY_J => 0x24,\n        OsCode::KEY_K => 0x25,\n        OsCode::KEY_L => 0x26,\n        OsCode::KEY_SEMICOLON => 0x27,\n        OsCode::KEY_APOSTROPHE => 0x28,\n        OsCode::KEY_GRAVE => 0x29,\n        OsCode::KEY_LEFTSHIFT => 0x2A,\n        OsCode::KEY_BACKSLASH => 0x2B,\n        OsCode::KEY_Z => 0x2C,\n        OsCode::KEY_X => 0x2D,\n        OsCode::KEY_C => 0x2E,\n        OsCode::KEY_V => 0x2F,\n        OsCode::KEY_B => 0x30,\n        OsCode::KEY_N => 0x31,\n        OsCode::KEY_M => 0x32,\n        OsCode::KEY_COMMA => 0x33,\n        OsCode::KEY_DOT => 0x34,\n        OsCode::KEY_SLASH => 0x35,\n        OsCode::KEY_RIGHTSHIFT => 0x36,\n        OsCode::KEY_KPASTERISK => 0x37,\n        OsCode::KEY_LEFTALT => 0x38,\n        OsCode::KEY_SPACE => 0x39,\n        OsCode::KEY_CAPSLOCK => 0x3A,\n        OsCode::KEY_F1 => 0x3B,\n        OsCode::KEY_F2 => 0x3C,\n        OsCode::KEY_F3 => 0x3D,\n        OsCode::KEY_F4 => 0x3E,\n        OsCode::KEY_F5 => 0x3F,\n        OsCode::KEY_F6 => 0x40,\n        OsCode::KEY_F7 => 0x41,\n        OsCode::KEY_F8 => 0x42,\n        OsCode::KEY_F9 => 0x43,\n        OsCode::KEY_F10 => 0x44,\n        OsCode::KEY_NUMLOCK => 0x45,\n        OsCode::KEY_SCROLLLOCK => 0x46,\n        OsCode::KEY_KP7 => 0x47,\n        OsCode::KEY_KP8 => 0x48,\n        OsCode::KEY_KP9 => 0x49,\n        OsCode::KEY_KPMINUS => 0x4A,\n        OsCode::KEY_KP4 => 0x4B,\n        OsCode::KEY_KP5 => 0x4C,\n        OsCode::KEY_KP6 => 0x4D,\n        OsCode::KEY_KPPLUS => 0x4E,\n        OsCode::KEY_KP1 => 0x4F,\n        OsCode::KEY_KP2 => 0x50,\n        OsCode::KEY_KP3 => 0x51,\n        OsCode::KEY_KP0 => 0x52,\n        OsCode::KEY_KPDOT => 0x53,\n        OsCode::KEY_102ND => 0x56, /* Key between the left shift and Z. */\n        OsCode::KEY_F11 => 0x57,\n        OsCode::KEY_F12 => 0x58,\n        OsCode::KEY_F13 => 0x64,\n        OsCode::KEY_F14 => 0x65,\n        OsCode::KEY_F15 => 0x66,\n        OsCode::KEY_F16 => 0x67,\n        OsCode::KEY_F17 => 0x68,\n        OsCode::KEY_F18 => 0x69,\n        OsCode::KEY_F19 => 0x6A,\n        OsCode::KEY_F20 => 0x6B,\n        OsCode::KEY_F21 => 0x6C,\n        OsCode::KEY_F22 => 0x6D,\n        OsCode::KEY_F23 => 0x6E,\n        OsCode::KEY_RO => 0x73,\n        OsCode::KEY_F24 => 0x76,\n        OsCode::KEY_KATAKANA => 0x70, // https://kbdlayout.info/kbdjpn/virtualkeys?arrangement=ISO105\n        // Apparantly SC 0x70 does not exist.\n        // But seems to be hiragana/katakana?\n        OsCode::KEY_HENKAN => 0x79,   // Convert\n        OsCode::KEY_MUHENKAN => 0x7B, // Noconvert\n        OsCode::KEY_YEN => 0x7D,      // Yen / Pipe\n        OsCode::KEY_PREVIOUSSONG => 0xE010,\n        OsCode::KEY_NEXTSONG => 0xE019,\n        OsCode::KEY_KPENTER => 0xE01C,\n        OsCode::KEY_RIGHTCTRL => 0xE01D,\n        OsCode::KEY_MUTE => 0xE020,\n        OsCode::KEY_PLAYPAUSE => 0xE022,  // sc_media_play\n        OsCode::KEY_VOLUMEDOWN => 0xE02E, // sc_volume_down\n        OsCode::KEY_VOLUMEUP => 0xE030,   // sc_volume_up\n        OsCode::KEY_KPSLASH => 0xE035,    // sc_numpad_divide\n        OsCode::KEY_PRINT => 0xE037,      // sc_printScreen\n        OsCode::KEY_RIGHTALT => 0xE038,   // sc_altRight\n        OsCode::KEY_HOME => 0xE047,       // sc_home\n        OsCode::KEY_UP => 0xE048,         // sc_arrowUp\n        OsCode::KEY_PAGEUP => 0xE049,     // sc_pageUp\n        OsCode::KEY_LEFT => 0xE04B,       // sc_arrowLeft\n        OsCode::KEY_RIGHT => 0xE04D,      // sc_arrowRight\n        OsCode::KEY_END => 0xE04F,        // sc_end\n        OsCode::KEY_DOWN => 0xE050,       // sc_arrowDown\n        OsCode::KEY_PAGEDOWN => 0xE051,   // sc_pageDown\n        OsCode::KEY_INSERT => 0xE052,     // sc_insert\n        OsCode::KEY_DELETE => 0xE053,     // sc_delete\n        OsCode::KEY_LEFTMETA => 0xE05B,   // sc_metaLeft\n        OsCode::KEY_RIGHTMETA => 0xE05C,  // sc_metaRight\n        OsCode::KEY_COMPOSE => 0xE05D,    // sc_application / compose\n        OsCode::KEY_FORWARD => 0xE069,    // sc_browser_forward\n        OsCode::KEY_BACK => 0xE06A,       // sc_browser_back\n        _ => return None,\n    })\n}\n"
  },
  {
    "path": "src/tcp_server.rs",
    "content": "use crate::Kanata;\nuse crate::oskbd::*;\n\n#[cfg(feature = \"tcp_server\")]\nuse kanata_tcp_protocol::*;\nuse parking_lot::Mutex;\nuse std::net::SocketAddr;\nuse std::sync::Arc;\nuse std::sync::mpsc::SyncSender as Sender;\n\n#[cfg(feature = \"tcp_server\")]\ntype HashMap<K, V> = rustc_hash::FxHashMap<K, V>;\n#[cfg(feature = \"tcp_server\")]\nuse kanata_parser::cfg::SimpleSExpr;\n#[cfg(feature = \"tcp_server\")]\nuse std::io::Write;\n#[cfg(feature = \"tcp_server\")]\nuse std::net::{TcpListener, TcpStream};\n\n#[cfg(feature = \"tcp_server\")]\npub type Connections = Arc<Mutex<HashMap<String, TcpStream>>>;\n\n#[cfg(not(feature = \"tcp_server\"))]\npub type Connections = ();\n\n#[cfg(feature = \"tcp_server\")]\nuse kanata_parser::custom_action::FakeKeyAction;\n\n#[cfg(feature = \"tcp_server\")]\nfn send_response(\n    stream: &mut TcpStream,\n    response: ServerResponse,\n    connections: &Connections,\n    addr: &str,\n) -> bool {\n    if let Err(write_err) = stream.write_all(&response.as_bytes()) {\n        log::error!(\"stream write error: {write_err}\");\n        connections.lock().remove(addr);\n        return false;\n    }\n    true\n}\n\n#[cfg(feature = \"tcp_server\")]\nfn to_action(val: FakeKeyActionMessage) -> FakeKeyAction {\n    match val {\n        FakeKeyActionMessage::Press => FakeKeyAction::Press,\n        FakeKeyActionMessage::Release => FakeKeyAction::Release,\n        FakeKeyActionMessage::Tap => FakeKeyAction::Tap,\n        FakeKeyActionMessage::Toggle => FakeKeyAction::Toggle,\n    }\n}\n\n/// Handles reload commands with optional wait/timeout for completion confirmation.\n/// Returns false if the connection should be closed, true otherwise.\n#[cfg(feature = \"tcp_server\")]\nfn handle_reload_with_wait(\n    reload_cmd: ClientMessage,\n    wait: Option<bool>,\n    timeout_ms: Option<u64>,\n    stream: &mut TcpStream,\n    kanata: &Arc<Mutex<Kanata>>,\n    connections: &Connections,\n    addr: &str,\n) -> bool {\n    let (response, reload_ok) = match kanata.lock().handle_client_command(reload_cmd) {\n        Ok(_) => (ServerResponse::Ok, true),\n        Err(e) => (\n            ServerResponse::Error {\n                msg: format!(\"{e}\"),\n            },\n            false,\n        ),\n    };\n    if !send_response(stream, response, connections, addr) {\n        return false;\n    }\n\n    // If wait flag is set and reload succeeded, poll for completion\n    if reload_ok && wait.unwrap_or(false) {\n        let timeout_ms = timeout_ms.unwrap_or(5000);\n        let start = std::time::Instant::now();\n        let timeout_duration = std::time::Duration::from_millis(timeout_ms);\n\n        while start.elapsed() < timeout_duration {\n            if kanata.lock().is_reload_complete() {\n                break;\n            }\n            std::thread::sleep(std::time::Duration::from_millis(50));\n        }\n        let timed_out = start.elapsed() >= timeout_duration;\n\n        let ok = kanata.lock().last_reload_succeeded();\n        let msg = ServerMessage::ReloadResult {\n            ok,\n            timeout_ms: if timed_out { Some(timeout_ms) } else { None },\n        };\n        if let Err(err) = stream.write_all(&msg.as_bytes()) {\n            log::error!(\"Error writing ReloadResult: {err}\");\n            connections.lock().remove(addr);\n            return false;\n        }\n        let _ = stream.flush();\n    }\n    true\n}\n\n#[cfg(feature = \"tcp_server\")]\npub struct TcpServer {\n    pub address: SocketAddr,\n    pub connections: Connections,\n    pub wakeup_channel: Sender<KeyEvent>,\n}\n\n#[cfg(not(feature = \"tcp_server\"))]\npub struct TcpServer {\n    pub connections: Connections,\n}\n\nimpl TcpServer {\n    #[cfg(feature = \"tcp_server\")]\n    pub fn new(address: SocketAddr, wakeup_channel: Sender<KeyEvent>) -> Self {\n        Self {\n            address,\n            connections: Arc::new(Mutex::new(HashMap::default())),\n            wakeup_channel,\n        }\n    }\n\n    #[cfg(not(feature = \"tcp_server\"))]\n    pub fn new(_address: SocketAddr, _wakeup_channel: Sender<KeyEvent>) -> Self {\n        Self { connections: () }\n    }\n\n    #[cfg(feature = \"tcp_server\")]\n    pub fn start(&mut self, kanata: Arc<Mutex<Kanata>>) {\n        use kanata_parser::cfg::FAKE_KEY_ROW;\n\n        use crate::kanata::handle_fakekey_action;\n\n        let listener = TcpListener::bind(self.address).expect(\"TCP server starts\");\n\n        let connections = self.connections.clone();\n        let wakeup_channel = self.wakeup_channel.clone();\n\n        std::thread::spawn(move || {\n            for stream in listener.incoming() {\n                match stream {\n                    Ok(mut stream) => {\n                        {\n                            let k = kanata.lock();\n                            log::info!(\n                                \"new client connection, sending initial LayerChange event to inform them of current layer\"\n                            );\n                            if let Err(e) = stream.write(\n                                &ServerMessage::LayerChange {\n                                    new: k.layer_info[k.layout.b().current_layer()].name.clone(),\n                                }\n                                .as_bytes(),\n                            ) {\n                                log::warn!(\"failed to write to stream, dropping it: {e:?}\");\n                                continue;\n                            }\n                        }\n\n                        let addr = match stream.peer_addr() {\n                            Ok(addr) => addr.to_string(),\n                            Err(e) => {\n                                log::warn!(\"failed to get peer address, using fallback: {e:?}\");\n                                format!(\"unknown_{}\", std::ptr::addr_of!(stream) as usize)\n                            }\n                        };\n\n                        connections.lock().insert(\n                            addr.clone(),\n                            stream.try_clone().expect(\"stream is clonable\"),\n                        );\n                        let reader = serde_json::Deserializer::from_reader(\n                            stream.try_clone().expect(\"stream is clonable\"),\n                        )\n                        .into_iter::<ClientMessage>();\n\n                        log::info!(\"listening for incoming messages {addr}\");\n\n                        let connections = connections.clone();\n                        let kanata = kanata.clone();\n                        let wakeup_channel = wakeup_channel.clone();\n                        std::thread::spawn(move || {\n                            for v in reader {\n                                match v {\n                                    Ok(event) => {\n                                        log::debug!(\"tcp server received command: {:?}\", event);\n                                        match event {\n                                            ClientMessage::ChangeLayer { new } => {\n                                                kanata.lock().change_layer(new);\n                                            }\n                                            ClientMessage::RequestLayerNames {} => {\n                                                let msg = ServerMessage::LayerNames {\n                                                    names: kanata\n                                                        .lock()\n                                                        .layer_info\n                                                        .iter()\n                                                        .map(|info| info.name.clone())\n                                                        .collect::<Vec<_>>(),\n                                                };\n                                                match stream.write_all(&msg.as_bytes()) {\n                                                    Ok(_) => {}\n                                                    Err(err) => log::error!(\n                                                        \"server could not send response: {err}\"\n                                                    ),\n                                                }\n                                            }\n                                            ClientMessage::RequestFakeKeyNames {} => {\n                                                let msg = ServerMessage::FakeKeyNames {\n                                                    names: kanata\n                                                        .lock()\n                                                        .virtual_keys\n                                                        .keys()\n                                                        .cloned()\n                                                        .collect::<Vec<_>>(),\n                                                };\n                                                match stream.write_all(&msg.as_bytes()) {\n                                                    Ok(_) => {}\n                                                    Err(err) => log::error!(\n                                                        \"server could not send response: {err}\"\n                                                    ),\n                                                }\n                                            }\n                                            ClientMessage::ActOnFakeKey { name, action } => {\n                                                let mut k = kanata.lock();\n                                                let index = match k.virtual_keys.get(&name) {\n                                                    Some(index) => Some(*index as u16),\n                                                    None => {\n                                                        if let Err(e) = stream.write_all(\n                                                            &ServerMessage::Error {\n                                                                msg: format!(\n                                                                    \"unknown virtual/fake key: {name}\"\n                                                                ),\n                                                            }\n                                                            .as_bytes(),\n                                                        ) {\n                                                            log::error!(\"stream write error: {e}\");\n                                                            connections.lock().remove(&addr);\n                                                            break;\n                                                        }\n                                                        continue;\n                                                    }\n                                                };\n                                                if let Some(index) = index {\n                                                    log::info!(\n                                                        \"tcp server fake-key action: {name},{action:?}\"\n                                                    );\n                                                    handle_fakekey_action(\n                                                        to_action(action),\n                                                        k.layout.bm(),\n                                                        FAKE_KEY_ROW,\n                                                        index,\n                                                    );\n                                                }\n                                                drop(k);\n                                            }\n                                            ClientMessage::SetMouse { x, y } => {\n                                                log::info!(\n                                                    \"tcp server SetMouse action: x {x} y {y}\"\n                                                );\n                                                match kanata.lock().kbd_out.set_mouse(x, y) {\n                                                    Ok(_) => {\n                                                        log::info!(\n                                                            \"sucessfully did set mouse position to: x {x} y {y}\"\n                                                        );\n                                                    }\n                                                    Err(e) => {\n                                                        log::error!(\n                                                            \"Failed to set mouse position: {}\",\n                                                            e\n                                                        );\n                                                    }\n                                                }\n                                            }\n                                            ClientMessage::RequestCurrentLayerInfo {} => {\n                                                let mut k = kanata.lock();\n                                                let cur_layer = k.layout.bm().current_layer();\n                                                let msg = ServerMessage::CurrentLayerInfo {\n                                                    name: k.layer_info[cur_layer].name.clone(),\n                                                    cfg_text: k.layer_info[cur_layer]\n                                                        .cfg_text\n                                                        .clone(),\n                                                };\n                                                drop(k);\n                                                match stream.write_all(&msg.as_bytes()) {\n                                                    Ok(_) => {}\n                                                    Err(err) => log::error!(\n                                                        \"Error writing response to RequestCurrentLayerInfo: {err}\"\n                                                    ),\n                                                }\n                                            }\n                                            ClientMessage::RequestCurrentLayerName {} => {\n                                                let mut k = kanata.lock();\n                                                let cur_layer = k.layout.bm().current_layer();\n                                                let msg = ServerMessage::CurrentLayerName {\n                                                    name: k.layer_info[cur_layer].name.clone(),\n                                                };\n                                                drop(k);\n                                                match stream.write_all(&msg.as_bytes()) {\n                                                    Ok(_) => {}\n                                                    Err(err) => log::error!(\n                                                        \"Error writing response to RequestCurrentLayerName: {err}\"\n                                                    ),\n                                                }\n                                            }\n                                            // New command: Hello - capability detection\n                                            ClientMessage::Hello {} => {\n                                                let version = env!(\"CARGO_PKG_VERSION\").to_string();\n                                                let capabilities = vec![\n                                                    \"reload\".to_string(),\n                                                    \"layer-names\".to_string(),\n                                                    \"fake-key-names\".to_string(),\n                                                    \"layer-change\".to_string(),\n                                                    \"hold-activated\".to_string(),\n                                                    \"tap-activated\".to_string(),\n                                                    \"current-layer-name\".to_string(),\n                                                    \"current-layer-info\".to_string(),\n                                                    \"fake-key\".to_string(),\n                                                    \"set-mouse\".to_string(),\n                                                ];\n                                                let msg = ServerMessage::HelloOk {\n                                                    version,\n                                                    protocol: 1,\n                                                    capabilities,\n                                                };\n                                                match stream.write_all(&msg.as_bytes()) {\n                                                    Ok(_) => {\n                                                        let _ = stream.flush();\n                                                    }\n                                                    Err(err) => {\n                                                        log::error!(\n                                                            \"Error writing HelloOk response: {err}\"\n                                                        );\n                                                        connections.lock().remove(&addr);\n                                                        break;\n                                                    }\n                                                }\n                                            }\n                                            // Reload commands with optional wait/timeout\n                                            ClientMessage::Reload { wait, timeout_ms } => {\n                                                log::info!(\"tcp server Reload action\");\n                                                if !handle_reload_with_wait(\n                                                    ClientMessage::Reload { wait, timeout_ms },\n                                                    wait,\n                                                    timeout_ms,\n                                                    &mut stream,\n                                                    &kanata,\n                                                    &connections,\n                                                    &addr,\n                                                ) {\n                                                    break;\n                                                }\n                                            }\n                                            ClientMessage::ReloadNext { wait, timeout_ms } => {\n                                                log::info!(\"tcp server ReloadNext action\");\n                                                if !handle_reload_with_wait(\n                                                    ClientMessage::ReloadNext { wait, timeout_ms },\n                                                    wait,\n                                                    timeout_ms,\n                                                    &mut stream,\n                                                    &kanata,\n                                                    &connections,\n                                                    &addr,\n                                                ) {\n                                                    break;\n                                                }\n                                            }\n                                            ClientMessage::ReloadPrev { wait, timeout_ms } => {\n                                                log::info!(\"tcp server ReloadPrev action\");\n                                                if !handle_reload_with_wait(\n                                                    ClientMessage::ReloadPrev { wait, timeout_ms },\n                                                    wait,\n                                                    timeout_ms,\n                                                    &mut stream,\n                                                    &kanata,\n                                                    &connections,\n                                                    &addr,\n                                                ) {\n                                                    break;\n                                                }\n                                            }\n                                            ClientMessage::ReloadNum {\n                                                index,\n                                                wait,\n                                                timeout_ms,\n                                            } => {\n                                                log::info!(\n                                                    \"tcp server ReloadNum action: index {index}\"\n                                                );\n                                                if !handle_reload_with_wait(\n                                                    ClientMessage::ReloadNum {\n                                                        index,\n                                                        wait,\n                                                        timeout_ms,\n                                                    },\n                                                    wait,\n                                                    timeout_ms,\n                                                    &mut stream,\n                                                    &kanata,\n                                                    &connections,\n                                                    &addr,\n                                                ) {\n                                                    break;\n                                                }\n                                            }\n                                            ClientMessage::ReloadFile {\n                                                path,\n                                                wait,\n                                                timeout_ms,\n                                            } => {\n                                                log::info!(\n                                                    \"tcp server ReloadFile action: path {path}\"\n                                                );\n                                                if !handle_reload_with_wait(\n                                                    ClientMessage::ReloadFile {\n                                                        path,\n                                                        wait,\n                                                        timeout_ms,\n                                                    },\n                                                    wait,\n                                                    timeout_ms,\n                                                    &mut stream,\n                                                    &kanata,\n                                                    &connections,\n                                                    &addr,\n                                                ) {\n                                                    break;\n                                                }\n                                            }\n                                        }\n                                        use kanata_parser::keys::*;\n                                        wakeup_channel\n                                            .send(KeyEvent {\n                                                code: OsCode::KEY_RESERVED,\n                                                value: KeyValue::WakeUp,\n                                            })\n                                            .expect(\"write key event\");\n                                    }\n                                    Err(e) => {\n                                        log::warn!(\n                                            \"client sent an invalid message, disconnecting them. Err: {e:?}\"\n                                        );\n                                        // Send proper error response for malformed JSON\n                                        let response = ServerResponse::Error {\n                                            msg: format!(\"Failed to deserialize command: {e}\"),\n                                        };\n                                        let _ = stream.write_all(&response.as_bytes());\n                                        connections.lock().remove(&addr);\n                                        break;\n                                    }\n                                }\n                            }\n                        });\n                    }\n                    Err(_) => log::error!(\"not able to accept client connection\"),\n                }\n            }\n        });\n    }\n\n    #[cfg(not(feature = \"tcp_server\"))]\n    pub fn start(&mut self, _kanata: Arc<Mutex<Kanata>>) {}\n}\n\n#[cfg(feature = \"tcp_server\")]\npub fn simple_sexpr_to_json_array(exprs: &[SimpleSExpr]) -> serde_json::Value {\n    let mut result = Vec::new();\n\n    for expr in exprs.iter() {\n        match expr {\n            SimpleSExpr::Atom(s) => result.push(serde_json::Value::String(s.clone())),\n            SimpleSExpr::List(list) => result.push(simple_sexpr_to_json_array(list)),\n        }\n    }\n\n    serde_json::Value::Array(result)\n}\n"
  },
  {
    "path": "src/tests/passthru_macos_tests.rs",
    "content": "use crate::oskbd::{KeyEvent, KeyValue};\nuse crate::{Kanata, ValidatedArgs, str_to_oscode};\nuse std::path::PathBuf;\nuse std::sync::mpsc;\n\nfn passthru_args() -> ValidatedArgs {\n    ValidatedArgs {\n        paths: vec![PathBuf::from(\"./cfg_samples/minimal.kbd\")],\n        #[cfg(feature = \"tcp_server\")]\n        tcp_server_address: None,\n        nodelay: true,\n    }\n}\n\n#[test]\nfn passthru_runtime_output_channel_is_ready_and_emits_events() {\n    let args = passthru_args();\n    let (tx, rx) = mpsc::channel();\n    let runtime = Kanata::new_with_output_channel(&args, Some(tx)).expect(\"passthru runtime\");\n\n    {\n        let mut runtime = runtime.lock();\n        assert!(runtime.kbd_out.output_ready());\n\n        let key = str_to_oscode(\"a\").expect(\"key code\");\n        runtime\n            .kbd_out\n            .write_key(key, KeyValue::Press)\n            .expect(\"write key through passthru output\");\n    }\n\n    let event = rx.try_recv().expect(\"passthru output event\");\n    let key_event = KeyEvent::try_from(event).expect(\"output event should decode\");\n    assert_eq!(key_event.code, str_to_oscode(\"a\").expect(\"key code\"));\n    assert_eq!(key_event.value, KeyValue::Press);\n}\n"
  },
  {
    "path": "src/tests/sim_tests/block_keys_tests.rs",
    "content": "use super::*;\n\n#[test]\nfn block_does_not_block_buttons() {\n    let result = simulate(\n        \"(defcfg process-unmapped-keys yes\n                   block-unmapped-keys yes)\n        (defsrc)\n        (deflayer base)\",\n        \"d:mlft d:mrgt d:mmid d:mbck d:mfwd t:10 d:f1\n         u:mlft u:mrgt u:mmid u:mbck u:mfwd t:10 u:f1\",\n    );\n    assert_eq!(\n        \"out🖰:↓Left\\nt:1ms\\nout🖰:↓Right\\nt:1ms\\nout🖰:↓Mid\\nt:1ms\\nout🖰:↓Backward\\n\\\n               t:1ms\\nout🖰:↓Forward\\nt:7ms\\nout🖰:↑Left\\nt:1ms\\nout🖰:↑Right\\nt:1ms\\nout🖰:↑Mid\\n\\\n               t:1ms\\nout🖰:↑Backward\\nt:1ms\\nout🖰:↑Forward\",\n        result\n    );\n}\n\n#[test]\nfn block_does_not_block_wheel() {\n    let result = simulate(\n        \"(defcfg process-unmapped-keys yes\n                   block-unmapped-keys yes)\n        (defsrc)\n        (deflayer base)\",\n        \"d:mwu d:mwd d:mwl d:mwr t:10 d:f1\n         u:mwu u:mwd u:mwl u:mwr t:10 u:f1\",\n    );\n    assert_eq!(\n        \"scroll:Up,120\\nt:1ms\\nscroll:Down,120\\nt:1ms\\nscroll:Left,120\\nt:1ms\\nscroll:Right,120\",\n        result\n    );\n}\n"
  },
  {
    "path": "src/tests/sim_tests/capsword_sim_tests.rs",
    "content": "use super::*;\n\nconst CFG: &str = r##\"\n (defcfg)\n (defsrc 7 8 9 0)\n (deflayer base\n     (caps-word 1000)\n     (caps-word-custom 200 (a) (b))\n     (caps-word-toggle 1000)\n     (caps-word-custom-toggle 200 (a) (b))\n )\n\"##;\n\n#[test]\nfn caps_word_behaves_correctly() {\n    let result = simulate(\n        CFG,\n        \"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\",\n    )\n    .no_time();\n    assert_eq!(\n        \"out:↓LShift out:↓A out:↑LShift out:↑A \\\n         out:↓Kb1 out:↑Kb1 out:↓LShift out:↓A out:↑LShift out:↑A \\\n         out:↓Space out:↑Space out:↓A out:↑A\",\n        result\n    );\n}\n\n#[test]\nfn caps_word_custom_behaves_correctly() {\n    let result = simulate(\n        CFG,\n        \"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\",\n    )\n    .no_time();\n    assert_eq!(\n        \"out:↓LShift out:↓A out:↑LShift out:↑A \\\n         out:↓B out:↑B out:↓LShift out:↓A out:↑LShift out:↑A \\\n         out:↓Kb1 out:↑Kb1 out:↓A out:↑A\",\n        result\n    );\n}\n\n#[test]\nfn caps_word_times_out() {\n    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();\n    assert_eq!(\n        \"out:↓LShift out:↓A out:↑LShift out:↑A \\\n         out:↓LShift out:↓A out:↑LShift out:↑A \\\n         out:↓A out:↑A\",\n        result\n    );\n}\n\n#[test]\nfn caps_word_custom_times_out() {\n    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();\n    assert_eq!(\n        \"out:↓LShift out:↓A out:↑LShift out:↑A \\\n         out:↓LShift out:↓A out:↑LShift out:↑A \\\n         out:↓A out:↑A\",\n        result\n    );\n}\n\n#[test]\nfn caps_word_does_not_toggle() {\n    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();\n    assert_eq!(\n        \"out:↓LShift out:↓A out:↑LShift out:↑A \\\n         out:↓LShift out:↓A out:↑LShift out:↑A\",\n        result\n    );\n}\n\n#[test]\nfn caps_word_custom_does_not_toggle() {\n    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();\n    assert_eq!(\n        \"out:↓LShift out:↓A out:↑LShift out:↑A \\\n         out:↓LShift out:↓A out:↑LShift out:↑A\",\n        result\n    );\n}\n\n#[test]\nfn caps_word_toggle_does_toggle() {\n    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();\n    assert_eq!(\n        \"out:↓LShift out:↓A out:↑LShift out:↑A \\\n         out:↓A out:↑A\",\n        result\n    );\n}\n\n#[test]\nfn caps_word_custom_toggle_does_toggle() {\n    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();\n    assert_eq!(\n        \"out:↓LShift out:↓A out:↑LShift out:↑A \\\n         out:↓A out:↑A\",\n        result\n    );\n}\n"
  },
  {
    "path": "src/tests/sim_tests/chord_sim_tests.rs",
    "content": "use super::*;\n\nstatic SIMPLE_NONOVERLAPPING_CHORD_CFG: &str = \"\\\n(defcfg process-unmapped-keys yes concurrent-tap-hold yes) \\\n(defsrc) \\\n(defalias c c)\n(defvar d d)\n(deflayer base) \\\n(defchordsv2 \\\n  (a b) @c 200 all-released () \\\n  (b z) $d 200 first-release () \\\n)\";\n\n#[test]\nfn sim_chord_basic_repeated_last_release() {\n    let result = simulate(\n        SIMPLE_NONOVERLAPPING_CHORD_CFG,\n        \"d:a t:50 d:b t:50 u:a t:50 u:b t:50 \\\n         d:b t:50 d:a t:50 u:b t:50 u:a t:50 \",\n    );\n    assert_eq!(\n        \"t:50ms\\nout:↓C\\nt:102ms\\nout:↑C\\nt:98ms\\nout:↓C\\nt:102ms\\nout:↑C\",\n        result\n    );\n}\n\n#[test]\nfn sim_chord_min_idle_takes_effect() {\n    let result = simulate(\n        SIMPLE_NONOVERLAPPING_CHORD_CFG,\n        \"d:z t:20 d:a t:20 d:b t:20 d:d t:20\",\n    );\n    assert_eq!(\n        \"t:21ms\nout:↓Z\nt:1ms\nout:↓A\nt:39ms\nout:↓B\nt:1ms\nout:↓D\",\n        result\n    );\n}\n\n#[test]\nfn sim_timeout_hold_key() {\n    let result = simulate(SIMPLE_NONOVERLAPPING_CHORD_CFG, \"d:z t:201 d:b t:200\");\n    assert_eq!(\n        \"t:201ms\nout:↓Z\nt:1ms\nout:↓B\",\n        result\n    );\n}\n\n#[test]\nfn sim_chord_basic_repeated_first_release() {\n    let result = simulate(\n        SIMPLE_NONOVERLAPPING_CHORD_CFG,\n        \"d:z t:50 d:b t:50 u:z t:50 u:b t:50 \\\n        d:z t:50 d:b t:50 u:z t:50 u:b t:50 \",\n    );\n    assert_eq!(\n        \"t:50ms\\nout:↓D\\nt:52ms\\nout:↑D\\nt:148ms\\nout:↓D\\nt:52ms\\nout:↑D\",\n        result\n    );\n}\n\nstatic SIMPLE_OVERLAPPING_CHORD_CFG: &str = \"\\\n(defcfg process-unmapped-keys yes concurrent-tap-hold yes\n chords-v2-min-idle-experimental 5)\n(defsrc)\n(deflayer base)\n(defchordsv2-experimental\n  (a b) c 200 all-released ()\n  (a b z) d 250 first-release ()\n  (a b z y) e 400 first-release ()\n)\";\n\n#[test]\nfn sim_chord_overlapping_timeout() {\n    let result = simulate(SIMPLE_OVERLAPPING_CHORD_CFG, \"d:a d:b t:201 d:z t:500\").to_ascii();\n    assert_eq!(\"t:400ms dn:D\", result);\n}\n\n#[test]\nfn sim_chord_overlapping_timeout_v2() {\n    let result = simulate(\n        \"\n(defcfg process-unmapped-keys yes\n concurrent-tap-hold yes)\n(defsrc e f j)\n(deflayermap (layer1))\n(defchordsv2\n (e f) lctl 5 all-released ()\n (f j) ret  10 first-release ()\n)\n        \",\n        \"d:f t:7 d:j t:100\",\n    )\n    .to_ascii();\n    assert_eq!(\"t:7ms dn:Enter\", result);\n}\n\n#[test]\nfn sim_chord_overlapping_release() {\n    let result = simulate(\n        SIMPLE_OVERLAPPING_CHORD_CFG,\n        \"d:a d:b t:100 u:a d:z t:300 u:b t:300\",\n    )\n    .to_ascii();\n    assert_eq!(\"t:100ms dn:C t:301ms up:C t:100ms dn:Z\", result);\n}\n\n#[test]\nfn sim_presses_for_old_chord_repress_into_new_chord() {\n    let result = simulate(\n        SIMPLE_OVERLAPPING_CHORD_CFG,\n        \"d:a d:b t:50 u:a t:50 d:z t:50 u:b t:50 d:a d:b t:50 u:a t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\"t:50ms dn:C t:101ms up:C t:99ms dn:D t:11ms up:D\", result);\n}\n\n#[test]\nfn sim_chord_activate_largest_overlapping() {\n    let result = simulate(\n        SIMPLE_OVERLAPPING_CHORD_CFG,\n        \"d:a t:50 d:b t:50 d:z t:50 d:y t:50 u:b t:50\",\n    );\n    assert_eq!(\"t:150ms\\nout:↓E\\nt:52ms\\nout:↑E\", result);\n}\n\nstatic SIMPLE_DISABLED_LAYER_CHORD_CFG: &str = \"\\\n(defcfg process-unmapped-keys yes concurrent-tap-hold yes)\n(defsrc)\n(deflayermap (1) 2 (layer-switch 2)\n                 3 (layer-switch 3))\n(deflayermap (2) 3 (layer-while-held 3)\n                 1 (layer-while-held 1))\n(deflayermap (3) 2 (layer-while-held 2)\n                 1 (layer-while-held 1))\n(defchordsv2\n  (a b) x 200 all-released (1)\n  (c d) y 200 all-released (2)\n  (e f) z 200 all-released (3)\n)\";\n\n#[test]\nfn sim_chord_layer_1_switch_disabled() {\n    let result = simulate(\n        SIMPLE_DISABLED_LAYER_CHORD_CFG,\n        \"d:a t:50 d:b t:50 d:c t:50 d:d t:50 d:e t:50 d:f t:50\",\n    );\n    assert_eq!(\n        \"t:1ms\\nout:↓A\\nt:50ms\\nout:↓B\\nt:99ms\\nout:↓Y\\nt:100ms\\nout:↓Z\",\n        result\n    );\n}\n\n#[test]\nfn sim_chord_layer_2_switch_disabled() {\n    let result = simulate(\n        SIMPLE_DISABLED_LAYER_CHORD_CFG,\n        \"d:2 t:50 d:a t:50 d:b t:50 d:c t:50 d:d t:50 d:e t:50 d:f t:50\",\n    );\n    assert_eq!(\n        \"t:100ms\\nout:↓X\\nt:51ms\\nout:↓C\\nt:50ms\\nout:↓D\\nt:99ms\\nout:↓Z\",\n        result\n    );\n}\n\n#[test]\nfn sim_chord_layer_3_switch_disabled() {\n    let result = simulate(\n        SIMPLE_DISABLED_LAYER_CHORD_CFG,\n        \"d:3 t:50 d:a t:50 d:b t:50 d:c t:50 d:d t:50 d:e t:50 d:f t:50\",\n    );\n    assert_eq!(\n        \"t:100ms\\nout:↓X\\nt:100ms\\nout:↓Y\\nt:51ms\\nout:↓E\\nt:50ms\\nout:↓F\",\n        result\n    );\n}\n\n#[test]\nfn sim_chord_layer_1_held_disabled() {\n    let result = simulate(\n        SIMPLE_DISABLED_LAYER_CHORD_CFG,\n        \"d:3 t:50 d:1 t:50 d:a t:50 d:b t:50 d:c t:50 d:d t:50 d:e t:50 d:f t:50\",\n    );\n    assert_eq!(\n        \"t:101ms\\nout:↓A\\nt:50ms\\nout:↓B\\nt:99ms\\nout:↓Y\\nt:100ms\\nout:↓Z\",\n        result\n    );\n}\n\n#[test]\nfn sim_chord_layer_2_held_disabled() {\n    let result = simulate(\n        SIMPLE_DISABLED_LAYER_CHORD_CFG,\n        \"d:3 t:50 d:2 t:50 d:a t:50 d:b t:50 d:c t:50 d:d t:50 d:e t:50 d:f t:50\",\n    );\n    assert_eq!(\n        \"t:150ms\\nout:↓X\\nt:51ms\\nout:↓C\\nt:50ms\\nout:↓D\\nt:99ms\\nout:↓Z\",\n        result\n    );\n}\n\n#[test]\nfn sim_chord_layer_3_held_disabled() {\n    let result = simulate(\n        SIMPLE_DISABLED_LAYER_CHORD_CFG,\n        \"d:2 t:50 d:3 t:50 d:a t:50 d:b t:50 d:c t:50 d:d t:50 d:e t:50 d:f t:50\",\n    );\n    assert_eq!(\n        \"t:150ms\\nout:↓X\\nt:100ms\\nout:↓Y\\nt:51ms\\nout:↓E\\nt:50ms\\nout:↓F\",\n        result\n    );\n}\n\n#[test]\nfn sim_chord_layer_3_repeat() {\n    let result = simulate(\n        SIMPLE_DISABLED_LAYER_CHORD_CFG,\n        \"d:3 t:50 d:a t:50 d:b t:50 r:b t:50 r:b t:50\\n\\\n         d:d t:50 d:c t:50 r:c t:50 r:d t:50\",\n    );\n    assert_eq!(\n        \"t:100ms\\nout:↓X\\nt:50ms\\nout:↓X\\nt:50ms\\nout:↓X\\n\\\n         t:100ms\\nout:↓Y\\nt:50ms\\nout:↓Y\\nt:50ms\\nout:↓Y\",\n        result\n    );\n}\n\nstatic CHORD_INTO_TAP_HOLD_CFG: &str = \"\\\n(defcfg process-unmapped-keys yes concurrent-tap-hold yes)\n(defsrc)\n(deflayer base)\n(defchordsv2\n  (a b) (tap-hold 200 200 x y) 200 all-released ()\n)\";\n\n#[test]\nfn sim_chord_into_tap_hold() {\n    let result = simulate(\n        CHORD_INTO_TAP_HOLD_CFG,\n        \"d:a t:50 d:b t:150 u:a u:b t:5 \\\n         d:a t:50 d:b t:149 u:a u:b t:1000\",\n    );\n    assert_eq!(\n        \"t:199ms\\nout:↓Y\\nt:10ms\\nout:↑Y\\nt:195ms\\nout:↓X\\nt:10ms\\nout:↑X\",\n        result\n    );\n}\n\nstatic CHORD_WITH_PENDING_UNDERLYING_TAP_HOLD: &str = \"\\\n(defcfg process-unmapped-keys yes concurrent-tap-hold yes)\n(defsrc)\n(deflayermap (base) a (tap-hold 200 200 a b))\n(defchordsv2\n  (b c) d 100 all-released ()\n)\";\n\n#[test]\nfn sim_chord_pending_tap_hold() {\n    let result = simulate(\n        CHORD_WITH_PENDING_UNDERLYING_TAP_HOLD,\n        \"d:a t:10 d:b t:10 d:c t:300\",\n    );\n    // unlike other actions, chordv2 activations\n    // are intentionally not delayed by waiting actions like tap-hold.\n    assert_eq!(\"t:20ms\\nout:↓D\\nt:179ms\\nout:↓B\", result);\n}\n\nstatic CHORD_WITH_TRANSPARENCY: &str = \"\\\n(defcfg process-unmapped-keys yes concurrent-tap-hold yes)\n(defsrc)\n(deflayer base)\n(defchordsv2\n  (a b) _ 100 all-released ()\n)\";\n\n#[test]\n#[should_panic]\nfn sim_denies_transparent() {\n    simulate(CHORD_WITH_TRANSPARENCY, \"\");\n}\n\n#[test]\nfn sim_chord_eager_tapholdpress_activation() {\n    let result = simulate(\n        \"\n    (defcfg concurrent-tap-hold yes)\n    (defsrc caps j k bspc)\n    (deflayer one (tap-hold-press 0 200 esc lctl) j k bspc)\n    (defvirtualkeys bspc bspc)\n    (defchordsv2\n      (j k) (multi\n        (on-press press-vkey bspc)\n        (on-release release-vkey bspc)\n        (fork XX bspc (nop9))) 75 first-release ()\n    )\n        \",\n        \"d:caps t:10 d:j d:k t:100 r:bspc t:10 r:bspc t:10 u:j u:k t:100 u:caps t:1000\",\n    )\n    .to_ascii();\n    assert_eq!(\n        \"t:11ms dn:LCtrl t:7ms dn:BSpace t:92ms \\\n         dn:BSpace t:10ms dn:BSpace t:14ms up:BSpace t:96ms up:LCtrl\",\n        result\n    );\n}\n\n#[test]\nfn sim_chord_eager_tapholdrelease_activation() {\n    let result = simulate(\n        \"\n(defcfg concurrent-tap-hold yes)\n(defsrc)\n(deflayermap (base)\n  caps (tap-hold-release 0 200 esc lctl))\n\n;; defines a vkey named v-$key, example v-bspc\n;; and an alias @v-bspc that press and and releases the v-key\n;; within on-press and on-release respectively.\n(deftemplate v- (key)\n  (defvirtualkeys (concat v- $key) $key)\n  (defalias (concat v- $key)\n    (multi (on-press press-vkey (concat v- $key)) (on-release release-vkey (concat v- $key))))\n)\n\n(t! v- bspc)\n(t! v- del)\n(defchordsv2\n  (j k) @v-bspc 75 first-release ()\n  (s d) @v-del 75 first-release ()\n)\n        \",\n        \"d:caps t:10 d:j d:k t:10 u:j u:k t:100 u:caps t:1000\",\n    )\n    .to_ascii();\n    assert_eq!(\n        \"t:20ms dn:LCtrl t:7ms dn:BSpace t:5ms up:BSpace t:88ms up:LCtrl\",\n        result\n    );\n}\n\n#[test]\nfn sim_chord_release_nonchord_key_has_correct_order() {\n    let result = simulate(\n        \"\n    (defcfg concurrent-tap-hold yes)\n    (defsrc ralt j k)\n    (deflayer base _ _ _)\n    (defchordsv2\n      (j k) l 75 first-release ()\n    )\n        \",\n        \"d:ralt t:1000 d:j t:1 u:ralt t:100 u:j t:100\",\n    )\n    .to_ascii();\n    assert_eq!(\n        \"t:1ms dn:RAlt t:1075ms dn:J t:1ms up:RAlt t:24ms up:J\",\n        result\n    );\n}\n\n#[test]\nfn sim_chord_simultaneous_macro() {\n    let result = simulate(\n        \"\n        (defsrc a b o)\n        (deflayer default\n          (chord base a)\n          (chord base b)\n          (chord base o)\n        )\n        (defchords base 500\n          (a) (macro a z)\n          (b) (macro b)\n          (o) o\n          (a o) o\n        )\n        \",\n        \"d:a t:10 d:b t:500\",\n    )\n    .to_ascii();\n    assert_eq!(\n        \"t:502ms dn:A dn:B t:1ms up:A up:B t:1ms dn:Z t:1ms up:Z\",\n        result\n    );\n}\n\n#[test]\n#[should_panic]\nfn sim_chord_error_on_duplicate_keyset() {\n    simulate(\n        \"\n(defcfg concurrent-tap-hold yes)\n(defsrc)\n(deflayer base)\n(defchordsv2\n (1 2) (one-shot 2000 lsft) 20 all-released ()\n (2 1) (one-shot 2000 lctl) 20 all-released ()\n)\n        \",\n        \"\",\n    );\n}\n\n#[test]\nfn sim_chord_oneshot() {\n    let result = simulate(\n        \"\n(defcfg concurrent-tap-hold yes)\n(defsrc)(deflayer base)\n(defchordsv2\n  (a b) (one-shot 2500 rsft) 35 first-release ()\n)\n        \",\n        \"d:a t:10 d:b t:10 u:a t:10 u:b t:3000 \\\n         d:a t:10 d:b t:10 u:a t:10 u:b t:500 d:c u:c t:3000\",\n    )\n    .to_ascii();\n    assert_eq!(\n        \"t:10ms dn:RShift t:2500ms up:RShift t:530ms \\\n         dn:RShift t:521ms dn:C t:5ms up:RShift up:C\",\n        result\n    );\n}\n\n#[test]\nfn sim_chord_timeout_events() {\n    let result = simulate(\n        \"\n(defcfg\n concurrent-tap-hold yes\n process-unmapped-keys yes\n)\n(defvirtualkeys\n v-macro-word-end (macro spc)\n)\n(defsrc a b c)\n(defchordsv2-experimental\n (a b c) (macro x y z (on-press tap-vkey v-macro-word-end)) 200 all-released ()\n (a b) (macro x y (on-press tap-vkey v-macro-word-end)) 200 all-released ()\n)\n(deflayer base a b c)\n        \",\n        \"d:a t:10 d:b t:3000 u:a u:b t:100\",\n    )\n    .to_ascii();\n    assert_eq!(\n        \"t:201ms dn:X t:1ms up:X t:1ms dn:Y t:1ms up:Y t:4ms dn:Space t:1ms up:Space\",\n        result\n    );\n}\n\n#[test]\nfn sim_chordsv2_quick_release() {\n    let result = simulate(\n        \"\n(defsrc)\n(defcfg concurrent-tap-hold yes)\n(deflayermap layer1)\n(defchordsv2\n (k l) rsft 75 all-released ()\n)\n        \",\n        \"\nd:k d:l t:10\nd:t t:1\nu:k t:1 u:l t:10\nu:t t:10\n\",\n    )\n    .to_ascii();\n    assert_eq!(\"dn:RShift t:11ms dn:T t:4ms up:RShift t:7ms up:T\", result);\n}\n\n#[test]\nfn sim_chordsv1_key_history_updates_once() {\n    let result = simulate(\n        \"\n(defsrc)\n(defchords iobspc 50\n  (i o) bspc\n)\n(deflayermap (base)\n  i (chord iobspc i)\n  o (chord iobspc o)\n  t (switch\n    ((key-history bspc 3)) a fallthrough\n    ((key-history bspc 2)) b fallthrough\n    ((key-history bspc 1)) c fallthrough\n  )\n)\n        \",\n        \"\nd:i d:o t:10\nu:i u:o t:10\nd:t u:t t:10\nd:t u:t t:10\n\",\n    )\n    .to_ascii();\n    // For first press of `t`, only position 1 should be filled.\n    // The key history should unpause, so on pressing `t` again,\n    // position 2 is now where bspc is because C was sent.\n    assert_eq!(\n        \"t:1ms dn:BSpace t:10ms up:BSpace t:10ms dn:C t:1ms up:C t:9ms dn:B t:1ms up:B\",\n        result\n    );\n}\n"
  },
  {
    "path": "src/tests/sim_tests/delay_tests.rs",
    "content": "use super::*;\n\n#[test]\n#[ignore] // timing-based: fails intermittently\nfn on_press_delay_must_be_single_threaded() {\n    let start = std::time::Instant::now();\n    let result = simulate(\n        \"(defsrc) (deflayermap (base) a (on-press-delay 10))\",\n        \"d:a t:50 u:a t:50\",\n    );\n    assert_eq!(\"\", result);\n    let end = std::time::Instant::now();\n    let duration = end - start;\n    assert!(duration > std::time::Duration::from_millis(9));\n    assert!(duration < std::time::Duration::from_millis(19));\n}\n\n#[test]\n#[ignore] // timing-based: fails intermittently\nfn on_release_delay_must_be_single_threaded() {\n    let start = std::time::Instant::now();\n    let result = simulate(\n        \"(defsrc) (deflayermap (base) a (on-release-delay 10))\",\n        \"d:a t:50 u:a t:50\",\n    );\n    assert_eq!(\"\", result);\n    let end = std::time::Instant::now();\n    let duration = end - start;\n    assert!(duration > std::time::Duration::from_millis(9));\n    assert!(duration < std::time::Duration::from_millis(19));\n}\n\n#[test]\n#[ignore] // timing-based: fails intermittently\nfn no_delay_must_be_single_threaded() {\n    let start = std::time::Instant::now();\n    let result = simulate(\"(defsrc) (deflayermap (base) a XX)\", \"d:a t:50 u:a t:50\");\n    assert_eq!(\"\", result);\n    let end = std::time::Instant::now();\n    let duration = end - start;\n    assert!(duration < std::time::Duration::from_millis(10));\n}\n"
  },
  {
    "path": "src/tests/sim_tests/layer_sim_tests.rs",
    "content": "use super::*;\n\n#[test]\nfn transparent_base() {\n    let result = simulate(\n        \"(defcfg process-unmapped-keys yes concurrent-tap-hold yes) \\\n         (defsrc a) \\\n         (deflayer base _)\",\n        \"d:a t:50 u:a t:50\",\n    );\n    assert_eq!(\"out:↓A\\nt:50ms\\nout:↑A\", result);\n}\n\n#[test]\nfn delegate_base() {\n    let result = simulate(\n        \"(defcfg process-unmapped-keys   yes \\\n                 delegate-to-first-layer yes)\n         (defsrc a b) \\\n         (deflayer base c (layer-switch 2)) \\\n         (deflayer 2 _ _)\",\n        \"d:b t:50 u:b t:50 d:a t:50 u:a t:50\",\n    );\n    assert_eq!(\"t:100ms\\nout:↓C\\nt:50ms\\nout:↑C\", result);\n}\n\n#[test]\nfn delegate_base_but_base_is_transparent() {\n    let result = simulate(\n        \"(defcfg process-unmapped-keys   yes \\\n                 delegate-to-first-layer yes)\n         (defsrc a b) \\\n         (deflayer base _ (layer-switch 2)) \\\n         (deflayer 2 _ _)\",\n        \"d:b t:50 u:b t:50 d:a t:50 u:a t:50\",\n    );\n    assert_eq!(\"t:100ms\\nout:↓A\\nt:50ms\\nout:↑A\", result);\n}\n\n#[test]\nfn layer_switching() {\n    let result = simulate(\n        \"(defcfg process-unmapped-keys   yes\n                 delegate-to-first-layer yes)\n         (defsrc a b c d)\n         (deflayer base x y z (layer-switch 2))\n         (deflayer 2 e f _ (layer-switch 3))\n         (deflayer 3 g _ _ (layer-switch 4))\n         (deflayer 4 _ _ _ XX)\n        \",\n        \"d:c t:20 u:c t:20 d:d t:20 u:d t:20\n         d:b t:20 u:b t:20\n         d:c t:20 u:c t:20\n         d:d t:20 u:d t:20\n         d:a t:20 u:a t:20\n         d:b t:20 u:b t:20\n         d:d t:20 u:d t:20\n         d:a t:20 u:a t:20\",\n    );\n    assert_eq!(\n        \"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\",\n        result\n    );\n}\n\n#[test]\nfn layer_holding() {\n    let result = simulate(\n        \"(defcfg process-unmapped-keys   yes\n                 delegate-to-first-layer no)\n         (defsrc a b c d e f)\n         (deflayer base x y z (layer-while-held 2) XX XX)\n         (deflayer 2 e f _ XX (layer-while-held 3) XX)\n         (deflayer 3 g _ _ XX XX (layer-while-held 4))\n         (deflayer 4 _ _ _ XX XX XX)\n        \",\n        \"d:c t:20 u:c t:20\n         d:d t:20\n         d:a t:20 u:a t:20\n         d:b t:20 u:b t:20\n         d:c t:20 u:c t:20\n         d:e t:20\n         d:a t:20 u:a t:20\n         d:b t:20 u:b t:20\n         d:c t:20 u:c t:20\n         d:f t:20\n         d:a t:20 u:a t:20\n         d:b t:20 u:b t:20\n         d:c t:20 u:c t:20\",\n    );\n    assert_eq!(\n        \"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\",\n        result\n    );\n}\n\n// =============================================================================\n// Layer Switch Simulator Input Tests\n// =============================================================================\n// These tests verify the simulator's ability to directly switch layers\n// using the ls:name syntax in simulation input strings.\n\n/// Test ls:layer_name to switch to a named layer\n#[test]\nfn ls_sim_switch_to_layer() {\n    const CFG: &str = r\"\n        (defsrc a b)\n        (deflayer base a b)\n        (deflayer other 1 2)\n    \";\n    // Switch to 'other' layer, then press 'a' which should output '1'\n    let result = simulate(CFG, \"ls:other t:10 d:a t:10 u:a t:10\").to_ascii();\n    assert_eq!(\"t:10ms dn:Kb1 t:10ms up:Kb1\", result);\n}\n\n/// Test ls: can switch between multiple layers\n#[test]\nfn ls_sim_switch_multiple_layers() {\n    const CFG: &str = r\"\n        (defsrc a b)\n        (deflayer base a b)\n        (deflayer num 1 2)\n        (deflayer nav left right)\n    \";\n    // Switch to num, press a (outputs 1), switch to nav, press a (outputs left)\n    let result = simulate(CFG, \"ls:num d:a t:10 u:a t:10 ls:nav d:a t:10 u:a t:10\").to_ascii();\n    assert_eq!(\"dn:Kb1 t:10ms up:Kb1 t:10ms dn:Left t:10ms up:Left\", result);\n}\n\n/// Test ls: can switch back to base layer\n#[test]\nfn ls_sim_switch_back_to_base() {\n    const CFG: &str = r\"\n        (defsrc a)\n        (deflayer base a)\n        (deflayer other 1)\n    \";\n    // Switch to other (a->1), switch back to base (a->a)\n    let result = simulate(CFG, \"ls:other d:a t:10 u:a t:10 ls:base d:a t:10 u:a t:10\").to_ascii();\n    assert_eq!(\"dn:Kb1 t:10ms up:Kb1 t:10ms dn:A t:10ms up:A\", result);\n}\n\n/// Test layer-switch: as alternative syntax\n#[test]\nfn ls_sim_layer_switch_prefix() {\n    const CFG: &str = r\"\n        (defsrc a)\n        (deflayer base a)\n        (deflayer other 1)\n    \";\n    let result = simulate(CFG, \"layer-switch:other d:a t:10 u:a t:10\").to_ascii();\n    assert_eq!(\"dn:Kb1 t:10ms up:Kb1\", result);\n}\n\n/// Test ls: with delegate-to-first-layer - transparent keys should delegate to base\n#[test]\nfn ls_sim_with_delegate_to_first_layer() {\n    const CFG: &str = r\"\n        (defcfg process-unmapped-keys yes\n                delegate-to-first-layer yes)\n        (defsrc a b)\n        (deflayer base x y)\n        (deflayer nav left _)\n    \";\n    // Switch to nav: 'a' -> left, 'b' -> transparent -> delegates to base -> y\n    let result = simulate(CFG, \"ls:nav d:a t:10 u:a t:10 d:b t:10 u:b t:10\").to_ascii();\n    assert_eq!(\"dn:Left t:10ms up:Left t:10ms dn:Y t:10ms up:Y\", result);\n}\n\n/// Test ls: with transparent keys but NO delegate-to-first-layer\n#[test]\nfn ls_sim_transparent_no_delegate() {\n    const CFG: &str = r\"\n        (defcfg process-unmapped-keys yes\n                delegate-to-first-layer no)\n        (defsrc a b)\n        (deflayer base x y)\n        (deflayer nav left _)\n    \";\n    // Switch to nav: 'a' -> left, 'b' -> transparent -> passthrough (outputs B)\n    let result = simulate(CFG, \"ls:nav d:a t:10 u:a t:10 d:b t:10 u:b t:10\").to_ascii();\n    assert_eq!(\"dn:Left t:10ms up:Left t:10ms dn:B t:10ms up:B\", result);\n}\n\n// =============================================================================\n// End Layer Switch Simulator Input Tests\n// =============================================================================\n"
  },
  {
    "path": "src/tests/sim_tests/macro_sim_tests.rs",
    "content": "use super::*;\n\n#[test]\nfn macro_cancel_on_press() {\n    let cfg = \"\\\n(defsrc a b c)\n(deflayer base (macro-cancel-on-press z 100 y) (macro x 100 w) c)\";\n    test_on_press(cfg);\n    let cfg = \"\\\n(defsrc a b c)\n(deflayer base (macro-repeat-cancel-on-press z 100 y 100) (macro x 100 w) c)\";\n    test_on_press(cfg);\n}\n\nfn test_on_press(cfg: &str) {\n    // Cancellation should happen.\n    let result = simulate(cfg, \"d:a t:50 d:c t:100\").to_ascii();\n    assert_eq!(\"t:1ms dn:Z t:1ms up:Z t:48ms dn:C\", result);\n    // Macro should complete if allowed to.\n    let result = simulate(cfg, \"d:a u:a t:150 d:c t:100\").to_ascii();\n    assert_eq!(\n        \"t:1ms dn:Z t:1ms up:Z t:101ms dn:Y t:1ms up:Y t:46ms dn:C\",\n        result\n    );\n    // The window for macro cancellation should not persist to a new macro that is not cancellable.\n    let result = simulate(cfg, \"d:a t:120 d:b t:20 d:c t:100\").to_ascii();\n    assert_eq!(\n        \"t:1ms dn:Z t:1ms up:Z t:101ms dn:Y t:1ms up:Y \\\n                t:17ms dn:X t:1ms up:X t:18ms dn:C t:83ms dn:W t:1ms up:W\",\n        result\n    );\n    let result = simulate(cfg, \"d:a t:10 d:c u:c t:10 d:b t:20 d:c t:100\").to_ascii();\n    assert_eq!(\n        \"t:1ms dn:Z t:1ms up:Z t:8ms dn:C t:1ms up:C t:10ms \\\n                dn:X t:1ms up:X t:18ms dn:C t:83ms dn:W t:1ms up:W\",\n        result\n    );\n}\n\n#[test]\nfn macro_release_cancel_and_cancel_on_press() {\n    let cfg = \"\\\n(defsrc a b c)\n(deflayer base (macro-release-cancel-and-cancel-on-press z 100 y 100) (macro x 100 w) c)\";\n    test_release_and_on_press(cfg);\n    let cfg = \"\\\n(defsrc a b c)\n(deflayer base (macro-repeat-release-cancel-and-cancel-on-press z 100 y 100) (macro x 100 w) c)\";\n    test_release_and_on_press(cfg);\n}\n\nfn test_release_and_on_press(cfg: &str) {\n    // Cancellation should happen for press.\n    let result = simulate(cfg, \"d:a t:50 d:c t:100\").to_ascii();\n    assert_eq!(\"t:1ms dn:Z t:1ms up:Z t:48ms dn:C\", result);\n    // Cancellation should happen for release\n    let result = simulate(cfg, \"d:a u:a t:150 d:c t:100\").to_ascii();\n    assert_eq!(\"t:1ms dn:Z t:1ms up:Z t:148ms dn:C\", result);\n    // Macro should complete if allowed to.\n    let result = simulate(cfg, \"d:a t:150 d:c t:100\").to_ascii();\n    assert_eq!(\n        \"t:1ms dn:Z t:1ms up:Z t:101ms dn:Y t:1ms up:Y t:46ms dn:C\",\n        result\n    );\n    // The window for macro cancellation should not persist to a new macro that is not cancellable.\n    let result = simulate(cfg, \"d:a t:120 d:b t:20 d:c t:100\").to_ascii();\n    assert_eq!(\n        \"t:1ms dn:Z t:1ms up:Z t:101ms dn:Y t:1ms up:Y \\\n                t:17ms dn:X t:1ms up:X t:18ms dn:C t:83ms dn:W t:1ms up:W\",\n        result\n    );\n    let result = simulate(cfg, \"d:a t:10 d:c u:c t:10 d:b t:20 d:c t:100\").to_ascii();\n    assert_eq!(\n        \"t:1ms dn:Z t:1ms up:Z t:8ms dn:C t:1ms up:C t:10ms \\\n                dn:X t:1ms up:X t:18ms dn:C t:83ms dn:W t:1ms up:W\",\n        result\n    );\n    let result = simulate(cfg, \"d:a u:a t:10 t:10 d:b u:b t:20 d:c t:100\").to_ascii();\n    assert_eq!(\n        \"t:1ms dn:Z t:1ms up:Z t:19ms \\\n         dn:X t:1ms up:X t:18ms dn:C t:83ms dn:W t:1ms up:W\",\n        result\n    );\n}\n\n#[test]\nfn macro_repeat() {\n    let cfg = \"\\\n(defsrc a b c d)\n(deflayer base\n (macro-repeat Digit1 50)\n (macro-repeat-release-cancel Digit1 50)\n (macro-repeat-cancel-on-press Digit1 50)\n (macro-repeat-release-cancel-and-cancel-on-press Digit1 50))\";\n    let result = simulate(cfg, \"d:a t:125 u:a\").to_ascii();\n    assert_eq!(\n        \"t:1ms dn:Kb1 t:1ms up:Kb1 t:52ms dn:Kb1 t:1ms up:Kb1 t:52ms dn:Kb1 t:1ms up:Kb1\",\n        result\n    );\n    let result = simulate(cfg, \"d:b t:125 u:b\").to_ascii();\n    assert_eq!(\n        \"t:1ms dn:Kb1 t:1ms up:Kb1 t:52ms dn:Kb1 t:1ms up:Kb1 t:52ms dn:Kb1 t:1ms up:Kb1\",\n        result\n    );\n    let result = simulate(cfg, \"d:c t:125 u:c\").to_ascii();\n    assert_eq!(\n        \"t:1ms dn:Kb1 t:1ms up:Kb1 t:52ms dn:Kb1 t:1ms up:Kb1 t:52ms dn:Kb1 t:1ms up:Kb1\",\n        result\n    );\n    let result = simulate(cfg, \"d:d t:125 u:d\").to_ascii();\n    assert_eq!(\n        \"t:1ms dn:Kb1 t:1ms up:Kb1 t:52ms dn:Kb1 t:1ms up:Kb1 t:52ms dn:Kb1 t:1ms up:Kb1\",\n        result\n    );\n}\n\n#[test]\nfn macro_release_cancel() {\n    let cfg = \"\\\n(defsrc a b c)\n(deflayer base (macro-release-cancel z 100 y 100) (macro x 100 w) c)\";\n    test_release(cfg);\n    let cfg = \"\\\n(defsrc a b c)\n(deflayer base (macro-repeat-release-cancel z 100 y 100) (macro x 100 w) c)\";\n    test_release(cfg);\n}\n\nfn test_release(cfg: &str) {\n    // Cancellation should not happen for press.\n    let result = simulate(cfg, \"d:a t:50 d:c t:100\").to_ascii();\n    assert_eq!(\n        \"t:1ms dn:Z t:1ms up:Z t:48ms dn:C t:53ms dn:Y t:1ms up:Y\",\n        result\n    );\n    // Cancellation should happen for release\n    let result = simulate(cfg, \"d:a u:a t:150 d:c t:100\").to_ascii();\n    assert_eq!(\"t:1ms dn:Z t:1ms up:Z t:148ms dn:C\", result);\n    // Macro should complete if allowed to.\n    let result = simulate(cfg, \"d:a t:150 d:c t:20\").to_ascii();\n    assert_eq!(\n        \"t:1ms dn:Z t:1ms up:Z t:101ms dn:Y t:1ms up:Y t:46ms dn:C\",\n        result\n    );\n}\n"
  },
  {
    "path": "src/tests/sim_tests/mod.rs",
    "content": "//! Contains tests that use simulated inputs.\n//!\n//! One way to write tests is to write the configuration, write the simulated input, and then let\n//! the test fail by comparing the output to an empty string. Run the test then inspect the failure\n//! and see if the real output looks sensible according to what is expected.\n\nuse crate::tests::*;\nuse crate::{\n    FAKE_KEY_ROW, FakeKeyAction, Kanata,\n    kanata::handle_fakekey_action,\n    oskbd::{KeyEvent, KeyValue},\n    str_to_oscode,\n};\n\nuse rustc_hash::FxHashMap;\n\n/// Parse a fakekey specification like \"name\" or \"name:action\"\n/// Returns (name, action) where action defaults to Press if not specified.\nfn parse_fakekey_spec(spec: &str) -> (&str, FakeKeyAction) {\n    match spec.split_once(':') {\n        Some((name, action_str)) => {\n            let action = match action_str {\n                \"press\" | \"p\" => FakeKeyAction::Press,\n                \"release\" => FakeKeyAction::Release,\n                \"tap\" | \"t\" => FakeKeyAction::Tap,\n                \"toggle\" | \"g\" => FakeKeyAction::Toggle,\n                _ => panic!(\n                    \"unknown fakekey action: {action_str}. Expected: press, release, tap, or toggle\"\n                ),\n            };\n            (name, action)\n        }\n        None => (spec, FakeKeyAction::Press),\n    }\n}\n\n/// Apply a fakekey action to the kanata instance\nfn apply_fakekey_action(k: &mut Kanata, name: &str, action: FakeKeyAction) {\n    let index = k\n        .virtual_keys\n        .get(name)\n        .unwrap_or_else(|| panic!(\"unknown virtual key: {name}\"));\n    handle_fakekey_action(action, k.layout.bm(), FAKE_KEY_ROW, *index as u16);\n}\n\n/// Apply a layer switch to the kanata instance\nfn apply_layer_switch(k: &mut Kanata, layer_name: &str) {\n    let layer_idx = k\n        .layer_info\n        .iter()\n        .position(|l| l.name == layer_name)\n        .unwrap_or_else(|| panic!(\"unknown layer: {layer_name}\"));\n    k.layout.bm().set_default_layer(layer_idx);\n}\n\nmod block_keys_tests;\nmod capsword_sim_tests;\nmod chord_sim_tests;\nmod delay_tests;\nmod layer_sim_tests;\nmod macro_sim_tests;\nmod oneshot_tests;\nmod output_chord_tests;\nmod override_tests;\nmod release_sim_tests;\nmod repeat_sim_tests;\nmod seq_sim_tests;\nmod switch_sim_tests;\nmod tap_dance_tests;\nmod tap_hold_tests;\nmod template_sim_tests;\nmod timing_tests;\nmod unicode_sim_tests;\nmod unmod_sim_tests;\nmod use_defsrc_sim_tests;\nmod vkey_sim_tests;\n#[cfg(feature = \"zippychord\")]\nmod zippychord_sim_tests;\n\nfn simulate<S: AsRef<str>>(cfg: S, sim: S) -> String {\n    simulate_with_file_content(cfg, sim, Default::default())\n}\n\nfn simulate_with_file_content<S: AsRef<str>>(\n    cfg: S,\n    sim: S,\n    file_content: FxHashMap<String, String>,\n) -> String {\n    init_log();\n    let _lk = match CFG_PARSE_LOCK.lock() {\n        Ok(guard) => guard,\n        Err(poisoned) => poisoned.into_inner(),\n    };\n    let mut k = Kanata::new_from_str(cfg.as_ref(), file_content).expect(\"failed to parse cfg\");\n    for pair in sim.as_ref().split_whitespace() {\n        match pair.split_once(':') {\n            Some((kind, val)) => match kind {\n                \"t\" => {\n                    let ticks = str::parse::<u128>(val).expect(\"valid num for tick\");\n                    for _ in 0..ticks {\n                        let _ = k.tick_ms(1, &None);\n                        let _ = k.can_block_update_idle_waiting(1);\n                    }\n                }\n                \"d\" => {\n                    let key_code = str_to_oscode(val).expect(\"valid keycode\");\n                    k.handle_input_event(&KeyEvent {\n                        code: key_code,\n                        value: KeyValue::Press,\n                    })\n                    .expect(\"input handles fine\");\n                    #[cfg(not(all(target_os = \"windows\", not(feature = \"interception_driver\"))))]\n                    crate::PRESSED_KEYS.lock().insert(key_code);\n                    #[cfg(all(target_os = \"windows\", not(feature = \"interception_driver\")))]\n                    crate::PRESSED_KEYS\n                        .lock()\n                        .insert(key_code, web_time::Instant::now());\n                }\n                \"u\" => {\n                    let key_code = str_to_oscode(val).expect(\"valid keycode\");\n                    k.handle_input_event(&KeyEvent {\n                        code: key_code,\n                        value: KeyValue::Release,\n                    })\n                    .expect(\"input handles fine\");\n                    crate::PRESSED_KEYS.lock().remove(&key_code);\n                }\n                \"r\" => {\n                    let key_code = str_to_oscode(val).expect(\"valid keycode\");\n                    k.handle_input_event(&KeyEvent {\n                        code: key_code,\n                        value: KeyValue::Repeat,\n                    })\n                    .expect(\"input handles fine\");\n                }\n                // Virtual/fake key activation: vk:name[:action] or fakekey:name[:action]\n                // Supported actions: press (p), release, tap (t), toggle (g)\n                \"vk\" | \"fakekey\" | \"virtualkey\" | \"🎭\" => {\n                    let (vk_name, action) = parse_fakekey_spec(val);\n                    apply_fakekey_action(&mut k, vk_name, action);\n                }\n                // Layer switch: ls:layer_name\n                \"ls\" | \"layer-switch\" | \"🔀\" => {\n                    apply_layer_switch(&mut k, val);\n                }\n                _ => panic!(\"invalid item {pair}\"),\n            },\n            None => panic!(\"invalid item {pair}\"),\n        }\n    }\n    drop(_lk);\n    k.kbd_out.outputs.events.join(\"\\n\")\n}\n\n#[allow(unused)]\ntrait SimTransform {\n    /// Changes newlines to spaces.\n    fn to_spaces(self) -> Self;\n    /// Removes out:↑_ items from the string. Also transforms newlines to spaces.\n    fn no_releases(self) -> Self;\n    /// Removes t:_ms items from the string. Also transforms newlines to spaces.\n    fn no_time(self) -> Self;\n    /// Replaces out:↓_ with dn:_ and out:↑_ with up:_. Also transforms newlines to spaces.\n    fn to_ascii(self) -> Self;\n}\n\nimpl SimTransform for String {\n    fn to_spaces(self) -> Self {\n        self.replace('\\n', \" \")\n    }\n\n    fn no_time(self) -> Self {\n        self.split_ascii_whitespace()\n            .filter(|s| !s.starts_with(\"t:\"))\n            .collect::<Vec<_>>()\n            .join(\" \")\n    }\n\n    fn no_releases(self) -> Self {\n        self.split_ascii_whitespace()\n            .filter(|s| !s.starts_with(\"out:↑\") && !s.starts_with(\"up:\"))\n            .collect::<Vec<_>>()\n            .join(\" \")\n    }\n\n    fn to_ascii(self) -> Self {\n        self.split_ascii_whitespace()\n            .map(|s| s.replace(\"out:↑\", \"up:\").replace(\"out:↓\", \"dn:\"))\n            .collect::<Vec<_>>()\n            .join(\" \")\n    }\n}\n"
  },
  {
    "path": "src/tests/sim_tests/oneshot_tests.rs",
    "content": "use super::*;\n\n#[test]\nfn oneshot_pause() {\n    let result = simulate(\n        \"\n(defsrc a lmet rmet)\n(deflayer base\n  1 @lme @rme)\n(deflayer numbers\n  2 @lme @rme)\n(deflayer navigation\n  (one-shot 2000 lalt) @lme @rme)\n(deflayer symbols\n  4 @lme @rme)\n\n(defvirtualkeys\n  callum (switch\n    ((and nop1 nop2)) (layer-while-held numbers) break\n    (nop1) (layer-while-held navigation) break\n    (nop2) (layer-while-held symbols) break)\n  activate-callum (multi\n   (one-shot-pause-processing 5)\n   (switch\n    ((or nop1 nop2))\n     (multi (on-press release-vkey callum)\n            (on-press press-vkey callum))\n     break\n    () (on-press release-vkey callum) break)))\n\n(defalias\n  lme (multi nop1\n             (on-press tap-vkey activate-callum)\n             (on-release tap-vkey activate-callum))\n  rme (multi nop2\n             (on-press tap-vkey activate-callum)\n             (on-release tap-vkey activate-callum)))\n        \",\n        \"d:lmet t:10 d:a u:a t:10 u:lmet t:10 d:a u:a t:10\",\n    )\n    .to_ascii();\n    assert_eq!(\"t:10ms dn:LAlt t:20ms dn:Kb1 t:5ms up:LAlt up:Kb1\", result);\n}\n\n#[test]\nfn oneshot_multi_with_chord() {\n    let result = simulate(\n        \"(defsrc a)\n         (deflayer test (multi a (one-shot 1000 C-z)))\",\n        \"d:KeyA t:10 u:KeyA t:200 d:KeyC t:1000\",\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:A dn:LCtrl dn:Z t:10ms up:A t:200ms dn:C t:5ms up:LCtrl up:Z\",\n        result\n    );\n}\n\n#[test]\nfn oneshot_multi_with_layer() {\n    let result = simulate(\n        \"(defsrc a)\n         (deflayer l1 (multi a (one-shot 100 (layer-while-held l2))))\n         (deflayer l2 b)\n        \",\n        \"d:KeyA t:10 u:KeyA t:10 d:KeyA t:10 u:KeyA t:1000\",\n    )\n    .to_ascii();\n    // Known bug:\n    // The 5ms should be 10ms.\n    // The B is released (with instant action delay) even when it shouldn't\n    // because one-shot completion is releasing everything\n    // that is on the same coordinate as it.\n    //   issue: -------------------------------.\n    //                                         v\n    assert_eq!(\"dn:A t:10ms up:A t:10ms dn:B t:5ms up:B\", result);\n}\n"
  },
  {
    "path": "src/tests/sim_tests/output_chord_tests.rs",
    "content": "use super::*;\n\n#[test]\nfn output_chord_samekey_has_release() {\n    let config = \"\n        (defsrc a b)\n        (deflayer _ S-= =)\n        \";\n    let result = simulate(config, \"d:a t:10 d:b t:10\").to_ascii();\n    assert_eq!(\n        \"dn:LShift dn:Equal t:10ms up:LShift up:Equal t:1ms dn:Equal\",\n        result\n    );\n    let result = simulate(config, \"d:b t:10 d:a t:10\").to_ascii();\n    assert_eq!(\"dn:Equal t:10ms up:Equal dn:LShift t:1ms dn:Equal\", result);\n}\n\n#[test]\nfn output_chord_follows_processing_delay_config() {\n    let result = simulate(\n        \"\n        (defsrc)\n        (deflayermap (base)\n         a S-9\n         b S-0)\n        \",\n        \"d:a t:10 d:b t:10 u:b t:10 u:a t:10\",\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:LShift dn:Kb9 t:10ms up:Kb9 dn:Kb0 t:10ms up:LShift up:Kb0\",\n        result\n    );\n}\n"
  },
  {
    "path": "src/tests/sim_tests/override_tests.rs",
    "content": "use super::*;\n\n#[test]\nfn override_with_unmod() {\n    let result = simulate(\n        \"\n(defoverrides\n (a) (b)\n (b) (a)\n)\n\n(defalias\n b (unshift b)\n a (unshift a)\n)\n(defsrc a b)\n(deflayer base @a @b)\n        \",\n        \"d:lsft t:50 d:a t:50 u:a t:50 d:b t:50 u:b t:50\",\n    )\n    .to_ascii()\n    .no_time();\n    assert_eq!(\n        \"dn:LShift up:LShift dn:B up:B dn:LShift up:LShift dn:A up:A dn:LShift\",\n        result\n    );\n}\n\n#[test]\nfn override_release_mod_change_key() {\n    let cfg = \"\n(defsrc)\n(deflayer base)\n(defoverrides\n  (lsft a) (lsft 9)\n  (lsft 1) (lctl 2))\n        \";\n    let result = simulate(cfg, \"d:lsft t:10 d:a t:10 u:lsft t:10 u:a t:10\").to_ascii();\n    assert_eq!(\"dn:LShift t:10ms dn:Kb9 t:10ms up:LShift up:Kb9\", result);\n    let result = simulate(cfg, \"d:lsft t:10 d:a t:10 u:a t:10 u:lsft t:10\").to_ascii();\n    assert_eq!(\n        \"dn:LShift t:10ms dn:Kb9 t:10ms up:Kb9 t:10ms up:LShift\",\n        result\n    );\n    let result = simulate(cfg, \"d:lsft t:10 d:a t:10 d:c t:10\").to_ascii();\n    assert_eq!(\"dn:LShift t:10ms dn:Kb9 t:10ms up:Kb9 dn:C\", result);\n    let result = simulate(cfg, \"d:lsft t:10 d:1 t:10 d:c t:10\").to_ascii();\n    assert_eq!(\n        \"dn:LShift t:10ms up:LShift dn:LCtrl dn:Kb2 t:10ms up:LCtrl up:Kb2 dn:LShift dn:C\",\n        result\n    );\n}\n\n#[test]\nfn override_eagerly_releases() {\n    let result = simulate(\n        \"\n(defcfg override-release-on-activation yes)\n(defsrc)\n(deflayer base)\n(defoverrides (lsft a) (lsft 9))\n        \",\n        \"d:lsft t:10 d:a t:10 u:lsft t:10 u:a t:10\",\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:LShift t:10ms dn:Kb9 t:1ms up:Kb9 t:9ms up:LShift\",\n        result\n    );\n}\n\n#[test]\n#[should_panic]\nfn config_with_both_overrides() {\n    simulate(\n        \"\n(defcfg override-release-on-activation yes)\n(defsrc)\n(deflayer base)\n(defoverrides (lsft a) (lsft 9))\n(defoverridesv2 (lsft a) (lsft 9) () ())\n        \",\n        \"\",\n    );\n}\n\n#[test]\n#[should_panic]\nfn config_with_two_overrides() {\n    simulate(\n        \"\n(defcfg override-release-on-activation yes)\n(defsrc)\n(deflayer base)\n(defoverrides (lsft a) (lsft 9))\n(defoverrides (lsft a) (lsft 9))\n        \",\n        \"\",\n    );\n}\n\n#[test]\n#[should_panic]\nfn config_with_both_overridesv2() {\n    simulate(\n        \"\n(defcfg override-release-on-activation yes)\n(defsrc)\n(deflayer base)\n(defoverridesv2 (lsft a) (lsft 9) () ())\n(defoverridesv2 (lsft a) (lsft 9) () ())\n        \",\n        \"\",\n    );\n}\n\n#[test]\nfn config_with_overridev2() {\n    let cfg = \"\n(defsrc)\n(deflayermap (l1) b (layer-switch l2))\n(deflayermap (l2) b (layer-switch l1))\n(defoverridesv2 (lsft a) (lsft 9) (lctl) (l2))\n        \";\n\n    // Override works.\n    let result = simulate(cfg, \"d:lctl d:lsft d:a t:10 u:a u:lsft u:lctl t:10\").to_ascii();\n    assert_eq!(\n        \"dn:LCtrl t:1ms dn:LShift t:1ms dn:A t:8ms up:A t:1ms up:LShift t:1ms up:LCtrl\",\n        result\n    );\n\n    // Override doesn't apply while lctl is held\n    let result = simulate(cfg, \"d:lctl d:lsft d:a u:a t:10 u:lctl t:10 u:lsft t:10\").to_ascii();\n    assert_eq!(\n        \"dn:LCtrl t:1ms dn:LShift t:1ms dn:A t:1ms up:A t:7ms up:LCtrl t:10ms up:LShift\",\n        result\n    );\n    let result = simulate(cfg, \"d:lctl d:lsft d:a t:10 u:lctl t:10 u:lsft u:a t:10\").to_ascii();\n    assert_eq!(\n        // Note that this may not be desirable...\n        // One could imagine that the desired behavior is that A is released, or that A remains\n        // held, so that irrespective of which key is released first, the 9 never activates.\n        //\n        // Doing the latter not-implemented behaviour is more complicated in code though, so for\n        // now stick to \"worse is better\" and keep it as-is.\n        // The user workaround is to always release the A before releasing modifiers.\n        \"dn:LCtrl t:1ms dn:LShift t:1ms dn:A t:8ms up:LCtrl up:A dn:Kb9 t:10ms up:LShift up:Kb9\",\n        result\n    );\n\n    // Override doesn't apply while on disabled layer is held\n    let result = simulate(\n        cfg,\n        \"d:b u:b \\\n         t:10 d:lsft d:a t:10 u:lsft t:10 u:a t:10 \\\n         d:b u:b \\\n         t:10 d:lsft d:a t:10 u:lsft t:10 u:a t:10\",\n    )\n    .to_ascii();\n    assert_eq!(\n        \"t:10ms dn:LShift t:1ms dn:A t:9ms up:LShift t:10ms up:A \\\n         t:20ms dn:LShift t:1ms dn:Kb9 t:9ms up:LShift up:Kb9\",\n        result\n    );\n}\n"
  },
  {
    "path": "src/tests/sim_tests/release_sim_tests.rs",
    "content": "use super::*;\n\n#[test]\nfn release_standard() {\n    let result = simulate(\n        \"\n         (defsrc a)\n         (deflayer base (multi lalt a))\n        \",\n        \"\n         d:a t:10 u:a t:10\n        \",\n    )\n    .to_ascii();\n    assert_eq!(\"dn:LAlt dn:A t:10ms up:LAlt up:A\", result);\n}\n\n#[test]\nfn release_reversed() {\n    let result = simulate(\n        \"\n         (defsrc a)\n         (deflayer base (multi lalt a reverse-release-order))\n        \",\n        \"\n         d:a t:10 u:a t:10\n        \",\n    )\n    .to_ascii();\n    assert_eq!(\"dn:LAlt dn:A t:10ms up:A up:LAlt\", result);\n}\n"
  },
  {
    "path": "src/tests/sim_tests/repeat_sim_tests.rs",
    "content": "use super::*;\n\n#[test]\nfn repeat_standard() {\n    let result = simulate(\n        \"\n         (defsrc a)\n         (deflayer base b)\n        \",\n        \"\n         d:a t:10 r:a t:10 r:a t:10 u:a t:10 r:a\n        \",\n    );\n    assert_eq!(\n        \"out:↓B\\nt:10ms\\nout:↓B\\nt:10ms\\nout:↓B\\nt:10ms\\nout:↑B\",\n        result\n    );\n}\n\n#[test]\nfn repeat_layer_while_held() {\n    let result = simulate(\n        \"\n         (defsrc a b)\n         (deflayer base a (layer-while-held held))\n         (deflayer held c b)\n        \",\n        \"\n         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\n        \",\n    );\n    assert_eq!(\n        \"t:20ms\\nout:↓C\\nt:10ms\\nout:↓C\\nt:10ms\\nout:↓C\\nt:10ms\\nout:↑C\",\n        result\n    );\n}\n\n#[test]\nfn repeat_layer_switch() {\n    let result = simulate(\n        \"\n         (defsrc a b)\n         (deflayer base a (layer-switch swtc))\n         (deflayer swtc d b)\n        \",\n        \"\n         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\n        \",\n    );\n    assert_eq!(\n        \"t:20ms\\nout:↓D\\nt:10ms\\nout:↓D\\nt:10ms\\nout:↓D\\nt:10ms\\nout:↑D\",\n        result\n    );\n}\n\n#[test]\nfn repeat_layer_held_trans() {\n    let result = simulate(\n        \"\n         (defsrc a b)\n         (deflayer base e (layer-while-held held))\n         (deflayer held _ b)\n        \",\n        \"\n         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\n        \",\n    );\n    assert_eq!(\n        \"t:20ms\\nout:↓E\\nt:10ms\\nout:↓E\\nt:10ms\\nout:↓E\\nt:10ms\\nout:↑E\",\n        result\n    );\n}\n\n#[test]\nfn repeat_many_layer_held_trans() {\n    let result = simulate(\n        \"\n         (defsrc a b c d e)\n         (deflayer base e (layer-while-held held1) _ _ _)\n         (deflayer held1 f b (layer-while-held held2) _ _)\n         (deflayer held2 _ _ _ (layer-while-held held3) _)\n         (deflayer held3 _ _ _ _ (layer-while-held held4))\n         (deflayer held4 _ _ _ _ _)\n        \",\n        \"\n         d:b t:10 r:b t:10\n         d:c t:10 r:c t:10\n         d:d t:10 r:d t:10\n         d:e t:10 r:e t:10\n         d:a t:10 r:a t:10 r:a t:10 u:a t:10 r:a\n        \",\n    );\n    assert_eq!(\n        \"t:80ms\\nout:↓F\\nt:10ms\\nout:↓F\\nt:10ms\\nout:↓F\\nt:10ms\\nout:↑F\",\n        result\n    );\n}\n\n#[test]\nfn repeat_base_layer_trans() {\n    let result = simulate(\n        \"\n         (defsrc a)\n         (deflayer base _)\n        \",\n        \"\n         d:a t:10 r:a t:10 r:a t:10 u:a t:10 r:a\n        \",\n    );\n    assert_eq!(\n        \"out:↓A\\nt:10ms\\nout:↓A\\nt:10ms\\nout:↓A\\nt:10ms\\nout:↑A\",\n        result\n    );\n}\n\n#[test]\nfn repeat_delegate_to_base_layer_trans() {\n    let result = simulate(\n        \"\n         (defcfg delegate-to-first-layer yes)\n         (defsrc a c b)\n         (deflayer base e _ (layer-switch swtc))\n         (deflayer swtc _ _ _)\n        \",\n        \"\n         d:b t:10 r:b t:10\n         d:a t:10 r:a t:10 r:a t:10 u:a t:10 r:a\n         d:c t:10 r:c t:10 r:c t:10 u:c t:10 r:c\n        \",\n    );\n    assert_eq!(\n        \"t:20ms\\nout:↓E\\nt:10ms\\nout:↓E\\nt:10ms\\nout:↓E\\nt:10ms\\nout:↑E\\n\\\n         t:10ms\\nout:↓C\\nt:10ms\\nout:↓C\\nt:10ms\\nout:↓C\\nt:10ms\\nout:↑C\",\n        result\n    );\n}\n"
  },
  {
    "path": "src/tests/sim_tests/seq_sim_tests.rs",
    "content": "use super::*;\n\n#[test]\nfn special_nop_keys() {\n    let result = simulate(\n        \"(defcfg sequence-input-mode visible-backspaced)\n         (defsrc a b c d e)\n         (deflayer base sldr nop0 c nop9 0)\n         (defvirtualkeys s1 (macro h i))\n         (defseq s1 (nop0 c nop9))\n        \",\n        \"d:b d:d t:50 u:b u:d t:50\n         d:a d:b d:c t:50 u:a u:b u:c t:50 d:d t:50\",\n    );\n    assert_eq!(\n        \"t:102ms\\nout:↓C\\nt:50ms\\nout:↑C\\nt:48ms\\n\\\n        out:↓BSpace\\nout:↑BSpace\\n\\\n        t:2ms\\nout:↓H\\nt:1ms\\nout:↑H\\nt:1ms\\nout:↓I\\nt:1ms\\nout:↑I\",\n        result\n    );\n}\n\n#[test]\nfn chorded_keys_visible_backspaced() {\n    let result = simulate(\n        \"(defcfg sequence-input-mode visible-backspaced)\n         (defsrc 0)\n         (deflayer base sldr)\n         (defvirtualkeys s1 z)\n         (defseq s1 (S-(a b)))\n        \",\n        \"d:0 u:0 d:lsft t:50 d:a d:b t:50 u:lsft u:a u:b t:500\n         d:0 u:0 d:rsft t:50 d:a d:b t:50 u:rsft u:a u:b t:500\n         d:0 u:0 d:rsft t:50 d:a u:rsft t:50 d:b u:a u:b t:500\",\n    )\n    .no_time()\n    .to_ascii();\n    assert_eq!(\n        // 2nd row is buggy/unexpected! RShift isn't released before outputting Z\n        // Workarounds:\n        // - remap your rsft key to lsft\n        // - use release-key in the macro via virtual keys\n        // - accept and use the quirky behaviour; maybe it's what you wanted?\n        \"dn:LShift dn:A dn:B dn:BSpace up:BSpace dn:BSpace up:BSpace up:LShift up:A up:B dn:Z up:Z \\\n         dn:RShift dn:A dn:B dn:BSpace up:BSpace dn:BSpace up:BSpace up:A up:B dn:Z up:Z up:RShift \\\n         dn:RShift dn:A up:RShift dn:B up:A up:B\",\n        result\n    );\n}\n\nconst OVERLAP_CFG: &str = \"\n    (defcfg sequence-input-mode visible-backspaced)\n    (defsrc 0)\n    (deflayer base sldr)\n    (defvirtualkeys s1 y)\n    (defvirtualkeys s2 z)\n    (defvirtualkeys s3 l)\n    (defvirtualkeys s4 m)\n    (defvirtualkeys s5 n)\n    (defvirtualkeys s6 o)\n    (defvirtualkeys s7 p)\n    (defvirtualkeys s8 q)\n    (defseq s1 (O-(a b)))\n    (defseq s2 (a b))\n    (defseq s3 (O-(c d) e))\n    (defseq s4 (c d e))\n    (defseq s5 (O-(c d) O-(f g)))\n    (defseq s6 (O-(c d) f g))\n    (defseq s7 (c d O-(f g)))\n    ;; (defseq s8 (c d f g)) KNOWN BUGGY CASE! breaks s6 detection\n    \";\n\n#[test]\nfn overlapping_activate_overlap() {\n    let result = simulate(OVERLAP_CFG, \"d:0 d:a d:b t:100 u:a u:b u:0\");\n    assert_eq!(\n        \"t:1ms\\nout:↓A\\nt:1ms\\nout:↓B\\n\\\n         out:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\nt:1ms\\nout:↑A\\nout:↑B\\n\\\n         out:↓Y\\nt:1ms\\nout:↑Y\",\n        result\n    );\n}\n\n#[test]\nfn overlapping_activate_nonoverlap() {\n    let result = simulate(OVERLAP_CFG, \"d:0 d:a t:10 u:a t:10 d:b t:10 u:b t:10 u:0\");\n    assert_eq!(\n        \"t:1ms\\nout:↓A\\nt:9ms\\nout:↑A\\nt:10ms\\nout:↓B\\n\\\n        out:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\n\\\n        t:1ms\\nout:↑B\\nout:↓Z\\nt:1ms\\nout:↑Z\",\n        result\n    );\n}\n\n#[test]\nfn overlapping_then_nonoverlap_activate_overlap() {\n    let result = simulate(OVERLAP_CFG, \"d:0 d:c d:d d:e t:100 u:c u:d u:e u:0\");\n    assert_eq!(\n        \"t:1ms\\nout:↓C\\nt:1ms\\nout:↓D\\nt:1ms\\nout:↓E\\n\\\n         out:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\n\\\n         t:1ms\\nout:↑C\\nout:↑D\\nout:↑E\\nout:↓L\\nt:1ms\\nout:↑L\",\n        result\n    );\n}\n\n#[test]\nfn overlapping_then_nonoverlap_activate_non_overlap() {\n    let result = simulate(OVERLAP_CFG, \"d:0 d:c u:c d:d d:e t:100 u:d u:e u:0\");\n    assert_eq!(\n        \"t:1ms\\nout:↓C\\nt:1ms\\nout:↑C\\nt:1ms\\nout:↓D\\nt:1ms\\nout:↓E\\n\\\n         out:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\n\\\n         t:1ms\\nout:↑D\\nout:↑E\\nout:↓M\\nt:1ms\\nout:↑M\",\n        result\n    );\n}\n\n#[test]\nfn overlapping_then_overlap_activate_overlap1() {\n    let result = simulate(OVERLAP_CFG, \"d:0 d:c d:d d:f d:g t:100\");\n    assert_eq!(\n        \"t:1ms\\nout:↓C\\nt:1ms\\nout:↓D\\nt:1ms\\nout:↓F\\nt:1ms\\nout:↓G\\n\\\n         out:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\n\\\n         t:1ms\\nout:↑C\\nout:↑D\\nout:↑F\\nout:↑G\\nout:↓N\\nt:1ms\\nout:↑N\",\n        result\n    );\n}\n\n#[test]\nfn overlapping_then_overlap_activate_overlap2() {\n    let result = simulate(OVERLAP_CFG, \"d:0 d:c d:d u:c u:d d:f d:g t:100\");\n    assert_eq!(\n        \"t:1ms\\nout:↓C\\nt:1ms\\nout:↓D\\nt:1ms\\nout:↑C\\nt:1ms\\nout:↑D\\nt:1ms\\nout:↓F\\nt:1ms\\nout:↓G\\n\\\n         out:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\n\\\n         t:1ms\\nout:↑F\\nout:↑G\\nout:↓N\\nt:1ms\\nout:↑N\",\n        result\n    );\n}\n\n#[test]\nfn overlapping_then_overlap_activate_overlap3() {\n    let result = simulate(OVERLAP_CFG, \"d:0 d:c d:d u:c u:d t:10 d:f d:g t:100\");\n    assert_eq!(\n        \"t:1ms\\nout:↓C\\nt:1ms\\nout:↓D\\nt:1ms\\nout:↑C\\nt:1ms\\nout:↑D\\nt:6ms\\nout:↓F\\nt:1ms\\nout:↓G\\n\\\n         out:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\n\\\n         t:1ms\\nout:↑F\\nout:↑G\\nout:↓N\\nt:1ms\\nout:↑N\",\n        result\n    );\n}\n\n#[test]\nfn overlapping_then_overlap_activate_nonoverlap() {\n    let result = simulate(OVERLAP_CFG, \"d:0 d:c d:d u:c u:d t:10 d:f u:f d:g t:100\");\n    assert_eq!(\n        \"t:1ms\\nout:↓C\\nt:1ms\\nout:↓D\\nt:1ms\\nout:↑C\\nt:1ms\\nout:↑D\\nt:6ms\\nout:↓F\\nt:1ms\\nout:↑F\\nt:1ms\\nout:↓G\\n\\\n         out:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\n\\\n         t:1ms\\nout:↑G\\nout:↓O\\nt:1ms\\nout:↑O\",\n        result\n    );\n}\n\n#[test]\nfn non_overlapping_then_overlap_activate_overlap() {\n    let result = simulate(OVERLAP_CFG, \"d:0 d:c u:c d:d u:d d:f d:g t:100\");\n    assert_eq!(\n        \"t:1ms\\nout:↓C\\nt:1ms\\nout:↑C\\nt:1ms\\nout:↓D\\nt:1ms\\nout:↑D\\nt:1ms\\nout:↓F\\nt:1ms\\nout:↓G\\n\\\n         out:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\nout:↓BSpace\\nout:↑BSpace\\n\\\n         t:1ms\\nout:↑F\\nout:↑G\\nout:↓P\\nt:1ms\\nout:↑P\",\n        result\n    );\n}\n\n#[test]\nfn non_overlapping_then_overlap_activate_nothing() {\n    let result = simulate(OVERLAP_CFG, \"d:0 d:c u:c d:d u:d d:f u:f d:g t:100\");\n    assert_eq!(\n        \"t:1ms\\nout:↓C\\nt:1ms\\nout:↑C\\nt:1ms\\nout:↓D\\nt:1ms\\nout:↑D\\nt:1ms\\nout:↓F\\nt:1ms\\nout:↑F\\nt:1ms\\nout:↓G\",\n        result\n    );\n}\n\n/* BUG: chorded_hidden_delay_type\n *\n * Enable this test when fixing.\n *\n * Backtracking currently destroys information about held modifiers before finally outputting the\n * invalid sequence characters. There is also no logic to keep modifier keys held for the\n * appropriate duration according to the modifier bits information, even if the information was\n * preserved. Seems like a complicated low-value edge-case bug to fix so for now will just document\n * it... Nobody has reported it yet anyway. And visible-backspaced seems preferable in most cases\n * anyway.\n#[test]\nfn chorded_keys_hidden_delaytype() {\n    let result = simulate(\n        \"(defcfg sequence-input-mode hidden-delay-type)\n         (defsrc 0)\n         (deflayer base sldr)\n         (defvirtualkeys s1 z)\n         (defseq s1 (S-(a b)))\n        \",\n        \"d:0 u:0 d:lsft t:50 d:a d:b t:50 u:lsft u:a u:b t:500\n         d:0 u:0 d:rsft t:50 d:a d:b t:50 u:rsft u:a u:b t:500\n         d:0 u:0 d:rsft t:50 d:a u:rsft t:50 d:b u:a u:b t:500\",\n    );\n    assert_eq!(\n        \"\",\n        result\n    );\n}\n*/\n\n#[test]\nfn noerase() {\n    let result = simulate(\n        \"(defcfg sequence-input-mode visible-backspaced)\n         (defsrc)\n         (deflayermap (base)\n           0 sldr\n           u (t! maybe-noerase u)\n         )\n         (deftemplate maybe-noerase (char)\n            (multi\n             (switch\n              ((key-history ' 1)) (sequence-noerase 1) fallthrough\n              () $char break\n            ))\n         )\n         (defvirtualkeys s1 z)\n         (defseq s1 (' u))\n        \",\n        \"d:0 u:0 d:' t:50 d:u t:500\",\n    )\n    .no_time()\n    .to_ascii();\n    assert_eq!(\n        \"dn:Quote dn:U dn:BSpace up:BSpace up:Quote up:U dn:Z up:Z\",\n        result,\n    );\n}\n\n#[test]\nfn tap_hold_pending() {\n    let result = simulate(\n        \"\n(defalias md     (tap-hold 200 200 s S-s))\n(defsrc s d j)\n(deflayer base @md d sldr)\n(deffakekeys _u  (unicode μ))\n(defseq _u     (s))\n        \",\n        \"\nd:KeyJ t:10 u:KeyJ t:10\nd:KeyS t:10 u:KeyS t:10 d:KeyD t:10 u:KeyD t:10\n\nd:KeyJ t:10 u:KeyJ t:10\nd:KeyS t:10 d:KeyD t:10 u:KeyS t:10 u:KeyD t:10\",\n    )\n    .no_time()\n    .no_releases()\n    .to_ascii();\n    assert_eq!(\"outU:μ dn:D outU:μ dn:D\", result,);\n}\n"
  },
  {
    "path": "src/tests/sim_tests/switch_sim_tests.rs",
    "content": "use super::*;\n\n#[test]\nfn sim_switch_layer() {\n    let result = simulate(\n        \"\n         (defcfg)\n         (defsrc a b)\n         (defalias b (switch\n            ((layer base)) x break\n            ((layer other)) y break))\n         (deflayer base (layer-while-held other) @b)\n         (deflayer other XX @b)\n        \",\n        \"d:b u:b t:10 d:a d:b u:b u:a t:10\",\n    )\n    .no_time();\n    assert_eq!(\"out:↓X out:↑X out:↓Y out:↑Y\", result);\n}\n\n#[test]\nfn sim_switch_base_layer() {\n    let result = simulate(\n        \"\n         (defcfg)\n         (defsrc a b c)\n         (defalias b (switch\n            ((base-layer base)) x break\n            ((base-layer other)) y break))\n         (deflayer base (layer-switch other) @b c)\n         (deflayer other XX @b (layer-while-held base))\n        \",\n        \"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\",\n    )\n    .no_time();\n    assert_eq!(\"out:↓X out:↑X out:↓Y out:↑Y out:↓Y out:↑Y\", result);\n}\n\n#[test]\nfn sim_switch_noop() {\n    let result = simulate(\n        \"\n         (defsrc)\n         (deflayermap (-) a XX b (switch\n          ((input real a)) c break\n          () d break\n         ))\n        \",\n        \"d:a d:b t:10 u:a u:b t:10 d:b u:b t:10\",\n    )\n    .no_time();\n    assert_eq!(\"out:↓C out:↑C out:↓D out:↑D\", result);\n}\n\n#[test]\nfn sim_switch_trans_not_top_layer() {\n    let result = simulate(\n        \"\n        (defalias init (multi (layer-while-held l1) (layer-while-held l2) (layer-while-held l3) (layer-while-held l4)))\n        (defsrc a b)\n        (deflayer l0 c @init)\n        (deflayer l1 b @init)\n        (deflayer l2 (switch () _ break) @init)\n        (deflayer l3 _ @init)\n        (deflayer l4 _ @init)\n        \",\n        \"d:b t:20 d:a t:10 u:a t:100 d:a t:10 u:a t:100\",\n    )\n    .to_ascii();\n    assert_eq!(\"t:21ms dn:B t:9ms up:B t:101ms dn:B t:9ms up:B\", result);\n}\n"
  },
  {
    "path": "src/tests/sim_tests/tap_dance_tests.rs",
    "content": "use super::*;\r\n\r\n#[test]\r\nfn tap_dance_eager_with_mlft() {\r\n    let result = simulate(\r\n        \"\r\n        (defsrc)\r\n        (deflayermap (baselayer)\r\n            a (tap-dance-eager 200 (mlft mrgt mmid))\r\n        )\r\n        \",\r\n        \"\r\n        d:a t:1 u:a t:1\r\n        d:a t:1 u:a t:1\r\n        d:a t:1 u:a t:1\r\n        \",\r\n    )\r\n    .to_ascii();\r\n    assert_eq!(\r\n        \"out🖰:↓Left t:1ms out🖰:↑Left t:1ms out🖰:↓Right t:1ms out🖰:↑Right t:1ms out🖰:↓Mid t:1ms out🖰:↑Mid\",\r\n        result\r\n    );\r\n}\r\n"
  },
  {
    "path": "src/tests/sim_tests/tap_hold_tests.rs",
    "content": "use super::*;\n\n#[test]\nfn delayed_timedout_released_taphold_can_still_tap() {\n    let result = simulate(\n        \"\n        (defcfg concurrent-tap-hold yes )\n        (defsrc a b j)\n        (deflayer base @a @b @j)\n        (defalias\n         a (tap-hold 200 1000 a lctl)\n         b (tap-hold-tap-keys 0 100 b c (j))\n         j (tap-hold 200 500 j lsft))\n        \",\n        \"d:a t:50 d:b t:50 d:j t:10 u:j t:100 u:b t:100 u:a t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\n        \"t:310ms dn:A t:7ms dn:B t:7ms dn:J t:6ms up:J t:1ms up:B t:1ms up:A\",\n        result\n    );\n}\n\n#[test]\nfn delayed_timedout_released_taphold_can_hold() {\n    let result = simulate(\n        \"\n        (defcfg concurrent-tap-hold yes)\n        (defsrc a b)\n        (deflayer base @a @b)\n        (defalias\n          a (tap-hold 0 300 a b)\n          b (tap-hold 0 100 c d)\n        )\n        \",\n        \"d:a t:50 d:b t:150 u:b t:50 u:a t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\"t:250ms dn:A t:7ms dn:D t:1ms up:D t:1ms up:A\", result);\n}\n\n#[test]\nfn tap_hold_release_timeout_no_reset() {\n    let result = simulate(\n        \"\n        (defsrc a)\n        (deflayer l1 (tap-hold-release-timeout 100 100 x y z))\n        \",\n        \"d:a t:50 d:b t:75 u:b u:a t:25\",\n    )\n    .to_ascii();\n    assert_eq!(\"t:100ms dn:Z t:1ms dn:B t:24ms up:B t:1ms up:Z\", result);\n}\n\n#[test]\nfn tap_hold_release_timeout_with_reset() {\n    let result = simulate(\n        \"\n        (defsrc a)\n        (deflayer l1 (tap-hold-release-timeout 100 100 x y z reset-timeout-on-press))\n        \",\n        \"d:a t:50 d:b t:75 u:b u:a t:25\",\n    )\n    .to_ascii();\n    assert_eq!(\"t:125ms dn:Y t:6ms dn:B t:1ms up:B t:1ms up:Y\", result);\n}\n\n#[test]\nfn on_physical_idle_with_tap_repress() {\n    let result = simulate(\n        \"\n(defsrc a)\n(deflayer base @a)\n(deflayer nomods a)\n(defvirtualkeys to-base (layer-switch base))\n(defalias\n  tap (multi\n    (layer-switch nomods)\n    (on-physical-idle 20 tap-vkey to-base)\n  )\n  a (tap-hold 100 100 (multi a @tap) b)\n)\n        \",\n        \"d:a t:30 u:a t:30 d:a t:1000\",\n    )\n    .to_ascii();\n    // t:30ms dn:A t:6ms up:A t:124ms dn:B\n    assert_eq!(\"t:30ms dn:A t:6ms up:A t:24ms dn:A\", result);\n}\n\n#[test]\nfn tap_hold_release_tap_keys_release() {\n    let cfg = \"\n        (defsrc a b c)\n        (deflayer l1\n         (tap-hold-release-tap-keys-release 100 100 x y (v) (z))\n         (tap-hold-release-tap-keys-release 100 100 x y (w) (z v))\n         (tap-hold-release-tap-keys-release 100 100 x y () (z v))\n        )\n    \";\n    let result = simulate(cfg, \"d:a t:20 u:a t:20 d:a t:200\").to_ascii();\n    assert_eq!(\"t:20ms dn:X t:6ms up:X t:14ms dn:X\", result);\n    let result = simulate(cfg, \"d:a t:20 u:a t:200 d:a t:200\").to_ascii();\n    assert_eq!(\"t:20ms dn:X t:6ms up:X t:294ms dn:Y\", result);\n    let result = simulate(cfg, \"d:a t:50 u:a t:50\").to_ascii();\n    assert_eq!(\"t:50ms dn:X t:6ms up:X\", result);\n    let result = simulate(cfg, \"d:a t:150 u:a t:50\").to_ascii();\n    assert_eq!(\"t:100ms dn:Y t:50ms up:Y\", result);\n    let result = simulate(cfg, \"d:a t:50 d:z t:75\").to_ascii();\n    assert_eq!(\"t:100ms dn:Y t:1ms dn:Z\", result);\n    let result = simulate(cfg, \"d:a t:50 d:z u:z t:75\").to_ascii();\n    assert_eq!(\"t:50ms dn:X t:6ms dn:Z t:1ms up:Z\", result);\n    let result = simulate(cfg, \"d:a t:33 d:z t:33 d:v t:100\").to_ascii();\n    assert_eq!(\"t:66ms dn:X t:6ms dn:Z t:1ms dn:V\", result);\n    let result = simulate(cfg, \"d:b t:33 d:z t:33 d:v t:100\").to_ascii();\n    assert_eq!(\"t:100ms dn:Y t:1ms dn:Z t:1ms dn:V\", result);\n    let result = simulate(cfg, \"d:b t:20 d:z t:20 d:v t:20 d:w t:100\").to_ascii();\n    assert_eq!(\"t:60ms dn:X t:6ms dn:Z t:1ms dn:V t:1ms dn:W\", result);\n    let result = simulate(cfg, \"d:c t:33 d:z t:33 d:v t:100\").to_ascii();\n    assert_eq!(\"t:100ms dn:Y t:1ms dn:Z t:1ms dn:V\", result);\n}\n\n#[test]\nfn tap_hold_release_keys() {\n    let cfg = \"\n        (defsrc a)\n        (deflayer l1 (tap-hold-release-keys 100 100 x y (z)))\n    \";\n    let result = simulate(cfg, \"d:a t:20 u:a t:20 d:a t:200\").to_ascii();\n    assert_eq!(\"t:20ms dn:X t:6ms up:X t:14ms dn:X\", result);\n    let result = simulate(cfg, \"d:a t:20 u:a t:200 d:a t:200\").to_ascii();\n    assert_eq!(\"t:20ms dn:X t:6ms up:X t:294ms dn:Y\", result);\n    let result = simulate(cfg, \"d:a t:50 u:a t:50\").to_ascii();\n    assert_eq!(\"t:50ms dn:X t:6ms up:X\", result);\n    let result = simulate(cfg, \"d:a t:150 u:a t:50\").to_ascii();\n    assert_eq!(\"t:100ms dn:Y t:50ms up:Y\", result);\n    let result = simulate(cfg, \"d:a t:50 d:z t:75\").to_ascii();\n    assert_eq!(\"t:50ms dn:X t:6ms dn:Z\", result);\n    let result = simulate(cfg, \"d:a t:50 d:z u:z t:75\").to_ascii();\n    assert_eq!(\"t:50ms dn:X t:6ms dn:Z t:1ms up:Z\", result);\n}\n\n#[test]\nfn tap_hold_tap_keys() {\n    let cfg = \"\n        (defsrc a b z)\n        (deflayer l1 (tap-hold-tap-keys 100 100 x y (z)) b z)\n    \";\n\n    // Basic tap: release before timeout → tap action\n    let result = simulate(cfg, \"d:a t:50 u:a t:50\").to_ascii();\n    assert_eq!(\"t:50ms dn:X t:6ms up:X\", result);\n\n    // Basic hold: timeout elapsed → hold action\n    let result = simulate(cfg, \"d:a t:150 u:a t:50\").to_ascii();\n    assert_eq!(\"t:100ms dn:Y t:50ms up:Y\", result);\n\n    // $tap-keys (z) pressed → immediate tap\n    let result = simulate(cfg, \"d:a t:50 d:z t:75\").to_ascii();\n    assert_eq!(\"t:50ms dn:X t:6ms dn:Z\", result);\n\n    // KEY DIFFERENCE from tap-hold-release-keys:\n    // Other key (b) pressed+released → wait for timeout, NOT immediate hold\n    // This is the critical behavioral difference that tap-hold-tap-keys provides\n    let result = simulate(cfg, \"d:a t:50 d:b u:b t:100\").to_ascii();\n    // After 100ms timeout, hold activates, then b events are replayed\n    // d:b and u:b have no delay between them, so both replay with minimal gap\n    assert_eq!(\"t:100ms dn:Y t:1ms dn:B t:1ms up:B\", result);\n\n    // Tap repress behavior\n    let result = simulate(cfg, \"d:a t:20 u:a t:20 d:a t:200\").to_ascii();\n    assert_eq!(\"t:20ms dn:X t:6ms up:X t:14ms dn:X\", result);\n}\n\n// ========== tap-hold-opposite-hand tests ==========\n\nfn opposite_hand_cfg() -> &'static str {\n    \"\n    (defhands\n      (left a s d f g)\n      (right h j k l ;))\n    (defsrc f j h)\n    (deflayer base @f j h)\n    (defalias\n      f (tap-hold-opposite-hand 200 f lctl))\n    \"\n}\n\n#[test]\nfn opposite_hand_press_resolves_hold() {\n    // Press f (left hand), then j (right hand) -> should resolve as HOLD (lctl)\n    let result = simulate(opposite_hand_cfg(), \"d:f t:50 d:j t:50 u:j t:50 u:f t:50\").to_ascii();\n    assert_eq!(\n        \"t:50ms dn:LCtrl t:6ms dn:J t:44ms up:J t:50ms up:LCtrl\",\n        result\n    );\n}\n\n#[test]\nfn same_hand_press_resolves_tap() {\n    // Press f (left hand), then d (left hand) -> same hand default = tap\n    let result = simulate(\n        \"\n        (defhands (left a s d f g) (right h j k l ;))\n        (defsrc d f)\n        (deflayer base d @f)\n        (defalias f (tap-hold-opposite-hand 200 f lctl))\n        \",\n        \"d:f t:50 d:d t:50 u:d t:50 u:f t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\"t:50ms dn:F t:6ms dn:D t:44ms up:D t:50ms up:F\", result);\n}\n\n#[test]\nfn same_hand_ignore_defers_to_timeout() {\n    // With (same-hand ignore), a same-hand press is ignored, timeout fires\n    let result = simulate(\n        \"\n        (defhands (left a s d f g) (right h j k l ;))\n        (defsrc d f)\n        (deflayer base d @f)\n        (defalias f (tap-hold-opposite-hand 200 f lctl (same-hand ignore)))\n        \",\n        \"d:f t:50 d:d t:200 u:d t:50 u:f t:50\",\n    )\n    .to_ascii();\n    // timeout (default=tap) fires at 200ms from f press\n    assert_eq!(\"t:200ms dn:F t:1ms dn:D t:49ms up:D t:50ms up:F\", result);\n}\n\n#[test]\nfn neutral_key_ignore_defers() {\n    // With default (neutral ignore), neutral keys are skipped, timeout fires\n    let result = simulate(\n        \"\n        (defhands (left a s d f g) (right h j k l ;))\n        (defsrc f h spc)\n        (deflayer base @f h spc)\n        (defalias f (tap-hold-opposite-hand 200 f lctl (neutral-keys spc)))\n        \",\n        \"d:f t:50 d:spc t:200 u:spc t:50 u:f t:50\",\n    )\n    .to_ascii();\n    // spc is neutral, default (neutral ignore), so timeout fires\n    assert_eq!(\n        \"t:200ms dn:F t:1ms dn:Space t:49ms up:Space t:50ms up:F\",\n        result\n    );\n}\n\n#[test]\nfn neutral_key_tap_resolves_immediately() {\n    // With (neutral tap), neutral key press triggers TAP immediately\n    let result = simulate(\n        \"\n        (defhands (left a s d f g) (right h j k l ;))\n        (defsrc f h spc)\n        (deflayer base @f h spc)\n        (defalias f (tap-hold-opposite-hand 200 f lctl (neutral-keys spc) (neutral tap)))\n        \",\n        \"d:f t:50 d:spc t:50 u:spc t:50 u:f t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\n        \"t:50ms dn:F t:6ms dn:Space t:44ms up:Space t:50ms up:F\",\n        result\n    );\n}\n\n#[test]\nfn unknown_hand_key_defers_by_default() {\n    // Key not in defhands at all -> unknown hand, default = ignore\n    let result = simulate(\n        \"\n        (defhands (left a s d f g) (right h j k l ;))\n        (defsrc f b)\n        (deflayer base @f b)\n        (defalias f (tap-hold-opposite-hand 200 f lctl))\n        \",\n        \"d:f t:50 d:b t:200 u:b t:50 u:f t:50\",\n    )\n    .to_ascii();\n    // b is not in defhands, unknown-hand default = ignore, timeout fires\n    assert_eq!(\"t:200ms dn:F t:1ms dn:B t:49ms up:B t:50ms up:F\", result);\n}\n\n#[test]\nfn timeout_default_is_tap() {\n    // Default timeout behavior is tap\n    let result = simulate(opposite_hand_cfg(), \"d:f t:250 u:f t:50\").to_ascii();\n    assert_eq!(\"t:200ms dn:F t:50ms up:F\", result);\n}\n\n#[test]\nfn timeout_hold_option() {\n    // (timeout hold) makes timeout resolve to hold action\n    let result = simulate(\n        \"\n        (defhands (left a s d f g) (right h j k l ;))\n        (defsrc f j)\n        (deflayer base @f j)\n        (defalias f (tap-hold-opposite-hand 200 f lctl (timeout hold)))\n        \",\n        \"d:f t:250 u:f t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\"t:200ms dn:LCtrl t:50ms up:LCtrl\", result);\n}\n\n#[test]\nfn release_before_timeout_taps() {\n    // Release the hold-tap key before timeout -> immediate tap\n    let result = simulate(opposite_hand_cfg(), \"d:f t:50 u:f t:50\").to_ascii();\n    assert_eq!(\"t:50ms dn:F t:6ms up:F\", result);\n}\n\n#[test]\nfn multiple_options_combined() {\n    // Combine (same-hand hold), (timeout hold), (neutral-keys ...) with (neutral tap)\n    let result = simulate(\n        \"\n        (defhands (left a s d f g) (right h j k l ;))\n        (defsrc d f j spc)\n        (deflayer base d @f j spc)\n        (defalias f (tap-hold-opposite-hand 200 f lctl\n          (same-hand hold) (timeout hold)\n          (neutral-keys spc) (neutral tap)))\n        \",\n        \"d:f t:50 d:d t:50 u:d t:50 u:f t:50\",\n    )\n    .to_ascii();\n    // d is same hand, (same-hand hold) -> resolves as hold\n    assert_eq!(\n        \"t:50ms dn:LCtrl t:6ms dn:D t:44ms up:D t:50ms up:LCtrl\",\n        result\n    );\n}\n\n#[test]\nfn unknown_hand_tap_resolves_immediately() {\n    // (unknown-hand tap) makes unassigned keys resolve as tap\n    let result = simulate(\n        \"\n        (defhands (left a s d f g) (right h j k l ;))\n        (defsrc f b)\n        (deflayer base @f b)\n        (defalias f (tap-hold-opposite-hand 200 f lctl (unknown-hand tap)))\n        \",\n        \"d:f t:50 d:b t:50 u:b t:50 u:f t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\"t:50ms dn:F t:6ms dn:B t:44ms up:B t:50ms up:F\", result);\n}\n\n#[test]\nfn unknown_hand_hold_resolves_immediately() {\n    // (unknown-hand hold) makes unassigned keys resolve as hold\n    let result = simulate(\n        \"\n        (defhands (left a s d f g) (right h j k l ;))\n        (defsrc f b)\n        (deflayer base @f b)\n        (defalias f (tap-hold-opposite-hand 200 f lctl (unknown-hand hold)))\n        \",\n        \"d:f t:50 d:b t:50 u:b t:50 u:f t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\n        \"t:50ms dn:LCtrl t:6ms dn:B t:44ms up:B t:50ms up:LCtrl\",\n        result\n    );\n}\n\n#[test]\nfn neutral_key_hold_resolves_immediately() {\n    // (neutral hold) makes neutral keys resolve as hold\n    let result = simulate(\n        \"\n        (defhands (left a s d f g) (right h j k l ;))\n        (defsrc f h spc)\n        (deflayer base @f h spc)\n        (defalias f (tap-hold-opposite-hand 200 f lctl (neutral-keys spc) (neutral hold)))\n        \",\n        \"d:f t:50 d:spc t:50 u:spc t:50 u:f t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\n        \"t:50ms dn:LCtrl t:6ms dn:Space t:44ms up:Space t:50ms up:LCtrl\",\n        result\n    );\n}\n\n#[test]\nfn waiting_key_unassigned_in_defhands() {\n    // The hold-tap key (b) is NOT in defhands, so its hand is unknown.\n    // Pressing j (right hand) still triggers unknown-hand logic (both sides unknown = unknown).\n    // Default (unknown-hand ignore), so it defers; timeout fires as tap.\n    let result = simulate(\n        \"\n        (defhands (left a s d f g) (right h j k l ;))\n        (defsrc b j)\n        (deflayer base @b j)\n        (defalias b (tap-hold-opposite-hand 200 b lctl))\n        \",\n        \"d:b t:50 d:j t:200 u:j t:50 u:b t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\"t:200ms dn:B t:1ms dn:J t:49ms up:J t:50ms up:B\", result);\n}\n\n#[test]\nfn neutral_keys_override_defhands_assignment() {\n    // j is in defhands (right hand), but also in (neutral-keys ...).\n    // (neutral-keys ...) takes precedence, so j is treated as neutral.\n    // With (neutral tap), pressing j should resolve as tap (not hold).\n    let result = simulate(\n        \"\n        (defhands (left a s d f g) (right h j k l ;))\n        (defsrc f j)\n        (deflayer base @f j)\n        (defalias f (tap-hold-opposite-hand 200 f lctl (neutral-keys j) (neutral tap)))\n        \",\n        \"d:f t:50 d:j t:50 u:j t:50 u:f t:50\",\n    )\n    .to_ascii();\n    // j would normally be opposite-hand (hold), but neutral-keys overrides -> tap\n    assert_eq!(\"t:50ms dn:F t:6ms dn:J t:44ms up:J t:50ms up:F\", result);\n}\n\n// ========== tap-hold-require-prior-idle tests ==========\n\n#[test]\nfn tap_hold_require_prior_idle_typing_streak_resolves_tap() {\n    let result = simulate(\n        \"\n(defcfg tap-hold-require-prior-idle 150)\n(defsrc a b d)\n(deflayer base a b @d)\n(defalias d (tap-hold 200 200 d lctl))\n        \",\n        // Type a, release, then quickly press d within idle window.\n        // 'a' was pressed 20ms ago (10ms press + 10ms gap), well within 150ms threshold.\n        \"d:a t:10 u:a t:10 d:d t:50 u:d t:50\",\n    )\n    .to_ascii();\n    // d should resolve as tap immediately (no 200ms waiting state)\n    assert_eq!(\"dn:A t:10ms up:A t:10ms dn:D t:50ms up:D\", result);\n}\n\n#[test]\nfn tap_hold_require_prior_idle_idle_long_enough_enters_hold() {\n    let result = simulate(\n        \"\n(defcfg tap-hold-require-prior-idle 150)\n(defsrc a b d)\n(deflayer base a b @d)\n(defalias d (tap-hold 200 200 d lctl))\n        \",\n        // Press a, release, wait 200ms (longer than 150ms threshold), then press d.\n        // d should enter normal WaitingState (hold on other key press).\n        \"d:a t:10 u:a t:200 d:d t:250 u:d t:50\",\n    )\n    .to_ascii();\n    // After 200ms idle, d enters normal tap-hold. Timeout at 200ms → hold (lctl).\n    assert_eq!(\"dn:A t:10ms up:A t:400ms dn:LCtrl t:50ms up:LCtrl\", result);\n}\n\n#[test]\nfn tap_hold_require_prior_idle_no_prior_key_enters_hold() {\n    let result = simulate(\n        \"\n(defcfg tap-hold-require-prior-idle 150)\n(defsrc a b d)\n(deflayer base a b @d)\n(defalias d (tap-hold 200 200 d lctl))\n        \",\n        // No prior key at all. d should enter normal WaitingState.\n        \"d:d t:250 u:d t:50\",\n    )\n    .to_ascii();\n    // Timeout → hold (lctl)\n    assert_eq!(\"t:200ms dn:LCtrl t:50ms up:LCtrl\", result);\n}\n\n#[test]\nfn tap_hold_require_prior_idle_boundary_just_within_threshold() {\n    // Prior key pressed 149ms ago (just within 150ms threshold).\n    // ticks_since_occurrence will be ~150 (149 + 1 tick offset), which is\n    // <= 150 threshold, so tap fires.\n    let result = simulate(\n        \"\n(defcfg tap-hold-require-prior-idle 150)\n(defsrc a d)\n(deflayer base a @d)\n(defalias d (tap-hold 200 200 d lctl))\n        \",\n        \"d:a t:10 u:a t:139 d:d t:50 u:d t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\"dn:A t:10ms up:A t:139ms dn:D t:50ms up:D\", result);\n}\n\n#[test]\nfn tap_hold_require_prior_idle_boundary_just_outside_threshold() {\n    // Prior key pressed 150ms ago (just outside 150ms threshold).\n    // ticks_since_occurrence will be ~151 (150 + 1 tick offset), which is\n    // > 150 threshold, so normal tap-hold behavior applies.\n    let result = simulate(\n        \"\n(defcfg tap-hold-require-prior-idle 150)\n(defsrc a d)\n(deflayer base a @d)\n(defalias d (tap-hold 200 200 d lctl))\n        \",\n        // d enters WaitingState; released at 50ms → tap via release\n        \"d:a t:10 u:a t:140 d:d t:50 u:d t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\"dn:A t:10ms up:A t:190ms dn:D t:6ms up:D\", result);\n}\n\n#[test]\nfn tap_hold_require_prior_idle_with_opposite_hand() {\n    // tap-hold-require-prior-idle should short-circuit before tap-hold-opposite-hand\n    // evaluates hand membership. During a typing streak, even an opposite-hand\n    // key should resolve as tap.\n    let result = simulate(\n        \"\n(defcfg tap-hold-require-prior-idle 150)\n(defhands (left a s d f g) (right h j k l ;))\n(defsrc a f j)\n(deflayer base a @f j)\n(defalias f (tap-hold-opposite-hand 200 f lctl))\n        \",\n        // a is left hand, f is left hand tap-hold. a pressed 20ms ago.\n        // Without tap-hold-require-prior-idle, pressing j (opposite hand) would hold.\n        // With tap-hold-require-prior-idle active, f resolves as tap before hand check.\n        \"d:a t:10 u:a t:10 d:f t:50 u:f t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\"dn:A t:10ms up:A t:10ms dn:F t:50ms up:F\", result);\n}\n\n#[test]\nfn tap_hold_require_prior_idle_with_tap_hold_interval() {\n    // tap-hold-require-prior-idle check runs before tap-hold-interval (quick re-press).\n    // Both should work together: typing streak → tap immediately,\n    // idle re-press → tap via tap_hold_interval.\n    let cfg = \"\n(defcfg tap-hold-require-prior-idle 150)\n(defsrc a d)\n(deflayer base a @d)\n(defalias d (tap-hold 200 200 d lctl))\n    \";\n    // Case 1: typing streak (a then d quickly) → tap-hold-require-prior-idle fires\n    let result = simulate(cfg, \"d:a t:10 u:a t:10 d:d t:50 u:d t:50\").to_ascii();\n    assert_eq!(\"dn:A t:10ms up:A t:10ms dn:D t:50ms up:D\", result);\n    // Case 2: idle, then d pressed twice (tap-hold-interval re-press)\n    let result = simulate(cfg, \"d:d t:50 u:d t:50 d:d t:50 u:d t:50\").to_ascii();\n    assert_eq!(\"t:50ms dn:D t:6ms up:D t:44ms dn:D t:50ms up:D\", result);\n}\n\n#[test]\nfn tap_hold_require_prior_idle_ignores_virtual_keys() {\n    // Virtual key events (row 1) should not count as prior physical input.\n    // Only real physical key presses (row 0) trigger the typing streak.\n    let result = simulate(\n        \"\n(defcfg tap-hold-require-prior-idle 150)\n(defsrc d)\n(defvirtualkeys vk1 a)\n(deflayer base @d)\n(defalias d (tap-hold 200 200 d lctl))\n        \",\n        // Virtual key tap, then d pressed 10ms later.\n        // vk should NOT trigger typing streak — d should enter normal hold.\n        \"vk:vk1:tap t:10 d:d t:250 u:d t:50\",\n    )\n    .to_ascii();\n    // Virtual key outputs A, then d times out to hold (lctl).\n    assert_eq!(\"dn:A t:1ms up:A t:209ms dn:LCtrl t:50ms up:LCtrl\", result);\n}\n\n// ========== per-action require-prior-idle override tests ==========\n\n#[test]\nfn per_action_require_prior_idle_overrides_global() {\n    // Global defcfg sets 150ms, but per-action override sets 50ms.\n    // A prior key 60ms ago is within 150ms (global) but outside 50ms (per-action).\n    // Per-action should win: normal hold behavior, not tap.\n    let result = simulate(\n        \"\n(defcfg tap-hold-require-prior-idle 150)\n(defsrc a d)\n(deflayer base a @d)\n(defalias d (tap-hold 200 200 d lctl (require-prior-idle 50)))\n        \",\n        \"d:a t:10 u:a t:50 d:d t:250 u:d t:50\",\n    )\n    .to_ascii();\n    // 60ms gap > 50ms per-action threshold → normal hold\n    assert_eq!(\"dn:A t:10ms up:A t:250ms dn:LCtrl t:50ms up:LCtrl\", result);\n}\n\n#[test]\nfn per_action_require_prior_idle_disable_overrides_global() {\n    // Global defcfg sets 150ms, but per-action override sets 0 (disabled).\n    // Even during a typing streak, this action should use normal tap-hold.\n    let result = simulate(\n        \"\n(defcfg tap-hold-require-prior-idle 150)\n(defsrc a d)\n(deflayer base a @d)\n(defalias d (tap-hold 200 200 d lctl (require-prior-idle 0)))\n        \",\n        // a pressed 20ms ago, well within 150ms global threshold.\n        // But per-action disables it, so d enters normal WaitingState.\n        \"d:a t:10 u:a t:10 d:d t:250 u:d t:50\",\n    )\n    .to_ascii();\n    // d enters hold (250ms > 200ms timeout)\n    assert_eq!(\"dn:A t:10ms up:A t:210ms dn:LCtrl t:50ms up:LCtrl\", result);\n}\n\n#[test]\nfn per_action_require_prior_idle_enables_without_global() {\n    // No global defcfg (default 0), but per-action sets 150ms.\n    // The per-action value should enable the feature for this action only.\n    let result = simulate(\n        \"\n(defsrc a d)\n(deflayer base a @d)\n(defalias d (tap-hold 200 200 d lctl (require-prior-idle 150)))\n        \",\n        \"d:a t:10 u:a t:10 d:d t:50 u:d t:50\",\n    )\n    .to_ascii();\n    // a pressed 20ms ago, within 150ms per-action threshold → tap\n    assert_eq!(\"dn:A t:10ms up:A t:10ms dn:D t:50ms up:D\", result);\n}\n\n#[test]\nfn per_action_require_prior_idle_mixed_actions() {\n    // The issue #1967 use case: two tap-hold keys with different idle behavior.\n    // @a (HRM) uses the global 150ms threshold.\n    // @d (layer key) disables idle detection via per-action override.\n    // During a typing streak, @a should resolve as tap but @d should hold.\n    let cfg = \"\n(defcfg tap-hold-require-prior-idle 150)\n(defsrc a d e)\n(deflayer base @a @d e)\n(defalias\n  a (tap-hold-press 200 200 a lmet)\n  d (tap-hold-press 200 200 d lctl (require-prior-idle 0))\n)\n    \";\n    // Case 1: type e, then quickly press @a → global idle fires, a resolves as tap\n    let result = simulate(cfg, \"d:e t:10 u:e t:10 d:a t:50 u:a t:50\").to_ascii();\n    assert_eq!(\"dn:E t:10ms up:E t:10ms dn:A t:50ms up:A\", result);\n    // Case 2: type e, then quickly press @d → per-action 0 disables idle, d enters hold.\n    // tap-hold-press resolves on next key press: e pressed 10ms after d triggers hold.\n    let result = simulate(cfg, \"d:e t:10 u:e t:10 d:d t:10 d:e t:50 u:e t:50 u:d t:50\").to_ascii();\n    assert_eq!(\n        \"dn:E t:10ms up:E t:20ms dn:LCtrl t:6ms dn:E t:44ms up:E t:50ms up:LCtrl\",\n        result\n    );\n}\n\n#[test]\nfn per_action_require_prior_idle_with_tap_hold_release() {\n    // Per-action option works with tap-hold-release variant.\n    let result = simulate(\n        \"\n(defcfg tap-hold-require-prior-idle 150)\n(defsrc a d)\n(deflayer base a @d)\n(defalias d (tap-hold-release 200 200 d lctl (require-prior-idle 0)))\n        \",\n        // Typing streak: a pressed 20ms ago. Global would force tap,\n        // but per-action 0 disables it.\n        \"d:a t:10 u:a t:10 d:d t:250 u:d t:50\",\n    )\n    .to_ascii();\n    // d enters normal hold (per-action override disables idle check)\n    assert_eq!(\"dn:A t:10ms up:A t:210ms dn:LCtrl t:50ms up:LCtrl\", result);\n}\n\n#[test]\nfn per_action_require_prior_idle_with_opposite_hand() {\n    // Per-action option works with tap-hold-opposite-hand variant.\n    // Use j (right hand) pressing before f (left hand tap-hold).\n    // Without require-prior-idle, j is opposite-hand → f would hold.\n    // With global require-prior-idle 150, the typing streak forces tap.\n    // With per-action override 0, the override disables it and f holds.\n    let result = simulate(\n        \"\n(defcfg tap-hold-require-prior-idle 150)\n(defhands (left a s d f g) (right h j k l ;))\n(defsrc j f)\n(deflayer base j @f)\n(defalias f (tap-hold-opposite-hand 200 f lctl\n  (timeout hold)\n  (require-prior-idle 0)))\n        \",\n        // j (right) pressed 20ms ago, then f (left) pressed.\n        // Global 150ms would force tap, but per-action 0 disables it.\n        // j is opposite hand → f should hold. timeout → hold.\n        \"d:j t:10 u:j t:10 d:f t:250 u:f t:50\",\n    )\n    .to_ascii();\n    // f enters normal opposite-hand behavior: j is opposite → hold\n    assert_eq!(\"dn:J t:10ms up:J t:210ms dn:LCtrl t:50ms up:LCtrl\", result);\n}\n\n// ========== tap-hold-order simulation tests ==========\n// Note: t:6ms gaps after resolution are sim framework processing overhead for\n// event-triggered resolution (as opposed to timeout-triggered). This is consistent\n// with other event-driven tap-hold variants (e.g., tap-hold-opposite-hand).\n\n#[test]\nfn tap_hold_order_clean_tap() {\n    // Press and release tap-hold-order key with no other keys → Tap.\n    let result = simulate(\n        \"\n(defsrc a b)\n(deflayer base @a b)\n(defalias a (tap-hold-order 200 50 a lctl))\n        \",\n        \"d:a t:100 u:a t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\"t:100ms dn:A t:6ms up:A\", result);\n}\n\n#[test]\nfn tap_hold_order_hold_other_released_first() {\n    // TH down → other down → other up (released first) → Hold.\n    let result = simulate(\n        \"\n(defsrc a b)\n(deflayer base @a b)\n(defalias a (tap-hold-order 200 0 a lctl))\n        \",\n        \"d:a t:10 d:b t:10 u:b t:10 u:a t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\n        \"t:20ms dn:LCtrl t:6ms dn:B t:1ms up:B t:3ms up:LCtrl\",\n        result\n    );\n}\n\n#[test]\nfn tap_hold_order_tap_modifier_released_first() {\n    // TH down → other down → TH up first → Tap.\n    let result = simulate(\n        \"\n(defsrc a b)\n(deflayer base @a b)\n(defalias a (tap-hold-order 200 0 a lctl))\n        \",\n        \"d:a t:10 d:b t:10 u:a t:10 u:b t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\"t:20ms dn:A t:6ms dn:B t:1ms up:A t:3ms up:B\", result);\n}\n\n#[test]\nfn tap_hold_order_buffer_ignores_fast_typing() {\n    // Other key pressed+released within buffer window → ignored by\n    // release-order logic. TH released → Tap.\n    let result = simulate(\n        \"\n(defsrc a b)\n(deflayer base @a b)\n(defalias a (tap-hold-order 200 50 a lctl))\n        \",\n        \"d:a t:10 d:b t:10 u:b t:10 u:a t:50\",\n    )\n    .to_ascii();\n    // Without buffer, b's press+release would trigger Hold.\n    // With buffer=50, b's press at 10ms is within window → ignored → Tap.\n    assert_eq!(\"t:30ms dn:A t:6ms dn:B t:1ms up:B t:1ms up:A\", result);\n}\n\n#[test]\nfn tap_hold_order_hold_after_buffer_expires() {\n    // Other key pressed after buffer window expires → release-order applies.\n    // Other released first → Hold.\n    let result = simulate(\n        \"\n(defsrc a b)\n(deflayer base @a b)\n(defalias a (tap-hold-order 200 50 a lctl))\n        \",\n        \"d:a t:60 d:b t:10 u:b t:10 u:a t:50\",\n    )\n    .to_ascii();\n    // b pressed at 60ms (after 50ms buffer) → release-order active.\n    // b released first → Hold.\n    assert_eq!(\n        \"t:70ms dn:LCtrl t:6ms dn:B t:1ms up:B t:3ms up:LCtrl\",\n        result\n    );\n}\n\n#[test]\nfn tap_hold_order_with_require_prior_idle() {\n    // Per-action require-prior-idle short-circuits to tap during typing streak.\n    let result = simulate(\n        \"\n(defsrc a d)\n(deflayer base a @d)\n(defalias d (tap-hold-order 200 50 d lctl (require-prior-idle 150)))\n        \",\n        // a pressed 20ms ago → within 150ms idle threshold → tap immediately.\n        \"d:a t:10 u:a t:10 d:d t:50 u:d t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\"dn:A t:10ms up:A t:10ms dn:D t:50ms up:D\", result);\n}\n\n#[test]\nfn tap_hold_order_no_prior_idle_enters_normal_resolution() {\n    // No recent keypress → require-prior-idle doesn't fire → normal release-order.\n    // Other key released first → Hold.\n    let result = simulate(\n        \"\n(defsrc a d)\n(deflayer base a @d)\n(defalias d (tap-hold-order 200 0 d lctl (require-prior-idle 150)))\n        \",\n        \"d:d t:10 d:a t:10 u:a t:10 u:d t:50\",\n    )\n    .to_ascii();\n    assert_eq!(\n        \"t:20ms dn:LCtrl t:6ms dn:A t:1ms up:A t:3ms up:LCtrl\",\n        result\n    );\n}\n"
  },
  {
    "path": "src/tests/sim_tests/template_sim_tests.rs",
    "content": "use super::*;\n\n#[test]\nfn nested_template() {\n    let result = simulate(\n        \"\n        (deftemplate one (v1)\n         a b c $v1\n        )\n        (deftemplate two (v2)\n         (t! one $v2)\n         e f g\n        )\n        (defsrc        (t! two d))\n        (deflayer base (t! two x))\n        \",\n        \"d:a t:10 u:a t:10 d:d t:10 u:d t:10 d:g t:10 u:g t:10\",\n    )\n    .no_time();\n    assert_eq!(\"out:↓A out:↑A out:↓X out:↑X out:↓G out:↑G\", result);\n}\n"
  },
  {
    "path": "src/tests/sim_tests/timing_tests.rs",
    "content": "use std::thread::sleep;\nuse std::time::Duration;\n\nuse crate::Kanata;\n\nuse web_time::Instant;\n\n#[test]\nfn one_second_is_roughly_1000_counted_ticks() {\n    let mut k = Kanata::new_from_str(\"(defsrc)(deflayer base)\", Default::default())\n        .expect(\"failed to parse cfg\");\n\n    let mut accumulated_ticks = 0;\n\n    let start = Instant::now();\n    while start.elapsed() < Duration::from_secs(1) {\n        sleep(Duration::from_millis(1));\n        accumulated_ticks += k.get_ms_elapsed();\n    }\n\n    let actually_elapsed_ms = start.elapsed().as_millis();\n\n    // Allow fudge of 1%\n    // In practice this is within 1ms purely due to the remainder.\n    eprintln!(\"ticks:{accumulated_ticks}, actual elapsed:{actually_elapsed_ms}\");\n    assert!(accumulated_ticks < (actually_elapsed_ms + 10));\n    assert!(accumulated_ticks > (actually_elapsed_ms - 10));\n}\n"
  },
  {
    "path": "src/tests/sim_tests/unicode_sim_tests.rs",
    "content": "use super::*;\n\n#[test]\nfn unicode() {\n    let result = simulate(\n        r##\"\n         (defcfg)\n         (defsrc 6 7 8 9 0 f1)\n         (deflayer base\n             (unicode r#\"(\"#)\n             (unicode r#\")\"#)\n             (unicode r#\"\"\"#)\n             (unicode \"(\")\n             (unicode \")\")\n             (tap-dance 200 (f1(unicode 😀)f2(unicode 🙂)))\n         )\n        \"##,\n        \"d:6 d:7 d:8 d:9 d:0 t:100\",\n    )\n    .no_time();\n    assert_eq!(r#\"outU:( outU:) outU:\" outU:( outU:)\"#, result);\n}\n\n#[test]\n#[cfg(target_os = \"macos\")]\nfn macos_unicode_handling() {\n    let result = simulate(\n        r##\"\n         (defcfg)\n         (defsrc a)\n         (deflayer base\n             (unicode \"🎉\")  ;; Test with an emoji that uses multi-unit UTF-16\n         )\n        \"##,\n        \"d:a t:100\",\n    )\n    .no_time();\n    assert_eq!(\"outU:🎉\", result);\n}\n\n#[test]\nfn unicode_pulus() {\n    let result = simulate(\n        \"\n(defsrc a b)\n(deflayer _\n (unicode 🚆)\n (unicode U+1F686)\n)\n        \",\n        \"d:a t:10 d:b t:10\",\n    )\n    .no_time();\n    assert_eq!(\"outU:🚆 outU:🚆\", result);\n}\n"
  },
  {
    "path": "src/tests/sim_tests/unmod_sim_tests.rs",
    "content": "use super::*;\n\n#[test]\nfn unmod_keys_functionality_works() {\n    let result = simulate(\n        \"\n         (defcfg)\n         (defsrc f1 1 2 3 4 5 6 7 8 9 0)\n         (deflayer base\n             (multi lctl rctl lsft rsft lmet rmet lalt ralt)\n             (unmod a)\n             (unmod (lctl) b)\n             (unmod (rctl) c)\n             (unmod (lsft) d)\n             (unmod (rsft) e)\n             (unmod (lmet) f)\n             (unmod (rmet) g)\n             (unmod (lalt) h)\n             (unmod (ralt) i)\n             (unmod (lctl lsft lmet lalt) j)\n         )\n        \",\n        \"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\n                  d:7 u:7 t:5 d:8 u:8 t:5 d:9 u:9 t:5 d:0 u:0 t:5\",\n    )\n    .no_time()\n    .to_ascii();\n    assert_eq!(\n        \"dn:LCtrl dn:RCtrl dn:LShift dn:RShift dn:LGui dn:RGui dn:LAlt dn:RAlt \\\n         up:LCtrl up:RCtrl up:LShift up:RShift up:LGui up:RGui up:LAlt up:RAlt dn:A up:A \\\n         dn:LCtrl dn:RCtrl dn:LShift dn:RShift dn:LGui dn:RGui dn:LAlt dn:RAlt \\\n         up:LCtrl dn:B up:B dn:LCtrl \\\n         up:RCtrl dn:C up:C dn:RCtrl \\\n         up:LShift dn:D up:D dn:LShift \\\n         up:RShift dn:E up:E dn:RShift \\\n         up:LGui dn:F up:F dn:LGui \\\n         up:RGui dn:G up:G dn:RGui \\\n         up:LAlt dn:H up:H dn:LAlt \\\n         up:RAlt dn:I up:I dn:RAlt \\\n         up:LCtrl up:LShift up:LGui up:LAlt dn:J up:J dn:LCtrl dn:LShift dn:LGui dn:LAlt\",\n        result\n    );\n}\n\n#[test]\n#[should_panic]\nfn unmod_keys_mod_list_cannot_be_empty() {\n    simulate(\n        \"\n         (defcfg)\n         (defsrc a)\n         (deflayer base (unmod () a))\n        \",\n        \"\",\n    );\n}\n\n#[test]\n#[should_panic]\nfn unmod_keys_mod_list_cannot_have_nonmod_key() {\n    simulate(\n        \"\n         (defcfg)\n         (defsrc a)\n         (deflayer base (unmod (lmet c) a))\n        \",\n        \"\",\n    );\n}\n\n#[test]\n#[should_panic]\nfn unmod_keys_mod_list_cannot_have_empty_keys_after_mod_list() {\n    simulate(\n        \"\n         (defcfg)\n         (defsrc a)\n         (deflayer base (unmod (lmet)))\n        \",\n        \"\",\n    );\n}\n\n#[test]\n#[should_panic]\nfn unmod_keys_mod_list_cannot_have_empty_keys() {\n    simulate(\n        \"\n         (defcfg)\n         (defsrc a)\n         (deflayer base (unmod))\n        \",\n        \"\",\n    );\n}\n\n#[test]\n#[should_panic]\nfn unmod_keys_mod_list_cannot_have_invalid_keys() {\n    simulate(\n        \"\n         (defcfg)\n         (defsrc a)\n         (deflayer base (unmod invalid-key))\n        \",\n        \"\",\n    );\n}\n"
  },
  {
    "path": "src/tests/sim_tests/use_defsrc_sim_tests.rs",
    "content": "use super::*;\n\n#[test]\nfn use_defsrc_deflayer() {\n    let result = simulate(\n        r##\"\n         (defcfg)\n         (defsrc a b c d)\n         (deflayer base\n            1 2 3 (layer-while-held other)\n         )\n         (deflayer other\n            4 5 (layer-while-held src) XX\n         )\n         (deflayer src\n            use-defsrc use-defsrc XX XX\n         )\n        \"##,\n        \"d:d d:c d:b d:a t:100\",\n    )\n    .to_ascii();\n    assert_eq!(\"t:2ms dn:B t:1ms dn:A\", result);\n}\n\n#[test]\nfn use_defsrc_deflayermap() {\n    const CFG: &str = \"\n         (defcfg process-unmapped-keys yes)\n         (defsrc a b c d)\n         (deflayer base\n            1\n            (layer-while-held othermap1)\n            (layer-while-held othermap2)\n            (layer-while-held othermap3)\n         )\n         (deflayermap (othermap1)\n            a 5\n            ___ use-defsrc\n         )\n         (deflayermap (othermap2)\n            a 6\n            __ use-defsrc\n            _ x\n         )\n         (deflayermap (othermap3)\n            a 7\n            _ use-defsrc\n            __ x\n         )\n        \";\n    let result = simulate(CFG, \"d:b d:a d:c d:e t:10\").to_ascii();\n    assert_eq!(\"t:1ms dn:Kb5 t:1ms dn:C t:1ms dn:E\", result);\n    let result = simulate(CFG, \"d:c d:a d:c d:e t:10\").to_ascii();\n    assert_eq!(\"t:1ms dn:Kb6 t:1ms dn:X t:1ms dn:E\", result);\n    let result = simulate(CFG, \"d:d d:a d:c d:e t:10\").to_ascii();\n    assert_eq!(\"t:1ms dn:Kb7 t:1ms dn:C t:1ms dn:X\", result);\n}\n"
  },
  {
    "path": "src/tests/sim_tests/vkey_sim_tests.rs",
    "content": "use super::*;\n\n#[test]\nfn hold_for_duration() {\n    const CFG: &str = r\"\n     (defsrc a b c)\n     (defvirtualkeys lmet lmet)\n     (defalias hm (hold-for-duration 50 lmet))\n     (deflayer base\n        (multi @hm (macro-repeat 40 @hm))\n        (multi 1 @hm)\n        (release-key lmet)\n     )\n    \";\n    let result = simulate(CFG, \"d:a t:200 u:a t:60\").to_ascii();\n    assert_eq!(\"t:1ms dn:LGui t:258ms up:LGui\", result);\n    let result = simulate(CFG, \"d:a u:a t:25 d:c u:c t:25\").to_ascii();\n    assert_eq!(\"t:2ms dn:LGui t:23ms up:LGui\", result);\n    let result = simulate(CFG, \"d:a u:a t:25 d:b u:b t:25 d:b u:b t:60\").to_ascii();\n    assert_eq!(\n        \"t:2ms dn:LGui t:23ms dn:Kb1 t:1ms up:Kb1 t:24ms dn:Kb1 t:1ms up:Kb1 t:49ms up:LGui\",\n        result\n    );\n}\n\n// =============================================================================\n// Virtual Key Simulator Input Tests\n// =============================================================================\n// These tests verify the simulator's ability to directly activate virtual keys\n// using the vk:name[:action] syntax in simulation input strings.\n\n/// Test vk:name with default press action\n#[test]\nfn vk_sim_default_press() {\n    const CFG: &str = r\"\n        (defsrc a)\n        (defvirtualkeys vk_test (multi lctl lalt))\n        (deflayer base a)\n    \";\n    // vk:name without action should default to press\n    // Virtual key actions happen immediately (no preceding tick)\n    let result = simulate(CFG, \"vk:vk_test t:10\").to_ascii();\n    assert_eq!(\"dn:LCtrl dn:LAlt\", result);\n}\n\n/// Test vk:name:press explicit action\n#[test]\nfn vk_sim_explicit_press() {\n    const CFG: &str = r\"\n        (defsrc a)\n        (defvirtualkeys vk_test lmet)\n        (deflayer base a)\n    \";\n    // Virtual key actions happen immediately (no preceding tick)\n    let result = simulate(CFG, \"vk:vk_test:press t:10\").to_ascii();\n    assert_eq!(\"dn:LGui\", result);\n}\n\n/// Test vk:name:p shorthand for press\n#[test]\nfn vk_sim_press_shorthand() {\n    const CFG: &str = r\"\n        (defsrc a)\n        (defvirtualkeys vk_test lmet)\n        (deflayer base a)\n    \";\n    let result = simulate(CFG, \"vk:vk_test:p t:10\").to_ascii();\n    assert_eq!(\"dn:LGui\", result);\n}\n\n/// Test vk:name:release action\n#[test]\nfn vk_sim_release() {\n    const CFG: &str = r\"\n        (defsrc a)\n        (defvirtualkeys vk_test lmet)\n        (deflayer base a)\n    \";\n    // Press first, then release after tick\n    let result = simulate(CFG, \"vk:vk_test:press t:10 vk:vk_test:release t:10\").to_ascii();\n    assert_eq!(\"dn:LGui t:10ms up:LGui\", result);\n}\n\n/// Test vk:name:tap action (press + release)\n#[test]\nfn vk_sim_tap() {\n    const CFG: &str = r\"\n        (defsrc a)\n        (defvirtualkeys vk_test lmet)\n        (deflayer base a)\n    \";\n    // For tap, press happens immediately, then 1ms tick, then release\n    let result = simulate(CFG, \"vk:vk_test:tap t:10\").to_ascii();\n    assert_eq!(\"dn:LGui t:1ms up:LGui\", result);\n}\n\n/// Test vk:name:t shorthand for tap\n#[test]\nfn vk_sim_tap_shorthand() {\n    const CFG: &str = r\"\n        (defsrc a)\n        (defvirtualkeys vk_test lmet)\n        (deflayer base a)\n    \";\n    let result = simulate(CFG, \"vk:vk_test:t t:10\").to_ascii();\n    assert_eq!(\"dn:LGui t:1ms up:LGui\", result);\n}\n\n/// Test vk:name:toggle action\n#[test]\nfn vk_sim_toggle() {\n    const CFG: &str = r\"\n        (defsrc a)\n        (defvirtualkeys vk_test lmet)\n        (deflayer base a)\n    \";\n    // First toggle: press (key not active -> activate)\n    // Second toggle: release (key active -> deactivate)\n    let result = simulate(CFG, \"vk:vk_test:toggle t:10 vk:vk_test:toggle t:10\").to_ascii();\n    assert_eq!(\"dn:LGui t:10ms up:LGui\", result);\n}\n\n/// Test vk:name:g shorthand for toggle\n#[test]\nfn vk_sim_toggle_shorthand() {\n    const CFG: &str = r\"\n        (defsrc a)\n        (defvirtualkeys vk_test lmet)\n        (deflayer base a)\n    \";\n    let result = simulate(CFG, \"vk:vk_test:g t:10 vk:vk_test:g t:10\").to_ascii();\n    assert_eq!(\"dn:LGui t:10ms up:LGui\", result);\n}\n\n/// Test fakekey: prefix (alias for vk:)\n#[test]\nfn vk_sim_fakekey_prefix() {\n    const CFG: &str = r\"\n        (defsrc a)\n        (defvirtualkeys vk_test lmet)\n        (deflayer base a)\n    \";\n    let result = simulate(CFG, \"fakekey:vk_test:tap t:10\").to_ascii();\n    assert_eq!(\"dn:LGui t:1ms up:LGui\", result);\n}\n\n/// Test virtualkey: prefix (alias for vk:)\n#[test]\nfn vk_sim_virtualkey_prefix() {\n    const CFG: &str = r\"\n        (defsrc a)\n        (defvirtualkeys vk_test lmet)\n        (deflayer base a)\n    \";\n    let result = simulate(CFG, \"virtualkey:vk_test:tap t:10\").to_ascii();\n    assert_eq!(\"dn:LGui t:1ms up:LGui\", result);\n}\n\n/// Test 🎭 emoji prefix\n#[test]\nfn vk_sim_emoji_prefix() {\n    const CFG: &str = r\"\n        (defsrc a)\n        (defvirtualkeys vk_test lmet)\n        (deflayer base a)\n    \";\n    let result = simulate(CFG, \"🎭:vk_test:tap t:10\").to_ascii();\n    assert_eq!(\"dn:LGui t:1ms up:LGui\", result);\n}\n\n/// Test virtual key with layer switching\n#[test]\nfn vk_sim_layer_switch() {\n    const CFG: &str = r\"\n        (defsrc a b)\n        (defvirtualkeys vk_layer (layer-switch other))\n        (deflayer base a b)\n        (deflayer other 1 2)\n    \";\n    // Activate the layer switch virtual key, then press 'a' which should output '1'\n    let result = simulate(CFG, \"vk:vk_layer t:10 d:a u:a t:10\").to_ascii();\n    assert_eq!(\"t:10ms dn:Kb1 t:1ms up:Kb1\", result);\n}\n\n/// Test multiple virtual keys in sequence\n#[test]\nfn vk_sim_multiple_vkeys() {\n    const CFG: &str = r\"\n        (defsrc a)\n        (defvirtualkeys\n            vk_ctrl lctl\n            vk_alt lalt\n        )\n        (deflayer base a)\n    \";\n    // Each vk action triggers a layout tick, so there's 1ms between each action\n    let result = simulate(\n        CFG,\n        \"vk:vk_ctrl:press vk:vk_alt:press t:10 vk:vk_alt:release vk:vk_ctrl:release t:10\",\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:LCtrl t:1ms dn:LAlt t:9ms up:LAlt t:1ms up:LCtrl\",\n        result\n    );\n}\n\n// =============================================================================\n// End Virtual Key Simulator Input Tests\n// =============================================================================\n\n/// Ignored because PRESSED_KEYS is a global static,\n/// so shares state with other tests and will fail at random.\n/// Should be run on its own until PRESSED_KEYS can be refactored\n/// to avoid being a global.\n///\n/// The \"must_be_single_threaded\" function naming is referenced\n/// in test runners, e.g. justfile and workflows.\n#[ignore]\n#[test]\nfn on_idle_must_be_single_threaded() {\n    const CFG: &str = r\"\n     (defvirtualkeys lmet lmet)\n     (defalias i1 (on-idle 20 tap-vkey lmet)\n               i2 (on-physical-idle 20 tap-vkey lmet))\n     (defsrc a b c)\n     (deflayer base\n        (caps-word 100) @i1 @i2\n     )\n    \";\n    let result = simulate(\n        CFG,\n        \"d:c t:10 u:c t:5 d:a t:50 u:a t:10 t:10 t:10 t:10 t:10 t:10 t:10 t:10 t:10 t:10 t:10 t:10\",\n    )\n    .to_ascii();\n    assert_eq!(\"t:86ms dn:LGui t:1ms up:LGui\", result);\n    let result = simulate(\n        CFG,\n        \"d:b t:10 u:b t:5 d:a t:50 u:a t:10 t:10 t:10 t:10 t:10 t:10 t:10 t:10 t:10 t:10 t:10 t:10\",\n    )\n    .to_ascii();\n    assert_eq!(\"t:137ms dn:LGui t:1ms up:LGui\", result);\n}\n"
  },
  {
    "path": "src/tests/sim_tests/zippychord_sim_tests.rs",
    "content": "use super::*;\n\nstatic ZIPPY_CFG: &str = \"(defsrc lalt)(deflayer base (caps-word 2000))(defzippy file)\";\nstatic ZIPPY_FILE_CONTENT: &str = \"\ndy\tday\ndy 1\tMonday\n abc\tAlphabet\npr\tpre ⌫\npra\tpartner\npr q\tpull request\nr df\trecipient\n w  a\tWashington\nxy\tWxYz\nrq\trequest\nrqa\trequest␣assistance\n.g\tgit\n.g f p\tgit fetch -p\n12\thi\n1234\tbye\n\";\n\nfn simulate_with_zippy_file_content(cfg: &str, input: &str, content: &str) -> String {\n    let mut fcontent = FxHashMap::default();\n    fcontent.insert(\"file\".into(), content.into());\n    simulate_with_file_content(cfg, input, fcontent)\n}\n\n#[test]\nfn sim_zippychord_capitalize() {\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:a t:10 d:b t:10 d:spc t:10 d:c u:a u:b u:c u:spc t:300 \\\n         d:a t:10 d:b t:10 d:spc t:10 d:c t:300\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:A t:10ms dn:B t:10ms dn:Space t:10ms \\\n         dn:BSpace up:BSpace dn:BSpace up:BSpace dn:BSpace up:BSpace \\\n         dn:LShift up:A dn:A up:LShift \\\n         dn:L up:L dn:P up:P dn:H up:H up:A dn:A up:B dn:B dn:E up:E dn:T up:T \\\n         t:1ms up:A t:1ms up:B t:1ms up:C t:1ms up:Space t:296ms \\\n         dn:A t:10ms dn:B t:10ms dn:Space t:10ms \\\n         dn:BSpace up:BSpace dn:BSpace up:BSpace dn:BSpace up:BSpace \\\n         dn:LShift up:A dn:A up:LShift \\\n         dn:L up:L dn:P up:P dn:H up:H up:A dn:A up:B dn:B dn:E up:E dn:T up:T\",\n        result\n    );\n}\n\n#[test]\nfn sim_zippychord_followup_with_prev() {\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:d t:10 d:y t:10 u:d u:y t:10 d:1 t:300\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:D t:10ms dn:BSpace up:BSpace \\\n         up:D dn:D dn:A up:A up:Y dn:Y \\\n         t:10ms up:D t:1ms up:Y t:9ms \\\n         dn:BSpace up:BSpace dn:BSpace up:BSpace dn:BSpace up:BSpace \\\n         dn:LShift dn:M up:M up:LShift dn:O up:O dn:N up:N dn:D up:D dn:A up:A dn:Y up:Y\",\n        result\n    );\n}\n\n#[test]\nfn sim_zippychord_followup_no_prev() {\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:r t:10 u:r t:10 d:d d:f t:10 t:300\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:R t:10ms up:R t:10ms dn:D t:1ms \\\n        dn:BSpace up:BSpace dn:BSpace up:BSpace \\\n        dn:R up:R dn:E up:E dn:C up:C dn:I up:I dn:P up:P dn:I up:I dn:E up:E dn:N up:N dn:T up:T\",\n        result\n    );\n}\n\n#[test]\nfn sim_zippychord_washington() {\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:w d:spc t:10\n         u:w u:spc t:10\n         d:a d:spc t:10\n         u:a u:spc t:300\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:W t:1ms dn:Space t:9ms up:W t:1ms up:Space t:9ms \\\n         dn:A t:1ms dn:BSpace up:BSpace dn:BSpace up:BSpace dn:BSpace up:BSpace \\\n         dn:LShift dn:W up:W up:LShift \\\n         up:A dn:A dn:S up:S dn:H up:H dn:I up:I dn:N up:N dn:G up:G dn:T up:T dn:O up:O dn:N up:N \\\n         t:9ms up:A t:1ms up:Space\",\n        result\n    );\n}\n\n#[test]\nfn sim_zippychord_overlap() {\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:r t:10  d:q t:10 d:a t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:R t:10ms dn:BSpace up:BSpace \\\n        up:R dn:R dn:E up:E up:Q dn:Q dn:U up:U dn:E up:E dn:S up:S dn:T up:T t:10ms \\\n        dn:Space up:Space \\\n        up:A dn:A dn:S up:S dn:S up:S dn:I up:I dn:S up:S dn:T up:T up:A dn:A dn:N up:N dn:C up:C dn:E up:E\",\n        result\n    );\n    let result =\n        simulate_with_zippy_file_content(ZIPPY_CFG, \"d:1 d:2 d:3 d:4 t:20\", ZIPPY_FILE_CONTENT)\n            .to_ascii();\n    assert_eq!(\n        \"dn:Kb1 t:1ms dn:BSpace up:BSpace dn:H up:H dn:I up:I t:1ms dn:Kb3 t:1ms \\\n         dn:BSpace up:BSpace dn:BSpace up:BSpace dn:BSpace up:BSpace \\\n         dn:B up:B dn:Y up:Y dn:E up:E\",\n        result\n    );\n}\n\n#[test]\nfn sim_zippychord_lsft() {\n    // test lsft behaviour while pressed\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:lsft t:10 d:d t:10 d:y t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:LShift t:10ms dn:D t:10ms dn:BSpace up:BSpace up:D dn:D up:LShift dn:A up:A up:Y dn:Y dn:LShift\",\n        result\n    );\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:lsft t:10 d:x t:10 d:y t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:LShift t:10ms dn:X t:10ms dn:BSpace up:BSpace \\\n         dn:W up:W up:LShift up:X dn:X dn:LShift up:Y dn:Y up:LShift dn:Z up:Z dn:LShift\",\n        result\n    );\n\n    // ensure lsft-held behaviour goes away when released\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:lsft t:10 d:d u:lsft t:10 d:y t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:LShift t:10ms dn:D t:1ms up:LShift t:9ms dn:BSpace up:BSpace up:D dn:D dn:A up:A up:Y dn:Y\",\n        result\n    );\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:lsft t:10 d:x u:lsft t:10 d:y t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:LShift t:10ms dn:X t:1ms up:LShift t:9ms dn:BSpace up:BSpace \\\n         dn:LShift dn:W up:W up:LShift up:X dn:X dn:LShift up:Y dn:Y up:LShift dn:Z up:Z\",\n        result\n    );\n}\n\n#[test]\nfn sim_zippychord_rsft() {\n    // test rsft behaviour while pressed\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:rsft t:10 d:d t:10 d:y t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:RShift t:10ms dn:D t:10ms dn:BSpace up:BSpace up:D dn:D up:RShift dn:A up:A up:Y dn:Y dn:RShift\",\n        result\n    );\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:rsft t:10 d:x t:10 d:y t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:RShift t:10ms dn:X t:10ms dn:BSpace up:BSpace \\\n         dn:W up:W up:RShift up:X dn:X dn:LShift up:Y dn:Y up:LShift dn:Z up:Z dn:RShift\",\n        result\n    );\n\n    // ensure rsft-held behaviour goes away when released\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:rsft t:10 d:d u:rsft t:10 d:y t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:RShift t:10ms dn:D t:1ms up:RShift t:9ms dn:BSpace up:BSpace up:D dn:D dn:A up:A up:Y dn:Y\",\n        result\n    );\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:rsft t:10 d:x u:rsft t:10 d:y t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:RShift t:10ms dn:X t:1ms up:RShift t:9ms dn:BSpace up:BSpace \\\n         dn:LShift dn:W up:W up:LShift up:X dn:X dn:LShift up:Y dn:Y up:LShift dn:Z up:Z\",\n        result\n    );\n}\n\n#[test]\nfn sim_zippychord_ralt() {\n    // test ralt behaviour while pressed\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:ralt t:10 d:d t:10 d:y t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:RAlt t:10ms dn:D t:10ms dn:BSpace up:BSpace up:RAlt up:D dn:D dn:A up:A up:Y dn:Y dn:RAlt\",\n        result\n    );\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:ralt t:10 d:x t:10 d:y t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:RAlt t:10ms dn:X t:10ms dn:BSpace up:BSpace \\\n         up:RAlt dn:LShift dn:W up:W up:LShift up:X dn:X dn:LShift up:Y dn:Y up:LShift dn:Z up:Z dn:RAlt\",\n        result\n    );\n\n    // ensure rsft-held behaviour goes away when released\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:ralt t:10 d:d u:ralt t:10 d:y t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:RAlt t:10ms dn:D t:1ms up:RAlt t:9ms dn:BSpace up:BSpace up:D dn:D dn:A up:A up:Y dn:Y\",\n        result\n    );\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:ralt t:10 d:x u:ralt t:10 d:y t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:RAlt t:10ms dn:X t:1ms up:RAlt t:9ms dn:BSpace up:BSpace \\\n         dn:LShift dn:W up:W up:LShift up:X dn:X dn:LShift up:Y dn:Y up:LShift dn:Z up:Z\",\n        result\n    );\n}\n\n#[test]\nfn sim_zippychord_caps_word() {\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:lalt u:lalt t:10 d:d t:10 d:y t:10 u:d u:y t:10 d:spc u:spc t:2000 d:d d:y t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"t:10ms dn:LShift dn:D t:10ms dn:BSpace up:BSpace up:D dn:D dn:A up:A up:Y dn:Y \\\n         t:10ms up:D t:1ms up:LShift up:Y t:9ms dn:Space t:1ms up:Space \\\n         t:1999ms dn:D t:1ms dn:BSpace up:BSpace up:D dn:D dn:A up:A up:Y dn:Y\",\n        result\n    );\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:lalt t:10 d:y t:10 d:x t:10 u:x u:y t:10 d:spc u:spc t:1000 d:y d:x t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"t:10ms dn:LShift dn:Y t:10ms dn:BSpace up:BSpace \\\n         dn:W up:W up:X dn:X up:Y dn:Y dn:Z up:Z \\\n         t:10ms up:X t:1ms up:LShift up:Y t:9ms dn:Space t:1ms up:Space \\\n         t:999ms dn:Y t:1ms dn:BSpace up:BSpace dn:LShift dn:W up:W up:LShift \\\n         up:X dn:X dn:LShift up:Y dn:Y up:LShift dn:Z up:Z\",\n        result\n    );\n}\n\n#[test]\nfn sim_zippychord_triple_combo() {\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:. d:g t:10 u:. u:g d:f t:10 u:f d:p t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:Dot t:1ms dn:BSpace up:BSpace up:G dn:G dn:I up:I dn:T up:T t:9ms up:Dot t:1ms up:G \\\n         t:1ms dn:F t:8ms up:F t:1ms \\\n         dn:BSpace up:BSpace dn:BSpace up:BSpace dn:BSpace up:BSpace dn:BSpace up:BSpace \\\n         dn:G up:G dn:I up:I dn:T up:T dn:Space up:Space \\\n         dn:F up:F dn:E up:E dn:T up:T dn:C up:C dn:H up:H dn:Space up:Space \\\n         dn:Minus up:Minus up:P dn:P\",\n        result\n    );\n}\n\n#[test]\nfn sim_zippychord_disabled_by_typing() {\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:v u:v t:10 d:d d:y t:100\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\"dn:V t:1ms up:V t:9ms dn:D t:1ms dn:Y\", result);\n}\n\n#[test]\nfn sim_zippychord_prefix() {\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:p d:r u:p u:r t:10 d:q u:q t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:P t:1ms dn:BSpace up:BSpace up:P dn:P up:R dn:R dn:E up:E dn:Space up:Space \\\n         dn:BSpace up:BSpace t:1ms up:P t:1ms up:R t:7ms \\\n         dn:BSpace up:BSpace dn:BSpace up:BSpace \\\n         dn:U up:U dn:L up:L dn:L up:L dn:Space up:Space \\\n         dn:R up:R dn:E up:E up:Q dn:Q dn:U up:U dn:E up:E dn:S up:S dn:T up:T t:1ms up:Q\",\n        result\n    );\n    let result = simulate_with_zippy_file_content(\n        ZIPPY_CFG,\n        \"d:p d:r d:a t:10 u:d u:r u:a\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii()\n    .no_time()\n    .no_releases();\n    assert_eq!(\n        \"dn:P dn:BSpace \\\n         dn:P dn:R dn:E dn:Space dn:BSpace \\\n         dn:BSpace dn:BSpace dn:A dn:R dn:T dn:N dn:E dn:R\",\n        result\n    );\n}\n\n#[test]\nfn sim_zippychord_smartspace_full() {\n    let result = simulate_with_zippy_file_content(\n        \"(defsrc)(deflayer base)(defzippy file\n         smart-space full)\",\n        \"d:d d:y t:10 u:d u:y t:100 d:. t:10 u:. t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:D t:1ms dn:BSpace up:BSpace up:D dn:D dn:A up:A up:Y dn:Y dn:Space up:Space \\\n         t:9ms up:D t:1ms up:Y t:99ms dn:BSpace up:BSpace dn:Dot t:10ms up:Dot\",\n        result\n    );\n\n    // Test that prefix works as intended.\n    let result = simulate_with_zippy_file_content(\n        \"(defsrc)(deflayer base)(defzippy file\n         smart-space add-space-only)\",\n        \"d:p d:r t:10 u:p u:r t:100 d:. t:10 u:. t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:P t:1ms dn:BSpace up:BSpace up:P dn:P up:R dn:R dn:E up:E \\\n         dn:Space up:Space dn:BSpace up:BSpace \\\n         t:9ms up:P t:1ms up:R t:99ms dn:Dot t:10ms up:Dot\",\n        result\n    );\n}\n\n#[test]\nfn sim_zippychord_smartspace_spaceonly() {\n    let result = simulate_with_zippy_file_content(\n        \"(defsrc)(deflayer base)(defzippy file\n         smart-space add-space-only)\",\n        \"d:d d:y t:10 u:d u:y t:100 d:. t:10 u:. t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:D t:1ms dn:BSpace up:BSpace up:D dn:D dn:A up:A up:Y dn:Y dn:Space up:Space \\\n         t:9ms up:D t:1ms up:Y t:99ms dn:Dot t:10ms up:Dot\",\n        result\n    );\n\n    // Test that prefix works as intended.\n    let result = simulate_with_zippy_file_content(\n        \"(defsrc)(deflayer base)(defzippy file\n         smart-space add-space-only)\",\n        \"d:p d:r t:10 u:p u:r t:100 d:. t:10 u:. t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:P t:1ms dn:BSpace up:BSpace up:P dn:P up:R dn:R dn:E up:E \\\n         dn:Space up:Space dn:BSpace up:BSpace \\\n         t:9ms up:P t:1ms up:R t:99ms dn:Dot t:10ms up:Dot\",\n        result\n    );\n}\n\n#[test]\nfn sim_zippychord_smartspace_none() {\n    let result = simulate_with_zippy_file_content(\n        \"(defsrc)(deflayer base)(defzippy file\n         smart-space none)\",\n        \"d:d d:y t:10 u:d u:y t:100 d:. t:10 u:. t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:D t:1ms dn:BSpace up:BSpace up:D dn:D dn:A up:A up:Y dn:Y \\\n         t:9ms up:D t:1ms up:Y t:99ms dn:Dot t:10ms up:Dot\",\n        result\n    );\n\n    // Test that prefix works as intended.\n    let result = simulate_with_zippy_file_content(\n        \"(defsrc)(deflayer base)(defzippy file\n         smart-space add-space-only)\",\n        \"d:p d:r t:10 u:p u:r t:100 d:. t:10 u:. t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:P t:1ms dn:BSpace up:BSpace up:P dn:P up:R dn:R dn:E up:E \\\n         dn:Space up:Space dn:BSpace up:BSpace \\\n         t:9ms up:P t:1ms up:R t:99ms dn:Dot t:10ms up:Dot\",\n        result\n    );\n}\n\n#[test]\nfn sim_zippychord_smartspace_overlap() {\n    let result = simulate_with_zippy_file_content(\n        \"(defsrc)(deflayer base)(defzippy file\n         smart-space full)\",\n        \"d:r t:10 d:q t:10 d:a t:10\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:R t:10ms dn:BSpace up:BSpace \\\n        up:R dn:R dn:E up:E up:Q dn:Q dn:U up:U dn:E up:E dn:S up:S dn:T up:T dn:Space up:Space t:10ms \\\n        dn:BSpace up:BSpace dn:Space up:Space \\\n        up:A dn:A dn:S up:S dn:S up:S dn:I up:I dn:S up:S dn:T up:T up:A dn:A dn:N up:N dn:C up:C dn:E up:E \\\n        dn:Space up:Space\",\n        result\n    );\n    let result = simulate_with_zippy_file_content(\n        \"(defsrc)(deflayer base)(defzippy file\n         smart-space full)\",\n        \"d:1 d:2 d:3 d:4 t:20\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:Kb1 t:1ms dn:BSpace up:BSpace dn:H up:H dn:I up:I dn:Space up:Space \\\n         t:1ms dn:Kb3 t:1ms \\\n         dn:BSpace up:BSpace dn:BSpace up:BSpace dn:BSpace up:BSpace dn:BSpace up:BSpace \\\n         dn:B up:B dn:Y up:Y dn:E up:E dn:Space up:Space\",\n        result\n    );\n}\n\n#[test]\nfn sim_zippychord_smartspace_followup() {\n    let result = simulate_with_zippy_file_content(\n        \"(defsrc)(deflayer base)(defzippy file\n         smart-space full)\",\n        \"d:d t:10 d:y t:10 u:d u:y t:10 d:1 t:300\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:D t:10ms dn:BSpace up:BSpace \\\n         up:D dn:D dn:A up:A up:Y dn:Y dn:Space up:Space \\\n         t:10ms up:D t:1ms up:Y t:9ms \\\n         dn:BSpace up:BSpace dn:BSpace up:BSpace dn:BSpace up:BSpace dn:BSpace up:BSpace \\\n         dn:LShift dn:M up:M up:LShift dn:O up:O dn:N up:N dn:D up:D dn:A up:A dn:Y up:Y dn:Space up:Space\",\n        result\n    );\n}\n\nconst CUSTOM_PUNC_CFG: &str = \"\\\n(defsrc)\n(deflayer base)\n(defzippy file\n smart-space full\n smart-space-punctuation (z ! ® *)\n output-character-mappings (\n   ® AG-r\n   * S-AG-v\n   ! S-1))\";\n\n#[test]\nfn sim_zippychord_smartspace_custom_punc() {\n    // 1 without lsft: no smart-space-erase\n    let result = simulate_with_zippy_file_content(\n        CUSTOM_PUNC_CFG,\n        \"d:d t:10 d:y t:10 u:d u:y t:10 d:1 t:300\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:D t:10ms dn:BSpace up:BSpace \\\n         up:D dn:D dn:A up:A up:Y dn:Y dn:Space up:Space \\\n         t:10ms up:D t:1ms up:Y t:9ms \\\n         dn:BSpace up:BSpace dn:BSpace up:BSpace dn:BSpace up:BSpace dn:BSpace up:BSpace \\\n         dn:LShift dn:M up:M up:LShift dn:O up:O dn:N up:N dn:D up:D dn:A up:A dn:Y up:Y dn:Space up:Space\",\n        result\n    );\n\n    // S-1 = !: smart-space-erase\n    let result = simulate_with_zippy_file_content(\n        CUSTOM_PUNC_CFG,\n        \"d:1 d:2 t:10 u:1 u:2 t:10 d:lsft d:1 u:1 u:lsft t:300\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:Kb1 t:1ms dn:BSpace up:BSpace \\\n         dn:H up:H dn:I up:I dn:Space up:Space t:9ms \\\n         up:Kb1 t:1ms up:Kb2 t:9ms \\\n         dn:LShift t:1ms dn:BSpace up:BSpace dn:Kb1 t:1ms up:Kb1 t:1ms up:LShift\",\n        result\n    );\n\n    // z: smart-space-erase\n    let result = simulate_with_zippy_file_content(\n        CUSTOM_PUNC_CFG,\n        \"d:1 d:2 t:10 u:1 u:2 t:10 d:z u:z t:300\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:Kb1 t:1ms dn:BSpace up:BSpace \\\n         dn:H up:H dn:I up:I dn:Space up:Space t:9ms \\\n         up:Kb1 t:1ms up:Kb2 t:9ms \\\n         dn:BSpace up:BSpace dn:Z t:1ms up:Z\",\n        result\n    );\n\n    // r no altgr: no smart-space-erase\n    let result = simulate_with_zippy_file_content(\n        CUSTOM_PUNC_CFG,\n        \"d:1 d:2 t:10 u:1 u:2 t:10 d:r u:r t:300\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:Kb1 t:1ms dn:BSpace up:BSpace \\\n         dn:H up:H dn:I up:I dn:Space up:Space t:9ms \\\n         up:Kb1 t:1ms up:Kb2 t:9ms \\\n         dn:R t:1ms up:R\",\n        result\n    );\n\n    // r with altgr: smart-space-erase\n    let result = simulate_with_zippy_file_content(\n        CUSTOM_PUNC_CFG,\n        \"d:1 d:2 t:10 u:1 u:2 t:10 d:ralt d:r u:r u:ralt t:300\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:Kb1 t:1ms dn:BSpace up:BSpace \\\n         dn:H up:H dn:I up:I dn:Space up:Space t:9ms \\\n         up:Kb1 t:1ms up:Kb2 t:9ms \\\n         dn:RAlt t:1ms dn:BSpace up:BSpace dn:R t:1ms up:R t:1ms up:RAlt\",\n        result\n    );\n\n    // v with altgr+lsft: smart-space-erase\n    let result = simulate_with_zippy_file_content(\n        CUSTOM_PUNC_CFG,\n        \"d:1 d:2 t:10 u:1 u:2 t:10 d:ralt d:lsft d:v u:v u:ralt u:lsft t:300\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:Kb1 t:1ms dn:BSpace up:BSpace \\\n         dn:H up:H dn:I up:I dn:Space up:Space t:9ms \\\n         up:Kb1 t:1ms up:Kb2 t:9ms \\\n         dn:RAlt t:1ms dn:LShift t:1ms dn:BSpace up:BSpace dn:V t:1ms up:V t:1ms up:RAlt t:1ms up:LShift\",\n        result\n    );\n}\n\n#[test]\nfn sim_zippychord_non_followup_subsequent_with_potential_followups_available() {\n    let result = simulate_with_zippy_file_content(\n        \"(defsrc)(deflayer base)(defzippy file\n         smart-space full)\",\n        \"d:g d:. t:10 u:g u:. t:1000 d:g d:. t:10 u:g u:. t:1000\",\n        ZIPPY_FILE_CONTENT,\n    )\n    .to_ascii();\n    assert_eq!(\n        \"dn:G t:1ms dn:BSpace up:BSpace up:G dn:G dn:I up:I dn:T up:T dn:Space up:Space t:9ms \\\n         up:G t:1ms up:Dot t:999ms \\\n         dn:G t:1ms dn:BSpace up:BSpace up:G dn:G dn:I up:I dn:T up:T dn:Space up:Space t:9ms \\\n         up:G t:1ms up:Dot\",\n        result\n    );\n}\n\nconst DEAD_KEYS_CFG: &str = \"\\\n(defsrc)\n(deflayer base)\n(defzippy file\n smart-space full\n output-character-mappings (\n   ’ (no-erase ')\n   ‘ (no-erase `)\n   é (single-output ' e)\n   è (single-output ` e)\n ))\";\nstatic DEAD_KEYS_FILE_CONTENT: &str = \"\nby\th’elo\nbye\tby‘e\nby d\tft‘a’ng\nby d a\taye\ncy\thélo\ncye\tbyè\ncy d\tftéèng\ncy d a\taye\n\";\n\n#[test]\nfn sim_zippychord_noerase() {\n    let result = simulate_with_zippy_file_content(\n        DEAD_KEYS_CFG,\n        \"d:b d:y t:100 d:e u:b u:y u:e t:1000\",\n        DEAD_KEYS_FILE_CONTENT,\n    )\n    .no_releases()\n    .no_time()\n    .to_ascii();\n    assert_eq!(\n        \"dn:B dn:BSpace dn:H dn:Quote dn:E dn:L dn:O dn:Space \\\n         dn:BSpace dn:BSpace dn:BSpace dn:BSpace dn:BSpace \\\n         dn:B dn:Y dn:Grave dn:E dn:Space\",\n        result,\n    );\n\n    let result = simulate_with_zippy_file_content(\n        DEAD_KEYS_CFG,\n        \"d:b d:y t:100 u:b u:y d:d t:10 u:d d:a t:10 u:a t:1000\",\n        DEAD_KEYS_FILE_CONTENT,\n    )\n    .no_releases()\n    .no_time()\n    .to_ascii();\n    assert_eq!(\n        \"dn:B dn:BSpace dn:H dn:Quote dn:E dn:L dn:O dn:Space \\\n         dn:BSpace dn:BSpace dn:BSpace dn:BSpace dn:BSpace \\\n         dn:F dn:T dn:Grave dn:A dn:Quote dn:N dn:G dn:Space \\\n         dn:BSpace dn:BSpace dn:BSpace dn:BSpace dn:BSpace dn:BSpace \\\n         dn:A dn:Y dn:E dn:Space\",\n        result,\n    );\n}\n\n#[test]\nfn sim_zippychord_single_output() {\n    let result = simulate_with_zippy_file_content(\n        DEAD_KEYS_CFG,\n        \"d:c d:y t:100 d:e u:c u:y u:e t:1000\",\n        DEAD_KEYS_FILE_CONTENT,\n    )\n    .no_releases()\n    .no_time()\n    .to_ascii();\n    assert_eq!(\n        \"dn:C dn:BSpace dn:H dn:Quote dn:E dn:L dn:O dn:Space \\\n         dn:BSpace dn:BSpace dn:BSpace dn:BSpace dn:BSpace \\\n         dn:B dn:Y dn:Grave dn:E dn:Space\",\n        result,\n    );\n\n    let result = simulate_with_zippy_file_content(\n        DEAD_KEYS_CFG,\n        \"d:c d:y t:100 u:c u:y d:d t:10 u:d d:a t:10 u:a t:1000\",\n        DEAD_KEYS_FILE_CONTENT,\n    )\n    .no_releases()\n    .no_time()\n    .to_ascii();\n    assert_eq!(\n        \"dn:C dn:BSpace dn:H dn:Quote dn:E dn:L dn:O dn:Space \\\n         dn:BSpace dn:BSpace dn:BSpace dn:BSpace dn:BSpace \\\n         dn:F dn:T dn:Quote dn:E dn:Grave dn:E dn:N dn:G dn:Space \\\n         dn:BSpace dn:BSpace dn:BSpace dn:BSpace dn:BSpace dn:BSpace dn:BSpace \\\n         dn:A dn:Y dn:E dn:Space\",\n        result,\n    );\n}\n"
  },
  {
    "path": "src/tests.rs",
    "content": "use kanata_parser::cfg::*;\nuse std::sync::Mutex;\n\n#[cfg(all(\n    feature = \"simulated_output\",\n    not(feature = \"simulated_input\"),\n    not(feature = \"interception_driver\")\n))]\nmod sim_tests;\n\n#[cfg(all(\n    target_os = \"macos\",\n    feature = \"simulated_input\",\n    feature = \"simulated_output\"\n))]\nmod passthru_macos_tests;\n\nstatic CFG_PARSE_LOCK: Mutex<()> = Mutex::new(());\n\nfn init_log() {\n    use simplelog::*;\n    use std::sync::OnceLock;\n    static LOG_INIT: OnceLock<()> = OnceLock::new();\n    LOG_INIT.get_or_init(|| {\n        let mut log_cfg = ConfigBuilder::new();\n        if let Err(e) = log_cfg.set_time_offset_to_local() {\n            eprintln!(\"WARNING: could not set log TZ to local: {e:?}\");\n        };\n        log_cfg.set_time_format_rfc3339();\n        CombinedLogger::init(vec![TermLogger::new(\n            // Note: set to a different level to see logs in tests.\n            // Also, not all tests call init_log so you might have to add the call there too.\n            LevelFilter::Off,\n            log_cfg.build(),\n            TerminalMode::Stderr,\n            ColorChoice::AlwaysAnsi,\n        )])\n        .expect(\"logger can init\");\n    });\n}\n\n#[test]\nfn parse_simple() {\n    init_log();\n    let _lk = match CFG_PARSE_LOCK.lock() {\n        Ok(guard) => guard,\n        Err(poisoned) => poisoned.into_inner(),\n    };\n    new_from_file(&std::path::PathBuf::from(\"./cfg_samples/simple.kbd\")).unwrap();\n}\n\n#[test]\nfn parse_minimal() {\n    init_log();\n    let _lk = match CFG_PARSE_LOCK.lock() {\n        Ok(guard) => guard,\n        Err(poisoned) => poisoned.into_inner(),\n    };\n    new_from_file(&std::path::PathBuf::from(\"./cfg_samples/minimal.kbd\")).unwrap();\n}\n\n#[test]\nfn parse_deflayermap() {\n    init_log();\n    let _lk = match CFG_PARSE_LOCK.lock() {\n        Ok(guard) => guard,\n        Err(poisoned) => poisoned.into_inner(),\n    };\n    new_from_file(&std::path::PathBuf::from(\"./cfg_samples/deflayermap.kbd\")).unwrap();\n}\n\n#[test]\nfn parse_default() {\n    init_log();\n    let _lk = match CFG_PARSE_LOCK.lock() {\n        Ok(guard) => guard,\n        Err(poisoned) => poisoned.into_inner(),\n    };\n    new_from_file(&std::path::PathBuf::from(\"./cfg_samples/kanata.kbd\")).unwrap();\n}\n\n#[test]\nfn parse_jtroo() {\n    init_log();\n    let _lk = match CFG_PARSE_LOCK.lock() {\n        Ok(guard) => guard,\n        Err(poisoned) => poisoned.into_inner(),\n    };\n    let cfg = new_from_file(&std::path::PathBuf::from(\"./cfg_samples/jtroo.kbd\")).unwrap();\n    assert_eq!(cfg.layer_info.len(), 8);\n}\n\n#[test]\nfn parse_f13_f24() {\n    init_log();\n    let _lk = match CFG_PARSE_LOCK.lock() {\n        Ok(guard) => guard,\n        Err(poisoned) => poisoned.into_inner(),\n    };\n    new_from_file(&std::path::PathBuf::from(\"./cfg_samples/f13_f24.kbd\")).unwrap();\n}\n\n#[test]\nfn parse_home_row_mods() {\n    init_log();\n    let _lk = match CFG_PARSE_LOCK.lock() {\n        Ok(guard) => guard,\n        Err(poisoned) => poisoned.into_inner(),\n    };\n    new_from_file(&std::path::PathBuf::from(\n        \"./cfg_samples/home-row-mod-basic.kbd\",\n    ))\n    .unwrap();\n    new_from_file(&std::path::PathBuf::from(\n        \"./cfg_samples/home-row-mod-advanced.kbd\",\n    ))\n    .unwrap();\n}\n\n#[test]\nfn parse_press_release_toggle_vkeys() {\n    init_log();\n    let _lk = match CFG_PARSE_LOCK.lock() {\n        Ok(guard) => guard,\n        Err(poisoned) => poisoned.into_inner(),\n    };\n    new_from_file(&std::path::PathBuf::from(\n        \"./cfg_samples/key-toggle_press-only_release-only.kbd\",\n    ))\n    .unwrap();\n}\n\n#[test]\nfn parse_automousekeys_only() {\n    init_log();\n    let _lk = match CFG_PARSE_LOCK.lock() {\n        Ok(guard) => guard,\n        Err(poisoned) => poisoned.into_inner(),\n    };\n    new_from_file(&std::path::PathBuf::from(\n        \"./cfg_samples/automousekeys-only.kbd\",\n    ))\n    .unwrap();\n}\n\n#[test]\nfn parse_automousekeys_full_map() {\n    init_log();\n    let _lk = match CFG_PARSE_LOCK.lock() {\n        Ok(guard) => guard,\n        Err(poisoned) => poisoned.into_inner(),\n    };\n    new_from_file(&std::path::PathBuf::from(\n        \"./cfg_samples/automousekeys-full-map.kbd\",\n    ))\n    .unwrap();\n}\n\n#[test]\nfn parse_push_msg() {\n    init_log();\n    let _lk = match CFG_PARSE_LOCK.lock() {\n        Ok(guard) => guard,\n        Err(poisoned) => poisoned.into_inner(),\n    };\n    new_from_file(&std::path::PathBuf::from(\"./cfg_samples/push-msg.kbd\")).unwrap();\n}\n\n#[test]\n#[cfg(target_pointer_width = \"64\")]\nfn sizeof_state() {\n    init_log();\n    assert_eq!(\n        std::mem::size_of::<\n            kanata_keyberon::layout::State<\n                &'static &'static [&'static kanata_parser::custom_action::CustomAction],\n            >,\n        >(),\n        2 * std::mem::size_of::<usize>()\n    );\n}\n"
  },
  {
    "path": "tcp_protocol/Cargo.toml",
    "content": "[package]\nname = \"kanata-tcp-protocol\"\nversion = \"0.1110.0\"\nedition = \"2021\"\ndescription = \"TCP protocol for kanata. This does not follow semver.\"\nlicense = \"LGPL-3.0-only\"\n\n[dependencies]\nserde = { version = \"1\", features = [\"alloc\", \"derive\"], default-features = false }\nserde_derive = \"1.0\"\nserde_json = { version = \"1\", features = [\"alloc\"], default-features = false }\n"
  },
  {
    "path": "tcp_protocol/src/lib.rs",
    "content": "//! Kanata TCP Protocol\n//!\n//! This crate defines the JSON message format for communication between\n//! TCP clients and the Kanata keyboard remapping daemon.\n\nuse serde::{Deserialize, Serialize};\nuse std::str::FromStr;\n\n/// Messages sent from the server to connected clients.\n#[derive(Debug, Serialize, Deserialize)]\npub enum ServerMessage {\n    LayerChange {\n        new: String,\n    },\n    LayerNames {\n        names: Vec<String>,\n    },\n    FakeKeyNames {\n        names: Vec<String>,\n    },\n    CurrentLayerInfo {\n        name: String,\n        cfg_text: String,\n    },\n    ConfigFileReload {\n        new: String,\n    },\n    CurrentLayerName {\n        name: String,\n    },\n    MessagePush {\n        message: serde_json::Value,\n    },\n    Error {\n        msg: String,\n    },\n    /// Response to `Hello` command with server capabilities.\n    /// Introduced in protocol v1.11.\n    HelloOk {\n        version: String,\n        protocol: u8,\n        capabilities: Vec<String>,\n    },\n    /// Response to Reload commands when `wait: true` was specified.\n    /// Introduced in protocol v1.11.\n    ReloadResult {\n        ok: bool,\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        timeout_ms: Option<u64>,\n    },\n    /// Sent when a tap-hold key transitions to hold state.\n    /// The `key` field is the physical key name (e.g., `\"caps\"`, `\"a\"`).\n    HoldActivated {\n        key: String,\n    },\n    /// Sent when a tap-hold key triggers its tap action.\n    /// The `key` field is the physical key name (e.g., `\"caps\"`, `\"a\"`).\n    TapActivated {\n        key: String,\n    },\n}\n\n#[derive(Serialize, Deserialize, Debug)]\n#[serde(tag = \"status\")]\npub enum ServerResponse {\n    Ok,\n    Error { msg: String },\n}\n\nimpl ServerResponse {\n    pub fn as_bytes(&self) -> Vec<u8> {\n        let mut msg = serde_json::to_vec(self).expect(\"ServerResponse should serialize\");\n        msg.push(b'\\n');\n        msg\n    }\n}\n\nimpl ServerMessage {\n    pub fn as_bytes(&self) -> Vec<u8> {\n        let mut msg = serde_json::to_vec(self).expect(\"ServerMessage should serialize\");\n        msg.push(b'\\n');\n        msg\n    }\n}\n\n/// Messages sent from clients to the server.\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub enum ClientMessage {\n    ChangeLayer {\n        new: String,\n    },\n    RequestLayerNames {},\n    RequestFakeKeyNames {},\n    RequestCurrentLayerInfo {},\n    RequestCurrentLayerName {},\n    ActOnFakeKey {\n        name: String,\n        action: FakeKeyActionMessage,\n    },\n    SetMouse {\n        x: u16,\n        y: u16,\n    },\n\n    /// Reload the current configuration file.\n    Reload {\n        /// If true, block until reload completes or times out.\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        wait: Option<bool>,\n        /// Maximum time to wait for reload (milliseconds). Default: 5000.\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        timeout_ms: Option<u64>,\n    },\n    ReloadNext {\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        wait: Option<bool>,\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        timeout_ms: Option<u64>,\n    },\n    ReloadPrev {\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        wait: Option<bool>,\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        timeout_ms: Option<u64>,\n    },\n    ReloadNum {\n        index: usize,\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        wait: Option<bool>,\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        timeout_ms: Option<u64>,\n    },\n    ReloadFile {\n        path: String,\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        wait: Option<bool>,\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        timeout_ms: Option<u64>,\n    },\n\n    /// Request server capabilities and version.\n    /// Introduced in protocol v1.11.\n    Hello {},\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]\npub enum FakeKeyActionMessage {\n    Press,\n    Release,\n    Tap,\n    Toggle,\n}\n\nimpl FromStr for ClientMessage {\n    type Err = serde_json::Error;\n\n    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {\n        serde_json::from_str(s)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_server_response_json_format() {\n        assert_eq!(\n            serde_json::to_string(&ServerResponse::Ok).unwrap(),\n            r#\"{\"status\":\"Ok\"}\"#\n        );\n    }\n\n    #[test]\n    fn test_as_bytes_includes_newline() {\n        let response = ServerResponse::Ok;\n        assert!(response.as_bytes().ends_with(b\"\\n\"));\n    }\n\n    #[test]\n    fn test_hello_ok_json_format() {\n        let msg = ServerMessage::HelloOk {\n            version: \"1.10.0\".to_string(),\n            protocol: 1,\n            capabilities: vec![\"reload\".to_string()],\n        };\n        let json = serde_json::to_string(&msg).unwrap();\n        assert!(json.contains(\"HelloOk\"));\n        assert!(json.contains(\"\\\"version\\\":\\\"1.10.0\\\"\"));\n    }\n\n    #[test]\n    fn test_reload_with_wait() {\n        let msg = ClientMessage::Reload {\n            wait: Some(true),\n            timeout_ms: Some(5000),\n        };\n        let json = serde_json::to_string(&msg).unwrap();\n        assert!(json.contains(\"wait\\\":true\"));\n        assert!(json.contains(\"timeout_ms\\\":5000\"));\n    }\n\n    #[test]\n    fn test_reload_minimal() {\n        // Backward compatible: no optional fields\n        let json = r#\"{\"Reload\":{}}\"#;\n        let msg: ClientMessage = serde_json::from_str(json).unwrap();\n        match msg {\n            ClientMessage::Reload { wait, timeout_ms } => {\n                assert!(wait.is_none());\n                assert!(timeout_ms.is_none());\n            }\n            _ => panic!(\"Expected Reload\"),\n        }\n    }\n\n    #[test]\n    fn test_existing_commands_unchanged() {\n        // Verify existing commands still parse without any new fields\n        let json = r#\"{\"ChangeLayer\":{\"new\":\"nav\"}}\"#;\n        let msg: ClientMessage = serde_json::from_str(json).unwrap();\n        assert!(matches!(msg, ClientMessage::ChangeLayer { new } if new == \"nav\"));\n\n        let json = r#\"{\"RequestLayerNames\":{}}\"#;\n        let _msg: ClientMessage = serde_json::from_str(json).unwrap();\n\n        let json = r#\"{\"ActOnFakeKey\":{\"name\":\"test\",\"action\":\"Tap\"}}\"#;\n        let _msg: ClientMessage = serde_json::from_str(json).unwrap();\n    }\n\n    #[test]\n    fn test_request_fake_key_names() {\n        let json = r#\"{\"RequestFakeKeyNames\":{}}\"#;\n        let msg: ClientMessage = serde_json::from_str(json).unwrap();\n        assert!(matches!(msg, ClientMessage::RequestFakeKeyNames {}));\n    }\n\n    #[test]\n    fn test_fake_key_names_response() {\n        let msg = ServerMessage::FakeKeyNames {\n            names: vec![\"email-sig\".to_string(), \"nav-mode\".to_string()],\n        };\n        let json = serde_json::to_string(&msg).unwrap();\n        assert_eq!(\n            json,\n            r#\"{\"FakeKeyNames\":{\"names\":[\"email-sig\",\"nav-mode\"]}}\"#\n        );\n\n        // Round-trip\n        let parsed: ServerMessage = serde_json::from_str(&json).unwrap();\n        match parsed {\n            ServerMessage::FakeKeyNames { names } => {\n                assert_eq!(names, vec![\"email-sig\", \"nav-mode\"]);\n            }\n            _ => panic!(\"Expected FakeKeyNames\"),\n        }\n    }\n\n    #[test]\n    fn test_fake_key_names_empty() {\n        let msg = ServerMessage::FakeKeyNames { names: vec![] };\n        let json = serde_json::to_string(&msg).unwrap();\n        assert_eq!(json, r#\"{\"FakeKeyNames\":{\"names\":[]}}\"#);\n    }\n\n    #[test]\n    fn test_hold_activated_json_format() {\n        let msg = ServerMessage::HoldActivated {\n            key: \"caps\".to_string(),\n        };\n        let json = serde_json::to_string(&msg).unwrap();\n        assert_eq!(json, r#\"{\"HoldActivated\":{\"key\":\"caps\"}}\"#);\n    }\n\n    #[test]\n    fn test_tap_activated_json_format() {\n        let msg = ServerMessage::TapActivated {\n            key: \"a\".to_string(),\n        };\n        let json = serde_json::to_string(&msg).unwrap();\n        assert_eq!(json, r#\"{\"TapActivated\":{\"key\":\"a\"}}\"#);\n    }\n}\n"
  },
  {
    "path": "wasm/.gitignore",
    "content": "# wasm-pack output\npkg/\n\n# do not commit lockfile; not important for wasm project\nCargo.lock\n"
  },
  {
    "path": "wasm/Cargo.toml",
    "content": "[package]\nname = \"kanata-wasm\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[lib]\ncrate-type = [ \"cdylib\", \"rlib\" ]\n\n[dependencies]\nwasm-bindgen = \"0.2.100\"\nkanata = { path = \"..\" , default-features = false, features = [ \"simulated_output\", \"zippychord\" ] }\nanyhow = \"1.0.81\"\nlog = \"0.4.21\"\nconsole_error_panic_hook = \"0.1.7\"\nrustc-hash = \"1.1.0\"\n"
  },
  {
    "path": "wasm/README.md",
    "content": "# Kanata WASM\n\nCode to expose kanata functionality over WASM.\n\nPrerequisites:\n\n- Install the wasm32-unknown-unknown rustc target\n- Install wasm-bindgen: `cargo install wasm-bindgen-cli`\n- Install wasm-opt: `cargo install wasm-opt`\n\nYou can run the command below to generate files for use in the browser:\n\n```\nwasm-pack build --target web\n```\n\n- Either install `just` and run `just wasm-build <output_directory>\n  or execute the commands for the recipe (see `justfile` in the repository).\n\nThis will output files into `pkg/` which can be used for a website.\nThis has yet not been tested with targets other than web (e.g. node).\n\nAn example project using this code is the\n[online kanata simulator](https://github.com/jtroo/jtroo.github.io).\n\nThe simulator files can be served by any webserver,\nsuch as the default Python webserver:\n\n```\npython -m http.server <tcp_port>\n```\n"
  },
  {
    "path": "wasm/src/lib.rs",
    "content": "use anyhow::{Result, anyhow, bail};\nuse kanata_state_machine::{kanata::handle_fakekey_action, oskbd::*, *};\nuse rustc_hash::FxHashMap;\nuse wasm_bindgen::prelude::*;\n\nuse std::sync::Once;\n\nstatic INIT: Once = Once::new();\n\n#[wasm_bindgen]\npub fn init() {\n    INIT.call_once(|| {\n        std::panic::set_hook(Box::new(console_error_panic_hook::hook));\n    });\n}\n\n#[wasm_bindgen]\npub fn check_config(cfg: &str) -> JsValue {\n    let (cfg, files) = split_cfg_and_sim_files(cfg);\n    let res = Kanata::new_from_str(&cfg, files);\n    JsValue::from_str(&match res {\n        Ok(_) => \"Config is good!\".to_owned(),\n        Err(e) => format!(\"{e:?}\"),\n    })\n}\n\n#[wasm_bindgen]\npub fn simulate(cfg: &str, sim: &str) -> JsValue {\n    JsValue::from_str(&match simulate_impl(cfg, sim) {\n        Ok(s) => s,\n        Err(e) => format!(\"Config or simulation input has error.\\n\\n{e:?}\"),\n    })\n}\n\nfn split_cfg_and_sim_files(original_cfg: &str) -> (String, FxHashMap<String, String>) {\n    let mut cfg = String::new();\n    let mut file_name = None;\n    let mut file = String::new();\n    let mut sim_files = Default::default();\n\n    let mut original_lines = original_cfg.lines();\n    const FILE_PREFIX: &str = \"=== file:\";\n\n    // Parse main configuration.\n    // Must not consume whole iterator here.\n    #[allow(clippy::while_let_on_iterator)]\n    while let Some(line) = original_lines.next() {\n        if line.starts_with(FILE_PREFIX) {\n            file_name = line.strip_prefix(FILE_PREFIX);\n            break;\n        }\n        cfg.push_str(line);\n        cfg.push('\\n');\n    }\n    if file_name.is_none() {\n        return (cfg, sim_files);\n    }\n\n    // Parse simulated sim_files.\n    for line in original_lines {\n        if line.starts_with(FILE_PREFIX) {\n            sim_files.insert(file_name.unwrap().to_string(), file.clone());\n            file_name = line.strip_prefix(FILE_PREFIX);\n            file.clear();\n            continue;\n        }\n        file.push_str(line);\n        file.push('\\n');\n    }\n    // Save the last file\n    sim_files.insert(file_name.unwrap().to_string(), file.clone());\n    (cfg, sim_files)\n}\n\nfn parse_fakekey_spec(spec: &str) -> Result<(&str, FakeKeyAction)> {\n    let (name, action) = match spec.split_once(':') {\n        Some((name, action_str)) => {\n            let action = match action_str {\n                \"press\" | \"p\" => FakeKeyAction::Press,\n                \"release\" => FakeKeyAction::Release,\n                \"tap\" | \"t\" => FakeKeyAction::Tap,\n                \"toggle\" | \"g\" => FakeKeyAction::Toggle,\n                _ => bail!(\n                    \"unknown fakekey action: {action_str}. Expected: press, release, tap, or toggle\"\n                ),\n            };\n            (name, action)\n        }\n        None => (spec, FakeKeyAction::Press),\n    };\n    if name.is_empty() {\n        bail!(\"fakekey name cannot be empty\");\n    }\n    Ok((name, action))\n}\n\nfn apply_fakekey_action(k: &mut Kanata, name: &str, action: FakeKeyAction) -> Result<()> {\n    let index = k\n        .virtual_keys\n        .get(name)\n        .ok_or_else(|| anyhow!(\"unknown virtual key: {name}\"))?;\n    handle_fakekey_action(action, k.layout.bm(), FAKE_KEY_ROW, *index as u16);\n    Ok(())\n}\n\nfn simulate_impl(cfg: &str, sim: &str) -> Result<String> {\n    let (cfg, files) = split_cfg_and_sim_files(cfg);\n    let mut k = Kanata::new_from_str(&cfg, files)?;\n    let mut accumulated_ticks = 0;\n    for l in sim.lines() {\n        for pair in l.split_whitespace() {\n            match pair.split_once(':') {\n                Some((kind, val)) => match kind {\n                    \"tick\" | \"🕐\" | \"t\" => {\n                        let ticks = str::parse::<u128>(val).map_err(|e| {\n                            anyhow!(\"line: {l}\\ninvalid number in {kind}:{val}\\n{e}\")\n                        })?;\n                        if ticks > 60000 {\n                            bail!(\"line: {l}\\nmax tick is 60000: {kind}:{val}\")\n                        }\n                        for _ in 0..ticks {\n                            if !k.can_block_update_idle_waiting(1) {\n                                k.tick_ms(1, &None)?;\n                            } else {\n                                k.kbd_out.tick();\n                            }\n                        }\n                        accumulated_ticks += ticks;\n                        if accumulated_ticks > 3600000 {\n                            bail!(\n                                \"You are trying to simulate over an hour's worth of time.\\nAborting to avoid wasting your CPU cycles.\"\n                            )\n                        }\n                    }\n                    \"press\" | \"↓\" | \"d\" | \"down\" => {\n                        let key_code = str_to_oscode(val)\n                            .ok_or_else(|| anyhow!(\"line: {l}\\nunknown key in {kind}:{val}\"))?;\n                        k.handle_input_event(&KeyEvent {\n                            code: key_code,\n                            value: KeyValue::Press,\n                        })?;\n                    }\n                    \"release\" | \"↑\" | \"u\" | \"up\" => {\n                        let key_code = str_to_oscode(val)\n                            .ok_or_else(|| anyhow!(\"line: {l}\\nunknown key in {kind}:{val}\"))?;\n                        k.handle_input_event(&KeyEvent {\n                            code: key_code,\n                            value: KeyValue::Release,\n                        })?;\n                    }\n                    \"repeat\" | \"⟳\" | \"r\" => {\n                        let key_code = str_to_oscode(val)\n                            .ok_or_else(|| anyhow!(\"line: {l}\\nunknown key in {kind}:{val}\"))?;\n                        k.handle_input_event(&KeyEvent {\n                            code: key_code,\n                            value: KeyValue::Repeat,\n                        })?;\n                    }\n                    // Virtual/fake key activation: vk:name[:action]\n                    \"vk\" | \"fakekey\" | \"virtualkey\" | \"🎭\" => {\n                        let (vk_name, action) = parse_fakekey_spec(val)?;\n                        apply_fakekey_action(&mut k, vk_name, action)?;\n                    }\n                    // Layer switch: ls:layer_name\n                    \"ls\" | \"layer-switch\" | \"🔀\" => {\n                        let layer_idx = k\n                            .layer_info\n                            .iter()\n                            .position(|l| l.name == val)\n                            .ok_or_else(|| anyhow!(\"line: {l}\\nunknown layer: {val}\"))?;\n                        k.layout.bm().set_default_layer(layer_idx);\n                    }\n                    _ => bail!(\n                        \"line: {l}\\ninvalid action: {kind}\\nvalid actions:\\nu | up\\nd | down\\nt | tick\\nvk | fakekey\\nls | layer-switch\"\n                    ),\n                },\n                None => bail!(\"line: {l}\\ninvalid item: {pair}\\nexpected format: action:item\"),\n            }\n        }\n    }\n    Ok(k.kbd_out\n        .outputs\n        .events\n        .join(\"\\n\")\n        .replace('↓', \"↓(press)   \")\n        .replace('↑', \"↑(release) \"))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    fn sim(cfg: &str, sim: &str) -> String {\n        simulate_impl(cfg, sim).expect(\"simulation should succeed\")\n    }\n\n    fn sim_err(cfg: &str, sim: &str) -> String {\n        simulate_impl(cfg, sim)\n            .expect_err(\"simulation should fail\")\n            .to_string()\n    }\n\n    #[test]\n    fn basic_key_press_release() {\n        let result = sim(\"(defsrc a)(deflayer base b)\", \"d:a t:10 u:a t:1\");\n        assert!(result.contains(\"↓(press)   B\"));\n        assert!(result.contains(\"↑(release) B\"));\n    }\n\n    #[test]\n    fn tick_syntax_variants() {\n        let result = sim(\n            \"(defsrc a)(deflayer base b)\",\n            \"d:a t:5 u:a tick:5 d:a 🕐:5 u:a t:1\",\n        );\n        assert!(result.contains(\"t:5ms\"));\n    }\n\n    #[test]\n    fn press_syntax_variants() {\n        let result = sim(\n            \"(defsrc a b c d)(deflayer base 1 2 3 4)\",\n            \"press:a t:1 u:a t:1 down:b t:1 u:b t:1 ↓:c t:1 u:c t:1 d:d t:1 u:d t:1\",\n        );\n        assert!(result.contains(\"↓(press)   Kb1\"));\n        assert!(result.contains(\"↓(press)   Kb2\"));\n        assert!(result.contains(\"↓(press)   Kb3\"));\n        assert!(result.contains(\"↓(press)   Kb4\"));\n    }\n\n    #[test]\n    fn release_syntax_variants() {\n        let result = sim(\n            \"(defsrc a b c d)(deflayer base 1 2 3 4)\",\n            \"d:a t:1 release:a t:1 d:b t:1 up:b t:1 d:c t:1 ↑:c t:1 d:d t:1 u:d t:1\",\n        );\n        assert!(result.contains(\"↑(release) Kb1\"));\n        assert!(result.contains(\"↑(release) Kb2\"));\n        assert!(result.contains(\"↑(release) Kb3\"));\n        assert!(result.contains(\"↑(release) Kb4\"));\n    }\n\n    #[test]\n    fn repeat_key() {\n        let result = sim(\"(defsrc a)(deflayer base b)\", \"d:a t:10 r:a t:10 u:a t:1\");\n        assert!(result.contains(\"↓(press)   B\"));\n        assert!(result.contains(\"↑(release) B\"));\n    }\n\n    #[test]\n    fn layer_switch() {\n        let result = sim(\n            \"(defsrc a)(deflayer base a)(deflayer other 1)\",\n            \"ls:other d:a t:10 u:a t:1\",\n        );\n        assert!(result.contains(\"↓(press)   Kb1\"));\n    }\n\n    #[test]\n    fn layer_switch_back_to_base() {\n        let result = sim(\n            \"(defsrc a)(deflayer base a)(deflayer other 1)\",\n            \"ls:other d:a t:10 u:a t:1 ls:base d:a t:10 u:a t:1\",\n        );\n        assert!(result.contains(\"↓(press)   Kb1\"));\n        assert!(result.contains(\"↓(press)   A\"));\n    }\n\n    #[test]\n    fn layer_switch_syntax_variants() {\n        let r1 = sim(\n            \"(defsrc a)(deflayer base a)(deflayer x 1)\",\n            \"ls:x d:a t:1 u:a t:1\",\n        );\n        let r2 = sim(\n            \"(defsrc a)(deflayer base a)(deflayer x 1)\",\n            \"layer-switch:x d:a t:1 u:a t:1\",\n        );\n        let r3 = sim(\n            \"(defsrc a)(deflayer base a)(deflayer x 1)\",\n            \"🔀:x d:a t:1 u:a t:1\",\n        );\n        assert!(r1.contains(\"Kb1\"));\n        assert!(r2.contains(\"Kb1\"));\n        assert!(r3.contains(\"Kb1\"));\n    }\n\n    #[test]\n    fn virtual_key_press() {\n        let result = sim(\n            \"(defsrc a)(defvirtualkeys vk1 lctl)(deflayer base a)\",\n            \"vk:vk1 t:10\",\n        );\n        assert!(result.contains(\"↓(press)   LCtrl\"));\n    }\n\n    #[test]\n    fn virtual_key_tap() {\n        let result = sim(\n            \"(defsrc a)(defvirtualkeys vk1 lctl)(deflayer base a)\",\n            \"vk:vk1:tap t:10\",\n        );\n        assert!(result.contains(\"↓(press)   LCtrl\"));\n        assert!(result.contains(\"↑(release) LCtrl\"));\n    }\n\n    #[test]\n    fn virtual_key_syntax_variants() {\n        let r1 = sim(\n            \"(defsrc a)(defvirtualkeys v lctl)(deflayer base a)\",\n            \"vk:v t:1\",\n        );\n        let r2 = sim(\n            \"(defsrc a)(defvirtualkeys v lctl)(deflayer base a)\",\n            \"fakekey:v t:1\",\n        );\n        let r3 = sim(\n            \"(defsrc a)(defvirtualkeys v lctl)(deflayer base a)\",\n            \"virtualkey:v t:1\",\n        );\n        let r4 = sim(\n            \"(defsrc a)(defvirtualkeys v lctl)(deflayer base a)\",\n            \"🎭:v t:1\",\n        );\n        assert!(r1.contains(\"LCtrl\"));\n        assert!(r2.contains(\"LCtrl\"));\n        assert!(r3.contains(\"LCtrl\"));\n        assert!(r4.contains(\"LCtrl\"));\n    }\n\n    #[test]\n    fn error_unknown_key() {\n        let err = sim_err(\"(defsrc a)(deflayer base a)\", \"d:notakey\");\n        assert!(err.contains(\"unknown key\"));\n    }\n\n    #[test]\n    fn error_unknown_layer() {\n        let err = sim_err(\"(defsrc a)(deflayer base a)\", \"ls:notalayer\");\n        assert!(err.contains(\"unknown layer\"));\n    }\n\n    #[test]\n    fn error_unknown_virtual_key() {\n        let err = sim_err(\"(defsrc a)(deflayer base a)\", \"vk:notavk\");\n        assert!(err.contains(\"unknown virtual key\"));\n    }\n\n    #[test]\n    fn error_invalid_action() {\n        let err = sim_err(\"(defsrc a)(deflayer base a)\", \"badaction:a\");\n        assert!(err.contains(\"invalid action\"));\n    }\n\n    #[test]\n    fn error_missing_colon() {\n        let err = sim_err(\"(defsrc a)(deflayer base a)\", \"nocolon\");\n        assert!(err.contains(\"expected format\"));\n    }\n}\n"
  },
  {
    "path": "windows_key_tester/.gitignore",
    "content": "Cargo.lock\r\n"
  },
  {
    "path": "windows_key_tester/Cargo.toml",
    "content": "[package]\nname = \"windows_key_tester\"\nversion = \"0.3.0\"\nauthors = [\"jtroo <j.andreitabs@gmail.com>\"]\ndescription = \"Windows keycode tester\"\nkeywords = []\ncategories = [\"command-line-utilities\"]\nhomepage = \"https://github.com/jtroo/kanata\"\nrepository = \"https://github.com/jtroo/kanata\"\nreadme = \"README.md\"\nlicense = \"LGPL-3.0\"\nedition = \"2021\"\n\n[target.'cfg(target_os = \"windows\")'.dependencies]\nclap = { version = \"4\", features = [ \"std\", \"derive\", \"help\", \"suggestions\" ], default-features = false }\nlog = \"0.4.8\"\nsimplelog = \"0.12.0\"\nanyhow = \"1\"\nwinapi = { version = \"0.3.9\", features = [\n    \"wincon\",\n    \"timeapi\",\n    \"mmsystem\",\n] }\nnative-windows-gui = { version = \"1.0.12\", default-features = false }\nkanata-interception = { version = \"0.3.0\", optional = true }\nkanata = { path = \"..\", optional = true }\n\n[features]\ninterception_driver = [ \"kanata-interception\" ]\nwiniov2 = [ \"kanata\" ]\n"
  },
  {
    "path": "windows_key_tester/README.md",
    "content": "# Windows key tester\n\nThis directory contains the code for a Windows key tester. This can be used to\nhelp test keyboard->keycode mappings in Windows that may not yet be listed in\nkanata. For Linux, use the existing [evtest](https://www.systutorials.com/docs/linux/man/1-evtest/).\n"
  },
  {
    "path": "windows_key_tester/src/main.rs",
    "content": "//! This program is intended to be similar to `evtest` but for Windows. It will read keyboard\n//! events, print out the event info, then forward it the keyboard event as-is to the rest of the\n//! operating system handling.\n\n#[cfg(target_os = \"windows\")]\nmod windows;\n#[cfg(target_os = \"windows\")]\nuse windows::*;\n\n#[cfg(target_os = \"windows\")]\nfn main() {\n    let ret = main_impl();\n    if let Err(ref e) = ret {\n        log::error!(\"main got error {}\", e);\n    }\n    eprintln!(\"\\nPress any key to exit\");\n    let _ = std::io::stdin().read_line(&mut String::new());\n}\n\n#[cfg(not(target_os = \"windows\"))]\nfn main() {\n    print!(\"Hello world! Wrong OS. Doing nothing.\");\n}\n"
  },
  {
    "path": "windows_key_tester/src/windows/interception.rs",
    "content": "use anyhow::Result;\nuse kanata_interception as ic;\nuse kanata_interception::{KeyState, ScanCode, Stroke};\n\npub fn start() -> Result<()> {\n    let intrcptn = ic::Interception::new().expect(\n        \"interception driver should init: have you completed the interception driver installation?\",\n    );\n    intrcptn.set_filter(ic::is_keyboard, ic::Filter::KeyFilter(ic::KeyFilter::all()));\n    let mut strokes = [ic::Stroke::Keyboard {\n        code: ic::ScanCode::Esc,\n        state: ic::KeyState::empty(),\n        information: 0,\n    }; 32];\n\n    log::info!(\"interception attached, you can type now\");\n    loop {\n        let dev = intrcptn.wait_with_timeout(std::time::Duration::from_millis(1));\n        if dev > 0 {\n            let num_strokes = intrcptn.receive(dev, &mut strokes);\n            let num_strokes = num_strokes as usize;\n\n            for i in 0..num_strokes {\n                log::info!(\n                    \"got stroke {:?}: num: {}\",\n                    strokes[i],\n                    OsCode::try_from(strokes[i])\n                        .map(|osc| osc.as_u16().to_string())\n                        .unwrap_or_else(|_| {\n                            \"unknown mapping! please file a bug containing these logs\".into()\n                        })\n                );\n                intrcptn.send(dev, &strokes[i..i + 1]);\n            }\n        }\n    }\n}\n\nimpl TryFrom<Stroke> for OsCode {\n    type Error = ();\n\n    fn try_from(item: Stroke) -> Result<Self, Self::Error> {\n        Ok(match item {\n            Stroke::Keyboard { code, state, .. } => {\n                match (state.contains(KeyState::E0), state.contains(KeyState::E1)) {\n                    (false, false) => {\n                        match code {\n                            ScanCode::Esc => OsCode::KEY_ESC,\n                            ScanCode::Num1 => OsCode::KEY_1,\n                            ScanCode::Num2 => OsCode::KEY_2,\n                            ScanCode::Num3 => OsCode::KEY_3,\n                            ScanCode::Num4 => OsCode::KEY_4,\n                            ScanCode::Num5 => OsCode::KEY_5,\n                            ScanCode::Num6 => OsCode::KEY_6,\n                            ScanCode::Num7 => OsCode::KEY_7,\n                            ScanCode::Num8 => OsCode::KEY_8,\n                            ScanCode::Num9 => OsCode::KEY_9,\n                            ScanCode::Num0 => OsCode::KEY_0,\n                            ScanCode::Minus => OsCode::KEY_MINUS,\n                            ScanCode::Equals => OsCode::KEY_EQUAL,\n                            ScanCode::Backspace => OsCode::KEY_BACKSPACE,\n                            ScanCode::Tab => OsCode::KEY_TAB,\n                            ScanCode::Q => OsCode::KEY_Q,\n                            ScanCode::W => OsCode::KEY_W,\n                            ScanCode::E => OsCode::KEY_E,\n                            ScanCode::R => OsCode::KEY_R,\n                            ScanCode::T => OsCode::KEY_T,\n                            ScanCode::Y => OsCode::KEY_Y,\n                            ScanCode::U => OsCode::KEY_U,\n                            ScanCode::I => OsCode::KEY_I,\n                            ScanCode::O => OsCode::KEY_O,\n                            ScanCode::P => OsCode::KEY_P,\n                            ScanCode::LeftBracket => OsCode::KEY_LEFTBRACE,\n                            ScanCode::RightBracket => OsCode::KEY_RIGHTBRACE,\n                            ScanCode::Enter => OsCode::KEY_ENTER,\n                            ScanCode::LeftControl => OsCode::KEY_LEFTCTRL,\n                            ScanCode::A => OsCode::KEY_A,\n                            ScanCode::S => OsCode::KEY_S,\n                            ScanCode::D => OsCode::KEY_D,\n                            ScanCode::F => OsCode::KEY_F,\n                            ScanCode::G => OsCode::KEY_G,\n                            ScanCode::H => OsCode::KEY_H,\n                            ScanCode::J => OsCode::KEY_J,\n                            ScanCode::K => OsCode::KEY_K,\n                            ScanCode::L => OsCode::KEY_L,\n                            ScanCode::SemiColon => OsCode::KEY_SEMICOLON,\n                            ScanCode::Apostrophe => OsCode::KEY_APOSTROPHE,\n                            ScanCode::Grave => OsCode::KEY_GRAVE,\n                            ScanCode::LeftShift => OsCode::KEY_LEFTSHIFT,\n                            ScanCode::BackSlash => OsCode::KEY_BACKSLASH,\n                            ScanCode::Z => OsCode::KEY_Z,\n                            ScanCode::X => OsCode::KEY_X,\n                            ScanCode::C => OsCode::KEY_C,\n                            ScanCode::V => OsCode::KEY_V,\n                            ScanCode::B => OsCode::KEY_B,\n                            ScanCode::N => OsCode::KEY_N,\n                            ScanCode::M => OsCode::KEY_M,\n                            ScanCode::Comma => OsCode::KEY_COMMA,\n                            ScanCode::Period => OsCode::KEY_DOT,\n                            ScanCode::Slash => OsCode::KEY_SLASH,\n                            ScanCode::RightShift => OsCode::KEY_RIGHTSHIFT,\n                            ScanCode::NumpadMultiply => OsCode::KEY_KPASTERISK,\n                            ScanCode::LeftAlt => OsCode::KEY_LEFTALT,\n                            ScanCode::Space => OsCode::KEY_SPACE,\n                            ScanCode::CapsLock => OsCode::KEY_CAPSLOCK,\n                            ScanCode::F1 => OsCode::KEY_F1,\n                            ScanCode::F2 => OsCode::KEY_F2,\n                            ScanCode::F3 => OsCode::KEY_F3,\n                            ScanCode::F4 => OsCode::KEY_F4,\n                            ScanCode::F5 => OsCode::KEY_F5,\n                            ScanCode::F6 => OsCode::KEY_F6,\n                            ScanCode::F7 => OsCode::KEY_F7,\n                            ScanCode::F8 => OsCode::KEY_F8,\n                            ScanCode::F9 => OsCode::KEY_F9,\n                            ScanCode::F10 => OsCode::KEY_F10,\n                            ScanCode::NumLock => OsCode::KEY_NUMLOCK,\n                            ScanCode::ScrollLock => OsCode::KEY_SCROLLLOCK,\n                            ScanCode::Numpad7 => OsCode::KEY_KP7,\n                            ScanCode::Numpad8 => OsCode::KEY_KP8,\n                            ScanCode::Numpad9 => OsCode::KEY_KP9,\n                            ScanCode::NumpadMinus => OsCode::KEY_KPMINUS,\n                            ScanCode::Numpad4 => OsCode::KEY_KP4,\n                            ScanCode::Numpad5 => OsCode::KEY_KP5,\n                            ScanCode::Numpad6 => OsCode::KEY_KP6,\n                            ScanCode::NumpadPlus => OsCode::KEY_KPPLUS,\n                            ScanCode::Numpad1 => OsCode::KEY_KP1,\n                            ScanCode::Numpad2 => OsCode::KEY_KP2,\n                            ScanCode::Numpad3 => OsCode::KEY_KP3,\n                            ScanCode::Numpad0 => OsCode::KEY_KP0,\n                            ScanCode::NumpadPeriod => OsCode::KEY_KPDOT,\n                            ScanCode::Int1 => OsCode::KEY_102ND, /* Key between the left shift and Z. */\n                            ScanCode::F11 => OsCode::KEY_F11,\n                            ScanCode::F12 => OsCode::KEY_F12,\n                            ScanCode::F13 => OsCode::KEY_F13,\n                            ScanCode::F14 => OsCode::KEY_F14,\n                            ScanCode::F15 => OsCode::KEY_F15,\n                            ScanCode::F16 => OsCode::KEY_F16,\n                            ScanCode::F17 => OsCode::KEY_F17,\n                            ScanCode::F18 => OsCode::KEY_F18,\n                            ScanCode::F19 => OsCode::KEY_F19,\n                            ScanCode::F20 => OsCode::KEY_F20,\n                            ScanCode::F21 => OsCode::KEY_F21,\n                            ScanCode::F22 => OsCode::KEY_F22,\n                            ScanCode::F23 => OsCode::KEY_F23,\n                            ScanCode::F24 => OsCode::KEY_F24,\n                            ScanCode::Katakana => OsCode::KEY_KATAKANA,\n                            // Note: the OEM keys below don't seem to correspond to the same VK OEM\n                            // mappings as the LLHOOK codes.\n                            // ScanCode::Oem1 = 0x5A, /* VK_OEM_WSCTRL */\n                            // ScanCode::Oem2 = 0x5B, /* VK_OEM_FINISH */\n                            // ScanCode::Oem3 = 0x5C, /* VK_OEM_JUMP */\n                            // ScanCode::Oem4 = 0x5E, /* VK_OEM_BACKTAB */\n                            // ScanCode::Oem5 = 0x5F, /* VK_OEM_AUTO */\n                            // ScanCode::Oem6 = 0x6F, /* VK_OEM_PA3 */\n                            // ScanCode::Oem7 = 0x71, /* VK_OEM_RESET */\n                            // ScanCode::EraseEOF = 0x5D,\n                            // ScanCode::Zoom => 0x62,\n                            // ScanCode::Help => 0x63,\n                            // ScanCode::AltPrintScreen = 0x55, /* Alt + print screen. */\n                            // ScanCode::SBCSChar = 0x77,\n                            // ScanCode::Convert = 0x79,\n                            // ScanCode::NonConvert = 0x7B,\n                            _ => return Err(()),\n                        }\n                    }\n\n                    (true, _) => {\n                        match code as u8 {\n                            0x10 => OsCode::KEY_PREVIOUSSONG,\n                            0x19 => OsCode::KEY_NEXTSONG,\n                            0x1C => OsCode::KEY_KPENTER,\n                            0x1D => OsCode::KEY_RIGHTCTRL,\n                            0x20 => OsCode::KEY_MUTE,\n                            0x22 => OsCode::KEY_PLAYPAUSE, // sc_media_play\n                            // 0x24 => OsCode::KEY_TODO, // sc_media_stop\n                            0x2E => OsCode::KEY_VOLUMEDOWN, // sc_volume_down\n                            0x30 => OsCode::KEY_VOLUMEUP,   // sc_volume_up\n                            // 0x32 => OsCode::KEY_TODO, // sc_browser_home\n                            0x35 => OsCode::KEY_KPSLASH, // sc_numpad_divide\n                            0x37 => OsCode::KEY_PRINT,   // sc_printScreen\n                            0x38 => OsCode::KEY_RIGHTALT, // sc_altRight\n                            // 0x46 => OsCode::KEY_TODO, // sc_cancel\n                            0x47 => OsCode::KEY_HOME,      // sc_home\n                            0x48 => OsCode::KEY_UP,        // sc_arrowUp\n                            0x49 => OsCode::KEY_PAGEUP,    // sc_pageUp\n                            0x4B => OsCode::KEY_LEFT,      // sc_arrowLeft\n                            0x4D => OsCode::KEY_RIGHT,     // sc_arrowRight\n                            0x4F => OsCode::KEY_END,       // sc_end\n                            0x50 => OsCode::KEY_DOWN,      // sc_arrowDown\n                            0x51 => OsCode::KEY_PAGEDOWN,  // sc_pageDown\n                            0x52 => OsCode::KEY_INSERT,    // sc_insert\n                            0x53 => OsCode::KEY_DELETE,    // sc_delete\n                            0x5B => OsCode::KEY_LEFTMETA,  // sc_metaLeft\n                            0x5C => OsCode::KEY_RIGHTMETA, // sc_metaRight\n                            // 0x5D => OsCode::KEY_TODO, // sc_application\n                            // 0x5E => OsCode::KEY_TODO, // sc_power\n                            // 0x5F => OsCode::KEY_TODO, // sc_sleep\n                            // 0x63 => OsCode::KEY_TODO, // sc_wake\n                            // 0x65 => OsCode::KEY_TODO, // sc_browser_search\n                            // 0x66 => OsCode::KEY_TODO, // sc_browser_favorites\n                            // 0x67 => OsCode::KEY_TODO, // sc_browser_refresh\n                            // 0x68 => OsCode::KEY_TODO, // sc_browser_stop\n                            0x69 => OsCode::KEY_FORWARD, // sc_browser_forward\n                            0x6A => OsCode::KEY_BACK,    // sc_browser_back\n                            // 0x6B => OsCode::KEY_TODO, // sc_launch_app1\n                            // 0x6C => OsCode::KEY_TODO, // sc_launch_email\n                            // 0x6D => OsCode::KEY_TODO, // sc_launch_media\n                            _ => return Err(()),\n                        }\n                    }\n\n                    _ => return Err(()),\n                }\n            }\n            _ => return Err(()),\n        })\n    }\n}\n\n#[allow(unused)]\n#[allow(non_camel_case_types)]\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]\npub enum OsCode {\n    KEY_RESERVED = 0,\n    KEY_ESC = 1,\n    KEY_1 = 2,\n    KEY_2 = 3,\n    KEY_3 = 4,\n    KEY_4 = 5,\n    KEY_5 = 6,\n    KEY_6 = 7,\n    KEY_7 = 8,\n    KEY_8 = 9,\n    KEY_9 = 10,\n    KEY_0 = 11,\n    KEY_MINUS = 12,\n    KEY_EQUAL = 13,\n    KEY_BACKSPACE = 14,\n    KEY_TAB = 15,\n    KEY_Q = 16,\n    KEY_W = 17,\n    KEY_E = 18,\n    KEY_R = 19,\n    KEY_T = 20,\n    KEY_Y = 21,\n    KEY_U = 22,\n    KEY_I = 23,\n    KEY_O = 24,\n    KEY_P = 25,\n    KEY_LEFTBRACE = 26,\n    KEY_RIGHTBRACE = 27,\n    KEY_ENTER = 28,\n    KEY_LEFTCTRL = 29,\n    KEY_A = 30,\n    KEY_S = 31,\n    KEY_D = 32,\n    KEY_F = 33,\n    KEY_G = 34,\n    KEY_H = 35,\n    KEY_J = 36,\n    KEY_K = 37,\n    KEY_L = 38,\n    KEY_SEMICOLON = 39,\n    KEY_APOSTROPHE = 40,\n    KEY_GRAVE = 41,\n    KEY_LEFTSHIFT = 42,\n    KEY_BACKSLASH = 43,\n    KEY_Z = 44,\n    KEY_X = 45,\n    KEY_C = 46,\n    KEY_V = 47,\n    KEY_B = 48,\n    KEY_N = 49,\n    KEY_M = 50,\n    KEY_COMMA = 51,\n    KEY_DOT = 52,\n    KEY_SLASH = 53,\n    KEY_RIGHTSHIFT = 54,\n    KEY_KPASTERISK = 55,\n    KEY_LEFTALT = 56,\n    KEY_SPACE = 57,\n    KEY_CAPSLOCK = 58,\n    KEY_F1 = 59,\n    KEY_F2 = 60,\n    KEY_F3 = 61,\n    KEY_F4 = 62,\n    KEY_F5 = 63,\n    KEY_F6 = 64,\n    KEY_F7 = 65,\n    KEY_F8 = 66,\n    KEY_F9 = 67,\n    KEY_F10 = 68,\n    KEY_NUMLOCK = 69,\n    KEY_SCROLLLOCK = 70,\n    KEY_KP7 = 71,\n    KEY_KP8 = 72,\n    KEY_KP9 = 73,\n    KEY_KPMINUS = 74,\n    KEY_KP4 = 75,\n    KEY_KP5 = 76,\n    KEY_KP6 = 77,\n    KEY_KPPLUS = 78,\n    KEY_KP1 = 79,\n    KEY_KP2 = 80,\n    KEY_KP3 = 81,\n    KEY_KP0 = 82,\n    KEY_KPDOT = 83,\n    KEY_ZENKAKUHANKAKU = 85,\n    KEY_102ND = 86,\n    KEY_F11 = 87,\n    KEY_F12 = 88,\n    KEY_RO = 89,\n    KEY_KATAKANA = 90,\n    KEY_HIRAGANA = 91,\n    KEY_HENKAN = 92,\n    KEY_KATAKANAHIRAGANA = 93,\n    KEY_MUHENKAN = 94,\n    KEY_KPJPCOMMA = 95,\n    KEY_KPENTER = 96,\n    KEY_RIGHTCTRL = 97,\n    KEY_KPSLASH = 98,\n    KEY_SYSRQ = 99,\n    KEY_RIGHTALT = 100,\n    KEY_LINEFEED = 101,\n    KEY_HOME = 102,\n    KEY_UP = 103,\n    KEY_PAGEUP = 104,\n    KEY_LEFT = 105,\n    KEY_RIGHT = 106,\n    KEY_END = 107,\n    KEY_DOWN = 108,\n    KEY_PAGEDOWN = 109,\n    KEY_INSERT = 110,\n    KEY_DELETE = 111,\n    KEY_MACRO = 112,\n    KEY_MUTE = 113,\n    KEY_VOLUMEDOWN = 114,\n    KEY_VOLUMEUP = 115,\n    KEY_POWER = 116,\n    KEY_KPEQUAL = 117,\n    KEY_KPPLUSMINUS = 118,\n    KEY_PAUSE = 119,\n    KEY_SCALE = 120,\n    KEY_KPCOMMA = 121,\n    KEY_HANGEUL = 122,\n    KEY_HANJA = 123,\n    KEY_YEN = 124,\n    KEY_LEFTMETA = 125,\n    KEY_RIGHTMETA = 126,\n    KEY_COMPOSE = 127,\n    KEY_STOP = 128,\n    KEY_AGAIN = 129,\n    KEY_PROPS = 130,\n    KEY_UNDO = 131,\n    KEY_FRONT = 132,\n    KEY_COPY = 133,\n    KEY_OPEN = 134,\n    KEY_PASTE = 135,\n    KEY_FIND = 136,\n    KEY_CUT = 137,\n    KEY_HELP = 138,\n    KEY_MENU = 139,\n    KEY_CALC = 140,\n    KEY_SETUP = 141,\n    KEY_SLEEP = 142,\n    KEY_WAKEUP = 143,\n    KEY_FILE = 144,\n    KEY_SENDFILE = 145,\n    KEY_DELETEFILE = 146,\n    KEY_XFER = 147,\n    KEY_PROG1 = 148,\n    KEY_PROG2 = 149,\n    KEY_WWW = 150,\n    KEY_MSDOS = 151,\n    KEY_COFFEE = 152,\n    KEY_ROTATE_DISPLAY = 153,\n    KEY_CYCLEWINDOWS = 154,\n    KEY_MAIL = 155,\n    KEY_BOOKMARKS = 156,\n    KEY_COMPUTER = 157,\n    KEY_BACK = 158,\n    KEY_FORWARD = 159,\n    KEY_CLOSECD = 160,\n    KEY_EJECTCD = 161,\n    KEY_EJECTCLOSECD = 162,\n    KEY_NEXTSONG = 163,\n    KEY_PLAYPAUSE = 164,\n    KEY_PREVIOUSSONG = 165,\n    KEY_STOPCD = 166,\n    KEY_RECORD = 167,\n    KEY_REWIND = 168,\n    KEY_PHONE = 169,\n    KEY_ISO = 170,\n    KEY_CONFIG = 171,\n    KEY_HOMEPAGE = 172,\n    KEY_REFRESH = 173,\n    KEY_EXIT = 174,\n    KEY_MOVE = 175,\n    KEY_EDIT = 176,\n    KEY_SCROLLUP = 177,\n    KEY_SCROLLDOWN = 178,\n    KEY_KPLEFTPAREN = 179,\n    KEY_KPRIGHTPAREN = 180,\n    KEY_NEW = 181,\n    KEY_REDO = 182,\n    KEY_F13 = 183,\n    KEY_F14 = 184,\n    KEY_F15 = 185,\n    KEY_F16 = 186,\n    KEY_F17 = 187,\n    KEY_F18 = 188,\n    KEY_F19 = 189,\n    KEY_F20 = 190,\n    KEY_F21 = 191,\n    KEY_F22 = 192,\n    KEY_F23 = 193,\n    KEY_F24 = 194,\n    KEY_PLAYCD = 200,\n    KEY_PAUSECD = 201,\n    KEY_PROG3 = 202,\n    KEY_PROG4 = 203,\n    KEY_DASHBOARD = 204,\n    KEY_SUSPEND = 205,\n    KEY_CLOSE = 206,\n    KEY_PLAY = 207,\n    KEY_FASTFORWARD = 208,\n    KEY_BASSBOOST = 209,\n    KEY_PRINT = 210,\n    KEY_HP = 211,\n    KEY_CAMERA = 212,\n    KEY_SOUND = 213,\n    KEY_QUESTION = 214,\n    KEY_EMAIL = 215,\n    KEY_CHAT = 216,\n    KEY_SEARCH = 217,\n    KEY_CONNECT = 218,\n    KEY_FINANCE = 219,\n    KEY_SPORT = 220,\n    KEY_SHOP = 221,\n    KEY_ALTERASE = 222,\n    KEY_CANCEL = 223,\n    KEY_BRIGHTNESSDOWN = 224,\n    KEY_BRIGHTNESSUP = 225,\n    KEY_MEDIA = 226,\n    KEY_SWITCHVIDEOMODE = 227,\n    KEY_KBDILLUMTOGGLE = 228,\n    KEY_KBDILLUMDOWN = 229,\n    KEY_KBDILLUMUP = 230,\n    KEY_SEND = 231,\n    KEY_REPLY = 232,\n    KEY_FORWARDMAIL = 233,\n    KEY_SAVE = 234,\n    KEY_DOCUMENTS = 235,\n    KEY_BATTERY = 236,\n    KEY_BLUETOOTH = 237,\n    KEY_WLAN = 238,\n    KEY_UWB = 239,\n    KEY_UNKNOWN = 240,\n    KEY_VIDEO_NEXT = 241,\n    KEY_VIDEO_PREV = 242,\n    KEY_BRIGHTNESS_CYCLE = 243,\n    KEY_BRIGHTNESS_AUTO = 244,\n    KEY_DISPLAY_OFF = 245,\n    KEY_WWAN = 246,\n    KEY_RFKILL = 247,\n    KEY_MICMUTE = 248,\n    KEY_OK = 352,\n    KEY_SELECT = 353,\n    KEY_GOTO = 354,\n    KEY_CLEAR = 355,\n    KEY_POWER2 = 356,\n    KEY_OPTION = 357,\n    KEY_INFO = 358,\n    KEY_TIME = 359,\n    KEY_VENDOR = 360,\n    KEY_ARCHIVE = 361,\n    KEY_PROGRAM = 362,\n    KEY_CHANNEL = 363,\n    KEY_FAVORITES = 364,\n    KEY_EPG = 365,\n    KEY_PVR = 366,\n    KEY_MHP = 367,\n    KEY_LANGUAGE = 368,\n    KEY_TITLE = 369,\n    KEY_SUBTITLE = 370,\n    KEY_ANGLE = 371,\n    KEY_FULL_SCREEN = 372,\n    KEY_MODE = 373,\n    KEY_KEYBOARD = 374,\n    KEY_ASPECT_RATIO = 375,\n    KEY_PC = 376,\n    KEY_TV = 377,\n    KEY_TV2 = 378,\n    KEY_VCR = 379,\n    KEY_VCR2 = 380,\n    KEY_SAT = 381,\n    KEY_SAT2 = 382,\n    KEY_CD = 383,\n    KEY_TAPE = 384,\n    KEY_RADIO = 385,\n    KEY_TUNER = 386,\n    KEY_PLAYER = 387,\n    KEY_TEXT = 388,\n    KEY_DVD = 389,\n    KEY_AUX = 390,\n    KEY_MP3 = 391,\n    KEY_AUDIO = 392,\n    KEY_VIDEO = 393,\n    KEY_DIRECTORY = 394,\n    KEY_LIST = 395,\n    KEY_MEMO = 396,\n    KEY_CALENDAR = 397,\n    KEY_RED = 398,\n    KEY_GREEN = 399,\n    KEY_YELLOW = 400,\n    KEY_BLUE = 401,\n    KEY_CHANNELUP = 402,\n    KEY_CHANNELDOWN = 403,\n    KEY_FIRST = 404,\n    KEY_LAST = 405,\n    KEY_AB = 406,\n    KEY_NEXT = 407,\n    KEY_RESTART = 408,\n    KEY_SLOW = 409,\n    KEY_SHUFFLE = 410,\n    KEY_BREAK = 411,\n    KEY_PREVIOUS = 412,\n    KEY_DIGITS = 413,\n    KEY_TEEN = 414,\n    KEY_TWEN = 415,\n    KEY_VIDEOPHONE = 416,\n    KEY_GAMES = 417,\n    KEY_ZOOMIN = 418,\n    KEY_ZOOMOUT = 419,\n    KEY_ZOOMRESET = 420,\n    KEY_WORDPROCESSOR = 421,\n    KEY_EDITOR = 422,\n    KEY_SPREADSHEET = 423,\n    KEY_GRAPHICSEDITOR = 424,\n    KEY_PRESENTATION = 425,\n    KEY_DATABASE = 426,\n    KEY_NEWS = 427,\n    KEY_VOICEMAIL = 428,\n    KEY_ADDRESSBOOK = 429,\n    KEY_MESSENGER = 430,\n    KEY_DISPLAYTOGGLE = 431,\n    KEY_SPELLCHECK = 432,\n    KEY_LOGOFF = 433,\n    KEY_DOLLAR = 434,\n    KEY_EURO = 435,\n    KEY_FRAMEBACK = 436,\n    KEY_FRAMEFORWARD = 437,\n    KEY_CONTEXT_MENU = 438,\n    KEY_MEDIA_REPEAT = 439,\n    KEY_10CHANNELSUP = 440,\n    KEY_10CHANNELSDOWN = 441,\n    KEY_IMAGES = 442,\n    KEY_DEL_EOL = 448,\n    KEY_DEL_EOS = 449,\n    KEY_INS_LINE = 450,\n    KEY_DEL_LINE = 451,\n    KEY_FN = 464,\n    KEY_FN_ESC = 465,\n    KEY_FN_F1 = 466,\n    KEY_FN_F2 = 467,\n    KEY_FN_F3 = 468,\n    KEY_FN_F4 = 469,\n    KEY_FN_F5 = 470,\n    KEY_FN_F6 = 471,\n    KEY_FN_F7 = 472,\n    KEY_FN_F8 = 473,\n    KEY_FN_F9 = 474,\n    KEY_FN_F10 = 475,\n    KEY_FN_F11 = 476,\n    KEY_FN_F12 = 477,\n    KEY_FN_1 = 478,\n    KEY_FN_2 = 479,\n    KEY_FN_D = 480,\n    KEY_FN_E = 481,\n    KEY_FN_F = 482,\n    KEY_FN_S = 483,\n    KEY_FN_B = 484,\n    KEY_BRL_DOT1 = 497,\n    KEY_BRL_DOT2 = 498,\n    KEY_BRL_DOT3 = 499,\n    KEY_BRL_DOT4 = 500,\n    KEY_BRL_DOT5 = 501,\n    KEY_BRL_DOT6 = 502,\n    KEY_BRL_DOT7 = 503,\n    KEY_BRL_DOT8 = 504,\n    KEY_BRL_DOT9 = 505,\n    KEY_BRL_DOT10 = 506,\n    KEY_NUMERIC_0 = 512,\n    KEY_NUMERIC_1 = 513,\n    KEY_NUMERIC_2 = 514,\n    KEY_NUMERIC_3 = 515,\n    KEY_NUMERIC_4 = 516,\n    KEY_NUMERIC_5 = 517,\n    KEY_NUMERIC_6 = 518,\n    KEY_NUMERIC_7 = 519,\n    KEY_NUMERIC_8 = 520,\n    KEY_NUMERIC_9 = 521,\n    KEY_NUMERIC_STAR = 522,\n    KEY_NUMERIC_POUND = 523,\n    KEY_NUMERIC_A = 524,\n    KEY_NUMERIC_B = 525,\n    KEY_NUMERIC_C = 526,\n    KEY_NUMERIC_D = 527,\n    KEY_CAMERA_FOCUS = 528,\n    KEY_WPS_BUTTON = 529,\n    KEY_TOUCHPAD_TOGGLE = 530,\n    KEY_TOUCHPAD_ON = 531,\n    KEY_TOUCHPAD_OFF = 532,\n    KEY_CAMERA_ZOOMIN = 533,\n    KEY_CAMERA_ZOOMOUT = 534,\n    KEY_CAMERA_UP = 535,\n    KEY_CAMERA_DOWN = 536,\n    KEY_CAMERA_LEFT = 537,\n    KEY_CAMERA_RIGHT = 538,\n    KEY_ATTENDANT_ON = 539,\n    KEY_ATTENDANT_OFF = 540,\n    KEY_ATTENDANT_TOGGLE = 541,\n    KEY_LIGHTS_TOGGLE = 542,\n    KEY_ALS_TOGGLE = 560,\n    KEY_ROTATE_LOCK_TOGGLE = 561,\n    KEY_BUTTONCONFIG = 576,\n    KEY_TASKMANAGER = 577,\n    KEY_JOURNAL = 578,\n    KEY_CONTROLPANEL = 579,\n    KEY_APPSELECT = 580,\n    KEY_SCREENSAVER = 581,\n    KEY_VOICECOMMAND = 582,\n    KEY_ASSISTANT = 583,\n    KEY_KBD_LAYOUT_NEXT = 584,\n    KEY_BRIGHTNESS_MIN = 592,\n    KEY_BRIGHTNESS_MAX = 593,\n    KEY_KBDINPUTASSIST_PREV = 608,\n    KEY_KBDINPUTASSIST_NEXT = 609,\n    KEY_KBDINPUTASSIST_PREVGROUP = 610,\n    KEY_KBDINPUTASSIST_NEXTGROUP = 611,\n    KEY_KBDINPUTASSIST_ACCEPT = 612,\n    KEY_KBDINPUTASSIST_CANCEL = 613,\n    KEY_RIGHT_UP = 614,\n    KEY_RIGHT_DOWN = 615,\n    KEY_LEFT_UP = 616,\n    KEY_LEFT_DOWN = 617,\n    KEY_ROOT_MENU = 618,\n    KEY_MEDIA_TOP_MENU = 619,\n    KEY_NUMERIC_11 = 620,\n    KEY_NUMERIC_12 = 621,\n    KEY_AUDIO_DESC = 622,\n    KEY_3D_MODE = 623,\n    KEY_NEXT_FAVORITE = 624,\n    KEY_STOP_RECORD = 625,\n    KEY_PAUSE_RECORD = 626,\n    KEY_VOD = 627,\n    KEY_UNMUTE = 628,\n    KEY_FASTREVERSE = 629,\n    KEY_SLOWREVERSE = 630,\n    KEY_DATA = 631,\n    KEY_ONSCREEN_KEYBOARD = 632,\n    KEY_MAX = 767,\n    BTN_0 = 256,\n    BTN_1 = 257,\n    BTN_2 = 258,\n    BTN_3 = 259,\n    BTN_4 = 260,\n    BTN_5 = 261,\n    BTN_6 = 262,\n    BTN_7 = 263,\n    BTN_8 = 264,\n    BTN_9 = 265,\n    BTN_LEFT = 272,\n    BTN_RIGHT = 273,\n    BTN_MIDDLE = 274,\n    BTN_SIDE = 275,\n    BTN_EXTRA = 276,\n    BTN_FORWARD = 277,\n    BTN_BACK = 278,\n    BTN_TASK = 279,\n    BTN_TRIGGER = 288,\n    BTN_THUMB = 289,\n    BTN_THUMB2 = 290,\n    BTN_TOP = 291,\n    BTN_TOP2 = 292,\n    BTN_PINKIE = 293,\n    BTN_BASE = 294,\n    BTN_BASE2 = 295,\n    BTN_BASE3 = 296,\n    BTN_BASE4 = 297,\n    BTN_BASE5 = 298,\n    BTN_BASE6 = 299,\n    BTN_DEAD = 303,\n    BTN_SOUTH = 304,\n    BTN_EAST = 305,\n    BTN_C = 306,\n    BTN_NORTH = 307,\n    BTN_WEST = 308,\n    BTN_Z = 309,\n    BTN_TL = 310,\n    BTN_TR = 311,\n    BTN_TL2 = 312,\n    BTN_TR2 = 313,\n    BTN_SELECT = 314,\n    BTN_START = 315,\n    BTN_MODE = 316,\n    BTN_THUMBL = 317,\n    BTN_THUMBR = 318,\n    BTN_TOOL_PEN = 320,\n    BTN_TOOL_RUBBER = 321,\n    BTN_TOOL_BRUSH = 322,\n    BTN_TOOL_PENCIL = 323,\n    BTN_TOOL_AIRBRUSH = 324,\n    BTN_TOOL_FINGER = 325,\n    BTN_TOOL_MOUSE = 326,\n    BTN_TOOL_LENS = 327,\n    BTN_TOOL_QUINTTAP = 328,\n    BTN_STYLUS3 = 329,\n    BTN_TOUCH = 330,\n    BTN_STYLUS = 331,\n    BTN_STYLUS2 = 332,\n    BTN_TOOL_DOUBLETAP = 333,\n    BTN_TOOL_TRIPLETAP = 334,\n    BTN_TOOL_QUADTAP = 335,\n    BTN_GEAR_DOWN = 336,\n    BTN_GEAR_UP = 337,\n    BTN_DPAD_UP = 544,\n    BTN_DPAD_DOWN = 545,\n    BTN_DPAD_LEFT = 546,\n    BTN_DPAD_RIGHT = 547,\n    BTN_TRIGGER_HAPPY1 = 704,\n    BTN_TRIGGER_HAPPY2 = 705,\n    BTN_TRIGGER_HAPPY3 = 706,\n    BTN_TRIGGER_HAPPY4 = 707,\n    BTN_TRIGGER_HAPPY5 = 708,\n    BTN_TRIGGER_HAPPY6 = 709,\n    BTN_TRIGGER_HAPPY7 = 710,\n    BTN_TRIGGER_HAPPY8 = 711,\n    BTN_TRIGGER_HAPPY9 = 712,\n    BTN_TRIGGER_HAPPY10 = 713,\n    BTN_TRIGGER_HAPPY11 = 714,\n    BTN_TRIGGER_HAPPY12 = 715,\n    BTN_TRIGGER_HAPPY13 = 716,\n    BTN_TRIGGER_HAPPY14 = 717,\n    BTN_TRIGGER_HAPPY15 = 718,\n    BTN_TRIGGER_HAPPY16 = 719,\n    BTN_TRIGGER_HAPPY17 = 720,\n    BTN_TRIGGER_HAPPY18 = 721,\n    BTN_TRIGGER_HAPPY19 = 722,\n    BTN_TRIGGER_HAPPY20 = 723,\n    BTN_TRIGGER_HAPPY21 = 724,\n    BTN_TRIGGER_HAPPY22 = 725,\n    BTN_TRIGGER_HAPPY23 = 726,\n    BTN_TRIGGER_HAPPY24 = 727,\n    BTN_TRIGGER_HAPPY25 = 728,\n    BTN_TRIGGER_HAPPY26 = 729,\n    BTN_TRIGGER_HAPPY27 = 730,\n    BTN_TRIGGER_HAPPY28 = 731,\n    BTN_TRIGGER_HAPPY29 = 732,\n    BTN_TRIGGER_HAPPY30 = 733,\n    BTN_TRIGGER_HAPPY31 = 734,\n    BTN_TRIGGER_HAPPY32 = 735,\n    BTN_TRIGGER_HAPPY33 = 736,\n    BTN_TRIGGER_HAPPY34 = 737,\n    BTN_TRIGGER_HAPPY35 = 738,\n    BTN_TRIGGER_HAPPY36 = 739,\n    BTN_TRIGGER_HAPPY37 = 740,\n    BTN_TRIGGER_HAPPY38 = 741,\n    BTN_TRIGGER_HAPPY39 = 742,\n    BTN_TRIGGER_HAPPY40 = 743,\n    BTN_MAX = 744,\n}\n\n#[allow(unused)]\nmod keys {\n    // Taken from:\n    // https://github.com/retep998/winapi-rs/blob/0.3/src/um/winuser.rs#L253\n    pub const VK_LBUTTON: u16 = 0x01;\n    pub const VK_RBUTTON: u16 = 0x02;\n    pub const VK_CANCEL: u16 = 0x03;\n    pub const VK_MBUTTON: u16 = 0x04;\n    pub const VK_XBUTTON1: u16 = 0x05;\n    pub const VK_XBUTTON2: u16 = 0x06;\n    pub const VK_BACK: u16 = 0x08;\n    pub const VK_TAB: u16 = 0x09;\n    pub const VK_CLEAR: u16 = 0x0C;\n    pub const VK_RETURN: u16 = 0x0D;\n    pub const VK_SHIFT: u16 = 0x10;\n    pub const VK_CONTROL: u16 = 0x11;\n    pub const VK_MENU: u16 = 0x12;\n    pub const VK_PAUSE: u16 = 0x13;\n    pub const VK_CAPITAL: u16 = 0x14;\n    pub const VK_KANA: u16 = 0x15;\n    pub const VK_HANGEUL: u16 = 0x15;\n    pub const VK_HANGUL: u16 = 0x15;\n    pub const VK_JUNJA: u16 = 0x17;\n    pub const VK_FINAL: u16 = 0x18;\n    pub const VK_HANJA: u16 = 0x19;\n    pub const VK_KANJI: u16 = 0x19;\n    pub const VK_ESCAPE: u16 = 0x1B;\n    pub const VK_CONVERT: u16 = 0x1C;\n    pub const VK_NONCONVERT: u16 = 0x1D;\n    pub const VK_ACCEPT: u16 = 0x1E;\n    pub const VK_MODECHANGE: u16 = 0x1F;\n    pub const VK_SPACE: u16 = 0x20;\n    pub const VK_PRIOR: u16 = 0x21;\n    pub const VK_NEXT: u16 = 0x22;\n    pub const VK_END: u16 = 0x23;\n    pub const VK_HOME: u16 = 0x24;\n    pub const VK_LEFT: u16 = 0x25;\n    pub const VK_UP: u16 = 0x26;\n    pub const VK_RIGHT: u16 = 0x27;\n    pub const VK_DOWN: u16 = 0x28;\n    pub const VK_SELECT: u16 = 0x29;\n    pub const VK_PRINT: u16 = 0x2A;\n    pub const VK_EXECUTE: u16 = 0x2B;\n    pub const VK_SNAPSHOT: u16 = 0x2C;\n    pub const VK_INSERT: u16 = 0x2D;\n    pub const VK_DELETE: u16 = 0x2E;\n    pub const VK_HELP: u16 = 0x2F;\n    pub const VK_LWIN: u16 = 0x5B;\n    pub const VK_RWIN: u16 = 0x5C;\n    pub const VK_APPS: u16 = 0x5D;\n    pub const VK_SLEEP: u16 = 0x5F;\n    pub const VK_NUMPAD0: u16 = 0x60;\n    pub const VK_NUMPAD1: u16 = 0x61;\n    pub const VK_NUMPAD2: u16 = 0x62;\n    pub const VK_NUMPAD3: u16 = 0x63;\n    pub const VK_NUMPAD4: u16 = 0x64;\n    pub const VK_NUMPAD5: u16 = 0x65;\n    pub const VK_NUMPAD6: u16 = 0x66;\n    pub const VK_NUMPAD7: u16 = 0x67;\n    pub const VK_NUMPAD8: u16 = 0x68;\n    pub const VK_NUMPAD9: u16 = 0x69;\n    pub const VK_MULTIPLY: u16 = 0x6A;\n    pub const VK_ADD: u16 = 0x6B;\n    pub const VK_SEPARATOR: u16 = 0x6C;\n    pub const VK_SUBTRACT: u16 = 0x6D;\n    pub const VK_DECIMAL: u16 = 0x6E;\n    pub const VK_DIVIDE: u16 = 0x6F;\n    pub const VK_F1: u16 = 0x70;\n    pub const VK_F2: u16 = 0x71;\n    pub const VK_F3: u16 = 0x72;\n    pub const VK_F4: u16 = 0x73;\n    pub const VK_F5: u16 = 0x74;\n    pub const VK_F6: u16 = 0x75;\n    pub const VK_F7: u16 = 0x76;\n    pub const VK_F8: u16 = 0x77;\n    pub const VK_F9: u16 = 0x78;\n    pub const VK_F10: u16 = 0x79;\n    pub const VK_F11: u16 = 0x7A;\n    pub const VK_F12: u16 = 0x7B;\n    pub const VK_F13: u16 = 0x7C;\n    pub const VK_F14: u16 = 0x7D;\n    pub const VK_F15: u16 = 0x7E;\n    pub const VK_F16: u16 = 0x7F;\n    pub const VK_F17: u16 = 0x80;\n    pub const VK_F18: u16 = 0x81;\n    pub const VK_F19: u16 = 0x82;\n    pub const VK_F20: u16 = 0x83;\n    pub const VK_F21: u16 = 0x84;\n    pub const VK_F22: u16 = 0x85;\n    pub const VK_F23: u16 = 0x86;\n    pub const VK_F24: u16 = 0x87;\n    pub const VK_NAVIGATION_VIEW: u16 = 0x88;\n    pub const VK_NAVIGATION_MENU: u16 = 0x89;\n    pub const VK_NAVIGATION_UP: u16 = 0x8A;\n    pub const VK_NAVIGATION_DOWN: u16 = 0x8B;\n    pub const VK_NAVIGATION_LEFT: u16 = 0x8C;\n    pub const VK_NAVIGATION_RIGHT: u16 = 0x8D;\n    pub const VK_NAVIGATION_ACCEPT: u16 = 0x8E;\n    pub const VK_NAVIGATION_CANCEL: u16 = 0x8F;\n    pub const VK_NUMLOCK: u16 = 0x90;\n    pub const VK_SCROLL: u16 = 0x91;\n    pub const VK_OEM_NEC_EQUAL: u16 = 0x92;\n    pub const VK_OEM_FJ_JISHO: u16 = 0x92;\n    pub const VK_OEM_FJ_MASSHOU: u16 = 0x93;\n    pub const VK_OEM_FJ_TOUROKU: u16 = 0x94;\n    pub const VK_OEM_FJ_LOYA: u16 = 0x95;\n    pub const VK_OEM_FJ_ROYA: u16 = 0x96;\n    pub const VK_LSHIFT: u16 = 0xA0;\n    pub const VK_RSHIFT: u16 = 0xA1;\n    pub const VK_LCONTROL: u16 = 0xA2;\n    pub const VK_RCONTROL: u16 = 0xA3;\n    pub const VK_LMENU: u16 = 0xA4;\n    pub const VK_RMENU: u16 = 0xA5;\n    pub const VK_BROWSER_BACK: u16 = 0xA6;\n    pub const VK_BROWSER_FORWARD: u16 = 0xA7;\n    pub const VK_BROWSER_REFRESH: u16 = 0xA8;\n    pub const VK_BROWSER_STOP: u16 = 0xA9;\n    pub const VK_BROWSER_SEARCH: u16 = 0xAA;\n    pub const VK_BROWSER_FAVORITES: u16 = 0xAB;\n    pub const VK_BROWSER_HOME: u16 = 0xAC;\n    pub const VK_VOLUME_MUTE: u16 = 0xAD;\n    pub const VK_VOLUME_DOWN: u16 = 0xAE;\n    pub const VK_VOLUME_UP: u16 = 0xAF;\n    pub const VK_MEDIA_NEXT_TRACK: u16 = 0xB0;\n    pub const VK_MEDIA_PREV_TRACK: u16 = 0xB1;\n    pub const VK_MEDIA_STOP: u16 = 0xB2;\n    pub const VK_MEDIA_PLAY_PAUSE: u16 = 0xB3;\n    pub const VK_LAUNCH_MAIL: u16 = 0xB4;\n    pub const VK_LAUNCH_MEDIA_SELECT: u16 = 0xB5;\n    pub const VK_LAUNCH_APP1: u16 = 0xB6;\n    pub const VK_LAUNCH_APP2: u16 = 0xB7;\n    pub const VK_OEM_1: u16 = 0xBA;\n    pub const VK_OEM_PLUS: u16 = 0xBB;\n    pub const VK_OEM_COMMA: u16 = 0xBC;\n    pub const VK_OEM_MINUS: u16 = 0xBD;\n    pub const VK_OEM_PERIOD: u16 = 0xBE;\n    pub const VK_OEM_2: u16 = 0xBF;\n    pub const VK_OEM_3: u16 = 0xC0;\n    pub const VK_GAMEPAD_A: u16 = 0xC3;\n    pub const VK_GAMEPAD_B: u16 = 0xC4;\n    pub const VK_GAMEPAD_X: u16 = 0xC5;\n    pub const VK_GAMEPAD_Y: u16 = 0xC6;\n    pub const VK_GAMEPAD_RIGHT_SHOULDER: u16 = 0xC7;\n    pub const VK_GAMEPAD_LEFT_SHOULDER: u16 = 0xC8;\n    pub const VK_GAMEPAD_LEFT_TRIGGER: u16 = 0xC9;\n    pub const VK_GAMEPAD_RIGHT_TRIGGER: u16 = 0xCA;\n    pub const VK_GAMEPAD_DPAD_UP: u16 = 0xCB;\n    pub const VK_GAMEPAD_DPAD_DOWN: u16 = 0xCC;\n    pub const VK_GAMEPAD_DPAD_LEFT: u16 = 0xCD;\n    pub const VK_GAMEPAD_DPAD_RIGHT: u16 = 0xCE;\n    pub const VK_GAMEPAD_MENU: u16 = 0xCF;\n    pub const VK_GAMEPAD_VIEW: u16 = 0xD0;\n    pub const VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON: u16 = 0xD1;\n    pub const VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON: u16 = 0xD2;\n    pub const VK_GAMEPAD_LEFT_THUMBSTICK_UP: u16 = 0xD3;\n    pub const VK_GAMEPAD_LEFT_THUMBSTICK_DOWN: u16 = 0xD4;\n    pub const VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT: u16 = 0xD5;\n    pub const VK_GAMEPAD_LEFT_THUMBSTICK_LEFT: u16 = 0xD6;\n    pub const VK_GAMEPAD_RIGHT_THUMBSTICK_UP: u16 = 0xD7;\n    pub const VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN: u16 = 0xD8;\n    pub const VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT: u16 = 0xD9;\n    pub const VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT: u16 = 0xDA;\n    pub const VK_OEM_4: u16 = 0xDB;\n    pub const VK_OEM_5: u16 = 0xDC;\n    pub const VK_OEM_6: u16 = 0xDD;\n    pub const VK_OEM_7: u16 = 0xDE;\n    pub const VK_OEM_8: u16 = 0xDF;\n    pub const VK_OEM_AX: u16 = 0xE1;\n    pub const VK_OEM_102: u16 = 0xE2;\n    pub const VK_ICO_HELP: u16 = 0xE3;\n    pub const VK_ICO_00: u16 = 0xE4;\n    pub const VK_PROCESSKEY: u16 = 0xE5;\n    pub const VK_ICO_CLEAR: u16 = 0xE6;\n    pub const VK_PACKET: u16 = 0xE7;\n    pub const VK_OEM_RESET: u16 = 0xE9;\n    pub const VK_OEM_JUMP: u16 = 0xEA;\n    pub const VK_OEM_PA1: u16 = 0xEB;\n    pub const VK_OEM_PA2: u16 = 0xEC;\n    pub const VK_OEM_PA3: u16 = 0xED;\n    pub const VK_OEM_WSCTRL: u16 = 0xEE;\n    pub const VK_OEM_CUSEL: u16 = 0xEF;\n    pub const VK_OEM_ATTN: u16 = 0xF0;\n    pub const VK_OEM_FINISH: u16 = 0xF1;\n    pub const VK_OEM_COPY: u16 = 0xF2;\n    pub const VK_OEM_AUTO: u16 = 0xF3;\n    pub const VK_OEM_ENLW: u16 = 0xF4;\n    pub const VK_OEM_BACKTAB: u16 = 0xF5;\n    pub const VK_ATTN: u16 = 0xF6;\n    pub const VK_CRSEL: u16 = 0xF7;\n    pub const VK_EXSEL: u16 = 0xF8;\n    pub const VK_EREOF: u16 = 0xF9;\n    pub const VK_PLAY: u16 = 0xFA;\n    pub const VK_ZOOM: u16 = 0xFB;\n    pub const VK_NONAME: u16 = 0xFC;\n    pub const VK_PA1: u16 = 0xFD;\n    pub const VK_OEM_CLEAR: u16 = 0xFE;\n}\n\nuse keys::*;\n\nimpl OsCode {\n    pub fn as_u16(self) -> u16 {\n        match self {\n            OsCode::KEY_0 => 0x30,\n            OsCode::KEY_1 => 0x31,\n            OsCode::KEY_2 => 0x32,\n            OsCode::KEY_3 => 0x33,\n            OsCode::KEY_4 => 0x34,\n            OsCode::KEY_5 => 0x35,\n            OsCode::KEY_6 => 0x36,\n            OsCode::KEY_7 => 0x37,\n            OsCode::KEY_8 => 0x38,\n            OsCode::KEY_9 => 0x39,\n            OsCode::KEY_A => 0x41,\n            OsCode::KEY_B => 0x42,\n            OsCode::KEY_C => 0x43,\n            OsCode::KEY_D => 0x44,\n            OsCode::KEY_E => 0x45,\n            OsCode::KEY_F => 0x46,\n            OsCode::KEY_G => 0x47,\n            OsCode::KEY_H => 0x48,\n            OsCode::KEY_I => 0x49,\n            OsCode::KEY_J => 0x4A,\n            OsCode::KEY_K => 0x4B,\n            OsCode::KEY_L => 0x4C,\n            OsCode::KEY_M => 0x4D,\n            OsCode::KEY_N => 0x4E,\n            OsCode::KEY_O => 0x4F,\n            OsCode::KEY_P => 0x50,\n            OsCode::KEY_Q => 0x51,\n            OsCode::KEY_R => 0x52,\n            OsCode::KEY_S => 0x53,\n            OsCode::KEY_T => 0x54,\n            OsCode::KEY_U => 0x55,\n            OsCode::KEY_V => 0x56,\n            OsCode::KEY_W => 0x57,\n            OsCode::KEY_X => 0x58,\n            OsCode::KEY_Y => 0x59,\n            OsCode::KEY_Z => 0x5A,\n            OsCode::KEY_SEMICOLON => VK_OEM_1,\n            OsCode::KEY_SLASH => VK_OEM_2,\n            OsCode::KEY_GRAVE => VK_OEM_3,\n            OsCode::KEY_LEFTBRACE => VK_OEM_4,\n            OsCode::KEY_BACKSLASH => VK_OEM_5,\n            OsCode::KEY_RIGHTBRACE => VK_OEM_6,\n            OsCode::KEY_APOSTROPHE => VK_OEM_7,\n            OsCode::KEY_MINUS => VK_OEM_MINUS,\n            OsCode::KEY_DOT => VK_OEM_PERIOD,\n            OsCode::KEY_EQUAL => VK_OEM_PLUS,\n            OsCode::KEY_BACKSPACE => VK_BACK,\n            OsCode::KEY_ESC => VK_ESCAPE,\n            OsCode::KEY_TAB => VK_TAB,\n            OsCode::KEY_ENTER => VK_RETURN,\n            OsCode::KEY_LEFTCTRL => VK_LCONTROL,\n            OsCode::KEY_LEFTSHIFT => VK_LSHIFT,\n            OsCode::KEY_COMMA => VK_OEM_COMMA,\n            OsCode::KEY_RIGHTSHIFT => VK_RSHIFT,\n            OsCode::KEY_KPASTERISK => VK_MULTIPLY,\n            OsCode::KEY_LEFTALT => VK_LMENU,\n            OsCode::KEY_SPACE => VK_SPACE,\n            OsCode::KEY_CAPSLOCK => VK_CAPITAL,\n            OsCode::KEY_F1 => VK_F1,\n            OsCode::KEY_F2 => VK_F2,\n            OsCode::KEY_F3 => VK_F3,\n            OsCode::KEY_F4 => VK_F4,\n            OsCode::KEY_F5 => VK_F5,\n            OsCode::KEY_F6 => VK_F6,\n            OsCode::KEY_F7 => VK_F7,\n            OsCode::KEY_F8 => VK_F8,\n            OsCode::KEY_F9 => VK_F9,\n            OsCode::KEY_F10 => VK_F10,\n            OsCode::KEY_F11 => VK_F11,\n            OsCode::KEY_F12 => VK_F12,\n            OsCode::KEY_NUMLOCK => VK_NUMLOCK,\n            OsCode::KEY_CLEAR => VK_CLEAR,\n            OsCode::KEY_SCROLLLOCK => VK_SCROLL,\n            OsCode::KEY_KP0 => VK_NUMPAD0,\n            OsCode::KEY_KP1 => VK_NUMPAD1,\n            OsCode::KEY_KP2 => VK_NUMPAD2,\n            OsCode::KEY_KP3 => VK_NUMPAD3,\n            OsCode::KEY_KP4 => VK_NUMPAD4,\n            OsCode::KEY_KP5 => VK_NUMPAD5,\n            OsCode::KEY_KP6 => VK_NUMPAD6,\n            OsCode::KEY_KP7 => VK_NUMPAD7,\n            OsCode::KEY_KP8 => VK_NUMPAD8,\n            OsCode::KEY_KP9 => VK_NUMPAD9,\n            OsCode::KEY_KPMINUS => VK_SUBTRACT,\n            OsCode::KEY_KPPLUS => VK_ADD,\n            OsCode::KEY_KPDOT => VK_DECIMAL,\n            OsCode::KEY_RIGHTCTRL => VK_RCONTROL,\n            OsCode::KEY_KPSLASH => VK_DIVIDE,\n            OsCode::KEY_RIGHTALT => VK_RMENU,\n            OsCode::KEY_HOME => VK_HOME,\n            OsCode::KEY_UP => VK_UP,\n            OsCode::KEY_PAGEUP => VK_PRIOR,\n            OsCode::KEY_LEFT => VK_LEFT,\n            OsCode::KEY_RIGHT => VK_RIGHT,\n            OsCode::KEY_END => VK_END,\n            OsCode::KEY_DOWN => VK_DOWN,\n            OsCode::KEY_PAGEDOWN => VK_NEXT,\n            OsCode::KEY_INSERT => VK_INSERT,\n            OsCode::KEY_DELETE => VK_DELETE,\n            OsCode::KEY_MUTE => VK_VOLUME_MUTE,\n            OsCode::KEY_VOLUMEDOWN => VK_VOLUME_DOWN,\n            OsCode::KEY_VOLUMEUP => VK_VOLUME_UP,\n            OsCode::KEY_PAUSE => VK_PAUSE,\n            OsCode::KEY_LEFTMETA => VK_LWIN,\n            OsCode::KEY_RIGHTMETA => VK_RWIN,\n            OsCode::KEY_COMPOSE => VK_APPS,\n            OsCode::KEY_BACK => VK_BROWSER_BACK,\n            OsCode::KEY_FORWARD => VK_BROWSER_FORWARD,\n            OsCode::KEY_NEXTSONG => VK_MEDIA_NEXT_TRACK,\n            OsCode::KEY_PLAYPAUSE => VK_MEDIA_PLAY_PAUSE,\n            OsCode::KEY_PREVIOUSSONG => VK_MEDIA_PREV_TRACK,\n            OsCode::KEY_STOP => VK_MEDIA_STOP,\n            OsCode::KEY_HOMEPAGE => VK_BROWSER_HOME,\n            OsCode::KEY_MAIL => VK_LAUNCH_MAIL,\n            OsCode::KEY_MEDIA => VK_LAUNCH_MEDIA_SELECT,\n            OsCode::KEY_REFRESH => VK_BROWSER_REFRESH,\n            OsCode::KEY_F13 => VK_F13,\n            OsCode::KEY_F14 => VK_F14,\n            OsCode::KEY_F15 => VK_F15,\n            OsCode::KEY_F16 => VK_F16,\n            OsCode::KEY_F17 => VK_F17,\n            OsCode::KEY_F18 => VK_F18,\n            OsCode::KEY_F19 => VK_F19,\n            OsCode::KEY_F20 => VK_F20,\n            OsCode::KEY_F21 => VK_F21,\n            OsCode::KEY_F22 => VK_F22,\n            OsCode::KEY_F23 => VK_F23,\n            OsCode::KEY_F24 => VK_F24,\n            OsCode::KEY_HANGEUL => VK_HANGEUL,\n            OsCode::KEY_HANJA => VK_HANJA,\n            OsCode::KEY_102ND => VK_OEM_102,\n            OsCode::KEY_PLAY => VK_PLAY,\n            OsCode::KEY_PRINT => VK_SNAPSHOT,\n            OsCode::KEY_SEARCH => VK_BROWSER_SEARCH,\n            OsCode::KEY_FAVORITES => VK_BROWSER_FAVORITES,\n            OsCode::KEY_RO => 0xC1,\n            OsCode::KEY_HENKAN => VK_CONVERT,\n            OsCode::KEY_MUHENKAN => VK_NONCONVERT,\n            _ => 0,\n        }\n    }\n}\n"
  },
  {
    "path": "windows_key_tester/src/windows/llhook.rs",
    "content": "//! Safe abstraction over the low-level windows keyboard hook API.\n\n// This file is taken from kbremap with modifications.\n// https://github.com/timokroeger/kbremap\n\nuse std::ptr;\n\nuse anyhow::Result;\nuse winapi::ctypes::*;\nuse winapi::shared::minwindef::*;\nuse winapi::shared::windef::*;\nuse winapi::um::winuser::*;\n\n/// Wrapper for the low-level keyboard hook API.\n/// Automatically unregisters the hook when dropped.\npub struct KeyboardHook {\n    handle: HHOOK,\n}\n\nimpl KeyboardHook {\n    /// Sets the low-level keyboard hook for this thread.\n    ///\n    /// Panics when a hook is already registered from the same thread.\n    #[must_use = \"The hook will immediatelly be unregistered and not work.\"]\n    pub fn attach_hook() -> KeyboardHook {\n        KeyboardHook {\n            handle: unsafe {\n                SetWindowsHookExW(WH_KEYBOARD_LL, Some(hook_proc), ptr::null_mut(), 0)\n                    .as_mut()\n                    .expect(\"install low-level keyboard hook successfully\")\n            },\n        }\n    }\n}\n\nimpl Drop for KeyboardHook {\n    fn drop(&mut self) {\n        unsafe { UnhookWindowsHookEx(self.handle) };\n    }\n}\n\n/// Key event received by the low level keyboard hook.\n#[allow(dead_code)]\n#[derive(Debug, Clone, Copy)]\npub struct InputEvent {\n    pub code: u32,\n    /// Key was released\n    pub up: bool,\n}\n\nimpl InputEvent {\n    #[cfg(not(feature = \"winiov2\"))]\n    fn from_hook_lparam(lparam: &KBDLLHOOKSTRUCT) -> Self {\n        Self {\n            code: lparam.vkCode,\n            up: lparam.flags & LLKHF_UP != 0,\n        }\n    }\n\n    #[cfg(feature = \"winiov2\")]\n    fn from_hook_lparam(lparam: &KBDLLHOOKSTRUCT) -> Self {\n        let extended = if lparam.flags & 0x1 == 0x1 { 0xE000 } else { 0 };\n        let code = kanata_state_machine::oskbd::u16_to_osc((lparam.scanCode as u16) | extended)\n            .map(Into::into)\n            .unwrap_or(lparam.vkCode);\n        Self {\n            code,\n            up: lparam.flags & LLKHF_UP != 0,\n        }\n    }\n}\n\n/// The actual WinAPI compatible callback.\nunsafe extern \"system\" fn hook_proc(code: c_int, wparam: WPARAM, lparam: LPARAM) -> LRESULT {\n    let hook_lparam = &*(lparam as *const KBDLLHOOKSTRUCT);\n    let is_injected = hook_lparam.flags & LLKHF_INJECTED != 0;\n    let key_event = InputEvent::from_hook_lparam(hook_lparam);\n    log::info!(\"{code}, {wparam:?}, {is_injected}, {key_event:?}\");\n    CallNextHookEx(ptr::null_mut(), code, wparam, lparam)\n}\n\npub fn start() -> Result<()> {\n    // Display debug and panic output when launched from a terminal.\n    unsafe {\n        use winapi::um::wincon::*;\n        if AttachConsole(ATTACH_PARENT_PROCESS) != 0 {\n            panic!(\"Could not attach to console\");\n        }\n    };\n    native_windows_gui::init()?;\n    // This callback should return `false` if the input event is **not** handled by the\n    // callback and `true` if the input event **is** handled by the callback. Returning false\n    // informs the callback caller that the input event should be handed back to the OS for\n    // normal processing.\n    let _kbhook = KeyboardHook::attach_hook();\n    log::info!(\"hook attached, you can type now\");\n    // The event loop is also required for the low-level keyboard hook to work.\n    native_windows_gui::dispatch_thread_events();\n    Ok(())\n}\n"
  },
  {
    "path": "windows_key_tester/src/windows.rs",
    "content": "use anyhow::Result;\nuse simplelog::*;\n\nuse clap::Parser;\n#[cfg(not(feature = \"interception_driver\"))]\nmod llhook;\n#[cfg(not(feature = \"interception_driver\"))]\nuse llhook::*;\n\n#[cfg(feature = \"interception_driver\")]\nmod interception;\n#[cfg(feature = \"interception_driver\")]\nuse interception::*;\n\n#[derive(Parser, Debug)]\n#[clap(author, version, about, long_about = None)]\nstruct Args {\n    /// Enable debug logging\n    #[clap(short, long)]\n    debug: bool,\n\n    /// Enable trace logging (implies --debug as well)\n    #[clap(short, long)]\n    trace: bool,\n}\n\n#[cfg(target_os = \"windows\")]\n/// Parse CLI arguments and initialize logging.\nfn cli_init() {\n    let args = Args::parse();\n\n    let log_lvl = match (args.debug, args.trace) {\n        (_, true) => LevelFilter::Trace,\n        (true, false) => LevelFilter::Debug,\n        (false, false) => LevelFilter::Info,\n    };\n\n    let mut log_cfg = ConfigBuilder::new();\n    if let Err(e) = log_cfg.set_time_offset_to_local() {\n        eprintln!(\"WARNING: could not set log TZ to local: {e:?}\");\n    };\n    CombinedLogger::init(vec![TermLogger::new(\n        log_lvl,\n        log_cfg.build(),\n        TerminalMode::Mixed,\n        ColorChoice::AlwaysAnsi,\n    )])\n    .expect(\"logger can init\");\n    log::info!(\"windows_key_tester v{} starting\", env!(\"CARGO_PKG_VERSION\"));\n}\n\npub(crate) fn main_impl() -> Result<()> {\n    cli_init();\n    log::info!(\"Sleeping for 2s. Please release all keys and don't press additional ones.\");\n    std::thread::sleep(std::time::Duration::from_secs(2));\n    start()?;\n    Ok(())\n}\n"
  }
]