Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

paw

A panda Wayland compositor and its desktop shell — a hard fork of dwl built on wlroots 0.20 and SceneFX 0.5, paired with a Quickshell-based shell that provides every piece of desktop UI.

⚠️ Pre-release

Developed and tested on Fedora 44 only; the prebuilt RPMs are Fedora 44, x86_64. Monitors, keybinds, window rules and compositor settings are all runtime configuration — nothing about your machine needs a recompile.

🤖 Built with AI

In the interest of transparency: paw was written with substantial help from AI coding assistants — Claude Code and Codex. This note is here because you deserve to know that before you run a compositor on your machine, not after.

Every line was reviewed, built, tested and run as a daily driver by a human, who is responsible for what ships. The regression tests, the sanitizer and static-analysis targets, and paw/SECURITY_AUDIT.md exist in no small part because of how it was written. Judge it on the code.

The two halves

paw/ The compositor. Shell-less by design: no bar, no menus, no tray. Keeps dwl's small readable core, plus a Niri-style scrolling layout, runtime configuration without recompiling, SceneFX visuals, and animations. Builds the paw binary.
pawshell/ The shell. Bar, launcher, clipboard, power menu, notifications, SNI tray, wallpaper and theme pickers, screenshots — all of it. A Quickshell config, so it is QML, not a compiled program.

They meet at a JSON-lines Unix socket. paw exports $PAW_SOCKET, streams full-state snapshots (tags, layouts, focus, windows per output), and accepts flat JSON commands. On the shell side the entire seam is one file, pawshell/services/Paw.qml.

Contents

Feature set

  • Quickshell-driven shell — bar, launcher, clipboard, power menu, notifications, SNI tray, wallpaper/theme pickers, and screenshots.
  • Scrolling layout — Niri-style horizontal columns, with one consume-or-expel key per direction to walk a window through the structure.
  • Tiled divider dragging — Super-right-drag moves the nearest shared dividers instead of floating the window.
  • Runtime configuration — settings, keybinds, and themes all apply live, with no recompile.
  • Gaming — tearing page flips for games that ask and content-type hints, overridable per application; adaptive sync, overridable per monitor.
  • SceneFX visuals — rounded corners, shadows, blur, inactive-window opacity, and animations. Optional at build time.

⚠️ This is a hard fork

The codebase has diverged structurally from upstream dwl. dwl and dwl-patches patches will not apply cleanly and are not supported here.

Install

Every runtime dependency is in Fedora proper — no extra repositories needed.

Quickshell version. paw is developed against Quickshell 0.3.0, which currently comes from the lionheartp/Hyprland COPR; Fedora ships 0.2.1, which is untested here. If the shell misbehaves on Fedora's build, that's the likely cause:

sudo dnf copr enable lionheartp/Hyprland && sudo dnf upgrade quickshell

From a release (Fedora 44, x86_64)

sudo dnf install \
  https://github.com/CodyWan1990/paw/releases/download/v0.9.1/scenefx-0.5.0-1.fc44.x86_64.rpm \
  https://github.com/CodyWan1990/paw/releases/download/v0.9.1/paw-0.9.1-1.fc44.x86_64.rpm

Both URLs go in one command so paw's dependency on SceneFX resolves. The package pulls in the rest from Fedora, ships the shell, and seeds ~/.config/quickshell/pawshell/ on first login.

These are prebuilt against Fedora 44 and wlroots 0.20 — on anything else, build from source. There is no dnf update path yet; see the Roadmap.

Log out and pick paw from your display manager's session list.

From source

git clone https://github.com/CodyWan1990/paw.git && cd paw

sudo dnf install gcc make pkgconf-pkg-config meson ninja-build git \
  wlroots-devel wayland-devel wayland-protocols-devel libxkbcommon-devel \
  libinput-devel pixman-devel libxcb-devel xcb-util-wm-devel \
  xorg-x11-server-Xwayland
sudo dnf install quickshell cliphist grim slurp swaybg wl-clipboard jq \
  wlr-randr kitty mate-polkit
sudo dnf install cascadia-code-nf-fonts cascadia-mono-nf-fonts \
  google-noto-color-emoji-fonts

