kdos

kdos-comp

kdos-comp is the KDOS compositor: the program that draws the screen, places and decorates windows, reads the keyboard and the pointer, and starts and supervises the panel, the desktop icons and the session’s small daemons. This chapter describes how it is configured, what it adds to the Wayland compositor it is forked from, how each addition behaves, and how to work on its code. It is for anyone configuring the desktop beyond the settings window, diagnosing a desktop problem, or changing the compositor itself. Read The desktop first for the user’s view of the same desktop, and The session for what starts the compositor.

If you only want to change a setting or a key binding, read Configuration and Bindings. If the desktop misbehaves, start with Debugging and the session log. To change the compositor’s code, read Working on the compositor. The case for forking an existing compositor rather than writing one is set out in Decisions, and how that desktop compares with those of other distributions in How KDOS differs.

Overview

kdos-comp is a hard fork of the labwc 0.20.0 Wayland compositor, built on wlroots (the wlroots port, version 0.20.2). A hard fork here means the source tree is labwc’s own, renamed and extended in place, and upstream changes are not merged into it. The KDOS additions live in files of their own (twenty-three kdos-*.c sources, two headers and the application-first switcher); upstream files carry only small, marked hooks into them (see Finding the KDOS additions).

   
Binary /usr/bin/kdos-comp
Upstream configuration ~/.config/kdos-comp/rc.xml and menu.xml, both copied from /etc/skel for a new user; /etc/xdg/kdos-comp/ is also searched, and nothing ships there
Generated theme ~/.config/kdos-comp/themerc-override, written by kdos theme
KDOS configuration ~/.config/kdos/comp.conf ($XDG_CONFIG_HOME/kdos/comp.conf)
Started by kdos-desktop-start, with no options — see The session
Log $XDG_RUNTIME_DIR/kdos-comp.log; the previous session’s is kept as kdos-comp.log.old
Command socket $XDG_RUNTIME_DIR/kdos-cmd.sock
Frame-timing socket $XDG_RUNTIME_DIR/kdos-frames.sock
Source and recipe src/desktop/kdos-comp/ in the repository, with its kpkgbuild and build.sh; built by meson in the 50_desktop phase, with no source archive to fetch

KDOS-FORK at the root of the source tree records the upstream tarball and its checksum. Upstream’s licence (GPL-2.0) and copyright headers are kept.

The program has four parts. labwc’s core does the window management: placement, focus, moving and resizing, workspaces, bindings, menus and decorations. The KDOS additions sit beside it: the phosphor pass, the wallpaper, idle, dim, lock and lid, window groups and window memory, box identity, accessibility, motion and render-late scheduling. Eight supervised children (the panel, the desktop icons, the dockapp column, four small session daemons and the on-screen keyboard) are started and restarted by the compositor. Two sockets let other KDOS programs talk to it: the command socket and the frames socket. A screen reader talks to it over the session bus, through the keyboard monitor.

How labwc’s documentation applies

Because this is a fork rather than a set of patches, labwc’s documentation for rc.xml, menu.xml and the theme applies as written: window management, key and mouse bindings, window rules, theme keys and menus all behave as labwc’s documentation describes. The differences are these:

The manual pages are not installed, so man labwc-config finds nothing on a KDOS machine. Read the 0.20.0 pages upstream at labwc.github.io: labwc-config(5) for rc.xml, labwc-actions(5) for the actions, labwc-menu(5) for menu.xml and labwc-theme(5) for the theme keys.

Command-line options

These are labwc’s, under the new name. kdos-comp -v prints kdos-comp (labwc fork), the labwc version, the compiled-in features and the wlroots version.

Option Does
-c, --config <file> Use this rc.xml
-C, --config-dir <dir> Use this configuration directory
-d, --debug Log everything, including debug messages
-e, --exit Tell the running compositor to exit
-h, --help Show the options and quit
-m, --merge-config Merge configuration and theme files from every XDG base directory
-r, --reconfigure Tell the running compositor to reload its configuration
-s, --startup <command> Run a command on startup
-S, --session <command> Run a command on startup and exit when it exits
-t, --title <fmtstr> The window title to use when running nested inside another compositor; the default is kdos-comp - %o
-v, --version Show the version and quit
-V, --verbose Informational logging, which is already the default here; after -d it lowers the level back

-r and -e find the running compositor through LABWC_PID, which kdos-comp sets in the environment of everything it starts. From a shell that the compositor did not start (a text console, or an SSH login) they fail with LABWC_PID not set; send SIGHUP or SIGTERM to the process instead. kdos theme reloads the compositor by sending SIGHUP directly.

Configuration

Configuration is split between two files, and the split is strict:

comp.conf is one key = value per line, parsed and never executed as a script. A line whose first non-blank character is # is a comment. The shipped file has every key commented out at its default, with an explanation beside each, so the defaults below are what an unedited machine runs with. The same keys are listed in Configuration, and most can be changed from the settings window.

Every line that does not take effect is written to the log with its file name and line number:

A setting that silently did nothing could not be told from a typo, so check the log when a change seems to be ignored.

A path value may start with ~/ or $HOME/; both are expanded. A yes/no value accepts yes/no, true/false, on/off and 1/0, in any case.

Keys applied on reload

These take effect when the compositor reloads, which happens on SIGHUP, kdos-comp -r, the Reload Configuration entry in the desktop’s right-click menu, and every kdos theme.

Key Default Range Does
wallpaper /usr/share/backgrounds/kdos/default-wallpaper.png a PNG path, or none The wallpaper; see The wallpaper for which file wins
crt 55 0–100 Strength of the phosphor pass, per cent. 0 turns the pass off
crt_scanlines 0 0–100 Scanline depth; 60 is the strength the rest of the pass is tuned against
crt_curve 0 0–100 Barrel distortion
crt_fullscreen yes yes/no Whether the pass runs over a fullscreen window; no lets that window’s frames scan out directly
idle_dim 300 0–86400 s Seconds of inactivity before the screen dims; 0 is never
idle_lock 600 0–86400 s Seconds before the session locks; 0 is never
idle_off 900 0–86400 s Seconds before the screens are powered off; 0 is never
lid_close suspend suspend, lock, off What closing a laptop lid does; any other value is refused by name
window_memory yes yes/no Whether an application opens where its window last was
motion yes yes/no The desktop’s reduce-motion switch; no makes every compositor fade a single frame. See Motion
window_motion no yes/no Window transitions; only with motion = yes
max_render_time off 1–100 ms, or off Render-late scheduling: how long before the vertical blank each frame is composited
sticky_keys no yes/no Sticky keys
slow_keys no yes/no Slow keys
slow_keys_delay 300 100–5000 ms How long a key must be held before slow keys accepts it
bounce_keys no yes/no Bounce keys
bounce_keys_delay 300 100–5000 ms How soon after its release a second press of the same key is ignored
dwell_click no yes/no Dwell click
dwell_click_delay 1200 100–5000 ms How long the pointer rests before the click
cursor_size 0 0–256 px The pointer size; 0 keeps XCURSOR_SIZE from the session
large_cursor no yes/no Whether the pointer is drawn at large_cursor_size
large_cursor_size 48 16–256 px The size large_cursor and ToggleLargeCursor switch to

