sway-groups (swayg)
Group-aware workspace management for sway, with waybar integration via waybar-dynamic.
On "optional" bar integration. In principle
swaygis bar-agnostic — it manages state in sway, and the bar integration is a separate output channel. In practice waybar + waybar-dynamic is currently the only supported renderer, and without it there is no visible feedback on which groups exist or which workspaces the active group contains. So unless you plan to write your own bar module against the waybar-dynamic IPC, treat waybar as a required dependency.
Workspaces are organised into named groups. Each output has an active group, and only workspaces that belong to the active group (plus globals and user-unhidden ones) are shown to waybar and included in group-aware navigation. Workspace state is persisted in a small SQLite DB so switching back to a group restores its last focus.
Key concepts
- Workspace — a sway workspace (
1,2,3:Firefox, …). - Group — a named collection of workspaces. Each output has one active group at a time.
- Global workspace — visible in all groups (e.g. a persistent notes workspace).
- Hidden workspace — a workspace marked as hidden in a specific group.
By default hidden workspaces are invisible to waybar and skipped by
navigation, so you can declutter the bar during presentations or deep
work. Toggle
show_hidden_workspacesto reveal them with a.hiddenCSS class applied (combinable with.global,.focused, …).
Requirements
- Rust toolchain (stable, edition 2024)
- sway
- waybar + waybar-dynamic — see note above
Installation
cargo install --git (recommended right now)
No crates.io publishing needed:
Both binaries land in ~/.cargo/bin/. Make sure that's in your PATH.
cargo install --path (from a local clone)
Later: cargo install from crates.io
Once the crates are published (in order: sway-groups-config →
sway-groups-core → sway-groups-cli / sway-groups-daemon) it becomes:
systemd user unit for the daemon
cargo install cannot install non-binary files, so copy this unit once
into ~/.config/systemd/user/swayg-daemon.service:
[Unit]
Description=swayg daemon - track external sway workspace events
After=graphical-session.target
PartOf=graphical-session.target
[Service]
Type=simple
ExecStart=%h/.cargo/bin/swayg-daemon
Restart=on-failure
RestartSec=5
Environment=RUST_LOG=sway_groups_daemon=info
[Install]
WantedBy=graphical-session.target
# paste the unit above into ~/.config/systemd/user/swayg-daemon.service
The unit is WantedBy=graphical-session.target. For sway users, make sure
the target actually gets activated — the common recipe is a small session
target that sway starts. Create
~/.config/systemd/user/sway-session.target:
[Unit]
Description=sway compositor session
BindsTo=graphical-session.target
…and in your sway config:
exec systemctl --user --no-block start sway-session.target
waybar-dynamic integration
Install waybar-dynamic, then add two modules to your waybar config:
"cffi/swayg_groups": {
"module_path": "/path/to/libwaybar_dynamic.so",
"name": "swayg_groups"
},
"cffi/swayg_workspaces": {
"module_path": "/path/to/libwaybar_dynamic.so",
"name": "swayg_workspaces"
}
swayg pushes widget updates to these modules automatically after every
state-changing command. For per-state CSS classes see Bar styling
below.
First-time setup
This seeds the DB from sway's current workspaces, creates the default
group (0), and pushes initial bar widgets.
CLI overview
Every command is documented under --help:
High-level tour:
# Groups
# Workspace membership
# Hiding
# Navigation (group-aware — skips hidden unless show_hidden=true)
# Container moves
# State
# Global flags
swayg status sample:
show_hidden_workspaces = false
eDP-1: active group = "dev"
Visible: 1, 3
Inactive: 2, 4
Hidden: 5
Global: 0
- Visible — in the active group (plus globals) and not user-hidden
- Inactive — belongs to other groups; exists in sway on this output
- Hidden — user-hidden in the active group (only shown if
show_hidden_workspaces = true) - Global —
is_global = trueworkspaces
Configuration
swayg config dump prints the default TOML. Save to
~/.config/swayg/config.toml (or any path passed via --config or
SWAYG_CONFIG=) and edit.
Current sections:
[defaults]—default_group,default_workspace(used when orphan workspaces need a home, e.g. aftergroup delete --force)[bar.workspaces]/[bar.groups]— per-bar tuning: socket instance name, display mode (all|active|none),show_global,show_empty
Runtime DB flags (separate from the config file):
show_hidden_workspaces— toggled viaswayg workspace show-hidden
Bar styling
Widgets emitted by swayg carry CSS classes you can style. For a
waybar-dynamic module named swayg_workspaces:
}
}
}
}
}
}
/* Classes combine: .focused.global, .hidden.global.focused, etc. */
For the groups bar (swayg_groups):
}
}
}
Storage locations
- SQLite DB:
~/.local/share/swayg/swayg.db - Log files:
~/.local/share/swayg/swayg.YYYY-MM-DD(daily rotation) - Config (optional):
~/.config/swayg/config.toml - Daemon state:
/tmp/swayg-daemon-test.state(test daemon only)
Reset all state:
Architecture
Workspace crates:
| Crate | Role |
|---|---|
sway-groups-config |
TOML config schema + loader |
sway-groups-core |
DB entities, services, sway/waybar IPC |
sway-groups-cli → swayg |
User-facing CLI |
sway-groups-daemon → swayg-daemon |
Catches sway IPC events (new/empty workspace, etc.), keeps DB + bars in sync |
sway-groups-dummy-window |
Wayland dummy window for tests (publish = false) |
sway-groups-tests |
Integration tests against a live sway session (publish = false) |
Tables
workspaces,groups— main entitiesworkspace_groups— many-to-many membershiphidden_workspaces— presence-based(workspace_id, group_id)pairsoutputs— per-output state (including active group)settings— global runtime flags (key/value)focus_history,group_state,pending_workspace_events— internal state for nav-back and daemon coordination
Troubleshooting
RUST_LOG=debug swayg <cmd>— verbose tracing to stderr- Log files under
~/.local/share/swayg/ swayg repair— reconcile DB with sway (removes stale workspaces etc.)swayg sync --all --init-bars --init-bars-retries 20 --init-bars-delay-ms 500— afterswaymsg reload, retry pushing to waybar until its socket is back up
Development
The integration test suite spawns a test-mode daemon, temporarily stops
the production daemon, and tears everything down in Drop. All tests
must be able to run against a real sway socket.
License
MIT