The fonts are not optional: every bar icon is a Nerd Font codepoint, so without one the bar is a row of tofu. Any Nerd Font works — Cascadia is simply the one Fedora packages, so it's the shipped default. Pick another in Settings → General; the picker lists Nerd Fonts first.

SceneFX isn't packaged in Fedora, so build it too:

sudo dnf install mesa-libgbm-devel libglvnd-devel lcms2-devel libdrm-devel
git clone -b 0.5 https://github.com/wlrfx/scenefx ~/src/scenefx
meson setup ~/src/scenefx/build ~/src/scenefx -Dexamples=false --prefix=/usr
sudo meson install -C ~/src/scenefx/build

Then build paw and deploy the shell:

make -C paw && sudo make -C paw install
cp -a pawshell ~/.config/quickshell/pawshell
  • make install covers the compositor only — the cp -a is required, and the session script leaves an existing copy (or a stow symlink) alone.
  • The default prefix is /usr/local, which all the paths below assume. Pass PREFIX=/usr to match the package.
  • wlroots must be 0.20. XWayland is on by default; comment out the XWAYLAND flags in paw/config.mk to drop it. SceneFX is optional too — make SCENEFX= SCENEFXLIBS= loses the visual effects and nothing else.

Running

Pick paw from your display manager, or run it directly:

paw -s /usr/local/libexec/paw/session.sh

session.sh is compositor infrastructure only — it wires up D-Bus and the portal stack, runs your autostart, and hands off to qs -c pawshell. It deliberately starts no daemons of its own.

First run

The first login opens a setup screen: primary monitor, resolution and refresh rate, colour scheme, wallpaper folder, the current keybinds, default terminal and editor, and which optional tools are missing. Finish puts it away for good; closing it any other way brings it back next login. Reopen it whenever from Settings → General, or with qs -c pawshell ipc call shell welcome.

Tab moves between pages, the arrows move within one, Enter applies what is highlighted, Escape closes.

Default apps are detected, not typed: the terminal and editor lists come from installed desktop entries, and each carries its own launch arguments, so nothing has to guess how to start it. Choosing a terminal also retargets whichever keybind spawns one — see below.

~/.config/paw/autostart.sh

Anything that's your choice rather than the compositor's goes here: paw starts no clipboard manager, polkit agent, or wallpaper setter itself. Seeded once from the shipped example, then never touched again, so your edits survive upgrades. The defaults start a polkit agent, cliphist, and the last wallpaper.

Java AWT IDEs may need export _JAVA_AWT_WM_NONREPARENTING=1.

Configuration

~/.config/paw/compositor.settings

Gaps, borders, cursor, focus behaviour, shadows, blur, corner radius, animations, tearing, the mod key, keyboard repeat, and every libinput trackpad option.

One key is worth knowing up front:

[input]
# super | alt
modkey = alt

modkey is what the mod token in keybinds.conf resolves to, and it also drives the mouse bindings (mod+drag to move a window) and the scroller's wheel bindings. Set it to alt if you are trying paw in a VM or over remote desktop, where the host keeps Super for itself. Also on the setup screen's Keys page and in Settings → Compositor.

Games get two more:

[behavior]
# off | fullscreen | always
tearing = fullscreen
# off | auto | always
vrr = auto

Tearing trades a torn frame for a frame of latency. paw honours a game's wp_tearing_control_v1 request only where the default says it may — when that game is fullscreen — so the desktop, the bar and video never tear. always extends it to any focused window that asks, off refuses everyone. Nothing tears unless it asked; for games that never learned to ask, set tearing per application in rules.conf.

Adaptive sync (VRR) is separate. auto, the default, turns it on for a fullscreen window or one that declares itself a game or a video via wp_content_type_v1 — so a windowed game gets it too — and off again when nothing on that output needs it. always and off skip that policy entirely.

Some panels flicker or shift brightness on VRR, and that is a property of the display rather than of the moment, so it is settable per monitor: Settings → Monitors has an Auto / On / Off control for each one, greyed out when the display doesn't report VRR support. Choosing On or Off there writes an adaptive-sync key to outputs.conf that overrides vrr for that display; Auto removes it again.

mkdir -p ~/.config/paw
cp /usr/local/share/paw/compositor.settings.example ~/.config/paw/compositor.settings