Two limits apply to the phosphor keys, because two decisions about the pass are made once, when the compositor starts:

In a virtual machine the idle timers and the lid default to off unless comp.conf sets them; see Idle, dim, lock and lid.

Keys applied at the next login

Each of these becomes part of a supervised child’s command line, so a change takes effect when the session next starts. A reload logs the change, for example comp.conf: panel_font changed — applies at the next login, and keeps the running value until then, so the compositor never believes a setting that the panel on screen is not using.

Key Default Range Does
panel bottom bottom, top, off (or none) Where the panel goes, or no panel
panel_cells 2 1–4 Panel height in text cells
panel_font Terminus:pixelsize=20 a fontconfig pattern The bar’s own font; it does not follow chrome_font
panel_autohide no yes/no Whether the bar hides when the pointer leaves it
panel_margin 0 0–64 px Gap between the bar and the screen edge
panel_opacity 80 20–100 % Opacity of the bar’s background
desktop_icons yes yes/no Whether the desktop icon surface runs
slit no yes/no The dockapp column (the slit): one line of text per dockapp, a command re-run on an interval, as set in ~/.config/kdos/slit.conf
clipboard yes yes/no The clipboard history daemon
icons yes yes/no Whether the panel and the desktop draw pictures at all
chrome_font Terminus:pixelsize=32 a fontconfig pattern The font of every supervised child except the panel: the desktop icons, the slit and the session daemons. The panel does not pass it on to the menus and popups it opens, which draw in libkwl’s default
clock_format %H:%M a strftime format The panel clock
osk off off, manual, auto The on-screen keyboard: not started, started hidden, or also shown while a text field has the focus

chrome_font and clock_format are empty in the compositor when unset, and the defaults shown are what the receiving programs use in that case: the Terminus:pixelsize=32 of libkwl (the Wayland drawing library, see The C libraries) and the panel’s %H:%M.

panel_opacity stops at 20 rather than 0. A bar at zero would not be see-through; it would be invisible while still catching the pointer, with no way back except editing this file from a text console.

The panel’s height follows its font. A cell is half as wide as it is tall, so Terminus:pixelsize=20 with two cells makes a 40-pixel bar, while the menus it opens stay at the 32-pixel chrome font. Terminus is a bitmap face, so name a size it has (12, 14, 16, 18, 20, 22, 24, 28 or 32 pixels); any other size is answered with the nearest one it has.

The font page of kdos-style also writes both font keys. It changes only the family, keeps the size each key already carried and leaves every other line of comp.conf as it was. kdos-settings edits the two keys as plain text, so the value typed there, size included, is the value written. Surfaces that read comp.conf when they start, such as a menu opened after the change, show the new family at once; the supervised children receive their font from the compositor, which keeps the value it read at login.

Files whose existence is the setting

Two files ship absent, and absent is a working default for both. The compositor does not read them; they are listed here because they sit beside comp.conf.

File Enables Read by
~/.config/kdos/session-restore Reopening the previous session’s applications at login The session scripts
~/.config/kdos/a11y The accessibility stack inside boxes kdos-appbox

~/.config/kdos/favorites has a similar role but ships with seven pinned entries, because an empty list leaves the Start menu’s pinned column and the palette’s pinned rows blank on a new machine. Delete every line if you want it empty. The panel’s quick-launch row reads the whole line as the identifier, and every shipped line carries a code=, so on a new account that row is empty; see Configuration. An identifier with no matching desktop entry is skipped silently, so an application that is not installed leaves no launcher that opens nothing.

Bindings

The shipped bindings are listed in The desktop, and Super+F1 shows a card generated from your own rc.xml. Two rules about editing rc.xml matter more than any single binding, because breaking either one breaks the whole desktop without any message.

The one line that must not be lost

<default /> must be the first child of both <keyboard> and <mouse> in rc.xml:

<keyboard>
  <default />
  <keybind key="W-Return"><action name="Execute" command="foot"/></keybind>
</keyboard>

The compositor loads its built-in bindings only when your file defines none of that kind, so a file that binds a single key throws every default away: click-to-focus, dragging by the title bar, the window buttons, resizing by the border, the root menu, window cycling, close and the snap arrows. On a running system the symptom is that the mouse appears not to work, and nothing at run time warns about it. Put your own bindings after <default />: when a key is bound twice, the later binding wins.

testing/preflight.sh fails a shipped rc.xml that binds anything in <keyboard> or <mouse> without <default /> in that section. It does not see the copy in your home directory.

-- may not appear inside an XML comment

rc.xml is XML, and XML forbids -- inside a <!-- --> comment. A comment that mentions a command-line option such as --app-id makes the whole file invalid, and a compositor that cannot parse its configuration loads none of the bindings in it. Nothing says so; the chords are missing, one by one, in whatever order you happen to try them. testing/preflight.sh parses the shipped file with a real XML parser for this reason. Run xmllint --noout rc.xml after editing yours.

KDOS actions

Besides labwc’s actions, the fork adds nine, usable in any binding or menu and through kdos hey run. Six are the accessibility switches, bound in the shipped rc.xml beside labwc’s magnifier actions:

Action Shipped binding Does
ToggleStickyKeys Super+Alt+S Sticky keys on or off for the session
ToggleSlowKeys Super+Alt+L Slow keys on or off
ToggleBounceKeys Super+Alt+B Bounce keys on or off
ToggleDwellClick Super+Alt+D Dwell click on or off
ToggleLargeCursor Super+Alt+C Switch the pointer between its size and large_cursor_size
ToggleOnScreenKeyboard Super+Alt+K Show or hide the on-screen keyboard; with osk = off a notification says so
ToggleMagnify (labwc’s) Super+= The magnifier on or off
ZoomIn, ZoomOut (labwc’s) Super+Alt+=, Super+Alt+- Magnify more or less

Each of the six switches posts a notification saying which way it went, such as Sticky keys on. A keyboard aid switched on by accident otherwise leaves a keyboard that misbehaves with nothing on the screen to say why. A switch lasts for the session. A reload changes it only when the value comp.conf gives it has changed since the last load, because every kdos theme is a reload and an accent switch must not undo a keyboard aid somebody has just switched on.

