summaryrefslogtreecommitdiff
path: root/noctalia/.config/plugins/keybind-cheatsheet/README.md
blob: 5c30b87e3f0edd000b6ffcfb566d502a9f4b3b7a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# Keybind Cheatsheet for Noctalia

Universal keyboard shortcuts cheatsheet plugin for Noctalia that **automatically detects** your compositor (Hyprland, Niri or MangoWC) and displays your keybindings with **recursive config parsing**. Supports the classic Hyprland `.conf` format, the new **Hyprland 0.55+ Lua config**, Niri KDL and MangoWC.

![Preview](preview.png)

## Features

- **Automatic compositor detection** (Hyprland, Niri, MangoWC)
- **Hyprland Lua config support** (Hyprland 0.55+) via `hyprctl binds -j`
- **Recursive config parsing** - follows `source` (Hyprland `.conf`), `require()` (Hyprland Lua) and `include` (Niri) directives
- **Glob pattern support** - parses `~/.config/hypr/*.conf` style includes
- **Without Description section** - binds with no description are surfaced, not dropped; add a custom description or hide them inline
- **Full per-category color customization** - background + text color for every key category, with live preview and clipboard quick-paste
- **Search filter** - type to filter keybindings in the panel
- **Configurable paths & parser mode** - custom config locations and forced Lua/conf parser in settings
- **Smart key formatting** - XF86 keys display as readable names (Vol Up, Bright Down, etc.)
- **Flexible column layout** (1-4 columns) with auto-height
- **IPC support** - `toggle` and `refresh` for global hotkeys

## Supported Compositors

| Compositor | Default Config | How it's read |
|------------|----------------|----------------|
| **Hyprland 0.55+** | `~/.config/hypr/hyprland.lua` | `hyprctl binds -j` (live, already-evaluated binds) |
| **Hyprland (classic)** | `~/.config/hypr/hyprland.conf` | hyprlang `.conf` text parser (recursive) |
| **Niri** | `~/.config/niri/config.kdl` | KDL parser (recursive `include`) |
| **MangoWC** | `~/.config/mango/config.conf` | `bind=` / `axisbind=` / `mousebind=` parser |

## Installation

```bash
cp -r keybind-cheatsheet ~/.config/noctalia/plugins/
```

## Usage

### Bar Widget
Add the plugin to your bar configuration in Noctalia settings. Click the keyboard icon to open the cheatsheet.

### Global Hotkey

**Hyprland:**
```bash
bind = $mod, F1, exec, qs -c noctalia-shell ipc call plugin:keybind-cheatsheet toggle
```
You can set your custom Super key variable (e.g. `$mainMod`) in the plugin settings.

**Niri:**
```kdl
binds {
    Mod+F1 { spawn-sh "qs -c noctalia-shell ipc call plugin:keybind-cheatsheet toggle"; }
}
```

**MangoWC:**
```bash
bind=SUPER,F1,spawn,qs -c noctalia-shell ipc call plugin:keybind-cheatsheet toggle
```

### IPC Commands

| Command | Effect |
|---------|--------|
| `qs -c noctalia-shell ipc call plugin:keybind-cheatsheet toggle` | Open / close the cheatsheet panel |
| `qs -c noctalia-shell ipc call plugin:keybind-cheatsheet refresh` | Force a re-parse of your keybindings |

`refresh` is useful after editing your config — bind it to a key to reload without restarting the shell.

## Hyprland Lua Config (0.55+)

Hyprland 0.55 replaced the static hyprlang `.conf` with a **Lua config** (`hyprland.lua`). Binds can now be generated with `require()`, `for` loops and runtime logic, so they **cannot be recovered by reading the file as text**.

### How it works

Instead of parsing the Lua file, the plugin asks Hyprland directly:

1. **Authoritative bind list** — runs `hyprctl binds -j` and reads the already-evaluated JSON bind list. This correctly handles `require()` modules, `for` loops and multi-key chords — whatever Hyprland *actually* has bound at runtime.
2. **Category & description recovery** — the plugin then *lightly scans* `hyprland.lua` and every file it pulls in via `require()` to recover human-readable structure:
   - **Category headers**: a comment in the form `-- N. NAME` (e.g. `-- 1. Applications`)
   - **Static descriptions**: a `description = "..."` (or `desc = '...'`) literal maps that exact description to the current category
   - **Loop-generated descriptions**: a concatenated literal such as `description = "Workspace " .. i` is treated as a *prefix* — any bind whose description starts with `Workspace ` is filed under that category