Edit it from Settings → Compositor or by hand — both write this same file and changes apply live either way. Every key is optional and a bad value is reported and ignored, so a typo can't stop paw from starting.

~/.config/paw/outputs.conf

Per-monitor mode, position, scale, rotation, adaptive sync, and whether the output is on at all.

[DP-1]
mode = 2560x1440@143.998
position = 0,0
scale = 1

paw writes this file itself. It implements wlr-output-management-v1, so anything that speaks that protocol — the setup screen, wlr-randr, kanshi, nwg-displays — configures your monitors, and paw records the result here and replays it on the next start. Hand edits apply at the next start too.

With no file, every output comes up at its preferred mode and wlroots arranges them left to right in connection order. A mode the display doesn't report falls back to its preferred one, so swapping a monitor can't leave you dark.

For the common cases — which screen is primary, and each one's resolution and refresh rate — use Settings → Monitors. It is the same panel the setup screen's first step shows, so changing a mode later doesn't mean re-running setup.

~/.config/paw/rules.conf

Per-application overrides: open this app floating, fullscreen, on that output, on that tag. One section per app_id, matched as a substring so a prefix catches a whole family.

[steam_app_]
tags = 9
fullscreen = true

[steam_app_570]
fullscreen = false
float = true

Keys are title, float, fullscreen, monitor, tags, terminal, noswallow, tearing; all optional. Sections apply in file order and later ones layer over earlier ones, so you can set a broad default and carve out exceptions. Rules are applied after paw's own floating heuristics, so a rule always wins. Edits apply immediately.

tearing = true forces a tearing page flip for an application that never sends the wp_tearing_control_v1 hint — common in older and Proton-wrapped titles — and tearing = false refuses one that does. Either way behavior.tearing in compositor.settings still has to allow tearing at all.

paw never guesses fullscreen from a window's size. Under XWayland every X11 client sees one screen spanning all your outputs, so "as big as the desktop" says nothing about which monitor was meant, and windows that merely want to be large would get forced fullscreen. Applications that ask properly — Wayland fullscreen, or _NET_WM_STATE_FULLSCREEN on X11 — are honoured with no rule at all; this file is for the ones that don't ask. togglefullscreen is bindable if you'd rather do it by hand. This mirrors sway, which ignores a tiled X11 client's requested size outright, and Hyprland, whose equivalent is windowrulev2 = fullscreen, class:....

cp /usr/local/share/paw/rules.conf.example ~/.config/paw/rules.conf

Copying it is not enough — nearly everything in the example is commented out, so uncomment the lines you want.

To find an application's app_id, ask the compositor while the window is open:

socat -t1 - UNIX-CONNECT:$PAW_SOCKET </dev/null | head -1 | jq '.windows'

That prints every window's appid, output, tags, geometry, and its floating / fullscreen / wants_fullscreen / content_type / tearing state — paw's answer to hyprctl clients. content_type and tearing are the only way to see whether a game actually sent the hints it is supposed to. wants_fullscreen is what the application asked for and fullscreen is what paw did, so the two together tell you whether an app that won't go fullscreen never asked (write a rule) or was refused (a bug worth reporting). paw pushes a fresh snapshot on every change, so leaving socat running records the whole sequence:

socat -t1 - UNIX-CONNECT:$PAW_SOCKET > /tmp/paw-trace.jsonl

~/.config/paw/keybinds.conf

MODS+KEY = FUNCTION [ARG], one per line. See Settings → Keybinds. Seeded on first login from the example, which is byte-for-byte what paw compiles in:

cp /usr/local/share/paw/keybinds.conf.example ~/.config/paw/keybinds.conf

Modifiers are mod, super, shift, ctrl, alt. mod is whatever input.modkey in compositor.settings is set to — super by default, alt for a VM — so switching one setting moves every default binding at once. Spelling out super or alt pins that binding to that key regardless.

Defaults are navigation, the pawshell panels, and kitty on mod+Return — no other applications bound, those are yours to add.

Your copy replaces the defaults rather than extending them, so keep the chvt block at the bottom unless you're certain you never need a TTY. If nothing in the file parses, paw keeps the bindings it already had.

