{"id":31103799,"url":"https://github.com/cortesi/hotki","last_synced_at":"2025-09-17T02:55:50.403Z","repository":{"id":312934584,"uuid":"1039439772","full_name":"cortesi/hotki","owner":"cortesi","description":"A modal hotkey app for macOS","archived":false,"fork":false,"pushed_at":"2025-09-10T03:22:33.000Z","size":5746,"stargazers_count":5,"open_issues_count":1,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-10T07:07:56.646Z","etag":null,"topics":["hotkey","hotkeys","macos","shortcuts"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cortesi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-08-17T08:24:56.000Z","updated_at":"2025-09-10T03:22:37.000Z","dependencies_parsed_at":null,"dependency_job_id":"e7a0007c-b9c3-4298-afb9-1f0a76c4b163","html_url":"https://github.com/cortesi/hotki","commit_stats":null,"previous_names":["cortesi/hotki"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cortesi/hotki","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cortesi%2Fhotki","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cortesi%2Fhotki/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cortesi%2Fhotki/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cortesi%2Fhotki/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cortesi","download_url":"https://codeload.github.com/cortesi/hotki/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cortesi%2Fhotki/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275526435,"owners_count":25480460,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-17T02:00:09.119Z","response_time":84,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hotkey","hotkeys","macos","shortcuts"],"created_at":"2025-09-17T02:55:45.446Z","updated_at":"2025-09-17T02:55:50.390Z","avatar_url":"https://github.com/cortesi.png","language":"Rust","funding_links":[],"categories":["macos"],"sub_categories":[],"readme":"![Discord](https://img.shields.io/discord/1381424110831145070?style=flat-square\u0026logo=rust\u0026link=https%3A%2F%2Fdiscord.gg%2FfHmRmuBDxF)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"crates/hotki/assets/logo-doc.png\" alt=\"hotki logo\" width=\"200\" /\u003e\n\u003c/p\u003e\n\n# Hotki\n\nA modal hotkey app for macOS.\n\n- Modal hotkeys for macOS\n- A customizable HUD (Heads-Up Display) for displaying active mode hotkeys\n- Customizable notifications to display hotkey action outcomes\n- Hotkeys for any app with key relaying and focus matching\n\nHotki is now an early alpha - it's stable and my daily driver, but I'm not\ncutting binary releases yet. See the [Installation](#installation) section\nbelow for how to build it. Next steps:\n\n- Window management actions (move, resize, etc)\n- More sophisticated HUD patterns allowing text entry, selection, etc.\n- Tiled window layouts\n- Window groups\n\n\n## Window State Architecture\n\nHotki relies on the `hotki-world` crate as the single source of truth for\nwindow and focus state. Engines, smoketests, and auxiliary tooling subscribe\nto the [`WorldView`](crates/hotki-world/src/view.rs) trait instead of calling\n`mac_winops::list_windows` directly. This keeps CoreGraphics and AX access in\none place, simplifies testing via the in-memory `TestWorld`, and ensures that\nrefresh hints flow through a single channel.\n\n\n## Configuration\n\nHotki configuration lives at `~/.hotki.ron`, and uses RON ([Rusty Object\nNotation](https://github.com/ron-rs/ron)) for its configuration format. \nSee [`examples/complete.ron`](examples/complete.ron) for a comprehensive\nexample demonstrating all available settings and action types.\n\nThe high-level structure of the config file is as follows:\n\n```ron\n(\n    // Base theme (optional, \"default\" if omitted)\n    base_theme: \"solarized-dark\",\n    // Styles applied on top of base_theme (optional). \n    style: (\n        hud: (\n            // HUD style overrides ...\n        ),\n        notify: (\n            // Notification style overrides ...\n        ),\n    ),\n    keys: (\n        [\n            // Key bindings ...\n        ],\n        // Attributes (optional)\n        ( ... ) \n    ),\n)\n```\n\n## Keys\n\nThe keys section is the heart of Hotki. A key specification has the following format:\n\n```ron\n(\"key combo\", \"Description\", action)\n// OR\n(\"key combo\", \"Description\", action, (attributes))\n```\n\nKey combos are specified using a `+`-separated list of keys, with zero or more\nmodifiers and a single key. The following modifiers are supported:\n*cmd*, *ctrl*, *opt*, *shift*.\n\nThere is a complete list of supported actions and attributes below.\n\nLet's look some real snippets from the author's own config. First, a simple\nmode for controlling music:\n\n```ron\n(\"shift+cmd+m\", \"music\", keys([\n    (\n        \"k\", \"vol up\",\n        change_volume(5),\n        (noexit: true, repeat: true)\n    ),\n    (\n        \"j\", \"vol down\",\n        change_volume(-5),\n        (noexit: true, repeat: true)\n    ),\n    (\"l\", \"next \u003e\u003e\", shell(\"spotify next\")),\n    (\"h\", \"\u003c\u003c prev\", shell(\"spotify prev\")),\n    (\"p\", \"play/pause\", shell(\"spotify pause\")),\n    (\"shift+cmd+m\", \"exit\", exit, (global: true, hide: true)),\n]), (capture: true)),\n```\n\nHere, the global hotkey `shift+cmd+m` activates the \"music\" mode. The `capture:\ntrue` modifier instructs Hotki to swallow all non-bound keys while the HUD is\nvisible, preventing accidental input to the focused application. Once the music\nmode is active, we can adjust the volume with `j` and `k`, both of which have\n`repeat` enabled (letting us hold down the key to repeat) and `noexit` (so the\nmode remains active after the action - the default is to exit the mode on\naction). We also bind `shift+cmd+m` to exit the mode (but hide it from the HUD\ndisplay) so we can toggle music mode on and off with the same key combo.\n\nNext, let's look at a very powerful technique that combines focus matching\nwith key relaying to produce global hotkeys that work when specific apps have\nfocus.\n\n```ron\n(\"shift+cmd+j\", \"obsidian\", keys([\n   (\"f\", \"file\", keys([\n           (\"f\", \"show in finder\", relay(\"ctrl+shift+cmd+1\")),\n           (\"n\", \"rename\", relay(\"ctrl+shift+cmd+2\")),\n       ]),\n   ),\n   (\"s\", \"split\", keys([\n           (\"l\", \"right\", relay(\"ctrl+shift+cmd+3\")),\n           (\"j\", \"down\", relay(\"ctrl+shift+cmd+4\")),\n       ]),\n   ),\n]), (match_app : \"Obsidian\")),\n```\n\nThis example binds `shift+cmd+j` to enter a mode for controlling Obsidian. The\n`match_app` modifier means that this binding is only active when Obsidian is\nthe frontmost application. In fact, we can bind the same binding to different\napps, and only the matching key mode will be activated - so `shift+cmd+j` is my\nglobal shortcut for activating app-specific modes. The trick here is that we\ncombine the matching with the `relay` action, which sends keystrokes to the\nfocused app. We can then use Obsidian's own keyboard shortcuts to bind\nlittle-used shortcut combinations like `ctrl+shift+cmd+1` to specific actions,\nand then trigger those actions from anywhere using Hotki. Hotki's HUD means\nthat these shortcuts are instantly available, but they're also discoverable\nthrough the HUD.\n\n\n### Actions\n\nBelow is a table of all supported key binding actions. \n\n\u003ctable\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003eexit\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eExit from the HUD\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003epop\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003ePop up one level\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003eset_volume(50)\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eSet volume, value is 0-100\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003echange_volume(10)\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eChange volume by a relative amount (-100 to +100)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003emute(toggle)\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eEnable/disable volume mute explicitly (\u003ccode\u003eon\u003c/code\u003e\n    enables, \u003ccode\u003eoff\u003c/code\u003e disables, \u003ccode\u003etoggle\u003c/code\u003e toggles).\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003e\n# Simple form\nshell(\"echo 'Hello'\")\n# With default attributes\nshell(\n    \"echo 'Hello'\",\n    (ok_notify: ignore, err_notify: warn)\n)\n        \u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eRun a shell command. Attributes over-ride which notification style is\n    used if the command exits with or without error. Values can be \"ignore\",\n    \"info\", \"success\", \"warn\" and \"error\".\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003erelay(\"cmd+shift+n\")\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eRelay a keystroke to the focused application\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003e\nfullscreen(toggle)\nfullscreen(on, native)\nfullscreen(off, nonnative)\n        \u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e\n        Toggle or set fullscreen on the focused window.\n        \u003cbr/\u003e\n         \u003ccode\u003enative\u003c/code\u003e: Uses the app's native macOS Full Screen state\n          (AXFullScreen); if unsupported, Hotki falls back to sending the\n          standard \u003ccode\u003eCtrl+Cmd+F\u003c/code\u003e shortcut.\u003cbr/\u003e\n        \u003cbr/\u003e\n         \u003ccode\u003enonnative\u003c/code\u003e (default): Maximizes the window to the current\n          screen's \u003cem\u003evisibleFrame\u003c/em\u003e (does not create a new Space). Toggle\n          stores the prior frame per window and restores it on the next\n          toggle/off.\n        \u003cbr/\u003e\n    \u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003e\nplace(grid(3, 1), at(0, 0))\nplace(grid(2, 2), at(1, 0))\n        \u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e\n        Place the focused window into a grid cell on the current screen.\n        \u003cul\u003e\n          \u003cli\u003e\u003ccode\u003egrid(x, y)\u003c/code\u003e divides the screen into \u003cem\u003ex\u003c/em\u003e columns and \u003cem\u003ey\u003c/em\u003e rows. Both must be \u003ccode\u003e\u0026gt; 0\u003c/code\u003e.\u003c/li\u003e\n          \u003cli\u003e\u003ccode\u003eat(ix, iy)\u003c/code\u003e selects a zero‑based cell within that grid. The origin \u003ccode\u003e(0, 0)\u003c/code\u003e is \u003cstrong\u003etop‑left\u003c/strong\u003e.\u003c/li\u003e\n        \u003c/ul\u003e\n        Example: \u003ccode\u003eplace(grid(3, 1), at(0, 0))\u003c/code\u003e places the window in the left‑most third of the screen.\n    \u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003e\nplace_move(grid(3, 2), left)\nplace_move(grid(3, 2), right)\nplace_move(grid(3, 2), up)\nplace_move(grid(3, 2), down)\n        \u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e\n        Move the focused window by one cell within a grid on the current screen.\n        \u003cul\u003e\n          \u003cli\u003e\u003ccode\u003egrid(x, y)\u003c/code\u003e defines the grid (both \u003ccode\u003e\u0026gt; 0\u003c/code\u003e).\u003c/li\u003e\n          \u003cli\u003e\u003ccode\u003eup\u003c/code\u003e/\u003ccode\u003edown\u003c/code\u003e/\u003ccode\u003eleft\u003c/code\u003e/\u003ccode\u003eright\u003c/code\u003e move one cell and clamp at edges (no wrap).\u003c/li\u003e\n          \u003cli\u003eFirst invocation from a non‑aligned position places at the visual top‑left cell (0, 0).\u003c/li\u003e\n        \u003c/ul\u003e\n    \u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003e\nraise(app: \"^Safari$\")\nraise(title: \"Downloads\")\nraise(app: \"Safari\", title: \"Downloads\")\n        \u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e\n        Raise (activate) a window matching the given specification.\n        \u003cul\u003e\n          \u003cli\u003e\u003cstrong\u003eRegex matching\u003c/strong\u003e: \u003ccode\u003eapp\u003c/code\u003e and \u003ccode\u003etitle\u003c/code\u003e are regular expressions, identical in semantics to \u003ccode\u003ematch_app\u003c/code\u003e/\u003ccode\u003ematch_title\u003c/code\u003e.\u003c/li\u003e\n          \u003cli\u003e\u003cstrong\u003eAND semantics\u003c/strong\u003e: when both are provided, both must match.\u003c/li\u003e\n          \u003cli\u003e\u003cstrong\u003eScope\u003c/strong\u003e: on‑screen layer‑0 windows in the current Space (v1). Does not un‑minimize or switch Spaces.\u003c/li\u003e\n          \u003cli\u003e\u003cstrong\u003eCycling\u003c/strong\u003e: if the current frontmost window matches and there is another matching window behind it, focus moves to the next match; if there is no other match, it’s a no‑op.\u003c/li\u003e\n        \u003c/ul\u003e\n    \u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003ekeys([ /* ... */ ])\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eEnter a nested keys section (sub‑mode)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003ereload_config\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eReload the configuration file\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003eclear_notifications\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eClear all on‑screen notifications\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003eshow_details(toggle)\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eControl the details window visibility. Use\n    \u003ccode\u003eshow_details(on)\u003c/code\u003e to show, \u003ccode\u003eshow_details(off)\u003c/code\u003e to\n    hide, or \u003ccode\u003eshow_details(toggle)\u003c/code\u003e to toggle.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003eshow_hud_root\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eShow the HUD with root‑level key bindings\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003etheme_next\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eSwitch to the next theme\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003etheme_prev\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eSwitch to the previous theme\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003etheme_set(\"dark-blue\")\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eSet a specific theme by name\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\n        \u003cpre lang=\"ron\"\u003euser_style(toggle)\u003c/pre\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eEnable/disable user style configuration (\u003ccode\u003eon\u003c/code\u003e enables,\n    \u003ccode\u003eoff\u003c/code\u003e disables, \u003ccode\u003etoggle\u003c/code\u003e toggles). When off, the base\n    theme is revealed unmodified.\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n\n### Attributes\n\nPer-binding attributes are specified as the optional 4th element of a key tuple:\n\n\u003cpre lang=\"ron\"\u003e(\n    \"k\", \"Description\", action_here, (modifier1: value, modifier2: value)\n)\u003c/pre\u003e\n\nAttributes apply to the mode they are defined on, and all nested keys and modes\nbelow them.\n\n\u003ctable\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\u003cpre lang=\"ron\"\u003enoexit: true\u003c/pre\u003e\u003c/td\u003e\n    \u003ctd\u003eDo not exit the current mode after executing this action. Also serves as the default for \u003ccode\u003erepeat\u003c/code\u003e when \u003ccode\u003erepeat\u003c/code\u003e is not set.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\u003cpre lang=\"ron\"\u003eglobal: true\u003c/pre\u003e\u003c/td\u003e\n    \u003ctd\u003eMake this binding available in all descendant sub‑modes.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\u003cpre lang=\"ron\"\u003ehide: true\u003c/pre\u003e\u003c/td\u003e\n    \u003ctd\u003eHide this binding from the HUD while keeping it active.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\u003cpre lang=\"ron\"\u003ehud_only: true\u003c/pre\u003e\u003c/td\u003e\n    \u003ctd\u003eOnly bind this key when the HUD is visible.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\u003cpre lang=\"ron\"\u003ematch_app: \"Safari|Chrome\"\u003c/pre\u003e\u003c/td\u003e\n    \u003ctd\u003eEnable only when the frontmost application name matches this regex.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\u003cpre lang=\"ron\"\u003ematch_title: \".*\\\\.md$\"\u003c/pre\u003e\u003c/td\u003e\n    \u003ctd\u003eEnable only when the active window title matches this regex.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\u003cpre lang=\"ron\"\u003erepeat: true\u003c/pre\u003e\u003c/td\u003e\n    \u003ctd\u003eEnable hold‑to‑repeat for supported actions (shell, relay, change_volume). Defaults to the value of \u003ccode\u003enoexit\u003c/code\u003e when omitted.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\u003cpre lang=\"ron\"\u003erepeat_delay: 250\u003c/pre\u003e\u003c/td\u003e\n    \u003ctd\u003eInitial delay (ms) before the first repeat tick.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\u003cpre lang=\"ron\"\u003erepeat_interval: 33\u003c/pre\u003e\u003c/td\u003e\n    \u003ctd\u003eInterval (ms) between repeat ticks.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\u003cpre lang=\"ron\"\u003estyle: (hud: (...), notify: (...))\u003c/pre\u003e\u003c/td\u003e\n    \u003ctd\u003ePer‑binding style overlay applied while this binding’s mode is active.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\u003c/tr\u003e\n\u003ctr\u003e\n    \u003ctd\u003e\u003cpre lang=\"ron\"\u003ecapture: true\u003c/pre\u003e\u003c/td\u003e\n    \u003ctd\u003eWhile this mode is active and the HUD is visible, swallow all non‑bound keys so they are not delivered to the focused app.\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n\n\n\n## Themes and Styling\n\nEvery aspect of Hotki's UI is customizable. We have a few built-in\n[themes](./crates/config/themes) that you can build on, or you can override\neverything for complete control.\n\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e \n        \u003ccenter\u003e\u003cb\u003edefault\u003c/b\u003e\u003c/center\u003e\n        \u003cimg src=\"./assets/default/001_hud.png\" width=\"350px\"\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e \n        \u003cimg src=\"./assets/default/003_notify_info.png\" width=\"250px\"\u003e\n        \u003cimg src=\"./assets/default/002_notify_success.png\" width=\"250px\"\u003e\n        \u003cimg src=\"./assets/default/004_notify_warning.png\" width=\"250px\"\u003e\n        \u003cimg src=\"./assets/default/005_notify_error.png\"width=\"250px\"\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e \n        \u003ccenter\u003e\u003cb\u003esolarized-dark\u003c/b\u003e\u003c/center\u003e\n        \u003cimg src=\"./assets/solarized-dark/001_hud.png\" width=\"350px\"\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e \n        \u003cimg src=\"./assets/solarized-dark/003_notify_info.png\" width=\"250px\"\u003e\n        \u003cimg src=\"./assets/solarized-dark/002_notify_success.png\" width=\"250px\"\u003e\n        \u003cimg src=\"./assets/solarized-dark/004_notify_warning.png\" width=\"250px\"\u003e\n        \u003cimg src=\"./assets/solarized-dark/005_notify_error.png\"width=\"250px\"\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\n        \u003ccenter\u003e\u003cb\u003esolarized-light\u003c/b\u003e\u003c/center\u003e\n        \u003cimg src=\"./assets/solarized-light/001_hud.png\" width=\"350px\"\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e \n        \u003cimg src=\"./assets/solarized-light/003_notify_info.png\" width=\"250px\"\u003e\n        \u003cimg src=\"./assets/solarized-light/002_notify_success.png\" width=\"250px\"\u003e\n        \u003cimg src=\"./assets/solarized-light/004_notify_warning.png\" width=\"250px\"\u003e\n        \u003cimg src=\"./assets/solarized-light/005_notify_error.png\"width=\"250px\"\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\n        \u003ccenter\u003e\u003cb\u003edark-blue\u003c/b\u003e\u003c/center\u003e\n        \u003cimg src=\"./assets/dark-blue/001_hud.png\" width=\"350px\"\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e \n        \u003cimg src=\"./assets/dark-blue/003_notify_info.png\" width=\"250px\"\u003e\n        \u003cimg src=\"./assets/dark-blue/002_notify_success.png\" width=\"250px\"\u003e\n        \u003cimg src=\"./assets/dark-blue/004_notify_warning.png\" width=\"250px\"\u003e\n        \u003cimg src=\"./assets/dark-blue/005_notify_error.png\"width=\"250px\"\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\n        \u003ccenter\u003e\u003cb\u003echarcoal\u003c/b\u003e\u003c/center\u003e\n        \u003cimg src=\"./assets/charcoal/001_hud.png\" width=\"350px\"\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e \n        \u003cimg src=\"./assets/charcoal/003_notify_info.png\" width=\"250px\"\u003e\n        \u003cimg src=\"./assets/charcoal/002_notify_success.png\" width=\"250px\"\u003e\n        \u003cimg src=\"./assets/charcoal/004_notify_warning.png\" width=\"250px\"\u003e\n        \u003cimg src=\"./assets/charcoal/005_notify_error.png\"width=\"250px\"\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\n## Fonts\n\nThe default bundled font is a [Nerd Font](https://www.nerdfonts.com/)\n([0xProto](https://github.com/0xType/0xProto)\nNerd Font Mono). Nerd Fonts include a wide range of glyphs and symbols used\nthroughout the UI, and which can be used in styling.\n\n\n# Installation\n\nWe don't have binary releases yet. For the moment, the installation process is\nto compile the app bundle using the following script from the repo root:\n\n```sh\n./scripts/bundle.sh\n```\n\nThe bundle will be at `./target/bundle/Hotki.app`, ready to copy to your\n`/Applications` folder.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcortesi%2Fhotki","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcortesi%2Fhotki","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcortesi%2Fhotki/lists"}