3. Binds whose description can't be matched fall into a generic **Other** category; binds with no description at all go to the **Without Description** section (see below).

You don't need to change your Lua config — just keep using `-- N. NAME` comment headers and `description = "..."` fields where you want grouping.

**Example Lua structure the scanner understands:**
```lua
-- 1. Applications
Hyprland.config.bind("$mod, T", function() ... end, { description = "Terminal" })
Hyprland.config.bind("$mod, B", function() ... end, { description = "Browser" })

-- 2. Workspaces
for i = 1, 9 do
  Hyprland.config.bind("$mod, " .. i, function() ... end,
    { description = "Workspace " .. i })   -- prefix "Workspace " -> category "Workspaces"
end

require("keybinds")   -- followed and scanned recursively
```

### Parser mode

A **Parser Mode** setting controls which Hyprland parser is used:

| Mode | Behavior |
|------|----------|
| `auto` *(default)* | Use the Lua parser if `hyprland.lua` exists, otherwise fall back to the `.conf` parser |
| `lua` | Always use `hyprctl binds -j` (Lua parser) |
| `conf` | Always use the classic `.conf` text parser |

The legacy hyprlang `.conf` parser is **kept unchanged** as a fallback for users still on hyprlang configs.

## Without Description Section

Binds with no description are no longer silently dropped. They appear in a dedicated **Without Description** section where you can:

- **Add a custom description** inline — it is remembered and displayed like any other bind
- **Hide** a bind you don't care about

Overrides are keyed by a **stable bind identity** (`submap | modmask | key | flags | dispatcher`) that deliberately excludes Hyprland's unstable internal Lua registry reference, so your custom descriptions and hidden binds **survive Hyprland restarts**.

Settings provide a **"Show binds without a description"** toggle, plus **"Restore hidden"** and **"Clear all overrides"** actions.

## Config Format

### Hyprland (classic `.conf`)

Recursively parses your main config and all `source` includes.

```bash
# 1. APPLICATIONS
bind = $mainMod, T, exec, alacritty #"Terminal"
bind = $mainMod, B, exec, firefox #"Browser"

# 2. WINDOW MANAGEMENT
bind = $mainMod, Q, killactive, #"Close window"
bind = $mainMod, F, fullscreen, #"Toggle fullscreen"
```

- Categories: `# N. CATEGORY NAME` (N is a number)
- Descriptions: `#"description"` at end of the bind line
- `source = ~/.config/hypr/keybinds.conf` and globs like `source = ~/.config/hypr/apps/*.conf` are followed automatically

### Niri

Parses the `binds { }` block and follows all `include` directives.

```kdl
binds {
    // #"Applications"
    Mod+T hotkey-overlay-title="Terminal" { spawn "alacritty"; }

    // #"Window Management"
    Mod+Q hotkey-overlay-title="Close window" { close-window; }

    // #"Workspaces"
    Mod+1 { focus-workspace 1; }
}
```

- Categories: `// #"Category Name"` (exact format)
- Descriptions: `hotkey-overlay-title="description"` attribute
- Without descriptions, actions are auto-categorized by type (see table below)
- `include "~/.config/niri/binds.kdl"` is followed automatically

### MangoWC

Parses `bind=`, `axisbind=` and `mousebind=` directives from `~/.config/mango/config.conf`.

```bash
# Applications
bind=SUPER,T,spawn,alacritty #"Terminal"
bind=SUPER,B,spawn,firefox #"Browser"

# Window Management
bind=SUPER,Q,killclient, #"Close window"
```

- Categories: standalone `# Category Name` comment lines
- Descriptions: trailing `#"description"` on a bind line
- Modifier aliases recognized: `LOGO` = `SUPER`, `MOD1` = `ALT`
- XF86 media keys are formatted (Vol Up, Mute, Bright Up/Down, …)
- Lines without a preceding `# Category` use the localized default category

## Auto-Categorization (Niri)