Picking a terminal in Settings → Apps rewrites whichever line spawns the old one — the key itself and any arguments are left alone, so a terminal moved off mod+Return stays where you put it. Nothing else in the file is touched, and a command that isn't a plain token is refused rather than written.

paw/config.def.h

Compiled defaults, plus the parts that still need a rebuild: tags, layouts, the mouse bindings in buttons, and colorthemes. (Which modifier those mouse bindings use is runtime — that is input.modkey.) Monitors and window rules are not among them — they live in outputs.conf and rules.conf above. The compiled rules table is still there as the built-in baseline; rules.conf layers over it.

Theming and wallpapers

Themes are JSON files in pawshell/themes/drop a file in to add one, or build it in-app from the Themes tab.

{
  "meta":    { "name": "Dracula", "slug": "dracula", "dark": true },
  "palette": { "bg": "282a36", "fg": "f8f8f2", "red": "ff5555", ... },
  "wallpaper": { "path": "wallpapers/dracula.png", "mode": "fill" }
}

Switching a theme recolors the shell and the window borders instantly, and — via scripts/apply-theme-externals.sh — kitty and GTK apps too. Both externals are tick boxes at the bottom of the launcher's Themes tab, next to the themes they apply to.

kitty gets its colours generated from the same palette, written to ~/.config/kitty/current-theme.conf. kitty only reads that if something includes it, so paw manages a fenced include at the end of your kitty.conf — appended after a one-time backup to kitty.conf.pawbak, never replacing what is already there. With no kitty config at all, paw writes a starter one you are free to edit; it only ever rewrites the fence. Turning the toggle off removes the fence and leaves the rest alone. Open terminals recolor immediately — paw signals kitty to re-read its config.

GTK theming overwrites ~/.config/gtk-3.0/gtk.css and gtk-4.0/gtk.css; an existing file paw did not write is backed up to gtk.css.pawbak first and restored when you turn the toggle off. Install adw-gtk3-theme to have GTK3 apps match their GTK4 neighbours — paw recommends it, and points gtk-theme at adw-gtk3-dark.

A GTK app picks up a new palette the next time it starts. GTK reads user gtk.css once during init and never re-reads it, and libadwaita has no supported live-theming path at all, so this is a toolkit limitation rather than something paw can work around. Only the shell, the borders and kitty change under you.

Wallpapers

None ship with this repository; the wallpaper block above is only a fallback. Point Settings → General → Wallpapers at your image directory (default ~/Pictures/Wallpapers), then open the Wallpapers tab and click an image with "Also set for current theme" ticked — it applies and binds to the active theme, so switching back later restores it.

Bindings live in your own settings.json as bare filenames, so upgrades never overwrite them and moving your wallpaper directory carries them along. A theme with no wallpaper still switches colors.

Weather

The weather widget needs a free OpenWeatherMap key. Enter it in Settings → Weather — it's written to a gitignored weather.conf, never to the tracked settings.json.

Screen sharing / OBS

Wayland capture goes through xdg-desktop-portal, and on wlroots compositors the wlr backend is required — the GNOME and KDE backends cannot capture a paw session.

sudo dnf install xdg-desktop-portal-wlr

Create ~/.config/xdg-desktop-portal/paw-portals.conf:

[preferred]
default=gtk
org.freedesktop.impl.portal.ScreenCast=wlr
org.freedesktop.impl.portal.Screenshot=wlr

Relogin, and OBS's Screen Capture (PipeWire) source will see your outputs. Flatpak OBS needs nothing extra.

The portal needs graphical-session.target, which refuses manual start, so make install ships a paw-session.target that pulls it in; session.sh starts it and rebuilds the portal stack on every launch.

IPC

paw listens on $XDG_RUNTIME_DIR/paw-$WAYLAND_DISPLAY.sock, exported to children as PAW_SOCKET. Clients get a JSON state snapshot on every change and send flat JSON commands back:

socat - UNIX-CONNECT:$PAW_SOCKET
{"cmd":"set-layout","index":2}

paw also streams its live config over the same socket, and pawshell edits it with set-config rather than writing the file — the compositor stays the single source of truth, which is what keeps the Settings UI and hand-editing compatible. Shell panels are opened the other way round, by keybinds spawning qs -c pawshell ipc call shell <panel>.

