nwg-dock

August 15, 2026 · View on GitHub

crates.io License: MIT

A macOS-style dock for Hyprland and Sway, written in Rust.

Renamed from nwg-dock-hyprland. The Rust port supports both Hyprland and Sway through one binary (Compositor trait + runtime --wm auto-detection), so the compositor-specific name didn't fit anymore. Existing users: make install installs a nwg-dock-hyprlandnwg-dock symlink so your exec-once = nwg-dock-hyprland … autostart line keeps working. See Migrating from nwg-dock-hyprland below.

Ported from nwg-piotr/nwg-dock-hyprland (Hyprland-only Go) and informed by nwg-piotr/nwg-dock (Sway-only Go), with enhancements the Go versions don't have.

Features

  • Multi-monitor — dock appears on all monitors simultaneously
  • Multi-compositor — Hyprland and Sway via auto-detection (override with --wm)
  • Content-width — floats centered at screen edge, sized to its icons
  • Auto-hide — compositor IPC cursor tracking with configurable timeout
  • Drag-to-reorder — drag any pinned icon (running or not) to rearrange
  • Drag-to-remove — drag an icon off the dock to unpin it (like macOS)
  • Dock settings menu — right-click dock background to lock/unlock arrangement
  • Configurable opacity--opacity 0-100 for translucent or opaque dock
  • Right-click menus — pin/unpin, close, toggle floating, fullscreen, move to workspace
  • Launch animation — optional bounce animation on dock icons while an app is starting (--launch-animation)
  • Workspace switcher — optional pill-button row between pinned and tasks for jumping workspaces (--ws, default off)
  • Middle-click — launch new instance of any running app
  • Monitor hotplug — dock windows reconcile automatically when monitors are added/removed
  • Rotated/scaled monitors — cursor tracking works correctly with portrait and scaled displays
  • Icon scaling — icons shrink automatically when many apps are open
  • Instant pin sync — inotify-based, shared with nwg-drawer
  • Kill-proof — ignores compositor close requests (Hyprland killactive / Super+Q) so the dock can't be accidentally closed; use make stop or pkill -f nwg-dock to stop it intentionally
  • Go flag compatibility — accepts original Go nwg-dock-hyprland flag names

Install

Requirements

  • Rust 1.97 or later (pinned in rust-toolchain.toml; rustup picks it up automatically)
  • GTK4 and gtk4-layer-shell system libraries
  • A supported compositor: Hyprland or Sway — on other Wayland compositors (Niri, river, etc.) the dock starts in degraded mode: pinned apps still render and click-to-launch still works, but live features (auto-hide, workspace switcher, event-driven rebuilds) are disabled. A warning logs to stderr/journal so you know you're running degraded.

Install system dependencies

# Arch Linux
sudo pacman -S gtk4 gtk4-layer-shell

# Ubuntu/Debian
sudo apt install libgtk-4-dev libgtk4-layer-shell-dev

# Fedora
sudo dnf install gtk4-devel gtk4-layer-shell-devel
cargo install nwg-dock

Lands the binary at ~/.cargo/bin/nwg-dock. The default style sheet is created on first run by copying from the system data dir if available, otherwise the dock falls back to its embedded GTK4 defaults — so the binary alone is enough to launch the dock; you only need make install if you want the bundled style.css and the nwg-dock-hyprland legacy symlink.

Migrating from nwg-dock-hyprland via cargo install: the legacy symlink alias is a make install feature only — cargo install ships the nwg-dock binary alone. Update your autostart line to nwg-dock … (drop the -hyprland suffix).

The Makefile install path drops the binary, the bundled style.css, and the nwg-dock-hyprland legacy symlink. Three invocations depending on where the binary should land:

Default — system-wide (needs sudo):

sudo make install