The other three are for window groups: AddToTabGroup, RemoveFromTabGroup and NextInTabGroup. The shipped rc.xml does not bind them. To reach them, add bindings after <default />; Super+g, Super+Ctrl+g and Super+Alt+g are free in the shipped file:

<keybind key="W-g"><action name="AddToTabGroup"/></keybind>
<keybind key="W-C-g"><action name="RemoveFromTabGroup"/></keybind>
<keybind key="W-A-g"><action name="NextInTabGroup"/></keybind>

The app-first window switcher

The window switcher accepts a third on-screen style, apps, beside labwc’s classic and thumbnail. It shows one row per application rather than one per window, with a count when an application has several windows (Firefox ×4). Tab, Left and Right step between applications; Up and Down step between the windows of the selected application, and the selected row shows the title of the window that would be focused. Releasing the modifier focuses that window. Windows are grouped by application identifier, and a window with none is a group of its own.

The shipped rc.xml leaves the switcher at labwc’s default, classic. To use the application style:

<windowSwitcher>
  <osd style="apps" />
</windowSwitcher>

The deprecated attribute form, <windowSwitcher style="…">, does not accept apps.

Decorations

The window frame is generated from the same palette as everything else, into themerc-override, which the compositor reads over its built-in theme. Frames therefore retint live with the panel and the phosphor pass. kdos theme rewrites that file whole on every run, so edits made to it by hand are lost; lines that must survive belong in a style file (kdos theme style, see The kdos command), which keeps them in ~/.config/kdos/style-themerc and appends them after the generated block. The reasoning behind the frame’s look is in the design language; the mechanics are these:

The title-bar font must name a scalable face. Pango, which draws the titles, does not render bitmap fonts, so naming the bitmap console font silently falls back to a generic sans for every title bar and menu. The shipped rc.xml names Terminus (TTF) at 24 points (32 pixels at 96 dpi) for the title bars, the menus and the on-screen display, so a title bar is exactly one cell tall. The face comes from the terminus-ttf port. A machine without it falls back to Noto Sans, which the noto-fonts port’s preference file puts first for sans-serif.

To tell the two apart, measure rather than look: count the brightness levels of the text in a screenshot. Bitmap text has three and no midtones; an antialiased face has well over a hundred.

The generated theme sets menu.width.max: 900. The upstream default is sized for a small font, and at 32 pixels it truncates menu entries at about eleven characters. A maximum only ever truncates, so a generous one costs a short menu nothing.

The prompt command

<core><promptCommand> in rc.xml is:

kdos-prompt --message '%m' --no '%n' --yes '%y'

labwc’s conditional action (If with a <prompt>) runs the prompt command and acts on its exit status: 0 takes the then branch, 254 means cancelled, and anything else takes the else branch. Upstream’s own prompt program, labnag, is not built. kdos-prompt is kdos-shell under another name and answers with the same exit codes. It is what lets ending the session (Super+Escape), restarting and shutting down ask before they act.

Upstream runs this one command through /bin/sh -c. The fork splits it into an argument vector with glib’s shell-word parser and executes it directly, so the quoting in the default command works, but variables, globbing and other shell expansion do not. The same holds for every Execute action, as it does upstream.

Frame pacing and the output mode

Nothing in the compositor sets a frame rate. It draws only when an output’s backend reports that the screen is ready for another frame; the handler composites once and returns. There is no fixed period, so the rate is the display mode’s: a 144 Hz panel gets 144 frames a second for the same reason a 60 Hz one gets 60. An output with nothing to redraw skips the frame, which is why an idle desktop costs almost nothing. The one timer in the loop is optional: render-late scheduling moves the composite later within the same refresh, and never adds a frame.

Two rate limits exist, and neither holds the frame rate back. Interactive resizing sends a window at most one new size per refresh interval of its output (250 per second when the output reports no refresh rate). The shutdown animation steps every 16 ms, after the event loop has already stopped.

Choosing the mode

The resolution comes first and then the highest refresh rate that works:

  1. The panel’s preferred mode (its EDID preferred timing) fixes the resolution.
  2. Every mode at that resolution with a higher refresh rate is tried, fastest first.
  3. The preferred mode itself is tried.
  4. The first mode that passes its test is used. If none passes, every other mode the output lists is tried, in the order the output lists them. If none of those passes either, the output is committed with no fixed mode.

Taking the preferred mode’s own rate would leave many machines below their panel’s rate: panels commonly advertise 60 Hz as preferred and list 120 or 144 Hz elsewhere, and the session would sit at 60 Hz with nothing on the desktop offering a choice. Trying fastest first is safe, because a rate the cable or link cannot carry fails its test and the next one down is tried.

Two things override this. A mode a program requests through the output-management protocol (for example kdos-display) is tried exactly as asked, which is how you pin a rate. And reuseOutputMode in rc.xml keeps a mode that is already set ahead of any of this, which stops a handover from re-setting the mode of a screen that is already working.

Fullscreen: scanout, variable refresh and tearing

A frame can skip compositing altogether. When the only thing visible on an output is one window’s buffer and the display controller accepts that buffer as it is, wlroots hands it to the display instead of drawing it into a buffer of the compositor’s. This is direct scanout, and it is decided again on every frame of every output. It is allowed on a frame the phosphor pass leaves alone: crt = 0, a renderer that cannot run the pass, a pass in its failure cooldown, and, the case it is for, a fullscreen window under crt_fullscreen = no. A frame the pass draws never scans out, because the pass needs a picture of the whole desktop (see How it is implemented). Nor does a frame while the magnifier is on, because the magnified inset is drawn into the composited buffer. Setting WLR_SCENE_DISABLE_DIRECT_SCANOUT=1 in the session’s environment turns it off for the session.

wlroots still refuses a frame it cannot scan out, and composites it as usual:

The shipped rc.xml sets two upstream options in <core> for fullscreen windows:

A display or driver that supports neither refuses the request, and the output keeps its fixed rate. To see whether a frame scanned out, run the session with KDOS_COMP_DEBUG=1: wlroots logs Direct scan-out enabled and disabled on each change. The frame’s cost shows as render_ms in kdos stutter. The test rig cannot show either, because its virtio display forces a software cursor.

Render-late scheduling

By default a frame is composited the moment the output’s frame event arrives. On a directly driven display that is just after the previous frame was shown, so the finished frame then waits in the display for most of a refresh, and a program’s buffer that arrives a moment after the composite waits a whole refresh more. max_render_time = <ms> in comp.conf holds the composite back until that many milliseconds before the next vertical blank, predicted from the last presentation and the refresh the display reports, so whatever programs commit in the meantime is in it. At 60 Hz that brings such a commit to the screen up to one refresh, about 16 ms, sooner. This is an estimate: the test rig has no display with a vertical blank, so it has not been measured.

The budget is yours to size, and it is off by default for that reason. It must cover the composite and the phosphor pass on the machine’s GPU. The compositor cannot measure that: the render_ms it reports to kdos stutter is processor time up to the point the GL commands are submitted, not the time the GPU takes to finish them. Too small a budget misses the blank, and the frame is shown one refresh late, which kdos stutter reports as a miss. Start at half the refresh interval (8 at 60 Hz) and lower it while kdos stutter stays quiet.

The frame is composited at once, as with the key off, whenever the moment cannot be predicted:

While the timer runs, the output reports a frame as already pending, so a program’s damage does not start a second frame in the middle of the wait; it is picked up by the composite the timer starts. A commit that reaches the output some other way during the wait, such as a mode change or variable refresh switched on for a fullscreen window, cancels the timer, and that commit’s own frame event decides again; one that switches the output off lowers the pending flag the wait was holding. The key is read on every frame, so a reload applies it at once.

When an output appears

Each new output gets its own panel, desktop icons and dockapp column (see Supervised children), and the compositor runs kdos-display --apply, which re-applies the layout saved in ~/.config/kdos/displays.conf. The apply is delayed by one second and restarted by each further output, so a dock that brings up three screens causes one apply, not three.

The phosphor pass

The compositor draws the whole desktop through a shader that imitates a CRT: optional scanlines on every third physical row, a three-tap horizontal bleed, a vignette, optional barrel distortion, and a faint phosphor floor in the accent colour so that black is never quite black. It is on by default at 55 per cent, with scanlines and curvature off. The user’s guide is Theming.

Scanlines ship off because they fight the text underneath. The desktop is a grid of 16×32 cells, and a dark line on every third physical row crosses the glyphs at a period nothing on screen shares, so crisp two-colour text arrives striped. crt_scanlines = 60 turns them on.

The pass needs the GLES2 renderer. On software rendering, including a virtual machine with plain graphics, it is switched off at startup and the log says so.

Two short animations belong to the pass and run only when it is active and motion is on (see Motion):

How it is implemented

wlroots, the library under the compositor, has no shader interface: its rendering pass offers textures and rectangles, and its scene graph (the tree of buffers the compositor composites into each frame, called the scene below) has no callback node. What it does offer is a documented seam: the scene’s build-state call accepts a custom swapchain. So the scene composites into a buffer of the compositor’s own, and KDOS code draws that buffer into the output’s real buffer with the effect applied. Both swapchains come from the library’s own configuration call, so neither needs guesswork about formats or modifiers.

The pass redraws only what changed whenever it can. With crt_curve at 0 and no degauss running, an output pixel depends on the scene pixel under it and the ones beside it on its row, so the scene’s damage for the frame, grown by two columns and one row, contains what changed on screen. Each output keeps a buffer-age ring over its output buffers: for every buffer, what has changed since that buffer last held a picture. A frame redraws that region of the buffer it is handed, one scissored draw per rectangle (the region’s bounding box past 16), and commits with the frame’s own damage, which a display that supports damage clips can use to refresh only a strip. The whole output is redrawn, and committed as damage, instead when:

A buffer the pass has not drawn into before, after a mode change for example, is redrawn whole, but its commit carries only the frame’s damage: the screen already holds the previous frame.

The power-down always draws the whole output. The shader and the region code live in kdos-crt-pass.c, apart from everything that touches wlroots, so that testing/selftest.sh can run them offscreen (see Working on the compositor).

Five constraints shape it:

The curvature is normalised by how far the corners move, so no setting crops the desktop.

The phosphor colour is the accent’s primary colour from the shared palette, read from $XDG_CACHE_HOME/kdos/theme at startup and again on every reload, so a theme change retints the running shader with the same signal that repaints the panel.

What it costs

The pass does not limit the frame rate: it runs inside the same frame event as the composite, once per frame that has something to draw. Its costs are these:

crt_fullscreen = no skips the pass on an output whose topmost window on the current workspace is fullscreen, and that frame may scan out: no composite at all where the display accepts the window’s buffer, one instead of two where it does not. At 1920×1080 each full-screen pass reads and writes about 16 MB, so that is roughly 16–33 MB less memory traffic per frame (an estimate, not measured on hardware). crt = 0 removes the extra draw everywhere.

Inspecting it without a screen

KDOS_CRT_DUMP=<prefix> writes the pass’s input and output once, as <prefix>-in.ppm and <prefix>-out.ppm. KDOS_CRT_DUMP_FRAME=<n> waits until frame n before dumping, to get past the empty first frames. The input is read back through the texture the shader sampled rather than from the buffer, because that buffer is not always readable; when the input is an external image it cannot be read back at all, and only the output is written.

One property only real hardware can confirm. The composite and the pass run in wlroots’ one GL context, so their order is guaranteed, but the final display commit relies on implicit buffer fencing. Getting that wrong shows up as a torn frame, not as an error.

The wallpaper

The compositor draws the wallpaper itself: one scene buffer per output at the bottom of the scene, rebuilt when outputs are added, removed or rearranged. A separate wallpaper program, the usual answer on Wayland, would be the one program on this desktop that is not a character grid.

Which image is drawn:

  1. With wallpaper = none, no image.
  2. Otherwise, $XDG_CACHE_HOME/kdos/wallpaper.png (~/.cache/kdos/wallpaper.png) when it exists. kdos theme writes this file: the shipped wallpaper retinted to the accent.
  3. Otherwise, the wallpaper = path from comp.conf.

Under every output, image or no image, lies a rectangle in the accent’s deep colour, so wallpaper = none and a missing or unreadable file both show that colour; the log names the path that failed. Because the retinted cache takes precedence, a picture of your own is replaced at the next accent change; Theming explains how to keep one. The image is scaled to cover the output and centred, so an image of another aspect ratio is cropped rather than distorted. Only PNG is decoded.

A reload re-decodes the image only when the chosen file or its modification time has changed, and redraws the deep-colour rectangles every time, since the accent may have moved.

kdos-wallpaper.c implements its own minimal wlroots buffer, because wlroots has no public way to make a buffer from memory. The decoder converts the PNG’s channel order to the one the buffer needs, and only a fully initialised buffer can leave it, because libpng reports a bad file by jumping back after the allocation.

Idle, dim, lock and lid

One timer drives three stages (dim, then lock, then screens off), each measured from your last input rather than from the previous stage. A stage set to 0 is skipped, so idle_off alone works. Ten seconds before the lock, a notification says Locking soon, and any input keeps the session open. Input ends the dim and powers the screens back on; it never unlocks. A screen that fails to power back on is retried at the next input.

The idle policy stops completely while any program holds an idle inhibitor (a video player, for example) or while the stay-awake toggle is on (kdos toggle stay-awake, bound to Super+Ctrl+i). The toggle is read every time the timer is re-armed, so it takes effect without a reload. An inhibitor is either the Wayland idle-inhibit protocol, which a native client such as mpv uses, or a cookie from org.freedesktop.ScreenSaver’s Inhibit, which kdos-comp serves on the session bus at /ScreenSaver and /org/freedesktop/ScreenSaver for X11 clients under Xwayland, such as VLC. A program that leaves the bus without calling UnInhibit has its cookies returned.

The dim is a black layer at 55 per cent opacity raised over everything, with the lock screen raised above it; it does not touch gamma, which some backends cannot set and which would stay applied if the compositor died. The lock stage starts kdos-lock (see The daemons), unless the session is already locked.

In a virtual machine

The three timers default to zero (never) and lid_close to off, unless comp.conf sets them. A blanked screen over a remote display cannot be told from a crashed compositor. The machine counts as virtual when the firmware names a hypervisor, and also when the compositor runs nested or headless with no seat session. Setting any one of the three timers overrides the virtual-machine default for all three, so idle_dim = 0 alone leaves the lock and screen-off timers at their defaults of 600 and 900 seconds. Only a line that parses counts: idle_dim = 5m is refused and leaves the virtual-machine default in place.

The lid

No other program on KDOS watches the lid switch; the compositor receives it from libinput. lid_close = suspend hands the machine to kdos-power suspend, which locks first; lock locks and powers the screens off; off only powers the screens off, which suits a closed laptop driving an external monitor. Opening the lid powers the screens back on and counts as input; it never unlocks.

Who owns the locked state

The compositor, not the lock program, owns the locked state. If the lock program dies without unlocking, the session stays locked: the lock surfaces keep covering every screen, and a new lock program can take over from the one that died, which is the recovery a crash needs. A lock screen that unlocked when it crashed would be the failure the lock protocol exists to prevent.

The frames socket

The compositor writes one line of JSON per late frame to $XDG_RUNTIME_DIR/kdos-frames.sock. This is what kdos stutter reads.

A client first receives a hello record naming the compositor and wlroots versions, with the monotonic time and the session’s frame and miss counts so far. After that it receives one miss record per late frame:

{"event":"miss","mono_ms":…,"wall_ms":…,"output":"eDP-1","source":"present","late_ms":…,"dropped":7,"render_ms":…,"refresh_hz":60.00}

The socket never slows the frame loop: both ends are non-blocking, and a reader that cannot keep up loses lines. There is no history, so a reader that connects late has missed what happened. Each miss is also written to the log at debug level.

This is deliberately not a Wayland protocol. It is a channel between two KDOS programs; a client that wants its own timing has the standard presentation-time protocol.

The command socket

Other KDOS programs ask the compositor questions over $XDG_RUNTIME_DIR/kdos-cmd.sock: one JSON request line in, one JSON response line out, then the connection closes. kdos hey is the command-line front end.

Request Answers or does
{"cmd":"list"} Every window: id, app_id, title, workspace, x, y, w, h, pid, box, instance, and the states focused, minimized, maximized, fullscreen and shaded
{"cmd":"outputs"} Every output: name, w, h, scale, x, y and enabled
{"cmd":"boxes"} The distinct boxes that currently have a window on screen
{"cmd":"run","action":"Close","id":7} Runs a labwc action, on the window with that id when one is given
{"cmd":"peek","on":true} Fades every window to reveal the desktop; false or no on restores them
{"cmd":"thumb","app_id":"foot","w":64,"h":36} Writes a picture of that application’s front window and answers with its path

A success answers {"ok":true,…}. Every refusal is {"ok":false,"err":"…"} with a reason, never a silent no-op.

A window’s id is fixed for the life of the session and never reused, so an id handed back late cannot reach the wrong window. run resolves the action name against the same table rc.xml is parsed with, so it can do exactly what a key binding can. An action that needs an argument the request cannot carry, such as Execute’s command or SnapToEdge’s direction, is refused.

The socket protects the compositor and the machine:

kdos-box gc uses boxes to ask whether a box still has a window before stopping it.

Window thumbnails

A Wayland client cannot see another client’s pixels, so the compositor renders a window to a file on request (the thumb verb above). That is what the panel’s hover preview is made of.

The compositor picks the most recently active window with the requested application identifier, box-filters its buffer down to the requested size and writes a binary PPM. The size must be between 8×8 and 160×100 pixels; the file is small on purpose, because the consumer reduces it to a few dozen text cells. A box filter rather than point sampling is used because the content is mostly text, and a one-pixel stroke would rarely be the pixel a point sampler kept.

The panel draws the preview as solid blocks from the palette’s brightness ladder rather than through the shape-matching character renderer. A large window squeezed into a small grid puts many source pixels in each cell, so no shape is left to match and every textured cell would pick the same character.

The preview is never required. No compositor, no socket, a window whose pixels are not readable (a client drawing into GPU memory, which is common on real hardware), a buffer in a format other than 32-bit ARGB or XRGB, or a file that does not parse: each one leaves the tooltip as the two lines of text it always carries.

Window groups and window memory

Window groups

Window groups stack several windows into one frame with a tab per member, like tabs in a browser: AddToTabGroup stacks the focused window onto the one behind it, RemoveFromTabGroup takes it back out, and NextInTabGroup steps through the tabs. Clicking a tab raises that member. The hidden members are minimised, so focus cycling and the panel’s window list treat them correctly, and all members share the showing member’s position and size. Dragging a tab moves that window; it does not take it out of the group. Use RemoveFromTabGroup for that.

Window memory

Window memory (window_memory = yes) reopens an application where you left it. When a window closes, its application identifier, geometry, workspace and shaded state are saved to $XDG_STATE_HOME/kdos/winpos (~/.local/state/kdos/winpos); when a window of that application next opens, the record is applied. The file keeps the 200 most recently used applications, and is written to a temporary file, flushed, and renamed into place so that a crash cannot leave it half written. The record applies only where the compositor would otherwise have chosen the position:

Box identity

The compositor knows which box each window came from. A box is the container an application runs in (see Packs and boxes). kdos-boxsock gives every box its own Wayland socket and tags each client that connects through it with a security context naming the box, and a second field distinguishing two runs of the same application.

An X11 window needs another route. Its Wayland client is Xwayland, running on the host with no context, so the context lookup would answer nothing for every X11-only application in the catalogue. The window does carry its client’s process id, and every process a box starts has KDOS_BOX=<name> in its environment, so the compositor reads that from /proc/<pid>/environ and caches the answer.

The box chip

The box chip is a small square in the box’s colour at the left of the title. To give a box a colour, set accent = <name> in its profile, ~/.config/kdos/boxes/<name>.conf (see Configuration):

Grants

A client from a box may bind only a fixed allowlist of Wayland interfaces. A grant = … line in the same profile adds named interfaces, such as screencopy for a screen recorder in its own box. The grantable names are listed in The session. A box’s grants are read once, on its first request, and re-read after a reload; a client that is already running keeps what it bound.

Accessibility

The compositor is where input arrives, so the aids that change what a key or the pointer does live in it (kdos-a11y.c), and so does the interface a screen reader uses to hear the keyboard (kdos-a11ymon.c). The user’s side of all of this is in Accessibility.

The keyboard aids

Aid Key What it does
Sticky keys sticky_keys A modifier pressed and released on its own applies to the next key; pressed twice it stays on until pressed a third time
Slow keys slow_keys, slow_keys_delay A key counts only once it has been held for the delay. Released sooner, it never happened
Bounce keys bounce_keys, bounce_keys_delay A second press of the same key within the delay after its release is dropped, with its release

All three start off, take a changed value on a reload, and are flipped for the session by their actions. They share three rules:

Slow keys holds one key at a time. A second key pressed before the first has been held long enough replaces it, and the first stays swallowed until it is released. A key it accepts reaches the window stamped with the time it was accepted, not the time it went down.

Dwell click

With dwell_click on, the pointer resting for dwell_click_delay clicks the left button where it rests. Motion within 4 pixels of where the count started does not restart it, because a resting hand still moves a mouse a pixel or two. After a click the pointer has to travel 16 pixels before it can click again, so a pointer left alone clicks once. A real button press cancels the count, no dwell click fires while a button is held, and none fires during an interactive move or resize, where a click would end it. There is no visual countdown. The click goes through the same path as any emulated button, so window bindings, menus and the root menu all answer it.

The pointer size

cursor_size sets the pointer’s size, and large_cursor (or ToggleLargeCursor) switches it to large_cursor_size. The KDOS-cursors theme draws 24, 32, 48, 64 and 96 pixels. cursor_size = 0 keeps the size the session started with, which is XCURSOR_SIZE (24, from /etc/profile.d/10-wayland.sh, or whatever ~/.config/kdos-comp/environment sets). The chosen size is written back into the compositor’s own XCURSOR_SIZE, so a program started after the change inherits it; one already running keeps the size it read. A change applies at once.

The on-screen keyboard

osk decides whether the compositor runs wvkbd-deskintl, the on-screen keyboard, as a supervised child. It is started with --hidden and shown and hidden by signal: SIGUSR2 shows it and SIGUSR1 hides it.

osk Does
off The keyboard is not started. ToggleOnScreenKeyboard posts a notification saying so
manual Started hidden; ToggleOnScreenKeyboard (Super+Alt+K) shows and hides it
auto As manual, and also shown whenever a text field has the keyboard focus and hidden when none does

auto follows the text-input protocol: the keyboard is shown when a window’s text field enables text input and hidden 300 ms after the last one disables it, so moving from one field to the next does not drop the keyboard and raise it again. A window is told about text input only while an input method is running, so auto needs fcitx5, which the session starts (see Input methods). An X11 window under Xwayland does not speak the protocol, and auto never shows the keyboard for it.

wvkbd types through the virtual-keyboard protocol as an ordinary host program. It is not passed chrome_font: it names its font with -fn, and handed --font it would exit with a usage error at every start.

The keyboard monitor

A Wayland window receives keys only while it has the focus, so a screen reader in a window of its own can neither echo what is typed elsewhere nor answer its own shortcuts. The compositor therefore owns org.freedesktop.a11y.Manager on the session bus and serves org.freedesktop.a11y.KeyboardMonitor at /org/freedesktop/a11y/Manager, the interface at-spi2-core’s AtspiDeviceA11yManager uses and Orca reaches through it:

Member Does
WatchKeyboard, UnwatchKeyboard Every key reaches the caller as a KeyEvent and the focused window as well
GrabKeyboard, UngrabKeyboard Every key reaches the caller and nothing else
SetKeyGrabs(au modifiers, a(uu) keystrokes) modifiers are keysyms the caller uses as its own modifier, such as the Orca key: each is grabbed, and so is every key pressed while one is held. Pressed twice within the key-repeat delay with no other key between, the second press and its release go through as an ordinary key, so Caps Lock as the Orca key still toggles Caps Lock. keystrokes are keysym and modifier-state pairs, grabbed when the state matches exactly
signal KeyEvent(b released, u state, u keysym, u unichar, q keycode) Sent to each interested caller alone, never broadcast. state is the modifier mask before the key, keysym the first translated keysym, keycode the xkb code (the evdev code plus 8)

A grabbed key reaches no window. That includes the lock it would toggle: xkb has already flipped Caps Lock or Num Lock by the time the key is seen, so the flip is put back. A caller’s grabs end when it leaves the bus, because a reader that crashed never calls UngrabKeyboard.

Only a caller that owns one of the allowed well-known names may call; anything else is answered org.freedesktop.DBus.Error.AccessDenied. The one allowed name is org.gnome.Orca.KeyboardMonitor, which the client library requests for Orca before its first call. Boxes share the session bus (see The security model), and a program in a box could request the same name while no reader holds it. The name check keeps an ordinary program from subscribing to keystrokes by accident; it does not stop a hostile one. While the session is locked nothing is sent and nothing is grabbed, because a password is typed there.

The monitor hears physical keyboards only, and it hears each key after the keyboard aids have had it, so a reader announces what slow and bounce keys let through. The input method re-sends the keys it does not use through a virtual keyboard, and passing those on as well would give a reader every key twice; the price is that keys typed on the on-screen keyboard are not announced.

The compositor connects to the session bus when it starts. With no session bus it logs a11y monitor: no session bus and runs without the monitor; if the bus goes away during the session the monitor is switched off for the rest of it. The toggle notifications travel over the same connection, to org.freedesktop.Notifications, and are not sent without it.

Motion

The compositor fades three things, and a client takes no part in any of them. With window_motion on it also moves windows (see Window transitions). (The phosphor pass’s degauss and power-down are its other two animations.)

What Fade Length
A surface on the top or overlay layer mapping: a menu, a toast, a tooltip, an on-screen display, the panel at login from transparent to opaque 120 ms
The same surface unmapping from where it was to transparent 90 ms
Peek starting or ending to 12 per cent opacity, or back 150 ms

Each fade eases out: most of the change comes in the first frames, so a menu is legible from its first frame and settles rather than arrives. The client commits its buffer once and the compositor re-blends it at a rising or falling alpha, so the fade costs the client nothing and a surface that knows nothing about motion fades as well. Background and bottom layer surfaces (the wallpaper, the desktop icons) do not fade on map or unmap, and windows fade only with window_motion on.

A closing surface usually exits with its window, so its close fade is drawn from a snapshot: the compositor copies the surface’s last buffers into scene nodes of its own at unmap, keeps them alive until the fade ends, and lets the pointer pass through them to whatever is beneath. The snapshot goes with the output if the output is removed first.

A surface that took the keyboard exclusively gets no close fade and disappears at once. That is a selection overlay such as the one kdos-shot region draws with slurp, and the screenshot taken straight after it would otherwise contain the overlay fading out. Nor does a surface that was not on screen when it closed, such as a top-layer surface under a fullscreen window, which hides the top layer. A screenshot taken within 90 ms of a menu closing does contain the menu’s fading snapshot.

motion = no in comp.conf makes every fade a single frame: a surface appears and disappears in one frame, peek jumps and windows make no transitions whatever window_motion says. It also skips the degauss on a reload and the power-down at exit. It applies on the next fade after a reload; a fade already running ends at once, at its end state. motion is the desktop’s one reduce-motion key.

What a fade costs: wlroots treats a buffer as opaque only while its opacity is exactly 1, so a fading surface hides nothing and everything beneath it is drawn for the length of the fade. Each fade frame redraws the surface’s own area (and the phosphor pass redraws the same area); a fade always ends on exactly 1, after which the surface occludes again. A fade is timed by the clock, not by frames, so a dropped frame shortens a step rather than lengthening the fade.

Window transitions

window_motion = yes in comp.conf, with motion on as well, gives windows short transitions. It is off by default.

What Transition Length
A window mapping, or coming back from being minimised fades in from transparent while it rises 12 pixels into place 150 ms
A window unmapping, or being minimised fades out from where it was while it sinks 12 pixels 120 ms
A workspace switch the new workspace’s windows fade in while sliding 48 pixels in from the side the switch goes towards; the old workspace’s windows fade out while sliding 48 pixels the other way 150 ms

They use the same curve and clock as every other fade. A fullscreen window fades but does not move, because its edge would pull away from the screen’s edge. Windows shown on every workspace, and a window being dragged across a workspace switch, take no part in it.

Every transition is a picture of a change that has already happened. The window is mapped, closed, minimised or on the other workspace the moment the event arrives; focus has moved, and keys and the pointer act on the new state at once. Nothing waits for a transition to end, so any of them can be cut short by the next action, and a second transition on the same window starts from wherever the first had got to. That holds for a window coming back while its own snapshot is still leaving, as on a switch straight back to the workspace just left or a minimise undone at once: the window takes over the snapshot’s alpha and position and the snapshot goes, so it is never drawn twice. While a window rises into place it takes the pointer where it is drawn. A window that labwc places again part way through (window rules and window memory move a window after it maps) carries on towards its new position.

A closing window is drawn from a snapshot, as a closing menu is: its buffers and its border rectangles are copied into nodes of the compositor’s own, the pointer passes through them, and the copy is freed when the fade ends. The old workspace’s windows leave as snapshots in the same way. Only position and opacity change, never size. A window is a tree of the program’s buffers and the compositor’s border rectangles, and a buffer can be drawn at another size while a rectangle cannot, so a scaled window would pull its border away from its contents.

A live window’s border does not fade: a scene rectangle has no opacity of its own, only a colour that the compositor rewrites as focus changes. The outline is there from the first frame and the contents fade in inside it, which is also what peek leaves on screen. A peek ends any window transition running when it starts, and none starts while a peek holds the windows’ opacity.

What a transition costs is what a fade costs: a moving or fading window hides nothing beneath it, so everything under it is composited for its length, which is why it is off by default. A moving window is not snapped to the character grid on its way: the compositor has no single cell pitch, since every program chooses its own font size and scale.

Supervised children

The compositor starts eight programs from a table in kdos-child.c and restarts them when they exit. Three run once per output; five run once for the session, four of them because they own a single bus name, socket or subscription.

Child Per output Started when
kdos-shell (the panel) yes panel is not off
kdos-desk (desktop icons) yes desktop_icons
kdos-slit (dockapp column) yes slit
kdos-notifyd (notifications) no always
kdos-netagent (Wi-Fi and VPN passwords) no always
kdos-mediad (removable media) no always
kdos-clip (clipboard history) no clipboard
wvkbd-deskintl (on-screen keyboard) no osk is not off

The single-instance children are single for concrete reasons:

The per-output children are per output because a layer surface (a surface that a program places on a screen layer above or below the windows, through the wlr layer-shell protocol) without a named output is placed on one screen only, and the libraries these programs draw with hold a single cell buffer, so a second screen needs a second process. Each is started with --output <name>. When an output goes away, its children are sent SIGTERM and not restarted. The table holds 40 children: the five session-wide ones plus three per output, which is eleven outputs’ worth.

The children receive their settings from comp.conf on their command line, which is why those keys apply at the next login:

When a child keeps crashing

A child that exits more than five times within 30 seconds is not restarted again. The log records it, and a kdos-prompt dialog names the program that was given up on (one dialog per reload, however many children fail). Reload Configuration in the desktop’s right-click menu, or kdos-comp -r, resets the counters and gives it another run of attempts.

Before starting a child, the compositor clears the signal mask and resets every signal the session had ignored. Ignored signals survive exec, so a session started under something like nohup would otherwise pass that on, and the live retint, which is delivered as SIGHUP, would never arrive.

Programs launched by key bindings are not supervised: a terminal opened with a key is not part of the desktop’s chrome.

The session start-up around these children is in The session.

Xwayland

The compositor runs Xwayland rootless, and it is the one X server on the system: it serves X11-only applications in boxes and host applications that have no Wayland path. Xwayland starts when the first X11 client connects, unless rc.xml asks for it to persist. It is built with glamor, DRI3 and GLX (-Dglx=true in ports/core/x11/xwayland/build.sh), and Mesa is built with the X11 platform and -D glx=dri behind libglvnd, so an X11 client on the host that draws through GLX gets OpenGL. An X11 client in a box draws with its box’s Mesa. See Principles.

The compositor sets DISPLAY only in the environment of programs it starts itself. A launcher run from elsewhere may not have it, so kdos-appbox finds the X socket in /tmp/.X11-unix on its own and adds DISPLAY to a box’s environment.

Smaller changes to upstream behaviour

Several additions are too small for a section of their own:

Shutdown

