# uwsm: a systemd session manager for standalone Wayland compositors

> uwsm turns a standalone Wayland compositor into a systemd managed session, with unit dependencies, environment cleanup, XDG autostart handling and plugins for sway, Hyprland, niri and other compositors.

**Vladimir-csp/uwsm** — Universal Wayland Session Manager

- Repository: https://github.com/Vladimir-csp/uwsm
- Stars: 1,178 · Forks: 42
- Language: Python
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/vladimir-csp-uwsm

## The problem uwsm solves for a standalone compositor

Launching a Wayland compositor on its own is simple. Making the surrounding session behave is not. A compositor started by hand has no login session bound to it, its environment variables are not registered with the systemd user manager, applications started alongside it are not grouped so they can be stopped together, and nothing arranges for the session to be torn down in the right order when the compositor exits.

uwsm, the Universal Wayland Session Manager, is a set of systemd units and helper scripts that fixes that. The README states the goal plainly: it makes systemd do most of the work and needs no extra daemon running in the background, apart from a tiny waitpid process and a simple shell signal handler in the lightest case.

The approach is worth understanding before the feature list. Rather than supervising a compositor itself, uwsm models the session as systemd units and dependencies, and uses the existing session scope as the anchor. The result is that a graphical session becomes a target with a known lifecycle, which is what the rest of the feature list is built on.

The project is MIT licensed, its primary language is Python for the helper scripts, and the last push to the repository was on 2026-10-15. The README describes the project as being in a stable phase with a slow refactoring, and warns that commits marked with an exclamation point, such as fix!: or feat!:, carry breaking changes.

## The unit graph: targets, slices and the login scope

uwsm binds itself to the structure systemd already defines for desktop environments. The README lists the units it attaches to:

```bash
graphical-session-pre.target
graphical-session.target
xdg-desktop-autostart.target
```

Alongside those, it adds three custom nested slices to hold applications and to make termination orderly:

```bash
app-graphical.slice
background-graphical.slice
session-graphical.slice
```

Applications that belong to the session are placed into app-graphical.slice, and that slice receives the stop action before the compositor is stopped, which is what prevents a background program from outliving the desktop it belongs to.

The units are named from common to specific, and the README describes templated units with specifiers plus high level name-.d drop-ins, so a per compositor override does not require editing a shared file. The names use the compositor as the instance:

```bash
wayland-wm-env@${compositor}.service.d/50_custom.conf
wayland-wm@${compositor}.service.d/50_custom.conf
```

The bi-directional binding is the part that is not obvious from the unit names. Using the waitpid utility, or a built-in shim, uwsm ties the lifetime of the login session, a session-N.scope unit, to the graphical session and back, so a session that ends takes the compositor with it and a compositor that dies takes the session with it.

## Environment preparation, export and cleanup

A session is mostly environment variables, and the part of uwsm that has the most moving pieces is the environment handling. It is described in the README as idempotently, in the author's phrase, best-effort-idempotently, handling of the environment.

On startup a specialised unit prepares the environment by either loading the environment context saved by the uwsm start command or by sourcing the shell profile itself. It then sources files from several directories:

```bash
uwsm/env
uwsm/env.d/*
uwsm/env-${desktop}
uwsm/env-${desktop}.d/*
```

Those files are read from each directory of the reversed XDG sequence, ${XDG_CONFIG_HOME}:${XDG_CONFIG_DIRS}:${XDG_DATA_DIRS}, in increasing priority, where ${desktop} is each entry of ${XDG_CURRENT_DESKTOP} lowercased.

What makes this more than a shell script is what happens next. The difference between the environment before and after preparation is exported into the systemd user manager, and the same variables are unset on shutdown. Which variables are exported is decided algorithmically, by comparing the environment before and after preparation and combining that with predefined lists that can be configured, plus variables exported manually by the uwsm finalize action.

The cleanup step has a known asymmetry. The reference D-Bus implementation does not allow unsetting variables, so a best effort cleanup empties them instead. The README is explicit that the only way to fully clear the separate environment of the reference D-Bus daemon is:

```bash
loginctl terminate-user ""
```

That is why the README recommends dbus-broker, which reuses the systemd activation environment and therefore allows a proper cleanup.

## Compositor plugins and desktop entry selection

Behaviour that differs between compositors is handled by plugins, and the README lists the ones currently included: sway, wayfire, labwc, hyprland, niri and mango. LXQt variants of the same compositors are provided separately by the experimental LXQt Universal Wayland Session project.

A compositor can also be started from a desktop entry taken from the wayland-sessions directory in the XDG data hierarchy. The entry acts as the compositor instance ID, and these fields are read from it: Exec provides the argument list, DesktopNames feeds XDG_CURRENT_DESKTOP and XDG_SESSION_DESKTOP, and Name and Comment provide the unit Description. Entries can be overridden, masked or added under ${XDG_DATA_HOME}/wayland-sessions/.

There is an optional interactive selector, and it requires whiptail. The chosen compositor is saved in ${XDG_CONFIG_HOME}/uwsm/default-id, and the default is read from there, falling back through ${XDG_CONFIG_DIRS} and ${XDG_DATA_DIRS}. Desktop entry actions are supported, and a session can be launched through a desktop entry by a login or display manager.

Autostart applications are handled through the same unit system. XDG autostart services become app-*@autostart.service units inside app-graphical.slice, and the whole group can be controlled at once:

