An open API service indexing awesome lists of open source software.

https://github.com/cortesi/hotki

A modal hotkey app for macOS
https://github.com/cortesi/hotki

hotkey hotkeys macos shortcuts

Last synced: 10 months ago
JSON representation

A modal hotkey app for macOS

Awesome Lists containing this project

README

          

![Discord](https://img.shields.io/discord/1381424110831145070?style=flat-square&logo=rust&link=https%3A%2F%2Fdiscord.gg%2FfHmRmuBDxF)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)


hotki logo

# Hotki

A modal hotkey app for macOS.

- Modal hotkeys for macOS
- A customizable HUD (Heads-Up Display) for displaying active mode hotkeys
- Customizable notifications to display hotkey action outcomes
- Hotkeys for any app with key relaying and focus matching

Hotki is now an early alpha - it's stable and my daily driver, but I'm not
cutting binary releases yet. See the [Installation](#installation) section
below for how to build it. Next steps:

- Window management actions (move, resize, etc)
- More sophisticated HUD patterns allowing text entry, selection, etc.
- Tiled window layouts
- Window groups

## Window State Architecture

Hotki relies on the `hotki-world` crate as the single source of truth for
window and focus state. Engines, smoketests, and auxiliary tooling subscribe
to the [`WorldView`](crates/hotki-world/src/view.rs) trait instead of calling
`mac_winops::list_windows` directly. This keeps CoreGraphics and AX access in
one place, simplifies testing via the in-memory `TestWorld`, and ensures that
refresh hints flow through a single channel.

## Configuration

Hotki configuration lives at `~/.hotki.ron`, and uses RON ([Rusty Object
Notation](https://github.com/ron-rs/ron)) for its configuration format.
See [`examples/complete.ron`](examples/complete.ron) for a comprehensive
example demonstrating all available settings and action types.

The high-level structure of the config file is as follows:

```ron
(
// Base theme (optional, "default" if omitted)
base_theme: "solarized-dark",
// Styles applied on top of base_theme (optional).
style: (
hud: (
// HUD style overrides ...
),
notify: (
// Notification style overrides ...
),
),
keys: (
[
// Key bindings ...
],
// Attributes (optional)
( ... )
),
)
```

## Keys

The keys section is the heart of Hotki. A key specification has the following format:

```ron
("key combo", "Description", action)
// OR
("key combo", "Description", action, (attributes))
```

Key combos are specified using a `+`-separated list of keys, with zero or more
modifiers and a single key. The following modifiers are supported:
*cmd*, *ctrl*, *opt*, *shift*.

There is a complete list of supported actions and attributes below.

Let's look some real snippets from the author's own config. First, a simple
mode for controlling music:

```ron
("shift+cmd+m", "music", keys([
(
"k", "vol up",
change_volume(5),
(noexit: true, repeat: true)
),
(
"j", "vol down",
change_volume(-5),
(noexit: true, repeat: true)
),
("l", "next >>", shell("spotify next")),
("h", "<< prev", shell("spotify prev")),
("p", "play/pause", shell("spotify pause")),
("shift+cmd+m", "exit", exit, (global: true, hide: true)),
]), (capture: true)),
```

Here, the global hotkey `shift+cmd+m` activates the "music" mode. The `capture:
true` modifier instructs Hotki to swallow all non-bound keys while the HUD is
visible, preventing accidental input to the focused application. Once the music
mode is active, we can adjust the volume with `j` and `k`, both of which have
`repeat` enabled (letting us hold down the key to repeat) and `noexit` (so the
mode remains active after the action - the default is to exit the mode on
action). We also bind `shift+cmd+m` to exit the mode (but hide it from the HUD
display) so we can toggle music mode on and off with the same key combo.

Next, let's look at a very powerful technique that combines focus matching
with key relaying to produce global hotkeys that work when specific apps have
focus.

```ron
("shift+cmd+j", "obsidian", keys([
("f", "file", keys([
("f", "show in finder", relay("ctrl+shift+cmd+1")),
("n", "rename", relay("ctrl+shift+cmd+2")),
]),
),
("s", "split", keys([
("l", "right", relay("ctrl+shift+cmd+3")),
("j", "down", relay("ctrl+shift+cmd+4")),
]),
),
]), (match_app : "Obsidian")),
```

This example binds `shift+cmd+j` to enter a mode for controlling Obsidian. The
`match_app` modifier means that this binding is only active when Obsidian is
the frontmost application. In fact, we can bind the same binding to different
apps, and only the matching key mode will be activated - so `shift+cmd+j` is my
global shortcut for activating app-specific modes. The trick here is that we
combine the matching with the `relay` action, which sends keystrokes to the
focused app. We can then use Obsidian's own keyboard shortcuts to bind
little-used shortcut combinations like `ctrl+shift+cmd+1` to specific actions,
and then trigger those actions from anywhere using Hotki. Hotki's HUD means
that these shortcuts are instantly available, but they're also discoverable
through the HUD.

### Actions

Below is a table of all supported key binding actions.


exit


Exit from the HUD


pop


Pop up one level


set_volume(50)


Set volume, value is 0-100


change_volume(10)


Change volume by a relative amount (-100 to +100)


mute(toggle)


Enable/disable volume mute explicitly (on
enables, off disables, toggle toggles).



# Simple form
shell("echo 'Hello'")
# With default attributes
shell(
"echo 'Hello'",
(ok_notify: ignore, err_notify: warn)
)


Run a shell command. Attributes over-ride which notification style is
used if the command exits with or without error. Values can be "ignore",
"info", "success", "warn" and "error".


relay("cmd+shift+n")


Relay a keystroke to the focused application



fullscreen(toggle)
fullscreen(on, native)
fullscreen(off, nonnative)



Toggle or set fullscreen on the focused window.


native: Uses the app's native macOS Full Screen state
(AXFullScreen); if unsupported, Hotki falls back to sending the
standard Ctrl+Cmd+F shortcut.



nonnative (default): Maximizes the window to the current
screen's visibleFrame (does not create a new Space). Toggle
stores the prior frame per window and restores it on the next
toggle/off.




place(grid(3, 1), at(0, 0))
place(grid(2, 2), at(1, 0))



Place the focused window into a grid cell on the current screen.


  • grid(x, y) divides the screen into x columns and y rows. Both must be > 0.


  • at(ix, iy) selects a zero‑based cell within that grid. The origin (0, 0) is top‑left.


Example: place(grid(3, 1), at(0, 0)) places the window in the left‑most third of the screen.



place_move(grid(3, 2), left)
place_move(grid(3, 2), right)
place_move(grid(3, 2), up)
place_move(grid(3, 2), down)



Move the focused window by one cell within a grid on the current screen.


  • grid(x, y) defines the grid (both > 0).


  • up/down/left/right move one cell and clamp at edges (no wrap).

  • First invocation from a non‑aligned position places at the visual top‑left cell (0, 0).




raise(app: "^Safari$")
raise(title: "Downloads")
raise(app: "Safari", title: "Downloads")



Raise (activate) a window matching the given specification.


  • Regex matching: app and title are regular expressions, identical in semantics to match_app/match_title.


  • AND semantics: when both are provided, both must match.


  • Scope: on‑screen layer‑0 windows in the current Space (v1). Does not un‑minimize or switch Spaces.


  • Cycling: 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.



keys([ /* ... */ ])


Enter a nested keys section (sub‑mode)


reload_config


Reload the configuration file


clear_notifications


Clear all on‑screen notifications


show_details(toggle)


Control the details window visibility. Use
show_details(on) to show, show_details(off) to
hide, or show_details(toggle) to toggle.


show_hud_root


Show the HUD with root‑level key bindings


theme_next


Switch to the next theme


theme_prev


Switch to the previous theme


theme_set("dark-blue")


Set a specific theme by name


user_style(toggle)


Enable/disable user style configuration (on enables,
off disables, toggle toggles). When off, the base
theme is revealed unmodified.

### Attributes

Per-binding attributes are specified as the optional 4th element of a key tuple:

(

"k", "Description", action_here, (modifier1: value, modifier2: value)
)

Attributes apply to the mode they are defined on, and all nested keys and modes
below them.

noexit: true

Do not exit the current mode after executing this action. Also serves as the default for repeat when repeat is not set.

global: true

Make this binding available in all descendant sub‑modes.

hide: true

Hide this binding from the HUD while keeping it active.

hud_only: true

Only bind this key when the HUD is visible.

match_app: "Safari|Chrome"

Enable only when the frontmost application name matches this regex.

match_title: ".*\\.md$"

Enable only when the active window title matches this regex.

repeat: true

Enable hold‑to‑repeat for supported actions (shell, relay, change_volume). Defaults to the value of noexit when omitted.

repeat_delay: 250

Initial delay (ms) before the first repeat tick.

repeat_interval: 33

Interval (ms) between repeat ticks.

style: (hud: (...), notify: (...))

Per‑binding style overlay applied while this binding’s mode is active.

capture: true

While this mode is active and the HUD is visible, swallow all non‑bound keys so they are not delivered to the focused app.

## Themes and Styling

Every aspect of Hotki's UI is customizable. We have a few built-in
[themes](./crates/config/themes) that you can build on, or you can override
everything for complete control.



default












solarized-dark












solarized-light












dark-blue












charcoal








## Fonts

The default bundled font is a [Nerd Font](https://www.nerdfonts.com/)
([0xProto](https://github.com/0xType/0xProto)
Nerd Font Mono). Nerd Fonts include a wide range of glyphs and symbols used
throughout the UI, and which can be used in styling.

# Installation

We don't have binary releases yet. For the moment, the installation process is
to compile the app bundle using the following script from the repo root:

```sh
./scripts/bundle.sh
```

The bundle will be at `./target/bundle/Hotki.app`, ready to copy to your
`/Applications` folder.