When no category comment is provided, keybindings are grouped by action:

| Action prefix | Category |
|---------------|----------|
| `spawn` | Applications |
| `focus-column-*` | Column Navigation |
| `focus-window-*` | Window Focus |
| `focus-workspace-*` | Workspace Navigation |
| `move-column-*` | Move Columns |
| `move-window-*` | Move Windows |
| `close-window`, `fullscreen-window` | Window Management |
| `maximize-column` | Column Management |
| `set-column-width` | Column Width |
| `screenshot*` | Screenshots |
| `power-off-monitors` | Power |
| `quit` | System |

## Special Key Formatting

| Raw Key | Display |
|---------|---------|
| `XF86AudioRaiseVolume` | Vol Up |
| `XF86AudioLowerVolume` | Vol Down |
| `XF86AudioMute` | Mute |
| `XF86MonBrightnessUp` | Bright Up |
| `XF86MonBrightnessDown` | Bright Down |
| `Print` | PrtSc |
| `Prior` / `Next` | PgUp / PgDn |

## Color Customization

Every key category has independently themeable **background** and **text** colors:
`Super`, `Ctrl`, `Shift`, `Alt`, `XF86`, `Print`, numeric, mouse and default letter keys — plus the description text color.

- **Two-pill rows** per category: left pill = background, right pill = label text on that background. Click a pill to open the color picker.
- **Theme-aware defaults**: `Super` / `Ctrl` / `Shift` use an empty sentinel meaning "use the Material theme accent" (`mPrimary` / `mSecondary` / `mTertiary`), so themed setups stay untouched unless you deliberately override.
- **Clipboard quick-paste**: copy a `#RRGGBB` / `#RRGGBBAA` hex and a paste icon appears in each pill — one click applies it (clipboard polled via `wl-paste`).
- **Live preview + revert**: changes preview immediately; closing Settings without Save restores the snapshot taken when the panel opened.
- **Per-row reset** and a **"Reset all colors"** action restore theme defaults.

## Settings

Access settings via the gear icon in the panel header:

- **Window width / height** (auto or manual) and **column count** (1-4)
- **Hyprland config path** — classic `.conf` location
- **Hyprland Lua path** — `hyprland.lua` location (0.55+)
- **Parser mode** — `auto` / `lua` / `conf`
- **Niri config path** / **MangoWC config path**
- **Show binds without a description** toggle + **Restore hidden** / **Clear all overrides**
- **Merge sequential binds** and **split large workspace category** (threshold configurable)
- **Per-category colors** — background + text for every category, with clipboard paste and reset
- **Refresh** — force reload keybindings

## Troubleshooting

### "Loading..." stays forever
1. Check the compositor is detected: look for logs tagged `[KeybindCheatsheet]`
2. Verify the config file exists at the configured path
3. On Hyprland 0.55+, confirm `hyprctl binds -j` works in your terminal

### `hyprctl` error shown in the panel
The Lua parser depends on `hyprctl binds -j`. Ensure Hyprland is running and `hyprctl` is on your `PATH`, or force the classic parser by setting **Parser mode** to `conf`.

### No categories found
- **Hyprland `.conf` / MangoWC:** categories must be `# 1.` / `# Category Name` comment lines
- **Hyprland Lua:** add `-- N. NAME` headers and `description = "..."` fields in `hyprland.lua` (and `require()`d modules)
- **Niri:** use `// #"Category Name"` format

### Binds missing or under "Without Description"
On Lua configs, descriptions are matched from `description = "..."` literals against the live `hyprctl` binds. Loop-generated binds need a concatenated prefix literal (e.g. `"Workspace " .. i`). Anything unmatched lands in **Without Description**, where you can add a description or hide it.

### Keybinds from included files not showing
The plugin follows `source` (Hyprland `.conf`), `require()` (Hyprland Lua) and `include` (Niri) directives automatically. Check logs to see which files are parsed.

## Requirements

- Noctalia Shell 3.6.0+
- Hyprland (classic `.conf` or 0.55+ Lua), Niri, or MangoWC
- `hyprctl` on `PATH` for Hyprland Lua configs
- `wl-paste` (wl-clipboard) for the color clipboard quick-paste feature

## License

MIT