```bash
wayland-session-xdg-autostart@${compositor}.target
```

Stopping and starting that target controls the autostart set without touching the compositor itself.

## What v0.26.0 changed about session variables

The README calls out a specific change in v0.26.0 that affects anyone debugging environment problems. A set of session specific variables now gets special treatment:

```bash
XDG_SEAT
XDG_SEAT_PATH
XDG_SESSION_ID
XDG_SESSION_PATH
XDG_VTNR
```

These are no longer exported to activation environments, because they are tied to one session. Instead they are fed to the compositor unit through an EnvironmentFile= directive pointing at a runtime file, and the uwsm app command passes them to launched services automatically.

The same release moved shell code out of the static unit files and into separate files in the libexec directory, and made the static unit files permanent. It also changed how XDG_CURRENT_DESKTOP is filled: the compositor executable basename is only auto-added when other sources came up empty. That last point matters for scripts that branch on XDG_CURRENT_DESKTOP, because the compositor's executable basename now counts only when nothing else contributed.

The repository has no tagged GitHub releases, so the README and the commit history are where the behaviour of a given version is recorded.

## Building uwsm and applying a first override

The repository is a Meson project, with meson.build and meson.options at the top level, and it ships Debian packaging under debian/ along with a build-deb.sh script. The Python helper scripts are configured through pyproject.toml, where the type checker is set to Python 3.10 on Linux with extraPaths pointing at build/uwsm-generated, which is where generated helper code lands. The README does not spell out a single install command, so the build system and the packaging directory are what you work from.

A first practical override, and the mechanism the README highlights, is a drop-in for the compositor units. Create the directory for your compositor and place a custom file in it:

```bash
wayland-wm@${compositor}.service.d/50_custom.conf
```

Everything in the project follows that pattern of dropping a small file on top of a templated unit rather than editing a shipped unit, which is what lets the same session definition serve sway, Hyprland and niri with per compositor adjustments in the uwsm-plugins/ directory.

The tree also holds a man/ directory, uwsm-libexec/ for the shell helpers that the v0.26.0 refactoring moved out of the units, example-units/ for templates to copy, and a scripts/ directory.

## Where uwsm is the wrong tool

The first case is a desktop that already works. Distributions ship graphical-session.target integration, and a display manager that launches a Wayland session through its own session file already has a systemd unit graph. Adding uwsm there means taking over a session that was already managed, for the sake of features such as environment cleanup that a working setup may not need.

The second case is a compositor that uwsm knows nothing about. The plugin list is explicit, and an unlisted compositor gets generic behaviour only, which means no compositor specific handling of the seat, the output management or the shutdown sequence. Picking uwsm for a compositor the project has not written a plugin for is choosing the harder path.

The third limitation is the environment cleanup asymmetry described above, and it only disappears if you accept the README's recommendation to run dbus-broker rather than the reference D-Bus daemon. That is a change to a system component that affects everything on the machine, not only the session, so it belongs in the decision rather than in a setup script.

The alternative to uwsm is to use systemd's own pre-defined desktop environment units directly, launching the compositor as a user unit under graphical-session.target and relying on systemd-xdg-autostart-generator for autostart. The difference is scope. That approach gives you the unit graph and autostart handling without uwsm's environment diffing, its bi-directional login binding or its compositor plugins, so the trade is between a wider set of behaviours and a smaller amount of machinery to understand.

## Conclusion

uwsm fits a machine where a standalone compositor is launched from a display manager or a TTY and the session has to be started, tracked and torn down reliably. It does not fit a desktop that already ships a working Wayland session or a user who wants to run a compositor without systemd. Verify first that your compositor has a plugin in uwsm-plugins/, because an unlisted compositor gets the generic behaviour only.

## FAQ

### What is uwsm and which compositors does it support?

uwsm is the Universal Wayland Session Manager, a set of systemd units and helpers that run a standalone Wayland compositor as a managed session. The README lists plugins for sway, wayfire, labwc, hyprland, niri and mango.

### Why does the uwsm README recommend dbus-broker?

dbus-broker reuses the systemd activation environment instead of keeping a separate one, which lets uwsm unset variables on shutdown. The reference D-Bus implementation cannot unset, so those variables are emptied instead, and loginctl terminate-user "" is the only full cleanup.

### How do I override the command line of a compositor in uwsm?

Drop a file into the unit drop-in directory for your compositor, named wayland-wm@${compositor}.service.d/50_custom.conf, or the matching wayland-wm-env@ variant. The README states the units are named from common to specific and allow high level name-.d drop-ins.

### What changed in uwsm v0.26.0?

Session specific variables XDG_SEAT, XDG_SEAT_PATH, XDG_SESSION_ID, XDG_SESSION_PATH and XDG_VTNR are no longer exported to activation environments, and are instead passed to the compositor through an EnvironmentFile= runtime file. Static unit files became permanent, shell code moved to the libexec directory, and XDG_CURRENT_DESKTOP is only auto-filled from the compositor basename when other sources are empty.

## Sources

- [Issues](https://github.com/Vladimir-csp/uwsm/issues)
- [License: MIT](https://github.com/Vladimir-csp/uwsm/blob/master/LICENSE)
- [README](https://github.com/Vladimir-csp/uwsm/blob/master/README.md)
- [Vladimir-csp/uwsm on GitHub](https://github.com/Vladimir-csp/uwsm)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/vladimir-csp-uwsm
