Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
scripts/startcce (9K)
1 #!/bin/sh
2 # Launch cce-server with cce-client
3 # Usage: startcce [--logging] [--verbose] [--debug]
4 #
5 # --logging announce the log paths. The compositor's stderr goes to
6 # $CCE_RUN/cce.log regardless; this flag no longer raises the
7 # log level. The session entry in /usr/share/wayland-sessions
8 # passes it, and it used to mean `--log-level debug`: every
9 # transaction, key press and xwm property became a formatted
10 # line on the compositor's main thread, tens of MB per session,
11 # for a log that is only read when something goes wrong. Start
12 # with --verbose when it is.
13 # --verbose compositor at `--log-level debug` (what --logging did before
14 # 2026-09-11).
15 # --debug --verbose plus WAYLAND_DEBUG=1 on the client.
16
17 LOGGING=false
18 VERBOSE=false
19 DEBUG=false
20 for arg in "$@"; do
21 case "$arg" in
22 --logging) LOGGING=true ;;
23 --verbose) VERBOSE=true ;;
24 --debug) DEBUG=true ;;
25 esac
26 done
27
28 export XDG_RUNTIME_DIR=/run/user/$(id -u)
29 export XDG_CURRENT_DESKTOP=cce
30 export XDG_SESSION_TYPE=wayland
31 export PATH="$HOME/.local/bin:$PATH"
32 export XCURSOR_THEME="cce"
33 # No XCURSOR_SIZE. Wayland toolkits already default to 24 logical pixels (sctk,
34 # GTK and Chromium all do), and the two worlds want DIFFERENT numbers: under
35 # `xwayland_hidpi` X11 is a physical-pixel world, so an X11 cursor has to be
36 # 24*scale, which the compositor publishes as the `Xcursor.size` X resource
37 # when Xwayland comes up (server.rs). XCURSOR_SIZE beats that resource in both
38 # libXcursor and libxcb-cursor, so exporting it here made every X11 app ask for
39 # a 24-pixel cursor and get one drawn at half size.
40 export XCURSOR_PATH="${XDG_DATA_HOME:-$HOME/.local/share}/icons:$HOME/.icons:/usr/share/icons"
41 # KEEP THIS. Removed on 2026-09-11 (cce-compositor@43a9108) on the theory
42 # that the Intel cursor plane would work now that the session is pinned to
43 # card1 and wlroots falls back to software on failure; the very next login
44 # had NO visible cursor at all — the plane accepted the image and showed
45 # nothing, so there was no failure to fall back from. A software cursor
46 # costs a scene composite per pointer motion; an invisible one costs the
47 # session. Do not remove again without verifying on the real display.
48 export WLR_NO_HARDWARE_CURSORS=1
49 export WLR_DRM_DEVICES=/dev/dri/card1
50 # The session renders on the Intel GPU, so Vulkan is pinned to the Intel ICD.
51 # This is not only a compositor concern: an app that also loads the NVIDIA
52 # Vulkan ICD (libGLX_nvidia.so.0) and then asks NVIDIA OpenCL about its
53 # Mesa GL context segfaults inside the NVIDIA driver — Houdini did, six
54 # seconds after launch, every time it came from the launcher. It is
55 # imported into the systemd user manager below for that reason: apps spawned
56 # by cce-cloud (a user service) inherit the manager's environment, not this
57 # script's, and without the import they ran under a different GPU setup than
58 # anything the compositor spawned directly.
59 export VK_DRIVER_FILES=/usr/share/vulkan/icd.d/intel_icd.json
60 # EGL gets the same pin. WLR_DRM_DEVICES keeps the compositor off the NVIDIA
61 # KMS node, but glvnd's libEGL still probes every installed vendor when a
62 # display is created, and libEGL_nvidia opens /dev/nvidiactl and /dev/nvidia0
63 # to answer — so the compositor held the discrete GPU out of its runtime-D3
64 # sleep for the whole session (2026-09-28 audit: ~3 W on battery, never
65 # suspended). Pinning glvnd to Mesa's vendor file means the NVIDIA EGL is
66 # never loaded. Nothing is lost: every external port is wired to the
67 # discrete GPU and WLR_DRM_DEVICES already excludes them, and GLX (Xwayland
68 # apps, __GLX_VENDOR_LIBRARY_NAME) and Vulkan (CCE_VK_DEVICE lifts the pin
69 # per process) are separate mechanisms. Imported below like VK_DRIVER_FILES,
70 # for the same reason: a launcher-spawned EGL client would otherwise wake
71 # the GPU too.
72 export __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json
73 # Mesa's rusticl exposes NO OpenCL devices unless enabled per driver — without
74 # this the session has zero OpenCL platforms from Mesa, and every OpenCL
75 # consumer that wants the Intel GPU fails quietly: the designer's
76 # kernel-generated geometry (the default project's sphere) simply never
77 # appears. (The NVIDIA ICD is live too and serves the discrete GPU.)
78 export RUSTICL_ENABLE=iris
79
80 # The compositor binary this session runs, resolved once so the server launch
81 # and the generated client launcher cannot disagree. Absolute rather than
82 # leaning on the PATH set above: this is the one process a session cannot
83 # recover from failing to find. $HOME is guaranteed — cce-display-manager sets
84 # it explicitly when it execs this script, and the config read below already
85 # depends on it.
86 CCE_BIN="$HOME/.local/bin/cce"
87
88 # Session runtime files: per-user, not /tmp. In /tmp these were a single path
89 # every user of the machine raced for — whoever logged in first owned
90 # /tmp/cce.log, and /tmp's sticky bit then denied everyone else, so a second
91 # user's session logged nothing (or failed to write its launcher at all).
92 # XDG_RUNTIME_DIR is /run/user/UID, 0700, exported just above. It is cleared
93 # at logout, which costs nothing here: the compositor log is truncated on
94 # every login anyway, and a start failure is tailed to the console below
95 # while the directory still exists.
96 CCE_RUN="$XDG_RUNTIME_DIR/cce"
97 if ! mkdir -p "$CCE_RUN" 2>/dev/null; then
98 # Gate on the failure itself, not on a guess about when it can happen:
99 # pam_systemd normally creates /run/user/UID before this script runs, but
100 # if it is missing or unwritable the launcher below cannot be written and
101 # the session comes up with no client at all. /tmp is worse but it works.
102 echo "startcce: $CCE_RUN unusable, falling back to /tmp" >&2
103 CCE_RUN=/tmp
104 fi
105 CCE_LOG="$CCE_RUN/cce.log"
106 CCE_CLIENT_LOG="$CCE_RUN/cce-client.log"
107 CCE_LAUNCH="$CCE_RUN/launch.sh"
108
109 # The DE's default terminal (config.kdl `default_terminal`, set from the
110 # settings app) becomes $TERMINAL for the whole session, so non-cce tools
111 # that honor the convention agree with the launcher. A crude KDL read, but
112 # the key is a single top-level string node.
113 TERMINAL_CFG=$(sed -n 's/^[[:space:]]*default_terminal[[:space:]]*"\([^"]*\)".*/\1/p' "$HOME/.config/cce/config.kdl" 2>/dev/null | head -n 1)
114 if [ -n "$TERMINAL_CFG" ] && command -v "$TERMINAL_CFG" >/dev/null 2>&1; then
115 export TERMINAL="$TERMINAL_CFG"
116 fi
117
118 # Kill any stale processes to ensure DRM master and sockets are released.
119 # killall matches on comm (argv[0]'s basename), which reflects how a process
120 # was started, not which binary it is: started through the `cce` symlink comm
121 # is "cce", but a compositor left over from `make run` / `cargo run --bin
122 # cce-fx` has comm "cce-fx" and would survive this sweep still holding DRM
123 # master. So name both. ("cce-client" sat here for years and never matched
124 # anything: there is no such binary — the client is the subcommand
125 # `cce client`, whose comm is also "cce", already covered.)
126 killall -q cce cce-fx 2>/dev/null
127 # pkill -9 -f antigravity 2>/dev/null
128 # pkill -9 -f language_server 2>/dev/null
129
130
131 # Create the cce-client launch script
132 DEBUG_FLAG=""
133 if [ "$DEBUG" = true ]; then
134 DEBUG_FLAG="WAYLAND_DEBUG=1 "
135 fi
136
137 # Unquoted delimiter on purpose: $DEBUG_FLAG, $TERMINAL, $CCE_BIN and
138 # $CCE_CLIENT_LOG expand HERE, so the generated script carries concrete values
139 # and depends on none of this environment. Quoting LAUNCH_EOF would ship the
140 # literal '$CCE_BIN' and the client would never start.
141 cat > "$CCE_LAUNCH" << LAUNCH_EOF
142 #!/bin/sh
143 systemctl --user import-environment DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP XDG_SESSION_TYPE XDG_SESSION_ID XDG_RUNTIME_DIR GNOME_KEYRING_CONTROL SSH_AUTH_SOCK RUSTICL_ENABLE VK_DRIVER_FILES __EGL_VENDOR_LIBRARY_FILENAMES${TERMINAL:+ TERMINAL}
144 systemctl --user start cce-session.target
145 ${DEBUG_FLAG}exec "$CCE_BIN" client 2>"$CCE_CLIENT_LOG"
146 LAUNCH_EOF
147 chmod +x "$CCE_LAUNCH"
148
149 LOG_LEVEL_FLAG=""
150 if [ "$VERBOSE" = true ] || [ "$DEBUG" = true ]; then
151 LOG_LEVEL_FLAG="--log-level debug"
152 fi
153 if [ "$LOGGING" = true ] || [ "$VERBOSE" = true ] || [ "$DEBUG" = true ]; then
154 echo "Starting cce-server with cce..."
155 echo " Logs: $CCE_LOG + $CCE_CLIENT_LOG (compositor level: ${LOG_LEVEL_FLAG:---log-level info})"
156 if [ "$DEBUG" = true ]; then
157 echo " Wayland debug logging enabled (WAYLAND_DEBUG=1)"
158 fi
159 fi
160
161 if [ "$DEBUG" = true ]; then
162 export WAYLAND_DEBUG=1
163 fi
164
165 # Clear out any old log file so we only see errors from this run
166 > "$CCE_LOG"
167
168 # Run CCE in the background so we can check if it exits immediately
169 "$CCE_BIN" ${LOG_LEVEL_FLAG} -c "$CCE_LAUNCH" 2>>"$CCE_LOG" &
170 CCE_PID=$!
171
172 # Wait briefly to verify it launched successfully
173 sleep 1.2
174 if ! kill -0 $CCE_PID 2>/dev/null; then
175 wait $CCE_PID
176 EXIT_CODE=$?
177 echo ""
178 echo "Error: cce failed to start (exited with code $EXIT_CODE)."
179 echo "Showing last 15 lines of $CCE_LOG:"
180 echo "----------------------------------------"
181 tail -n 15 "$CCE_LOG"
182 echo "----------------------------------------"
183 exit $EXIT_CODE
184 fi
185
186 # If it runs fine, wait on it to finish
187 wait $CCE_PID
188 systemctl --user stop cce-session.target
189