git.lucas.co / cce-compositor
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