paw/ipc.h lists the commands; paw/CLAUDE.md covers the design.

Architecture

Compositor. paw.c keeps the wlroots event loop, lifecycle, and input dispatch. Testable pieces are split out: layout.c, effects.c, ipc.c, keybinds.c, and pawconf.c, each with unit tests under paw/tests/.

Shell. shell.qml is the entry point; services/ holds singletons, panels/ are overlay surfaces, widgets/ are bar widgets, and scripts/ holds the handful of helpers that need a shell (screenshots, wallpaper, theme externals, CPU and disk stats).

paw/CLAUDE.md is the detailed architecture reference.

Development

make -C paw check              # layout, ipc, keybinds, pawconf tests
make -C paw check-sanitize     # the same under ASan
make -C paw check-analyze      # GCC static analysis
make -C paw check-shell        # shell script syntax
make -C paw check-no-xwayland  # optional XWayland-free build
make -C paw check-no-scenefx   # optional SceneFX-free build

cd pawshell && ./scripts/check.sh   # qmllint gate

qmllint is static-only. To smoke-test the shell without logging out, run paw headless (WLR_BACKENDS=headless WLR_RENDERER=pixman paw/paw -s 'sleep 600'), point qs -p pawshell at the nested display, and screenshot with grim.

New bar widgets extend pawshell/components/BarWidget.qml (documented in its own header) and get registered in TopBar.qml's widgetRegistry and BarTab.qml's availableWidgets.

Cutting a release

Bump the version in packaging/fedora/paw.spec and paw/config.mk, tag, then build the SRPM and rebuild it:

./packaging/fedora/build-srpm.sh
rpmbuild --rebuild packaging/build/paw-*.src.rpm

scenefx builds the same way from its own spec and must exist first, since paw BuildRequires it. Attach both binary RPMs to the GitHub Release.

Roadmap

  • A real package repository — COPR, or createrepo_c on GitHub Pages — so installs get dnf update instead of pinned release URLs.
  • Validate builds on other distributions.
  • Theme sync for more terminals. kitty is wired up properly — generated colours, a managed include, and a toggle. foot, alacritty, wezterm and ghostty should follow the same shape from the same palette.
  • GTK, Qt and cursor pickers in the settings UI. The active theme already writes GTK 3/4 colour overrides, but nothing lets you choose a GTK or icon theme, Qt is not wired up at all (qt5ct/qt6ct/Kvantum), and the cursor has a size setting with no theme to go with it.
  • Deeper per-widget theming. Bar widgets take a style, a colour and an icon set today; more of the palette should be reachable per widget.
  • More gaming tweaks. The fullscreen and floating fixes and float_small_max landed in 0.9; tearing control and content-type hints followed, and adaptive sync is now a real per-monitor setting. What is left is the part paw cannot do alone: HDR, explicit sync beyond what wlroots already gives us, and a gamescope-style fixed-resolution session.

Contributing

Contributions are welcome, but this pre-release can't offer normal review or merge guarantees yet — changes may be deferred or become incompatible as the project moves. Opening one shouldn't be treated as an expectation of a response.

Acknowledgements

Built on upstream dwl by Devin J. Pohly and contributors, which began from the sway/wlroots TinyWL example (CC0). Effects use SceneFX by the wlrfx project; the close-animation technique is adapted from mango (GPL-3.0). Thanks to suckless.org and the dwm community.

License

GPL-3.0-or-later, covering both halves — paw is a hard fork of dwl, which is GPL-3.0-or-later, and pawshell is licensed to match.

Portions dwl absorbed keep their original permissive terms: dwm (MIT/X), sway (MIT), and TinyWL (CC0), in paw/LICENSE.*. The vendored paw/jsmn.h is MIT. wlroots and SceneFX are MIT and linked, not vendored. Quickshell is LGPL-3.0 and places no requirement on pawshell. Full text in LICENSE.

You are free to use, study, modify, redistribute, and fork this, provided derivatives stay under the same license.

paw is provided as-is, with no warranty of any kind — see GPL-3.0 sections 15 and 16 in LICENSE. It is pre-release software that takes over your display; run it accordingly.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages