treeland is a wayland compositor based on wlroots and QtQuick, designed to provide efficient and flexible graphical interface support.
Check the debian/control file to understand specific build and runtime dependencies, or use cmake to check for missing necessary components.
Core build dependencies:
- waylib: A Wayland compositor development library based on wlroots and QtQuick
- Qt >= 6.8.0
- wlroots = 0.20
- treeland-protocols: Private Wayland protocols used by treeland
Recommended runtime dependencies:
- ddm: A display manager optimized for multiple users
Treeland uses cmake for building. The WITH_SUBMODULE_WAYLIB option can force the use of the waylib code from the submodule. If you want to use the system-provided waylib, set this option to OFF.
Using the system-provided waylib:
$ git clone git@github.com:linuxdeepin/treeland.git
$ cd treeland
$ cmake -Bbuild -DWITH_SUBMODULE_WAYLIB=OFF
$ cmake --build buildUsing the waylib from the submodule:
$ git clone git@github.com:linuxdeepin/treeland.git --recursive
$ cd treeland
$ cmake -Bbuild -DWITH_SUBMODULE_WAYLIB=ON
$ cmake --build buildA debian folder is provided to build the package under the deepin linux desktop distribution. To build the package, use the following command:
$ sudo apt build-dep . # install build dependencies
$ dpkg-buildpackage -uc -us -nc -b # build binary package(s)treeland-debug is an adb-style command-line inspector and controller for the
running Treeland compositor. It connects to Treeland's debug Remote Object over
Qt Remote Objects and exposes subcommands for inspecting the window tree,
controlling windows, injecting input events and grabbing screenshots. Like adb,
it runs in two modes:
- Non-shell (one-shot) mode — the default:
treeland-debug <command> [args]. - Shell mode — an interactive REPL:
treeland-debug shell.
The client builds as part of the normal Treeland build (it is on by default; pass
-DBUILD_TREELAND_DEBUG=OFF to skip it):
cmake -B build
cmake --build build --target treeland-debug
sudo cmake --install build --component treeland-debugThe debug Remote Object is off by default: it hands out window contents and
input injection, which a compositor running inside a user session must not offer
unasked. remoteDebug is Treeland's master switch for it: while the switch is
on, Treeland publishes its debug socket and
serves the window tree remote source to whoever connects -- the source is a thin
QObject that only does work per call, so it costs nothing when no client is
attached. Turn the switch off to forbid debugging entirely; the socket then
disappears too. Release builds keep the socket root-only (mode 0000). A debug
build instead ignores the switch and opens the channel to every local user for
good: its -dev socket is 0666 and the directory Treeland owns is widened to
0711 at startup, so treeland-debug connects without a polkit prompt even when
the compositor is the deb-installed global service running as its own user
(/run/treeland-debug/...). The separate -dev name keeps such an instance
apart from a release one. A session instance's socket sits in $XDG_RUNTIME_DIR,
which stays 0700 like Qt requires, so there only its own user (and root)
connects -- that user is the one debugging it anyway.
The root restriction of a release build can be lifted for a while over the
debug protocol itself: treeland-debug no-auth (the requestNoAuth method of
WindowTreeRemote) asks the compositor to open its debug directory (0711) and
its socket (0666) to every local user. Only a caller that already got through the
sealed socket can ask, and in a release build that is root alone: mode 0000
blocks the compositor's own user as well, and only CAP_DAC_OVERRIDE passes. So
that connection is the authentication -- opening the window therefore costs root
one pkexec prompt, and everyone who connects during those 30 minutes needs
none. The window closes 30 minutes later; every remote call while it is open
pushes it out again, so a forgotten request cannot leave the channel open for
longer than an idle half hour. Closing it also drops the connections that were
opened while it was open, so the window cannot be kept alive by holding a
client connected.
Only the global service gets this window, and only for a directory of its own:
/run/treeland-debug (RuntimeDirectory=treeland-debug), never its
$XDG_RUNTIME_DIR (/run/treeland) -- Qt insists that one stays 0700 and would
move new sessions' wayland sockets to $TMPDIR otherwise. A session instance
runs inside /run/user/<uid>, which also holds the session bus and other
per-session sockets, so Treeland leaves that directory alone there and root
stays required.
The window is held in memory only, so nothing outlives the compositor: a crash
or a restart comes back root-only, and every socket is sealed on startup even if
the previous process died with the window open. The directory itself is widened
once (0711, traverse but not list) and stays that way until the process exits,
because several instances may share it and one closing its window must not cut
off a peer's; a restart under systemd recreates it 0700. Access is gated by the
socket modes alone. Any number of clients can attach at once while it is open.
If no socket is published, either the switch is off or Treeland is not running --
the client says so on stderr and points at the switch. Toggling remoteDebug
takes effect immediately: the remote source is created or destroyed on the fly,
no restart needed.
Flip the switch with treeland-debug debug on / treeland-debug debug off
(debug without an argument reports the current state). The switch is a
per-user DConfig key that Treeland reads for the user it runs as, so the command
finds that user through the running compositor and escalates through pkexec
when it is not you; --no-escalate reports the requirement instead of prompting.
Underneath it runs, as root, the equivalent of:
dde-dconfig -u <compositor uid> set \
-a org.deepin.dde.treeland \
-r org.deepin.dde.treeland \
-k remoteDebug \
-v trueAll commands below are run as the dde user, e.g.
sudo -u dde -- treeland-debug windows.
Treeland publishes the debug source on a local socket whose default name is
org.deepin.dde.treeland.debug, bound at an absolute path in a directory of
its own: /run/treeland-debug/org.deepin.dde.treeland.debug for the global
service (RuntimeDirectory=treeland-debug), /run/user/<uid>/... (i.e.
$XDG_RUNTIME_DIR) for a session. Nothing is put in /tmp, which is
world-writable and where a $TMPDIR-resolved path is invisible to a client with
a different TMPDIR. The global service's directory is created 0700 by systemd
and is not its $XDG_RUNTIME_DIR, because the no-auth window widens it to 0711
and Qt requires a runtime directory to stay 0700; $TREELAND_DEBUG_DIR overrides
it for development. The socket is reachable by root, and by the compositor's own
user in a debug build (0666).
A treeland-debug client probes, in order, its own runtime directory, the
global service's /run/treeland-debug, /run/treeland (where compositors from
before the dedicated directory bound), and every /run/user/<uid> it may list
(root only), because it cannot know the compositor's directory otherwise.
When the name is already taken
by another running treeland (global service, session service, local build),
a numeric suffix is appended (org.deepin.dde.treeland.debug-1, -2, …),
selected with a non-blocking advisory lock (the same approach libwayland uses
for Wayland sockets). A stale socket file left by a crashed instance is
reclaimed, and a live old-build instance (one without a lock file) is probed
and left alone.
Debug builds use a distinct base name (org.deepin.dde.treeland.debug-dev)
so a debug-compiled treeland can run alongside a release one. A debug-built
treeland-debug client automatically prefers the debug instance and falls back
to the release instance when the debug one is unreachable; a release-built
client uses the release instance. The socket is chosen in this order: --url,
then $TREELAND_DEBUG_SOCKET (a socket path or a bare name, treated like
local:<value>; a value with a scheme is used as-is), then the probing above.
An explicit URL always overrides this with no implicit processing -- that is
also the way to reach a socket published by an older treeland, which still
lives at the pre-migration /tmp/<name> path:
--url local:/tmp/org.deepin.dde.treeland.debug.
| Option | Default | Description |
|---|---|---|
--url <url> |
$TREELAND_DEBUG_SOCKET, else auto: debug socket (debug build) with release fallback, else release socket |
Remote object host URL. |
--name <name> |
WindowTree |
Remote object name. |
--timeout-ms <n> |
30000 |
Request timeout in ms (non-negative integer). |
--json |
off | Emit machine-readable JSON for tree/cursor/windows/clients. |
--preview |
auto | Force inline image preview in terminal (auto-detected by default). |
--no-preview |
off | Disable inline image preview. |
-h, --help |
— | Show help. |
-v, --version |
— | Show version. |
--tree / --cursor |
— | Backward-compatible aliases for the tree / cursor commands. |
Window-control commands accept a target given by numeric id (printed by
windows / clients / top) or by appId (the first matching window is
used).
| Command | Arguments | Output |
|---|---|---|
tree |
(none, default) | Layout tree (human-readable; --json for JSON). |
cursor |
(none) | Cursor position x=… y=… (--json for {"x","y"}). |
windows |
(none) | Window table; --json for a JSON array. |
clients |
(none) | Client + window table; --json for a JSON array. |
shortcuts |
(none) | Registered shortcuts (keys and gestures); --json for a JSON array. |
top |
[interval-ms] (default 1000) |
Live, top-like refreshing client view (Ctrl+C to quit). |
| Command | Arguments | Output |
|---|---|---|
activate |
<id> |
ok / failed. |
close |
<id> |
ok / failed. |
minimize |
<id> |
ok / failed. |
maximize |
<id> |
ok / failed (toggles maximized). |
fullscreen |
<id> |
ok / failed (toggles fullscreen). |
move |
<id> <x> <y> |
ok / failed. |
resize |
<id> <w> <h> |
ok / failed. |
workspace |
<id> <ws-id> |
ok / failed (move window to a workspace). |
| Command | Arguments | Output |
|---|---|---|
move-cursor |
<x> <y> |
ok / failed. |
event motion |
<x> <y> |
ok / failed. |
event button |
<left|right|middle|code> [press|release|click] |
ok / failed (default click). |
event key |
<name|evdev-code> [press|release|tap] |
ok / failed (default tap). |
Pointer buttons use Linux input codes (left=0x110, right=0x111,
middle=0x112, or a numeric code). Keyboard keys use Linux evdev keycodes;
common names are recognised (esc, enter, space, tab, a–z, 0–9,
arrow keys, f1–f12, home/end/pageup/pagedown/insert/del, …) or a
raw code may be passed. Keys are delivered to the keyboard-focused surface —
activate a window first to target it; pointer buttons go to the surface under
the cursor.
Screenshots are rendered server-side and returned to the treeland-debug
client as PNG bytes; the client writes them to a file and prints the path.
The compositor itself never touches the filesystem. If file is omitted a
path under /tmp is generated.
| Command | Arguments | Output |
|---|---|---|
screenshot output |
[name] [file] |
PNG file path (stdout). |
screenshot window |
<id> [file] |
PNG file path (stdout). |
| Command | Arguments | Output |
|---|---|---|
shell |
(none) | REPL (treeland>) accepting all commands plus help/exit. |
help |
(none) | Full help text. |
tree and cursor print a human-readable format by default; pass --json for
machine-readable JSON. windows and clients also default to human-readable
tables and use --json for JSON.
Window JSON object (WindowInfo):
| Field | Type | Notes |
|---|---|---|
id |
integer | Stable window id (the wl_surface wl_resource pointer); globally unique, accepted by control commands. |
appId |
string | Application id. |
title |
string | Window title. |
output |
string | Output name. |
container |
string | Container name. |
workspace |
integer | Workspace id. |
layer |
integer | Layer id. |
z |
integer | Z order. |
type |
integer | Window type. |
state |
integer | 0 Normal, 1 Maximized, 2 Minimized, 3 Fullscreen, 4 Tiling. |
visible |
boolean | Visibility. |
active |
boolean | Active / focused flag. |
geometry |
object | {"x","y","width","height"}. |
titlebarGeometry |
object | Same shape. |
boundingRect |
object | Same shape. |
iconGeometry |
object | Same shape. |
position |
object | {"x","y"}. |
frames |
integer | Number of committed frames (from wlr_surface). |
damage |
object | Last committed buffer-damage rectangle {"x","y","width","height"}. |
inputRegion |
array of object | Input region as surface-local boxes ([{"x","y","width","height"}, ...]), already clipped to the surface size. An unset (NULL) input region is infinite per protocol and therefore reads back as one box covering the whole surface; the list is empty only when the client committed an empty region (the surface then accepts no input) or before the surface has committed a buffer. |
opaqueRegion |
array of object | Opaque region as surface-local boxes, same shape. Empty means the client declared no opaque area — the protocol default, and also the value before the first commit. It is a repaint optimization hint, not a statement that the window is actually transparent. |
Client JSON object (ClientInfo): id (integer), appId (string), pid
(integer, 0 when unavailable), executable (string), windows (array of
WindowInfo).
The tree JSON is
{"currentMode": str, "layers": [{"name", "layer", "windows": [WindowInfo], "workspaces": [{"id", "isActive", "windows": [WindowInfo]}]}]}.
0 on success; 1 on connection failure, RPC failure, an unknown command, or a
control command that returns failed.
All commands run as the dde user:
# Inspect the window tree (human-readable, default command)
sudo -u dde -- treeland-debug tree
# Cursor position
sudo -u dde -- treeland-debug cursor
# → x=960 y=540
# List windows
sudo -u dde -- treeland-debug windows
# List clients and their windows
sudo -u dde -- treeland-debug clients
# Live refreshing top view (1 s interval)
sudo -u dde -- treeland-debug top
# (Ctrl+C to quit)
# Activate a window by id
sudo -u dde -- treeland-debug activate 1407374883553280
# Activate a window by appId
sudo -u dde -- treeland-debug activate dde-file-manager
# Move a window
sudo -u dde -- treeland-debug move 1407374883553280 100 200
# Resize a window
sudo -u dde -- treeland-debug resize 1407374883553280 800 600
# Move a window to workspace 2
sudo -u dde -- treeland-debug workspace 1407374883553280 2
# Move cursor
sudo -u dde -- treeland-debug move-cursor 960 540
# Send a pointer click
sudo -u dde -- treeland-debug event button left click
# Send a key tap
sudo -u dde -- treeland-debug event key enter tap
# Screenshot the primary output to a file
sudo -u dde -- treeland-debug screenshot output /tmp/ss.png
# Screenshot a window (with terminal preview if supported)
sudo -u dde -- treeland-debug screenshot window 1407374883553280
# Interactive shell mode
sudo -u dde -- treeland-debug shell
treeland> help
treeland> windows
treeland> exit
# Machine-readable JSON output
sudo -u dde -- treeland-debug --json windows
sudo -u dde -- treeland-debug --json cursorThe top view refreshes every interval-ms (default 1000 ms) using a QTimer.
Each cycle calls getClients() via Qt Remote Objects, clears the terminal
(\033[2J\033[H), and re-prints a header with the current timestamp and
client count, followed by the client + window table. Press Ctrl+C to stop.
This project uses GitHub Actions for continuous integration. The following workflows are configured:
- waylib builds: Triggered when
waylib/**files are modified - treeland builds: Main project builds
treeland is licensed under Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only.