Troubleshooting
Contents: it will not start · the config did nothing · it died · no sound · windows behave oddly · monitors · nested runs · debug switches · tests
It will not start
“wlroots-0.20 was built without Xwayland support” — a configure-time failure.
fwm needs Xwayland at runtime for X11 clients; install xorg-xwayland and a
wlroots built with it.
wlroots version errors — fwm pins wlroots 0.20 exactly. The API breaks between minor versions, so the build refuses 0.21 rather than failing halfway with confusing errors.
“XDG_RUNTIME_DIR is unset” — wlroots needs it for the socket. Your login setup
should provide it; from a bare TTY, a session manager or elogind/seatd usually
does.
Nothing on screen, no error — run with FWM_DEBUG=1 and read the log. On a
TTY, redirect it: FWM_DEBUG=1 fwm > /tmp/fwm.log 2>&1.
The config did nothing
Look at the tray: a ⚠ pill means fwm read your file and did not like parts of it.
Click it (or bind show_errors) for the list — it names the section and the key.
Things that are easy to get wrong:
- A missing quote. TOML needs
"super+q" = "killclient", quotes on both sides. A[binds]section where every line is broken falls back to the built-in binds, which is why your custom keys may all be gone at once. - An unknown action. Reported at load time. The complete list is in Keybindings.
- A
[[rule]]with no matcher, or a[physics.<name>]profile that names no desktop: both are reported as useless rather than silently ignored. tick_rateis read once at startup — a reload will not change it.- You are looking at a
fwmctl setoverride.Super+Shift+R(orfwmctl reload) discards every override and goes back to the file. - The UI remembered something. The mass mode and the collision sound come from
~/.local/state/fwm/modes, which wins over the config because it was written by a click. Delete the file to go back. - The wallpaper is not the one in the config. Same thing:
~/.local/state/fwm/wallpaperholds the picker’s choice.
It died
fwm-session writes ~/.local/state/fwm/crash.log and brings fwm back, with the
applications from ~/.local/state/fwm/session relaunched on the desktops they
were on. Three failures inside a minute stop the loop rather than flapping in
front of you.
Windows that came back on the wrong desktop, or did not come back at all: an
application whose window belongs to a different process than the one launched
(some browsers, Electron apps) cannot be matched, and at most 64 are recorded. Set
[session] restore = "never" to switch the whole thing off.
No sound
Neither the visualiser nor the collision knock will ever start a sound server — libpulse would happily autospawn one, and a second daemon fighting the first for the card costs you your audio and not just the bars. So:
- Is a server running?
pactl infoorpw-cli info 0. If your daemon is autospawned by the first client that wants sound, there is none at login: fwm keeps looking and the bars appear when something starts playing. - Was fwm built with the libraries? The log says
audio: no sound server runningwhen there is none, and CMake prints[cava] capture backends: …at configure time. No line at all means it was built without both. - Collision sound specifically needs libpulse-simple. PipeWire boxes serve
that through pipewire-pulse. Without it the log says
sound: built without libpulse-simple. [sound] path— if the WAV cannot be read, the log says why (not 16-bit PCM or 32-bit float,no data chunk, …) and the built-in click plays instead, which sounds exactly like a working feature.
Windows behave oddly
A window will not move. It is pinned (Super+P), fullscreen, or the desktop
is in tiling or floating mode — in all of those, physics is not allowed to move
it. A [[rule]] may have pinned it; fwmctl windows shows pinned and
nocollide per window, which is the only way to see whether a rule took hold.
Windows pass through each other. Either nocollide is set on one of them, or
you are dragging the mouse faster than about three times max_throw_speed — see
Physics for exactly what that limit is and why it exists.
Windows will not fall. fwm starts in zero-g whatever [physics] gravity says;
Super+G cycles it on. The tray’s gravity icon is lit when it is.
Windows drift forever. That is zero-g with friction close to 1. Lower
[physics] friction, or turn gravity on.
A window sits half off the screen. A window larger than the play area cannot fit in it; the walls hold what they can. Resize or fullscreen it.
Everything is jittery under a spin or a drag. [effects] live = 0 puts
windows under an effect back on a periodic still frame, which is the better trade
on slower hardware. FWM_DEBUG_EFFECTS=1 prints frame timings.
Screen sharing
Discord, Zoom, OBS and browsers do not capture a Wayland screen themselves. They
ask a portal — xdg-desktop-portal — which hands the request to a backend that
knows the compositor. fwm implements wlr-screencopy-v1, and the backend that
speaks it is xdg-desktop-portal-wlr. Install it alongside pipewire, which
carries the frames:
# Void
sudo xbps-install -S xdg-desktop-portal-wlr
# Arch
sudo pacman -S xdg-desktop-portal-wlr
# Debian/Ubuntu, Fedora
sudo apt install xdg-desktop-portal-wlr # sudo dnf install xdg-desktop-portal-wlr
Then log out and back in. fwm sets XDG_CURRENT_DESKTOP=fwm:wlroots and pushes
its environment to the D-Bus session bus at startup, and install.sh puts a
fwm-portals.conf in place that routes capture to the wlr backend — the portal
needs all three, and it only picks them up on a fresh session.
The share dialog lists nothing — no monitor to pick. If the backend is
installed, this is almost always its chooser. xdg-desktop-portal-wlr asks
which source to share before it captures anything, and it asks by running an
external menu — it tries slurp, wmenu, wofi, rofi, bemenu, mew and
fuzzel in turn. With none of them installed it exhausts the list, logs
no output found, and closes the session, which arrives at the application as
an empty list rather than as an error. It does this even with a single monitor.
Either install a chooser (slurp is the usual one), or skip the question in
~/.config/xdg-desktop-portal-wlr/config:
[screencast]
chooser_type=none
output_name=DVI-D-1 # a name from `fwmctl outputs`
none shares output_name without asking. With two monitors you want the
chooser instead, so that you can pick between them.
The shared image is black, or nothing is offered at all. Check that the backend is installed and reachable:
busctl --user list | grep portal # xdg-desktop-portal + a backend
echo "$XDG_CURRENT_DESKTOP" # must contain fwm or wlroots
If XDG_CURRENT_DESKTOP is empty inside a running session, the compositor was
started in a way that bypassed its own startup — or the D-Bus bus predates it and
never got the update. dbus-update-activation-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP repairs it for the session in progress.
fwm: no D-Bus session bus in the log, and no portal anywhere. There is no
session bus to publish anything to. A display manager gives you one; starting
fwm by hand from a bare VT on a system without systemd’s user manager usually
does not. Wrap the session in one:
exec dbus-run-session fwm
fwm checks for the bus and skips the publish when there is none, rather than letting D-Bus go looking for one on its own — left to itself it asks the X server for the address, and the X server it would ask is fwm’s own Xwayland, which cannot answer a compositor that is still starting up.
The picker offers windows and only the whole screen works. xdg-desktop-portal-wlr captures outputs, not individual windows; fwm does not implement the foreign-toplevel capture path a per-window share would need. Share the screen and put the window on a desktop of its own.
Flatpak applications still see nothing. They need the portal and PipeWire
across the sandbox boundary. flatpak info --show-permissions com.discordapp.Discord
should show a pipewire socket; if it does not, grant it:
flatpak override --user --socket=pipewire com.discordapp.Discord.
Monitors
fwmctl outputs lists what fwm sees, including every mode each screen offers, and
fwmctl output <name> … changes one live — that is the whole of display
configuration, and it applies through the same code [[output]] uses.
A screen went dark and the pointer is on it. outputs_on turns everything
back on and needs no pointer and no visible monitor. output_off refuses to turn
off the last lit screen.
Names are what fwm logs at startup (output eDP-1: 1920x1080, scale 1.00) and
what [[output]] name expects.
Nested runs
fwm runs inside your session, which is how it is developed:
./dev.sh -h # what it can do
./dev.sh -n 2 -g 1 # two terminals, gravity on
Three traps, all learned the hard way:
FWM_SOCKETtargets whatever it points at. Afwmctlcall from your shell goes to the outer session unless you set it:FWM_SOCKET=/run/user/1000/fwm-wayland-1.sock fwmctl state. The socket name is in the inner instance’s log (Wayland socket: wayland-1).- Session restore can fork fwm inside fwm. A nested run reads the same state
file as your real session, so it may relaunch what your session recorded. Point
XDG_STATE_HOMEsomewhere temporary for anything experimental. - Keybinds cannot be tested nested. The outer compositor claims the
Supercombinations first. Run from a bare TTY for those, or useFWM_TEST_ACTION=<action>to fire one action a second after startup.
Debug switches
Environment variables, all read at startup:
| Variable | Does |
|---|---|
FWM_DEBUG=1 |
wlroots debug logging |
FWM_DEBUG_EFFECTS=1 |
frame timings for spin/wobble, once a second while one is running |
FWM_THEME_DEBUG=1 |
what the palette was derived from |
FWM_TEST_ACTION=<action> |
dispatch one action ~1.5s after startup |
FWM_TEST_GRAVITY=<scale> |
start with gravity on |
FWM_TEST_CAMERA=<n> |
start parked on desktop n |
FWM_TEST_CAVA=1, FWM_TEST_CAVA_MODE=<mode> |
the visualiser on synthetic audio, for a nested run with nothing playing |
FWM_TEST_ORBIT, FWM_TEST_ORBIT_DIST |
open the desktop strip in the wider view |
FWM_OPEN_PICKER=1, FWM_SHOW_HINTS=1 |
open the picker / hints at startup |
They exist because a compositor cannot be driven by a test harness: there is no
way to inject a keypress or move the pointer, so the code paths a bind reaches
have to be reachable some other way. dev.sh wraps most of them in flags.
Tests
cmake -S . -B build-test -DFWM_TESTS=ON
cmake --build build-test
ctest --test-dir build-test --output-on-failure
Ten suites, none of which need a compositor, a display or a sound card. What each covers is in Architecture.