Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
scripts/startcce (8.1K)
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 # Mesa's rusticl exposes NO OpenCL devices unless enabled per driver — without
61 # this the session has zero OpenCL platforms from Mesa, and every OpenCL
62 # consumer that wants the Intel GPU fails quietly: the designer's
63 # kernel-generated geometry (the default project's sphere) simply never
64 # appears. (The NVIDIA ICD is live too and serves the discrete GPU.)
65 export RUSTICL_ENABLE=iris
66
67 # The compositor binary this session runs, resolved once so the server launch
68 # and the generated client launcher cannot disagree. Absolute rather than
69 # leaning on the PATH set above: this is the one process a session cannot
70 # recover from failing to find. $HOME is guaranteed — cce-display-manager sets
71 # it explicitly when it execs this script, and the config read below already
72 # depends on it.
73 CCE_BIN="$HOME/.local/bin/cce"
74
75 # Session runtime files: per-user, not /tmp. In /tmp these were a single path
76 # every user of the machine raced for — whoever logged in first owned
77 # /tmp/cce.log, and /tmp's sticky bit then denied everyone else, so a second
78 # user's session logged nothing (or failed to write its launcher at all).
79 # XDG_RUNTIME_DIR is /run/user/UID, 0700, exported just above. It is cleared
80 # at logout, which costs nothing here: the compositor log is truncated on
81 # every login anyway, and a start failure is tailed to the console below
82 # while the directory still exists.
83 CCE_RUN="$XDG_RUNTIME_DIR/cce"
84 if ! mkdir -p "$CCE_RUN" 2>/dev/null; then
85 # Gate on the failure itself, not on a guess about when it can happen:
86 # pam_systemd normally creates /run/user/UID before this script runs, but
87 # if it is missing or unwritable the launcher below cannot be written and
88 # the session comes up with no client at all. /tmp is worse but it works.
89 echo "startcce: $CCE_RUN unusable, falling back to /tmp" >&2
90 CCE_RUN=/tmp
91 fi
92 CCE_LOG="$CCE_RUN/cce.log"
93 CCE_CLIENT_LOG="$CCE_RUN/cce-client.log"
94 CCE_LAUNCH="$CCE_RUN/launch.sh"
95
96 # The DE's default terminal (config.kdl `default_terminal`, set from the
97 # settings app) becomes $TERMINAL for the whole session, so non-cce tools
98 # that honor the convention agree with the launcher. A crude KDL read, but
99 # the key is a single top-level string node.
100 TERMINAL_CFG=$(sed -n 's/^[[:space:]]*default_terminal[[:space:]]*"\([^"]*\)".*/\1/p' "$HOME/.config/cce/config.kdl" 2>/dev/null | head -n 1)
101 if [ -n "$TERMINAL_CFG" ] && command -v "$TERMINAL_CFG" >/dev/null 2>&1; then
102 export TERMINAL="$TERMINAL_CFG"
103 fi
104
105 # Kill any stale processes to ensure DRM master and sockets are released.
106 # killall matches on comm (argv[0]'s basename), which reflects how a process
107 # was started, not which binary it is: started through the `cce` symlink comm
108 # is "cce", but a compositor left over from `make run` / `cargo run --bin
109 # cce-fx` has comm "cce-fx" and would survive this sweep still holding DRM
110 # master. So name both. ("cce-client" sat here for years and never matched
111 # anything: there is no such binary — the client is the subcommand
112 # `cce client`, whose comm is also "cce", already covered.)
113 killall -q cce cce-fx 2>/dev/null
114 # pkill -9 -f antigravity 2>/dev/null
115 # pkill -9 -f language_server 2>/dev/null
116
117
118 # Create the cce-client launch script
119 DEBUG_FLAG=""
120 if [ "$DEBUG" = true ]; then
121 DEBUG_FLAG="WAYLAND_DEBUG=1 "
122 fi
123
124 # Unquoted delimiter on purpose: $DEBUG_FLAG, $TERMINAL, $CCE_BIN and
125 # $CCE_CLIENT_LOG expand HERE, so the generated script carries concrete values
126 # and depends on none of this environment. Quoting LAUNCH_EOF would ship the
127 # literal '$CCE_BIN' and the client would never start.
128 cat > "$CCE_LAUNCH" << LAUNCH_EOF
129 #!/bin/sh
130 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${TERMINAL:+ TERMINAL}
131 systemctl --user start cce-session.target
132 ${DEBUG_FLAG}exec "$CCE_BIN" client 2>"$CCE_CLIENT_LOG"
133 LAUNCH_EOF
134 chmod +x "$CCE_LAUNCH"
135
136 LOG_LEVEL_FLAG=""
137 if [ "$VERBOSE" = true ] || [ "$DEBUG" = true ]; then
138 LOG_LEVEL_FLAG="--log-level debug"
139 fi
140 if [ "$LOGGING" = true ] || [ "$VERBOSE" = true ] || [ "$DEBUG" = true ]; then
141 echo "Starting cce-server with cce..."
142 echo " Logs: $CCE_LOG + $CCE_CLIENT_LOG (compositor level: ${LOG_LEVEL_FLAG:---log-level info})"
143 if [ "$DEBUG" = true ]; then
144 echo " Wayland debug logging enabled (WAYLAND_DEBUG=1)"
145 fi
146 fi
147
148 if [ "$DEBUG" = true ]; then
149 export WAYLAND_DEBUG=1
150 fi
151
152 # Clear out any old log file so we only see errors from this run
153 > "$CCE_LOG"
154
155 # Run CCE in the background so we can check if it exits immediately
156 "$CCE_BIN" ${LOG_LEVEL_FLAG} -c "$CCE_LAUNCH" 2>>"$CCE_LOG" &
157 CCE_PID=$!
158
159 # Wait briefly to verify it launched successfully
160 sleep 1.2
161 if ! kill -0 $CCE_PID 2>/dev/null; then
162 wait $CCE_PID
163 EXIT_CODE=$?
164 echo ""
165 echo "Error: cce failed to start (exited with code $EXIT_CODE)."
166 echo "Showing last 15 lines of $CCE_LOG:"
167 echo "----------------------------------------"
168 tail -n 15 "$CCE_LOG"
169 echo "----------------------------------------"
170 exit $EXIT_CODE
171 fi
172
173 # If it runs fine, wait on it to finish
174 wait $CCE_PID
175 systemctl --user stop cce-session.target
176