niri-dynamic-workspaces 0.2.0

A dynamic workspace switcher for the niri Wayland compositor
niri-dynamic-workspaces-0.2.0 is not a library.

niri-dynamic-workspaces

niri-dynamic-workspaces

Install

Nix (flake)

# flake.nix inputs
niri-dynamic-workspaces.url = "github:nickolaj-jepsen/niri-dynamic-workspaces";

Build directly:

nix build github:nickolaj-jepsen/niri-dynamic-workspaces
nix run github:nickolaj-jepsen/niri-dynamic-workspaces

Home Manager module

# Add to your Home Manager imports
imports = [ inputs.niri-dynamic-workspaces.homeModules.default ];

# Enable
programs.niri-dynamic-workspaces = {
  enable = true;
  keybind = "Mod+D";              # default — open switcher
  deleteKeybind = "Mod+Ctrl+D";  # default — open delete overlay
  moveWindowKeybind = "Mod+Shift+D"; # default — open move-window overlay
  daemon = true;                  # default — start daemon at login
  settings = {
    general.workspace_prefix = "dyn-";
  };
};

This installs the package, adds a niri keybind, and writes the config file.

Cargo

Requires GTK4, gtk4-layer-shell, and pkg-config development headers.

cargo install --git https://github.com/nickolaj-jepsen/niri-dynamic-workspaces

Configuration

Config file: ~/.config/niri-dynamic-workspaces/config.toml

All fields are optional with sensible defaults.

[general]
workspace_prefix = "dyn-"          # prefix for dynamic workspace names
default_programs = ["kitty"]       # programs launched when creating any new workspace
auto_delete_empty = true           # daemon: auto-delete empty unfocused workspaces
layout = "qwerty"                  # keyboard layout for the overlay (see table below)

# [layout] options below are no longer used (kept for backwards compatibility).
# The overlay now displays a virtual keyboard layout instead of a card grid.
# max_columns = 4
# min_columns = 2
# max_windows_per_card = 4
# app_name_max_chars = 12
# window_title_max_chars = 18

[keybinds]
close = ["Escape", "Ctrl+c", "Ctrl+w", "Ctrl+q"]  # keys to dismiss the overlay

[workspace.a]                          # key: a-z or 0-9
name = "Browser"                       # optional display name shown on the key
programs = ["firefox", "slack"]        # programs launched on create (replaces defaults)
# Configured workspaces that don't exist yet appear as muted keys with a dashed border.

[workspace.b]
programs = ["kitty --title myterm"]    # arguments supported via whitespace splitting

[workspace.1]                          # digit workspaces work too
name = "Comms"
programs = ["slack", "discord"]

Available keyboard layouts

Layout Value
QWERTY qwerty
AZERTY azerty
QWERTZ qwertz
Dvorak dvorak
Colemak colemak

All layouts contain the same 36 keys (a–z, 0–9) arranged in the physical positions of each keyboard layout. The value is case-insensitive.

Usage

  • niri-dynamic-workspaces or niri-dynamic-workspaces switch — opens the switcher overlay (press key to switch/create)
  • niri-dynamic-workspaces delete — opens the delete overlay (press key to delete)
  • niri-dynamic-workspaces move-window — opens the move-window overlay (press key to move the focused window)
  • niri-dynamic-workspaces daemon — starts as a background daemon (no overlay shown)

All modes support toggle behavior: running the same command again closes the overlay.

Direct mode (no overlay)

Pass a workspace key to act immediately without opening the overlay:

niri-dynamic-workspaces switch a        # switch to / create dyn-a
niri-dynamic-workspaces delete a        # delete dyn-a
niri-dynamic-workspaces move-window a   # move focused window to dyn-a

Daemon mode

By default the overlay process starts fresh each time a keybind is pressed, which includes GTK and CSS initialization. For faster overlay display, start a background daemon at login:

spawn-at-startup "niri-dynamic-workspaces" "daemon"

The daemon keeps GTK initialized and subsequent switch/delete/move-window invocations are forwarded to it over D-Bus, skipping startup overhead.

The Home Manager module enables daemon mode by default. To disable it:

programs.niri-dynamic-workspaces.daemon = false;

Development

Enter the dev shell and build:

nix develop
cargo build

Lint and test:

cargo fmt -- --check   # check formatting
cargo clippy           # lint (clippy all + pedantic)
cargo test             # run unit tests