SIGTERM, SIGINT and kdos-comp -e stop the event loop. The KDOS parts are then taken down in this order:

  1. the command socket, so no kdos hey run can act on a session that is ending;
  2. the keyboard monitor’s bus connection and the accessibility timers;
  3. the render-late timers, so none fires inside the power-down animation;
  4. the power-down animation (see The phosphor pass), limited to 600 ms and skipped with motion = no;
  5. the wallpaper, the frames socket, window memory, window groups, box chips, the lid, peek, the fades (any running fade ends, a moving window is put back at rest, and every snapshot is freed) and the idle policy;
  6. the phosphor pass;

and then the server itself. The panel and the notification daemon notice the compositor has gone and exit on their own.

When the compositor exits with a non-zero status, kdos-desktop-start prints the last 20 lines of the log and offers to restart the session. At the third crash within 60 seconds it stops offering and returns you to the text console. A clean exit is a logout.

Debugging

The first place to look is $XDG_RUNTIME_DIR/kdos-comp.log (usually /run/user/1000/kdos-comp.log), with the previous session’s in kdos-comp.log.old. kdos doctor checks, among other things, that the compositor’s two sockets and the portals are up.

Variable or option Effect
KDOS_COMP_DEBUG=1, or -d Log at debug level; each late frame is logged as well
KDOS_CRT_DUMP=<prefix> Write the phosphor pass’s input and output once, to <prefix>-in.ppm and <prefix>-out.ppm
KDOS_CRT_DUMP_FRAME=<n> Wait until frame n before dumping

The fork logs at the informational level by default, where upstream logs errors only. At errors only, the KDOS additions’ decisions would be invisible: which wallpaper was loaded, whether the phosphor pass is on and why not, the idle timers and each idle stage as it fires, the lid policy, and which comp.conf lines were ignored. An empty log would look like a hung session.

Working on the compositor

This section is for someone changing the compositor’s code rather than configuring it.

Finding the KDOS additions

The KDOS code lives in twenty-three src/desktop/kdos-comp/src/kdos-*.c files, one shared header, src/desktop/kdos-comp/include/kdos.h, through which every addition enters, the phosphor pass’s own include/kdos-crt-pass.h, and one further source file, src/cycle/osd-apps.c, the application-first switcher. Upstream files carry only small hooks, each marked with a comment containing KDOS. Most begin /* KDOS: there are 148 such comments across 34 upstream files (27 sources and 7 headers), 37 of them in main.c. Some hooks sit inside a longer comment whose line begins ` * KDOS:, and three files carry only that form or the LAB_GRADIENT_KDOS_RULE fill: include/theme.h, include/buffer.h and src/ssd/ssd-button.c. Grep for KDOS rather than /* KDOS to find them all. The three meson.build files mark their changes with # KDOS`.

Three kinds of change carry no marker. The calls into libkwm (see below) are found by grepping for kwm_, and the stripped upstream directories are recorded only in the top-level meson.build. The fastest-first mode selection (see Choosing the mode) is highest_refresh_at() and the loop that calls it in output_test_auto() in src/output.c.

File (under src/desktop/kdos-comp/src/) What it adds
kdos-config.c The KDOS configuration file, its reload, and the accent lookup every addition shares
kdos-child.c Supervised children and the display-layout apply on a new output
kdos-wallpaper.c The wallpaper, as part of the scene rather than a separate program
kdos-crt.c The phosphor pass, the degauss and the power-down
kdos-crt-pass.c The pass’s shader and which pixels a frame redraws: GLES2 and pixman only, so a test can link it
kdos-frames.c The frames socket, reporting late frames
kdos-idle.c Idle, dim, lock and lid: the idle policy and the virtual-machine test
kdos-lid.c The lid switch and lid_close
kdos-cmd.c The command socket other KDOS programs query
kdos-thumb.c Window thumbnails for hover previews
kdos-peek.c Fading windows to reveal the desktop
kdos-motion.c Motion: the layer surfaces’ open and close fades, the moving and snapshot fades windows use, and the clock every fade shares
kdos-winmotion.c Window transitions: which windows move where on a map, an unmap, a minimise and a workspace switch
kdos-sched.c Render-late scheduling: the per-output timer and its prediction
kdos-appid.c The ledger of application identifiers windows actually present
kdos-boxchip.c The box colour chip on a title bar
kdos-grant.c Per-box grants beyond the sandbox allowlist
kdos-group.c Window groups (tabbed stacks)
kdos-layerfocus.c Closing menus and other on-demand surfaces when you click elsewhere
kdos-winpos.c Window memory: reopening windows where they were
kdos-a11y.c Accessibility: the keyboard aids, dwell click, the pointer size and the on-screen keyboard
kdos-a11ymon.c The keyboard monitor on the session bus, and the toggle notifications
kdos-screensaver.c org.freedesktop.ScreenSaver on the session bus: the idle inhibitor for X11 clients under Xwayland (Idle, dim, lock and lid)

The box lookup itself (kdos_view_box() and the /proc read for X11 windows) lives in the upstream view.c, beside the security-context lookup it extends.

What the compositor does not decide

The compositor owns no window-model arithmetic. Where a new window lands, what a tiled state becomes and what rectangle it occupies, which edge a moving edge stops against, and how workspace stepping skips empty workspaces all come from the libkwm library. kdos-comp calls eight of its functions: kwm_place (in placement.c), kwm_tile_geom and kwm_tile_next (in view.c), kwm_edge_check (in snap.c), kwm_ws_adjacent (in workspaces.c), and kwm_edge_best, kwm_clip_add and kwm_clip_sub (in include/edges.h, the edge-search arithmetic). The library is tested against a fixture with no compositor running, so every one of those rules can be checked without booting anything.

What stays here is what only a compositor can do: walking its own window list, asking the decoration how thick it is, working out which edges are actually visible, and deciding how a drag feels as it crosses one. See The window model.

Building and testing

build.sh compiles libkbase, libkcolor and libkwm from src/libs/ into one static archive, hands it to meson through LDFLAGS, and then runs the ordinary meson build and install. Editing any of those libraries therefore changes the compositor. The compositor is built in the 50_desktop phase and named in its list, script/phases/50_desktop/packages.txt, under the src-desktop heading; the same list builds wlroots (ports/core/wl/wlroots/) ahead of it. The Wayland base under both comes from the 41_system and 42_graphics phases (see How KDOS is built). The ports it ships beside are listed under src/desktop in the ports catalogue. To rebuild only the compositor:

make build BUILD_ARGS="--phases 50_desktop --rebuild kdos-comp"

Six checks cover it without a full build:

The QEMU rig boots with plain graphics and uses software rendering, so the phosphor pass never appears in its screenshots. Use KDOS_CRT_DUMP or real hardware to see it.

See also


Part IV — Programs, chapter 21. Previous: 20. The programs · Contents · Next: 22. kdos-shell