Writes:

  • nwg-dock/usr/local/bin/nwg-dock
  • Legacy symlink → /usr/local/bin/nwg-dock-hyprland (so old autostart lines keep working)
  • Data files → /usr/local/share/nwg-dock-hyprland/ (path kept on the Go-predecessor convention so existing users' ~/.config/nwg-dock-hyprland/style.css customizations keep resolving — a full-rename migration is planned for a later minor)

No-sudo, dev workflow (useful when working from a clone):

make install PREFIX=$HOME/.local BINDIR=$HOME/.cargo/bin

Distro-parity (matches Go upstream's /usr/bin exactly):

sudo make install PREFIX=/usr

Usage

# Basic — auto-hide, 48px icons, translucent
nwg-dock -d -i 48 --mb 10 --hide-timeout 400 --opacity 75

# With launch animation and drawer integration
nwg-dock -d -i 48 --mb 10 --hide-timeout 400 --opacity 75 --launch-animation -c "nwg-drawer --pb-auto"

# With workspace switcher row (between pinned and tasks)
nwg-dock -d -i 48 --mb 10 --ws --num-ws 5

# Force Sway backend (auto-detection is usually enough)
nwg-dock --wm sway

Configuration file

In addition to CLI flags, nwg-dock reads a TOML config file at:

$XDG_CONFIG_HOME/nwg-dock-hyprland/config.toml

(falling back to ~/.config/nwg-dock-hyprland/config.toml if XDG_CONFIG_HOME is unset). Override the path with --config <PATH>. The nwg-dock-hyprland directory name is shared with the existing style.css for continuity with the Go-era setup.

A commented example with every field documented is installed alongside the CSS:

cp /usr/local/share/nwg-dock-hyprland/config.example.toml \
   ~/.config/nwg-dock-hyprland/config.toml
$EDITOR ~/.config/nwg-dock-hyprland/config.toml

(Adjust the source prefix if you used make install PREFIX=$HOME/.local … — the example file ends up in $PREFIX/share/nwg-dock-hyprland/.)

Precedence: CLI flags > config file > built-in defaults. Anything you pass on the command line wins, regardless of what the file says — so --icon-size 32 on the autostart line still takes effect even if the file specifies icon-size = 64.

Hot-reload: Most fields apply immediately on save — the dock fires a desktop notification confirming the reload (or reporting a parse error). The following fields require the dock to be restarted to take effect:

  • multi, wm, autohide, resident, hotspot-layer, layer, exclusive, position, full

The dock surfaces a "change applies on next restart" notification when one of those is edited.

Inspect what's loaded:

nwg-dock --print-config

Dumps the currently-effective merged config (CLI + file + defaults) in TOML form — handy for verifying which value won. The output shows resolved values only, not per-field provenance; if you need to know whether a field came from the CLI vs the file, point --config at a non-existent path so the file layer is skipped:

nwg-dock --config /tmp/nope.toml --print-config

That snapshot reflects CLI + defaults only; diff it against nwg-dock --print-config (which loads your real file) to see which fields the file is overriding. Doesn't start the dock; safe to run alongside a running instance.

Schema: see data/nwg-dock-hyprland/config.example.toml for the full sectioned schema ([behavior], [layout], [appearance], [launcher], [filters]).

Example: shrink the autostart line. A typical autostart entry like:

exec-once = nwg-dock -d -i 48 --mb 10 --hide-timeout 400 --opacity 75 --launch-animation -c "nwg-drawer --opacity 88 --pb-auto"

becomes:

exec-once = nwg-dock -d -c "nwg-drawer --opacity 88 --pb-auto"

with the persistable bits moved into ~/.config/nwg-dock-hyprland/config.toml:

[appearance]
icon-size = 48
opacity = 75
launch-animation = true

[layout]
mb = 10

[behavior]
hide-timeout = 400

The -d (autohide) and -c (launcher command) stay on the CLI because launcher is invocation-specific and autohide is one of the restart-required fields — you might want to swap autohide on/off with a different exec-once line for testing.

Compositor setup

# Print Hyprland autostart snippets
make setup-hyprland

# Print Sway autostart snippets
make setup-sway

Hyprland autostart example

If you use uwsm for systemd-scoped session management (the typical Arch+Hyprland setup):

# ~/.config/hypr/autostart.conf
exec-once = uwsm-app -- nwg-dock -d -i 48 --mb 10 --hide-timeout 400 --opacity 75 --launch-animation -c "nwg-drawer --opacity 88 --pb-auto"

Without uwsm (Slackware, distros where uwsm isn't packaged, or by preference) — drop the uwsm-app -- prefix:

# ~/.config/hypr/autostart.conf
exec-once = nwg-dock -d -i 48 --mb 10 --hide-timeout 400 --opacity 75 --launch-animation -c "nwg-drawer --opacity 88 --pb-auto"

Hyprland Lua config (Omarchy 4.0 "Quattro" and other Lua setups)

Hyprland 0.55+ Lua configurations don't read autostart.conf. On Omarchy 4.0 the equivalent lives in ~/.config/hypr/autostart.lua:

-- ~/.config/hypr/autostart.lua
o.launch_on_start([[nwg-dock -d -i 48 --mb 10 --hide-timeout 400 --opacity 75 --launch-animation -c "nwg-drawer --opacity 88 --pb-auto"]])

On plain Lua setups without Omarchy's helpers, register the command on the start hook instead. This goes in a Lua file loaded by ~/.config/hypr/hyprland.lua (directly, or via a require(...) from it):

hl.on("hyprland.start", function()
  hl.exec_cmd([[nwg-dock -d -i 48 --mb 10 --hide-timeout 400 --opacity 75 --launch-animation -c "nwg-drawer --pb-auto"]])
end)

Migrating to Omarchy 4.0: the Quattro migration generates the new .lua config files but does not carry custom exec-once lines across from autostart.conf — after the upgrade the dock (and anything else you autostarted) silently stops launching until you re-add it in autostart.lua as above. The dock itself is fully functional on Lua sessions as of v0.6.0+ (window actions use the Lua dispatcher syntax automatically via nwg-common 0.7).

The dock has no runtime dependency on uwsm or systemd; the wrapper just buys you per-process cgroup tracking and clean teardown on logout if you want it.

Known issue: GTK4 crash on DPMS cycles (Hyprland ≥ 0.56)

GTK ≤ 4.22 has a bug in its Wayland dmabuf-feedback handler (gdk/wayland/gdkdmabuf-wayland.c munmaps the wrong pointer when the compositor re-sends zwp_linux_dmabuf_feedback_v1). Hyprland 0.56 started re-sending that feedback every time an output is disabled/re-enabled, so any GTK4 client — the dock included — can segfault in wl_display_dispatch_queue_pending after a DPMS off/on cycle. Whether a given cycle crashes depends on heap-allocation alignment, so it strikes intermittently.

Until a fixed GTK ships, launch the dock with the dmabuf protocol disabled:

# ~/.config/hypr/autostart.conf
exec-once = uwsm-app -- env GDK_WAYLAND_DISABLE=zwp_linux_dmabuf_v1 nwg-dock -d -i 48 --mb 10 --hide-timeout 400 --opacity 75 --launch-animation -c "nwg-drawer --opacity 88 --pb-auto"

The dock doesn't use dmabuf texture import or graphics offload, so the only effect of the switch is dodging the crash. The same prefix protects any other GTK4 layer-shell client that dies the same way after screen blanking.

Signal control

# Toggle visibility
pkill -f -35 nwg-dock     # SIGRTMIN+1

# Show
pkill -f -36 nwg-dock     # SIGRTMIN+2

# Hide
pkill -f -37 nwg-dock     # SIGRTMIN+3

Theming

The dock loads CSS from ~/.config/nwg-dock-hyprland/style.css (path kept for continuity with the Go predecessor). Changes are picked up instantly via live file-change detection — no restart or signal needed. Hot-reload follows the full @import graph, so theme managers like tinty or stylix work out of the box.

Override the path with -s /path/to/custom.css.

Three CSS layers are stacked, highest priority last:

  1. Embedded defaults — compact button sizing, indicator spacing, etc.
  2. Programmatic overrides--opacity and bounce animation keyframes
  3. Your CSS file — always wins

Selected classes available for overriding:

  • .dock-workspace-row — container for the workspace button row (when --ws is set)
  • .dock-workspace-button — individual workspace button
  • .dock-workspace-active — class added to the currently-focused workspace's button

base16 themes via tinty

tinty + tinted-nwg-dock templates retheme the dock live. See the tinted-nwg-dock README for setup; apply a theme with:

tinty apply base16-tokyo-night-dark

Migrating from nwg-dock-hyprland

If you're coming from either the Go nwg-dock-hyprland or an older Rust build where the binary was called nwg-dock-hyprland:

  • Installed via make install — nothing to do. The nwg-dock-hyprland symlink is installed alongside the new nwg-dock binary, so exec-once = nwg-dock-hyprland … keeps working.
  • Installed via cargo install — update your autostart to nwg-dock … (or invoke nwg-dock directly). cargo install doesn't create the symlink.

The preferred canonical command going forward is nwg-dock. The nwg-dock-hyprland symlink is deprecated and will be removed in a future minor release; CHANGELOGs will give advance notice.

Shared pin file

Pin state lives at ~/.cache/mac-dock-pinned, shared with nwg-drawer. Pin an app from either side (dock: right-click → Pin; drawer: right-click). Drag icons in the dock to reorder; drag off to unpin.

Contributing

PRs welcome. main is protected — open from a feature branch. Run make lint (fmt + clippy + test + deny + audit) locally before requesting review. CI runs the equivalent checks across separate workflows plus CodeRabbit.

User-visible PRs add a CHANGELOG bullet under ## [x.y.z] — Unreleased in CHANGELOG.md, following Keep a Changelog.

Deviations from Go nwg-dock-hyprland

  • Multi-compositor — Go version is Hyprland-only; Rust port supports both via nwg-common's Compositor trait.
  • Shared pin file — Go dock uses ~/.cache/nwg-dock-pinned; Rust port shares ~/.cache/mac-dock-pinned with the drawer for instant two-way sync.
  • Per-monitor windows — Go creates one window; Rust creates one per monitor for better multi-monitor support.
  • Smart rebuild — Go force-rebuilds on every active-window event; Rust rebuilds only when the client list or active window actually changes.
  • Drag-to-reorder — new feature not in the Go dock.
  • CLI flag naming — multi-word flags standardized to kebab-case. Multi-char Go short forms (-hd, -iw, -is) not available; use the long forms.
  • Fuzzy class matching — desktop file github-desktop vs compositor class github desktop are matched automatically.
  • Workspace switcher defaults OFF. The Go dock shows the workspace button row by default and provides -nows to hide it; we default the row OFF and provide --ws to enable. Existing dock users updating across this version line see no UI change unless they opt in.

Credits

Ported from nwg-piotr/nwg-dock-hyprland (MIT), informed by nwg-piotr/nwg-dock (MIT).

License

MIT. See LICENSE.