306 lines
14 KiB
Markdown
306 lines
14 KiB
Markdown
# Panama :: Hyprland
|
||
|
||
A Hyprland desktop built to reproduce the GNOME + Forge setup it replaces, so
|
||
that muscle memory transfers unchanged.
|
||
|
||
## The one thing to know first
|
||
|
||
**Hyprland 0.55 replaced hyprlang with Lua.** `hyprland.conf` still loads on
|
||
0.56 as a legacy fallback and is removed in 0.57. Everything here is Lua.
|
||
|
||
The authoritative option reference for the installed build is on disk:
|
||
|
||
| File | What it is |
|
||
|---|---|
|
||
| `/usr/share/hypr/stubs/hl.meta.lua` | Generated type stub — the exact option list for this build |
|
||
| `/usr/share/hypr/hyprland.lua` | Upstream example config |
|
||
|
||
Wire the stub into your editor with `.luarc.json` in this directory for
|
||
autocomplete.
|
||
|
||
**The Hypr ecosystem tools did NOT move to Lua.** `hyprlock.conf`,
|
||
`hypridle.conf`, `hyprpaper.conf` and `hyprtoolkit.conf` are still hyprlang.
|
||
Don't "fix" them.
|
||
|
||
## Layout
|
||
|
||
| File | Contents |
|
||
|---|---|
|
||
| `hyprland.lua` | Entry point. Each `require()` is its own error scope |
|
||
| `env.lua` | Environment. Note the uwsm caveat below |
|
||
| `monitors.lua` | DP-2 geometry, scaling, and the HDR decision |
|
||
| `looks.lua` | Colours, blur, glow, shadows, animations, VRR, scanout |
|
||
| `input.lua` | Keyboard/mouse. Click-to-focus, like GNOME |
|
||
| `rules.lua` | Window rules, gaming rules, layer rules for the shell |
|
||
| `keybinds.lua` | The full keymap |
|
||
| `autostart.lua` | Session startup |
|
||
| `overrides.lua` | Per-machine escape hatch, loaded last |
|
||
| `hyprlock.conf` / `hypridle.conf` / `hyprpaper.conf` / `hyprtoolkit.conf` | Ecosystem tools (hyprlang) |
|
||
|
||
Validate any change without leaving your session:
|
||
|
||
```sh
|
||
Hyprland --verify-config
|
||
```
|
||
|
||
## The look
|
||
|
||
Tokyo Night Moon, with two accents that come from the tmux theme:
|
||
|
||
| Token | Value | Role |
|
||
|---|---|---|
|
||
| `accent` | `#82aaff` | Carries every state meaning — focused, active, on |
|
||
| `accentSecondary` | `#b172b0` | Never used alone; only the far end of a gradient |
|
||
|
||
The signature is **the prism**: the two accents meeting. It appears in exactly
|
||
four places, and nowhere else —
|
||
|
||
1. A one-pixel hairline along the top edge of every glass surface (the bar,
|
||
dock, popovers), running blue on the left to orchid on the right and fading
|
||
out before the corners. See `quickshell/widgets/PrismEdge.qml`.
|
||
2. The focused window's border — `general.col.active_border`, blue→orchid at
|
||
115°. Unfocused windows get no colour at all, because the gradient only
|
||
means something if one window on screen is wearing it.
|
||
3. The active workspace pill in the bar.
|
||
4. Slider fills.
|
||
|
||
The restraint is the point. The pink stops being special the moment it's used
|
||
as a flat fill, so it never is.
|
||
|
||
### Typography
|
||
|
||
**Adwaita Sans for everything the user reads as text — no monospace in the UI.**
|
||
A monospaced clock or percentage reads as terminal output pasted into a panel,
|
||
which is the opposite of the intent.
|
||
|
||
The Nerd Font is still used, but only to draw **icon glyphs** — it is the
|
||
pragmatic alternative to freedesktop symbolic icons, which ship with a
|
||
hardcoded `#2e3436` fill that Qt (unlike GTK) will not recolour. Where a
|
||
themed freedesktop icon is wanted instead, `quickshell/widgets/ThemedIcon.qml`
|
||
paints a palette colour through the icon's alpha.
|
||
|
||
Anything whose digits change in place — the clock, the vitals percentages, the
|
||
recording timer, the selection readout — sets `font.features:
|
||
Theme.tabularFigures`. Tabular figures share one advance width, so the text
|
||
stops twitching as numbers tick without reaching for a monospaced face.
|
||
|
||
Window rounding is 18, matching the shell's popover radius, so a window and a
|
||
panel next to each other read as the same family of object.
|
||
|
||
**On animated gradient borders:** Hyprland can rotate the border gradient
|
||
continuously with `borderangle` + `style = "loop"`. Don't. The wiki is explicit
|
||
that it forces a full-refresh-rate repaint forever, even when no border is
|
||
visible — at 4500×3000 that is pure idle GPU burn. This config uses
|
||
`style = "once"`, so the gradient sweeps into place when a window takes focus
|
||
and then costs nothing.
|
||
|
||
## Session: use the uwsm one
|
||
|
||
Log in as **"Hyprland (uwsm-managed)"**, not plain "Hyprland".
|
||
|
||
`xdg-desktop-portal-hyprland`'s systemd unit requires `graphical-session.target`,
|
||
and Fedora ships no `hyprland-session.target`. Without uwsm that target never
|
||
activates and **screen sharing in OBS, Sunshine and Zoom silently fails.**
|
||
|
||
### The uwsm environment gotcha
|
||
|
||
`hl.env()` in `env.lua` exports into the *compositor's* environment, **not** the
|
||
systemd user manager. Anything started as a user unit — `vicinae.service`,
|
||
`hyprpaper.service`, `hyprpolkitagent.service`, `hypridle.service` — and every
|
||
app those launch will not see it.
|
||
|
||
That is why `~/.config/uwsm/env` and `~/.config/uwsm/env-hyprland` exist and
|
||
duplicate the important variables. Keep them in sync with `env.lua`; `env.lua`
|
||
remains the fallback for the plain session.
|
||
|
||
## HDR — read this before turning it on
|
||
|
||
GNOME ran this panel in bt2100/HDR. This config deliberately does not.
|
||
|
||
On Hyprland 0.56.x, `cm = "hdr"` currently breaks **screencopy**: `grim`
|
||
screenshots come back empty, and OBS/Sunshine capture and hyprlock's blurred
|
||
background go with them. Root cause is still open upstream.
|
||
|
||
So the desktop runs SDR at 10-bit (`cm = "auto"`) and HDR is handed to
|
||
fullscreen games only, via `render.cm_auto_hdr = 1` in `looks.lua`. Games get
|
||
HDR; screenshots keep working.
|
||
|
||
To try full-time HDR anyway, `overrides.lua` has the block and the ordered list
|
||
of workarounds to reach for when things break.
|
||
|
||
## Keymap
|
||
|
||
The mental model is unchanged from Forge:
|
||
|
||
- **SUPER** acts on windows
|
||
- **ALT** acts on workspaces
|
||
- **SUPER + CTRL** changes layout structure
|
||
|
||
### Windows
|
||
| Key | Action |
|
||
|---|---|
|
||
| `SUPER + H/J/K/L` (or arrows) | Focus |
|
||
| `SUPER + SHIFT + H/J/K/L` | Move window |
|
||
| `SUPER + CTRL + H/J/K/L` | Swap window |
|
||
| `SUPER + SHIFT + Y/O` · `B/M` | Wider · narrower |
|
||
| `SUPER + SHIFT + I/U` · `P/N` | Taller · shorter |
|
||
| `SUPER + [` / `]` / `=` | Shrink / expand / reset split |
|
||
| `SUPER + Q` | Close |
|
||
| `SUPER + U` | Fullscreen |
|
||
| `SUPER + CTRL + C` | Toggle float |
|
||
| `SUPER + CTRL + SHIFT + C` | Pin (nearest thing to Forge's "always float") |
|
||
| `SUPER + CTRL + G` / `Z` / `V` | Toggle split / preselect right / preselect down |
|
||
| `SUPER + Tab` / `SHIFT + Tab` | Cycle windows |
|
||
| `SUPER + SHIFT + grave` | Last window |
|
||
| `SUPER + X` / `SUPER + SHIFT + X` | Restore scratchpad / minimise to scratchpad |
|
||
|
||
### Workspaces (dynamic, like GNOME)
|
||
| Key | Action |
|
||
|---|---|
|
||
| `ALT + H` / `ALT + L` | Previous / next workspace |
|
||
| `ALT + SHIFT + H/L` | Move window to previous / next |
|
||
| `ALT + 1..9`, `ALT + 0` | Jump to workspace |
|
||
| `ALT + SHIFT + 1..9` | Send window to workspace |
|
||
| `SUPER + scroll` | Change workspace |
|
||
|
||
### Launcher and shell
|
||
| Key | Action |
|
||
|---|---|
|
||
| `SUPER + A` / `R` / `Space` | Launcher (vicinae) — all three, pick your favourite |
|
||
| `SUPER + SHIFT + R` | Fallback launcher (wofi) if the shell is broken |
|
||
| `SUPER + V` | Clipboard history |
|
||
| `SUPER + .` | Emoji picker |
|
||
| `SUPER + S` | Quick settings |
|
||
| `SUPER + I` | Panama Settings |
|
||
| `SUPER + SHIFT + F` | Start or reveal focus session |
|
||
| `SUPER + B` | Notification centre |
|
||
| `SUPER + grave` | Workspace overview |
|
||
| `Print` | Screenshot / record picker |
|
||
| `SHIFT` / `ALT + Print` | Screenshot screen / window immediately |
|
||
| `SUPER + SHIFT + S` | Screen Intelligence — read text and codes from a selection |
|
||
| `SUPER + SHIFT + P` | Colour picker |
|
||
| `CTRL + ALT + L` | Lock (SUPER+L is "focus right") |
|
||
| `CTRL + ALT + Delete` | Power menu |
|
||
|
||
### Apps
|
||
`SUPER + T` terminal · `N` neovim · `W` browser · `F` files · `C` calculator ·
|
||
`E` mail · `I` Panama Settings · `CTRL + SHIFT + Esc` system monitor. GNOME
|
||
Settings remains searchable in Vicinae for hardware and account panels.
|
||
|
||
## Calendar and notifications
|
||
|
||
Click the clock to open the two-column Daybook on **Agenda**. The month grid,
|
||
today's schedule, Up Next, weather, and conditional media controls stay visible
|
||
together. Its right pane has three first-class pages:
|
||
|
||
- **Agenda** — the selected day's calendar.
|
||
- **Ongoing** — Focus, Caffeine, recording, screen sharing, camera, and
|
||
microphone activity, visible until each state ends.
|
||
- **Notifications** — grouped history, Do Not Disturb, and clear actions.
|
||
|
||
`SUPER + B` opens Notifications directly. Switching pages preserves the
|
||
selected date and visible month. Brief completed events such as screenshots and
|
||
device changes remain in Signal Glass instead of accumulating in Ongoing.
|
||
|
||
Calendar data comes from Evolution Data Server, so the Google, iCloud,
|
||
Nextcloud, and local calendars already configured for GNOME remain the source
|
||
of truth. Panama never stores account credentials. Use GNOME Calendar or Online
|
||
Accounts to add and manage accounts, and click an event in Daybook to hand off
|
||
editing and full detail to GNOME Calendar.
|
||
|
||
The right side of the bar shows a small calendar capsule only from 15 minutes
|
||
before a timed event until 5 minutes after it starts. It displays relative time
|
||
(`12m` or `Now`); the event title is available on hover but otherwise stays out
|
||
of the bar. All-day events never trigger the capsule.
|
||
|
||
## Control Center
|
||
|
||
Click the right-side status cluster or press `SUPER + S` for Panama's Control
|
||
Center. It is attached directly beneath the bar and keeps Wi-Fi, Bluetooth,
|
||
Caffeine, Night Light, Focus, audio input/output, user, settings, and power in
|
||
one place. Home and Phone continue the same surface rather than opening extra
|
||
dashboard windows.
|
||
|
||
Home shows the first four selected favourites at rest and every selected light
|
||
when expanded. Use **Panama Settings → Home & Phone** to choose favourites,
|
||
set Panama-only aliases, and arrange their order. Dragging a brightness control
|
||
only previews the value; releasing it sends one brightness request. A normal
|
||
power toggle leaves Home Assistant responsible for restoring its previous
|
||
level.
|
||
|
||
Credentials stay private in the gitignored `config/bash/env` file, with the
|
||
existing GNOME extension and Secret Service setup retained as a compatibility
|
||
fallback. Favourites, aliases, and order live in Quickshell state. No shell
|
||
restart is required after changing credentials; close and reopen Control Center
|
||
to refresh. If Home Assistant is offline, the last known values stay visible
|
||
with a stale-state label and Retry action.
|
||
|
||
Phone uses KDE Connect for the capabilities the paired iPhone actually
|
||
advertises: Send File, Send Clipboard, and Ring. The device remains visible
|
||
while iOS suspends KDE Connect, but those actions stay disabled until it
|
||
reconnects. Messages opens BlueBubbles independently of KDE Connect. No battery
|
||
percentage is invented when iOS reports none. Active file sends appear in
|
||
Daybook's Ongoing page; successful sends become a quiet recent exchange.
|
||
|
||
## Screen Intelligence
|
||
|
||
`SUPER + SHIFT + S` opens the capture picker directly in Selection + Read
|
||
mode. Read is also available beside Screenshot and Record under `Print`, and
|
||
works on a whole display, a window, or a region. Tesseract recognizes English
|
||
text locally; ZBar recognizes QR codes and barcodes. The result sheet can copy
|
||
text, search it, translate it, or open a detected web address. No pixels leave
|
||
the workstation unless one of those explicit network actions is selected.
|
||
|
||
## Deliberate deviations from GNOME
|
||
|
||
These are the places a 1:1 port was impossible, and what was done instead:
|
||
|
||
- **Per-edge resize.** Forge resized one named edge; Hyprland resizes along an
|
||
axis and lets the layout pick the edge. The eight Forge keys collapse to four
|
||
behaviours, keeping the horizontal/vertical and grow/shrink pairing.
|
||
- **Overview on `SUPER + grave`, not a bare SUPER tap.** Tap-detection on a
|
||
modifier misfires when you're quick with SUPER combos.
|
||
- **Lock on `CTRL + ALT + L`.** `SUPER + L` is "focus right" in this keymap.
|
||
- **`SUPER + grave` was Forge's "cycle windows of same app"**, which Hyprland
|
||
has no equivalent for. "Last window" moved to `SUPER + SHIFT + grave`.
|
||
- **`gnome-control-center` is launched with `XDG_CURRENT_DESKTOP=GNOME`**,
|
||
because it hard-refuses to start otherwise. Panels backed by system services
|
||
work; panels backed by GNOME Shell (Displays, Keyboard Shortcuts,
|
||
Multitasking, Appearance) are inert — Hyprland owns those now.
|
||
|
||
## Gaming
|
||
|
||
`content = "game"` on the window rules in `rules.lua` is the keystone — it is
|
||
what `misc.vrr = 3`, `render.direct_scanout = 2` and `cursor.no_break_fs_vrr = 2`
|
||
key off. Games additionally get blur, animation, shadow and dim disabled, plus
|
||
`immediate` for tearing.
|
||
|
||
Tearing only takes effect when the game is fullscreen and the only thing on
|
||
screen — no bar, no notifications.
|
||
|
||
## Do not install a notification daemon
|
||
|
||
`mako`, `dunst` and `SwayNotificationCenter` all register
|
||
`Name=org.freedesktop.Notifications` for D-Bus activation. Installing any of
|
||
them creates a startup race against Quickshell's notification server, and
|
||
whoever wins keeps the name. If one gets installed as a dependency,
|
||
`systemctl --user mask <name>.service`.
|
||
|
||
## Troubleshooting
|
||
|
||
**Nothing autostarts** — check `systemctl --user status hyprland-session.target`
|
||
and `systemctl --user show-environment | grep WAYLAND_DISPLAY`. An empty
|
||
environment means uwsm didn't export it and units with
|
||
`ConditionEnvironment=WAYLAND_DISPLAY` silently skip.
|
||
|
||
**Screen sharing missing** — `busctl --user list | grep impl.portal` should show
|
||
`xdg-desktop-portal-hyprland`.
|
||
|
||
**Qt apps look wrong** — `QT_QPA_PLATFORMTHEME` must be `gtk3`. It is a single
|
||
value, not a list: Qt splits on `:` and uses only the first token.
|
||
|
||
**Wrong GPU / won't start** — `AQ_DRM_DEVICES` points at
|
||
`/dev/dri/amd-dgpu`, created by `config/copy/etc/udev/rules.d/99-panama-gpu.rules`.
|
||
Both `env.lua` and `uwsm/env-hyprland` guard on the symlink existing, so a
|
||
missing rule degrades to "let aquamarine choose" rather than failing to start.
|