Arch Linux desktop configuration for Hyprland (Wayland), managed with GNU Stow.
| Component | Role |
|---|---|
| Hyprland | Wayland compositor, launched by start-hyprland |
| systemd user units | hyprland-session.target binds the session to graphical-session.target |
| EWW | Top bar — workspaces, clock, system info, input method |
| Kitty | GPU-accelerated terminal |
| tmux + zsh | Multiplexer + shell (Powerlevel10k prompt) |
| Yazi | TUI file manager (used for wallpaper picker) |
| Fuzzel | App launcher |
| Mako | Notification daemon |
| fcitx5 | Input method (Rime for Chinese, Mozc for Japanese) |
| awww + Matugen | Wallpaper setter + Material You color generation |
| mise | Dev tool version manager |
| fastfetch | System info display |
| eza | Modern ls with icons and git status |
| bat | cat with syntax highlighting |
| ripgrep | Fast recursive search |
| delta | Git diff viewer with syntax highlighting |
| dust | Disk usage visualizer |
| duf | Disk free viewer |
| btop | System monitor |
| procs | Process viewer |
Each top-level directory is a stow package that mirrors $HOME:
dotfiles/
├── awww/.config/awww/ wallpaper manager + picker script
├── eww/.config/eww/ bar (yuck + scss + scripts)
├── fuzzel/.config/fuzzel/ app launcher
├── hypr/.config/hypr/ compositor (Lua config, modular sub-configs)
│ ├── hyprland.lua entry point — require()s the rest
│ ├── autostart.lua
│ ├── env.lua
│ ├── keybinds.lua
│ ├── rules.lua
│ ├── animations.lua
│ ├── input.lua
│ ├── monitors.lua
│ ├── theme.lua ← generated by matugen
│ ├── hyprlock.conf hyprlock still uses hyprlang
│ └── hypridle.conf hypridle still uses hyprlang
├── kitty/.config/kitty/ terminal emulator
├── mako/.config/mako/ notifications
├── matugen/.config/matugen/ color generation templates
├── gtk-3.0/.config/gtk-3.0/ GTK 3 settings
├── gtk-4.0/.config/gtk-4.0/ GTK 4 settings
├── mise/.config/mise/ dev tool versions
├── scripts/.local/bin/ user scripts (dark mode, tmux-launch, etc.)
├── systemd/.config/systemd/user/ hyprland-session.target
├── tmux/.config/tmux/ terminal multiplexer
├── fastfetch/.config/fastfetch/ system info (config + logos/)
├── vim/.vim/vimrc editor
├── yazi/.config/yazi/ file manager
└── zsh/
├── .config/zsh/ shell (.zshrc, aliases, plugins, keybinds, p10k)
└── .zshrc stub that sets ZDOTDIR
Hyprland is configured in Lua, not hyprlang — hyprlang was deprecated as the
compositor's config format in Hyprland 0.55. hyprland.lua is the entry point and
require()s the other modules; each require() is its own scope, so an error in one
file no longer takes the rest of the config down with it.
Validate edits without touching the running session:
Hyprland --verify-config -c ~/.config/hypr/hyprland.luaHyprland only decides between hyprland.lua and a legacy hyprland.conf at startup,
so switching config formats needs a full re-login, not hyprctl reload.
The rest of the Hypr ecosystem (hyprlock, hypridle) still links libhyprlang and
keeps its .conf format — only the compositor moved to Lua.
hyprctl dispatchtakes Lua now. The legacy form most guides still use fails:hyprctl dispatch dpms off # error: ')' expected near 'off' hyprctl dispatch 'hl.dsp.dpms({ action = "off" })' # okThis bites any config that shells out to
hyprctl—hypridle.confin particular.
Super + Shift + Q runs hyprshutdown if it is installed and falls back to Hyprland's
exit() otherwise. A bare exit() yanks the compositor out from under its clients
instead of asking them to quit, which upstream advises against; hyprshutdown
(pacman -S hyprshutdown, optional) prompts them to exit first.
Hyprland is launched by /usr/bin/start-hyprland, which is only a watchdog wrapper — it
does not tell systemd that a graphical session exists. Left alone,
graphical-session.target never activates, and any user service bound to it silently
never starts, even after systemctl --user enable. hypridle.service and
hyprpolkitagent.service are both WantedBy=graphical-session.target, so both would be
affected.
The fix is the minimal hyprland-session.target from the Hyprland wiki (its stated
alternative to full uwsm, which it calls "for advanced users"). autostart.lua starts it
on hyprland.start and stops it on hyprland.shutdown; BindsTo +
PropagatesStopTo pull graphical-session.target up and down with it.
systemctl --user is-active graphical-session.target # expect: activeEnabled session services (they rely on the target above, and would silently never start without it):
systemctl --user enable hypridle hyprpolkitagentbattery-notify.timer checks the battery every minute and warns at 20% / 10% / 5%,
once per threshold per discharge cycle. It matters because swap here is zram, which
is RAM-backed and cannot be hibernated to, so UPower's critical action at 2% degrades
to a hard power off. In the bar, colour tracks charge level only — green from 50%,
yellow from 20%, red below — so a reading means the same on AC or off it; a plug
glyph signals AC instead. Those three hues are fixed in eww.scss, not matugen
output: a wallpaper-derived palette cannot guarantee a green or a yellow, and the
warning only reads if red is actually red.
hypridle locks via hyprlock at 30 min and blanks the display at 35 min; it does not
suspend. It reaches hyprctl because Hyprland exports HYPRLAND_INSTANCE_SIGNATURE
and WAYLAND_DISPLAY into the systemd user environment on each login.
This is why autostart uses uwsm app -- without a uwsm-managed session: it puts each app
in its own systemd scope (journald logs, resource accounting) while
hyprland-session.target supplies the session target that uwsm would otherwise provide.
Env vars therefore stay in env.lua rather than moving to ~/.config/uwsm/env.
stow folds ~/.config/hypr into a symlink pointing straight at hypr/.config/hypr/
in this repo, and Hyprland reloads a config file the moment it changes. So any
git checkout, stash, rebase or merge that adds or removes files under hypr/
edits the live config dir, and Hyprland reacts within a second. Removing
hyprland.lua even briefly drops the session into emergency mode, where only three
binds survive: Super + Q (terminal), Super + R (launcher), Super + M (exit).
Suppress the reload for the duration, then reload once at the end:
hyprctl keyword misc:disable_autoreload true # runtime only, resets on next login
# ... git checkout / rebase / stash ...
hyprctl reloadIf a session is already in emergency mode, a plain hyprctl reload recovers it once
the files are back on disk — check with hyprctl configerrors and
hyprctl binds -j | jq length (50 is the full set).
Changing a wallpaper regenerates the entire color scheme across all apps:
wallpaper → awww (display) → matugen (extract colors) → template files → app reloads
Templates live in matugen/.config/matugen/templates/. Generated files (theme.lua, colors.scss, colors.ini, generated.conf, hyprlock-theme.conf) are gitignored — never edit them directly.
Drop images into ~/Pictures/Wallpapers/, then pick one of:
- Keyboard:
Super + Shift + W— opens a floating Kitty with the fuzzel picker - Picker script:
~/.config/awww/scripts/pick-wallpaper.sh - Direct:
~/.config/awww/scripts/wallpaper.sh /absolute/path/to/image.jpg
Either path runs awww img → matugen image -m dark → hyprctl reload, reloads Kitty (pkill -USR1 kitty), and restarts EWW.
# Core
sudo pacman -S hyprland uwsm xdg-desktop-portal-hyprland kitty tmux zsh fuzzel mako stow
# Bar and theming (AUR)
yay -S eww awww matugen
# Audio (PipeWire + WirePlumber)
sudo pacman -S pipewire wireplumber pipewire-pulse
# Bluetooth
sudo pacman -S bluez bluez-utils
sudo systemctl enable --now bluetooth
# Tools
sudo pacman -S yazi fzf zoxide jq grim slurp wl-copy brightnessctl networkmanager mpv glow
# CLI replacements
sudo pacman -S eza bat ripgrep git-delta dust duf btop procs fastfetch
# Input method
sudo pacman -S fcitx5 fcitx5-configtool fcitx5-gtk fcitx5-qt fcitx5-rime fcitx5-mozc
# Font
sudo pacman -S ttf-firacode-nerd
# Dev
sudo pacman -S misegit clone <repo-url> ~/dotfiles
cd ~/dotfiles
./setup.shOr stow individual packages:
stow hypr kitty zsh eww # etc.mkdir -p ~/.local/share/zsh
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ~/.local/share/zsh/powerlevel10k
git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions ~/.local/share/zsh/zsh-autosuggestions
git clone --depth=1 https://github.com/zsh-users/zsh-syntax-highlighting ~/.local/share/zsh/zsh-syntax-highlightingmkdir -p ~/Pictures/Wallpapers
touch ~/.config/hypr/theme.lua
set-dark-mode
fcitx5-configtool # add Rime and Mozc to input method list