Files
dotfiles/docs/desktop/hyprland.mdx
T

357 lines
19 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Hyprland
description: Hyprland Lua config layered on Omarchy — bindings ported from niri, master layout, terminal-only blur, monitors, and the omarchy-* helper scripts.
---
[Hyprland](https://hypr.land) is a dynamic tiling Wayland compositor. This config uses Hyprland's Lua configuration and sits on top of [Omarchy](https://omarchy.org), which provides the defaults, theme, menus, and `omarchy-*` commands. The files here only override. Most bindings are ported from the [niri config](/desktop/niri) so the same keys do the same jobs.
:::note
`~/.config/hypr` and the `omarchy-*` scripts are **not** listed in `.chezmoiignore.tmpl`. chezmoi applies them on every role and OS, including `mac` and `server`. They only take effect where Hyprland and Omarchy are installed.
:::
## Files
| Source (repo) | Target | Purpose |
| --- | --- | --- |
| `dot_config/hypr/hyprland.lua` | `~/.config/hypr/hyprland.lua` | Entry point, workspace and window rules |
| `dot_config/hypr/autostart.lua` | `~/.config/hypr/autostart.lua` | Extra autostart (empty) |
| `dot_config/hypr/bindings.lua` | `~/.config/hypr/bindings.lua` | Keybindings |
| `dot_config/hypr/input.lua` | `~/.config/hypr/input.lua` | Key repeat, tablet/touch mapping |
| `dot_config/hypr/looknfeel.lua` | `~/.config/hypr/looknfeel.lua` | Layout, decoration, blur |
| `dot_config/hypr/monitors.lua` | `~/.config/hypr/monitors.lua` | Monitor modes and positions |
| `dot_config/hypr/hyprsunset.conf` | `~/.config/hypr/hyprsunset.conf` | Night-light profile |
| `dot_config/hypr/xdph.conf` | `~/.config/hypr/xdph.conf` | xdg-desktop-portal-hyprland |
| `dot_config/hypr/dot_luarc.json` | `~/.config/hypr/.luarc.json` | Lua language server settings |
| `dot_local/bin/executable_omarchy-*` | `~/.local/bin/omarchy-*` | Helper scripts (see [below](#omarchy-scripts)) |
None are templates. Hyprland and Omarchy are not in `.chezmoidata/packages.yaml`. They come from the Omarchy install.
## Entry point
`hyprland.lua` loads Omarchy, then the personal files, then Omarchy's toggles:
```lua ~/.config/hypr/hyprland.lua
dofile((os.getenv("OMARCHY_PATH") or "/usr/share/omarchy") .. "/default/hypr/bootstrap.lua")
require("default.hypr.omarchy")
require("hypr.monitors")
require("hypr.input")
require("hypr.bindings")
require("hypr.looknfeel")
require("hypr.autostart")
require("default.hypr.toggles")
```
Omarchy's default bindings stay enabled. `omarchy_default_bindings` and `omarchy_preinstalled_bindings` are left commented. `bindings.lua` unbinds each default it replaces.
### Workspace rules
| Workspace | Monitor |
| --- | --- |
| 3 | `desc:Apple Computer Inc StudioDisplay 0x984262A7` (EDID match, survives connector changes) |
| 4 | `eDP-1` |
| 5 | `eDP-1` |
### Window rules
| Class | Rule |
| --- | --- |
| `^brave-www\.tldraw\.com__-Default$` | Workspace 3 |
| `^tldraw-offline$` | Workspace 5, fullscreen |
| `^com\.anthropic\.Claude$`, `^kitty-herdr$` | `special:scratchpad silent` |
| `^com\.fastmail\.Fastmail$` | `special:fastmail silent` |
| `^Beeper$` | `special:beeper silent` |
| `brave-(www\.)?youtube\.com__.*`, title not matching `.*Picture.?in.?[Pp]icture.*` | Float, pin, size 30% × 30% of the monitor, move to `10, monitor_h*0.7-10` (bottom-left corner) |
Scratch residents are placed `silent` so that [`omarchy-toggle-scratch`](#omarchy-toggle-scratch) decides when to reveal them. The YouTube rule matches both `youtube.com` and `www.youtube.com` because Chromium's class changes once a video plays. It excludes the picture-in-picture overlay so Omarchy's own PiP rule still applies. Its size and inset must stay in sync with [`omarchy-pip`](#omarchy-pip).
## Input
`input.lua`:
- `repeat_delay = 250` (Omarchy's value), `repeat_rate = 50` (raised from 40). The file's comments record that 200/50 felt too fast. The typing lag it was meant to fix came from the keyboard's firmware tap-hold handling and was fixed on the keyboard itself.
- Tablet and touch devices are pinned to one output each:
| Device | Output | Extra |
| --- | --- | --- |
| `elan9008:00-04f3:43c7-stylus` (Flow Z13 pen) | `eDP-1` | |
| `elan9008:00-04f3:43c7` (Flow Z13 touchscreen) | `eDP-1` | Taps land on the panel instead of the focused monitor |
| `wacom-intuos-bt-s-pen` | `desc:Apple Computer Inc StudioDisplay 0x984262A7` | `active_area_size = { 152, 85.5 }`, `active_area_position = { 0, 0 }` — a 16:9 band cropped from the 16:10 tablet |
The layout, touchpad, and gesture examples in the file are commented out. Palm rejection is done by a libinput quirks file in `/etc`, which chezmoi does not manage.
## Look and feel
`looknfeel.lua` loads after the Omarchy theme's `hyprland.lua`, so its values override the theme's. The theme sets colors. This file sets everything else. The approach is flat windows by default, with blur only behind terminals.
| Setting | Value |
| --- | --- |
| `general.layout` | `master` (instead of Omarchy's dwindle; per-workspace layouts saved by `omarchy-hyprland-workspace-layout-toggle` still win) |
| `decoration.rounding` | 0 |
| `decoration.blur` | enabled; `size 5`, `passes 2`, `brightness 0.8`, `contrast 1.1`, `vibrancy 0.15`, `noise 0.01`, `new_optimizations`, `popups = false` |
| `decoration.shadow` | disabled |
| `group.groupbar.gradient_rounding` | 0 |
| Workspace animation | disabled (`hl.animation({ leaf = "workspaces", enabled = false })`) |
Blur must be enabled globally before any window can use it, so every window tagged `default-opacity` gets `no_blur = true`, and terminals get it back:
```lua ~/.config/hypr/looknfeel.lua
o.window({ tag = "default-opacity" }, { no_blur = true })
o.window(
"^(kitty(-.*)?|Alacritty|com\\.mitchellh\\.ghostty|foot|org\\.codeberg\\.dnkl\\.foot|wezterm)$",
{ no_blur = false }
)
```
Omarchy's stock fade (0.985 active / 0.96 inactive) is kept. Terminals get their transparency from kitty's `background_opacity 0.9` (see [kitty](/terminal/kitty)). The comments give the reason for limiting blur: the cyberdream and Spectra themes enabled rounding, blur, and shadow on every window, and that costs frame time on an iGPU driving the laptop panel plus a 5K display.
## Monitors
`monitors.lua`:
| Output | Mode | Position | Scale |
| --- | --- | --- | --- |
| any (fallback) | `preferred` | `auto` | 2 |
| `DP-4` (Apple Studio Display) | `5120x2880@60` | `0x0` | 2 |
| `eDP-1` (laptop) | `2560x1600@180` | `2560x0` | 2 |
`GDK_SCALE=2` is exported. The Studio Display sits left of the laptop panel. At scale 2 its logical width is 2560, so `eDP-1` starts at x = 2560.
## hyprsunset and portal
```ini ~/.config/hypr/hyprsunset.conf
profile {
time = 07:00
identity = true
}
```
The identity profile stops hyprsunset from tinting the screen. A commented 20:00 / 4000 K night profile and an `o.launch_on_start("hyprsunset")` hint remain in the file.
```ini ~/.config/hypr/xdph.conf
screencopy {
allow_token_by_default = true
custom_picker_binary = hyprland-preview-share-picker
}
```
Screen-share requests get a restore token by default and use `hyprland-preview-share-picker` as the source picker.
## .luarc.json
Configures lua-language-server for the Lua config: the library path is `/usr/share/hypr/stubs`, `checkThirdParty` is off, and `hl` and `o` are declared as globals.
`autostart.lua` contains only a commented `o.launch_on_start` example.
## Keybindings
Every binding defined in `bindings.lua`. Bindings not listed here are Omarchy defaults. The *Replaces* column gives the Omarchy default that was unbound first.
### Windows
| Keys | Action | Replaces |
| --- | --- | --- |
| `SUPER + Q` | Close window | same (made explicit) |
| `SUPER + W` | `omarchy-toggle-layout` — flip workspace layout | close window |
| `SUPER + BACKSPACE` | Focus last window | toggle transparency |
| `SUPER + TAB` | Next window in the active workspace | next workspace |
| `SUPER + SHIFT + TAB` | Previous window in the active workspace | previous workspace |
| `SUPER + H` / `J` / `K` / `L` | Focus left / down / up / right | `J`: toggle split, `K`: keybindings menu, `L`: toggle layout |
| `SUPER + D` | Toggle split (dwindle `togglesplit`) | — |
| `SUPER + SHIFT + H` | `omarchy-smart-move l` — move/merge left; floating: bottom-left corner | — |
| `SUPER + SHIFT + J` | `omarchy-smart-move d` — move/merge down; floating: top-left | — |
| `SUPER + SHIFT + K` | `omarchy-smart-move u` — move/merge up; floating: top-right | — |
| `SUPER + SHIFT + L` | `omarchy-smart-move r` — move/merge right; floating: bottom-right | — |
| `SUPER + SHIFT + T` | `omarchy-pip` — toggle picture-in-picture | — |
`SUPER + TAB` walks a ring built only from the active workspace's mapped windows, ordered by `stable_id`. Grouped windows stay in the ring and hidden ones are skipped. It raises the focused window afterwards. It avoids `cyclenext`, which would pull in a hidden scratchpad. `binds.movefocus_cycles_groupfirst = true` makes `hjkl` focus step through a group's tabs before leaving the group.
### Workspaces and monitors
| Keys | Action | Replaces |
| --- | --- | --- |
| `SUPER + CTRL + J` | Next workspace (`e+1`) | — |
| `SUPER + CTRL + K` | Previous workspace (`e-1`) | Herdr keybindings |
| `ALT + Q` / `W` / `E` / `R` / `T` | Switch to workspace 1 / 2 / 3 / 4 / 5 | — |
| `ALT + SHIFT + Q` / `W` / `E` / `R` / `T` | Move window to workspace 1 / 2 / 3 / 4 / 5 | — |
| `SUPER + CTRL + S` | `omarchy-toggle-scratch scratchpad` | share menu |
| `SUPER + A` | `omarchy-toggle-scratch scratchpad` | — |
| `SUPER + O` | Focus next monitor | pop window out |
| `SUPER + SHIFT + O` | Move window to next monitor | Obsidian |
| `SUPER + CTRL + SHIFT + J` | Move window to next monitor | — |
| `SUPER + CTRL + SHIFT + K` | Move window to previous monitor | — |
| `SUPER + SHIFT + CTRL + ALT + W` | Workspace `name:w` | — |
| `SUPER + SHIFT + CTRL + ALT + H` | Workspace `name:h` | — |
| `SUPER + CTRL + ALT + X` | Workspace `name:x` | — |
Named workspaces replace niri-dynamic-workspaces. `SUPER + ALT + S` (send to scratchpad) keeps its Omarchy default.
### Sizing
| Keys | Action |
| --- | --- |
| `SUPER + R` | `omarchy-cycle-window-size` — next preset size |
| `SUPER + SHIFT + R` | `omarchy-cycle-window-size prev` |
| `ALT + H` | Width −100 px |
| `ALT + L` | Width +100 px |
| `ALT + K` | Height −100 px |
| `ALT + J` | Height +100 px |
| `SUPER + ALT + C` | Fit active column (scrolling layout only) |
| `SUPER + SHIFT + ALT + C` | Fit visible columns (scrolling layout only) |
### Applications
| Keys | Action | Replaces |
| --- | --- | --- |
| `SUPER + CTRL + A` | Claude: `omarchy-launch-or-focus claude "gtk-launch com.anthropic.Claude"` | audio panel |
| `SUPER + CTRL + G` | Gemini: `omarchy-launch-or-focus gemini "gtk-launch gemini-desktop"` | — |
| `SUPER + CTRL + ALT + H` | `omarchy-cycle ai` | — |
| `SUPER + CTRL + ALT + J` | `omarchy-cycle-terminal` | — |
| `SUPER + CTRL + ALT + K` | `omarchy-cycle-browser` | — |
| `SUPER + CTRL + ALT + G` | `omarchy-cycle ghostty` | — |
| `SUPER + CTRL + ALT + O` | `omarchy-cycle obsidian` | — |
| `SUPER + CTRL + ALT + F` | Nautilus: `omarchy-launch-or-focus nautilus "gtk-launch org.gnome.Nautilus"` | — |
| `SUPER + CTRL + ALT + E` | `omarchy-toggle-scratch fastmail` | — |
| `SUPER + CTRL + ALT + M` | `omarchy-toggle-scratch beeper` | — |
| `SUPER + CTRL + ALT + Y` | `omarchy-summon youtube` | web apps cycle |
| `SUPER + SHIFT + Y` | `omarchy-summon youtube` | YouTube desktop entry |
| `SUPER + CTRL + ALT + T` | tldraw offline: `omarchy-launch-or-focus tldraw-offline "gtk-launch tldraw-offline"` | show time |
| `SUPER + CTRL + ALT + W` | tldraw: `omarchy-launch-or-focus tldraw "gtk-launch TLDraw"` | toggle weather |
| `SUPER + CTRL + ALT + SPACE` | Sticky Notes Canvas via `gtk-launch io.github.faridjaff.StickyNotesCanvas` | — |
| `SUPER + SHIFT + G` | Goliath: `kitty --class=kitty-goliath --title=goliath --config ~/.config/kitty/goliath.conf` | Signal |
| `SUPER + SHIFT + F` | `flea --gui` | unbound first (default unnamed) |
| `SUPER + ALT + SHIFT + F` | `flea --gui "$(omarchy-cmd-terminal-cwd)"` — file manager in the terminal's cwd | unbound first (default unnamed) |
The two `flea` bindings and a rule tagging `com.thisisgm.flea.picker` as `+floating-window` sit between `flea --default` / `flea --picker` marker comments. The `flea` tool writes and removes those blocks itself.
### Screenshots, session, menus
| Keys | Action | Replaces |
| --- | --- | --- |
| `SUPER + S` | `omarchy-capture-screenshot region` | toggle scratchpad |
| `SUPER + SHIFT + S` | `omarchy-capture-screenshot windows` | Google Maps |
| `SUPER + SHIFT + ALT + S` | `omarchy-capture-screenshot fullscreen` | — |
| `SUPER + SHIFT + E` | `omarchy-menu toggle system` | Email |
| `SUPER + SHIFT + Q` | `omarchy-menu toggle system` | — |
| `SUPER + F24` | `voxtype record toggle --profile hermes` — dictation | — |
| `SUPER + SHIFT + SPACE` | Emoji picker (`omarchy-shell shell toggle omarchy.emojis`) | toggle top bar |
| `SUPER + P` | Clipboard manager (`omarchy-shell shell toggle omarchy.clipboard`) | pseudo window |
| `ALT + TAB` | ScrollOverview `toggle all` (only when the plugin is loaded) | Omarchy's two `ALT + TAB` binds |
`SUPER + SPACE` (Omarchy menu), `SUPER + ALT + SPACE` (apps menu), and `SUPER + SHIFT + RETURN` (browser) keep their Omarchy defaults.
### ScrollOverview plugin
When `hl.plugin.scrolloverview` is present, `bindings.lua` configures it:
| Option | Value |
| --- | --- |
| `gesture_distance` | 300 |
| `scale` | 0.5 |
| `workspace_gap` | 100 |
| `layout` | `vertical` |
| `wallpaper` | 2 (global and per-workspace) |
| `blur` | true |
| `shadow` | enabled, range 50 |
## omarchy scripts
The scripts live in `dot_local/bin/` as `executable_omarchy-*` and deploy to `~/.local/bin/`. Each one calls the Lua dispatcher first (`hyprctl dispatch "hl.dsp…"`) and falls back to the classic dispatcher name on older Hyprland.
### omarchy-cycle
```sh
omarchy-cycle <app|browser|brave|terminal|ai|ghostty|kitty|kitty-CLASS|webapp|webapp-NAME>
```
Focuses an app's window, cycles to the next one on repeated presses, or launches the app when none is open. Windows are matched on `class` or `initialClass` (case-insensitive) and sorted by workspace, then address. If the focused window is not in the set, focus goes to the first one. Launches use `setsid uwsm-app --`.
| Argument | Matches | Launches when none |
| --- | --- | --- |
| `webapp` | `^(webapp-\|chrome-)` | re-runs as `webapp-youtube` |
| `webapp-NAME` | `webapp-NAME` or `^chrome-.*NAME` | `gtk-launch` on the first `WebApp-NAME*.desktop` or `*NAME*.desktop` in `~/.local/share/applications` |
| `kitty` | `^kitty` | `kitty` |
| `kitty-CLASS` | `^kitty-CLASS$` | `kitty --class kitty-CLASS` (command from the empty `KITTY_CMDS` map, if set) |
| `ghostty` | `ghostty` | `ghostty` |
| `browser` | zen, brave, chromium, google-chrome, firefox, librewolf, vivaldi, microsoft-edge, helium — excluding `chrome-`/`webapp-` web apps | `omarchy-launch-browser` |
| `brave` | `^brave` (Brave Origin and its web apps) | `brave-origin` |
| `terminal` | kitty, alacritty, ghostty, foot, wezterm — excluding `^kitty-herdr$` (the scratchpad terminal) | `omarchy-launch-terminal` |
| `ai` | `claude\|hermes` | `hermes desktop` |
| anything else | `^APP$` | the argument as a command |
Bound to `SUPER + CTRL + ALT + H` (`ai`), `G` (`ghostty`), and `O` (`obsidian`).
### omarchy-cycle-browser / omarchy-cycle-terminal
One-line wrappers: `omarchy-cycle brave` and `omarchy-cycle terminal`. Bound to `SUPER + CTRL + ALT + K` and `SUPER + CTRL + ALT + J`.
### omarchy-cycle-window-size
```sh
omarchy-cycle-window-size [next|prev]
```
Steps the focused window through preset widths `0.333, 0.5, 0.667` of the monitor.
- **Scrolling layout (tiled):** sends `colresize +conf` / `-conf`, so the layout's own preset list is used.
- **Floating:** resizes to the fraction × monitor width and 0.8 × monitor height, then centers. The index is stored per window.
- **Dwindle/master (tiled):** sets `splitratio` to `2 × fraction`, mirrored when the window is on the right or bottom half, clamped to 0.1–1.9. The index is stored per workspace.
Indices are stored in `$XDG_STATE_HOME/omarchy/window-sizes/`. Bound to `SUPER + R` and `SUPER + SHIFT + R`.
### omarchy-pip
```sh
omarchy-pip [toggle|on] [address:0x...]
```
Picture-in-picture for any window. Floats and pins it, resizes it to 30% of the focused monitor on both axes, and places it in the bottom-left corner with a 10 px inset (matching `general:gaps_out`). `toggle` (the default) unpins and re-tiles a window that is already floating and pinned. `on` always applies the fit. Defaults to the active window. The dispatcher is called with `on`/`off` because `set` toggles on Hyprland 0.56. Bound to `SUPER + SHIFT + T`. `omarchy-summon` also calls it.
### omarchy-smart-move
```sh
omarchy-smart-move <l|r|u|d>
```
- **Tiled:** finds the nearest tiled, visible window in that direction that overlaps on the other axis. If that neighbor, or the focused window, is in a group, it runs a group-aware move (`movewindoworgroup`), which merges into or leaves a group. Otherwise it swaps with the neighbor.
- **Floating:** moves the window to a corner of its monitor without resizing, inside the reserved area (the bar) and inset 10 px. Corners go clockwise from bottom-left: `l` bottom-left, `d` top-left, `u` top-right, `r` bottom-right.
Bound to `SUPER + SHIFT + H/J/K/L`.
### omarchy-summon
```sh
omarchy-summon [youtube]
```
The only profile is `youtube`, which matches class `^brave-(www\.)?youtube\.com__.*$` and skips Picture-in-Picture titles. With no window open, it runs `gtk-launch YouTube`, and the window rule in `hyprland.lua` floats and positions it. With a window open, it moves the window to the focused monitor, runs `omarchy-pip on` to refit it (the two panels differ in logical size), and focuses it. Bound to `SUPER + CTRL + ALT + Y` and `SUPER + SHIFT + Y`.
### omarchy-toggle-layout
```sh
omarchy-toggle-layout [width]
```
Runs `omarchy-hyprland-workspace-layout-toggle`, then polls the workspace layout (6 × 50 ms). If it became `scrolling`, it runs `colresize all <width>` (default `0.75`). Dwindle needs no widths. Bound to `SUPER + W`.
### omarchy-toggle-scratch
```sh
omarchy-toggle-scratch [scratchpad|fastmail|beeper]
```
| Profile | Special workspace | Home monitor | Residents (class → launch) |
| --- | --- | --- | --- |
| `scratchpad` | `special:scratchpad` | Apple Studio Display | `com.anthropic.Claude` → `claude-desktop`; `kitty-herdr` → `kitty --class=kitty-herdr --config ~/.config/kitty/herdr.conf` |
| `fastmail` | `special:fastmail` | focused | `com.fastmail.Fastmail` → `gtk-launch fastmail` |
| `beeper` | `special:beeper` | focused | `Beeper` → `gtk-launch beeper` |
If the workspace is visible, the script hides it. If not, it launches any resident with no window there, waits up to 8 s for the first new window, and reveals the workspace on the home monitor (or the focused one if the home monitor is absent). Launching only missing residents avoids duplicate web-app windows. Bound to `SUPER + CTRL + S`, `SUPER + A`, `SUPER + CTRL + ALT + E`, and `SUPER + CTRL + ALT + M`.
## Related
- [niri](/desktop/niri): the original config these bindings mirror.
- [Waybar](/desktop/waybar): the bar used with this setup.
- [kitty](/terminal/kitty): the `herdr` and `goliath` kitty configs launched here.