Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
ccebuild: install app icons from any crate's hicolor/ tree
app_icons() mirrors a crate's hicolor/ tree verbatim into
$XDG_DATA_HOME/icons/hicolor/ and refreshes the GTK icon cache, so an
icon's size/context is its directory, not a rule in this script. Two
things are load-bearing and commented: -type l in the find (every file
in cce-icons/hicolor is a symlink into its svg/, and -type f alone
matches none of them — a clean install of nothing), and the deliberate
absence of package filtering (cce-icons has no Cargo.toml, so
crate_selected() could never select it).
Also documents the desktop entries' new Icon= convention (Icon=<binary
name>, backed by the theme) in WORKSPACE.md — before this, entries
borrowed generic freedesktop names that resolved only if another theme
happened to provide them, and cce-preview 'worked' only via five PNGs
hand-copied into ~/.local/share/icons, unversioned and gone on a fresh
clone.
Co-Authored-By: Claude <[email protected]>
CLAUDE.md | 10 ++++++++++
WORKSPACE.md | 19 ++++++++++++++++++
scripts/ccebuild | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 89 insertions(+)
diff --git a/CLAUDE.md b/CLAUDE.md
index 53c2c77..e0b1820 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -71,6 +71,16 @@ It also installs the **`.desktop` entries** crates ship at their own root into
in `~/.local/share/applications` until 2026-08-16; see `./WORKSPACE.md` for the
`Exec=`/`MimeType=` rules that go with them.
+**App icons** install from any crate's `hicolor/` tree (`app_icons()`), mirrored
+verbatim into `$XDG_DATA_HOME/icons/hicolor/` — so an icon's size and context are
+its directory, not a rule in the script, and `48x48/apps` would need no edit here.
+In practice the tree is `cce-icons/hicolor/`, whose files are all **symlinks** into
+its own `svg/`; that makes `-type l` load-bearing in the `find`, because `-type f`
+alone matches none of them and would report a clean install of nothing. The install
+is deliberately *not* package-filtered: `cce-icons` has no `Cargo.toml`, so
+`crate_selected()` can never match it. See `cce-icons/hicolor/README.md` for why the
+target is `hicolor` rather than the `cce` theme, and why it ships no `index.theme`.
+
**Helper scripts** are installed from **any** crate's `scripts/` dir, not just
this one's (`crate_scripts()`, same per-package filtering). A script belongs in
the repo whose code it is about — `cce-keyring-selftest` reports on the keyring
diff --git a/WORKSPACE.md b/WORKSPACE.md
index 04807f7..dfd8309 100644
--- a/WORKSPACE.md
+++ b/WORKSPACE.md
@@ -114,6 +114,25 @@ Two rules, both learned the hard way when these files lived only in
also means honoring it: the app has to accept the path or URL argv the field code
(`%f`/`%u`) passes it.
+### App icons
+
+An entry's `Icon=` should be the app's own name (`Icon=cce-files`), backed by
+`cce-icons/hicolor/scalable/apps/cce-files.svg`. `ccebuild install` mirrors any
+crate's `hicolor/` tree into `$XDG_DATA_HOME/icons/hicolor/` and refreshes the GTK
+icon cache; `cce-icons/hicolor/README.md` documents the naming and the symlink
+convention that keeps `svg/` the sole source of the artwork.
+
+Before 2026-08-16 the entries borrowed generic freedesktop names
+(`preferences-system`, `system-file-manager`), which resolved only if some other
+installed theme happened to provide them, and `cce-preview` "worked" only because
+five PNGs had been hand-copied into `~/.local/share/icons` — unversioned, and gone
+on a fresh clone. The same failure as the `.desktop` files themselves.
+
+Note that **nothing displayed an `Icon=` key at all** until the launcher was taught
+to: `cce-cloud`'s `AppInfo` had no icon field. `cce_ui::icon` is the shared
+resolver (theme name or absolute path → file); it is distinct from
+`cce_ui::upload_icon`, which loads a *bundled* cce-icons glyph for in-widget use.
+
`ccebuild status` is the tool for "is what's running actually the code I built?".
Because `install` unlinks before writing, a process still on the old inode reports its
exe as `(deleted)`, which is how both `status` and `restart` detect drift. It also
diff --git a/scripts/ccebuild b/scripts/ccebuild
index 1ac156a..bec344b 100755
--- a/scripts/ccebuild
+++ b/scripts/ccebuild
@@ -30,6 +30,7 @@ PREFIX="${CCE_PREFIX:-$HOME/.local}"
BINDIR="$PREFIX/bin"
UNITDIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
DESKTOPDIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
+ICONDIR="${XDG_DATA_HOME:-$HOME/.local/share}/icons"
DBUSDIR="${XDG_DATA_HOME:-$HOME/.local/share}/dbus-1/services"
die() { printf 'ccebuild: %s\n' "$*" >&2; exit 1; }
@@ -134,6 +135,38 @@ desktop_entries() {
find "$WS" -maxdepth 2 -name '*.desktop' -not -path "$WS/target/*" 2>/dev/null
}
+# App icons, as a whole XDG icon theme tree a crate ships under hicolor/ —
+# cce-icons/hicolor/scalable/apps/cce-files.svg installs to
+# $XDG_DATA_HOME/icons/hicolor/scalable/apps/cce-files.svg. The path IS the
+# install path, so an icon's size and context are its directory rather than a
+# rule in this script: adding 48x48/apps or scalable/mimetypes later needs no
+# edit here. The icon's basename must equal the Icon= key in the crate's
+# .desktop entry, which is what makes them resolve at all.
+#
+# Two things are load-bearing:
+#
+# - **`-type l` as well as `-type f`.** Every file in cce-icons/hicolor is a
+# symlink into ../../../svg (svg/ is the sole source; an entry there is a name
+# for a glyph, not a second copy of it). `-type f` alone does not match a
+# symlink, so the plain `-type f` used by crate_scripts/dbus_services would
+# find nothing at all here and report success. `install` dereferences, so the
+# destination is a real file either way.
+# - **hicolor, and no index.theme.** hicolor is the spec's fallback theme,
+# searched whatever the user's icon theme is; the sibling cce cursor theme
+# installs to icons/cce and nothing anywhere selects it. The system
+# hicolor-icon-theme package owns the index.theme listing every size/context
+# dir, and a theme is the union of its trees across base dirs — shipping a
+# second index here that named only scalable/apps would be read first and hide
+# every other hicolor directory.
+#
+# The extension filter is the icon formats the icon-theme spec defines, not a
+# per-crate list: it is what keeps the tree's own README.md out of the install.
+app_icons() {
+ find "$WS" -mindepth 3 -path '*/hicolor/*' \( -type f -o -type l \) \
+ \( -name '*.svg' -o -name '*.png' -o -name '*.xpm' \) \
+ -not -path "$WS/target/*" 2>/dev/null
+}
+
# The crate directory a shipped data file belongs to. Units may sit one level
# deeper (cce-compositor/scripts/gpu-watcher.service), so that case unwraps.
file_crate_dir() {
@@ -245,6 +278,33 @@ cmd_install() {
# Absent on a minimal install and non-fatal there, so failure is ignored.
[ "$desktops" -gt 0 ] && { update-desktop-database "$DESKTOPDIR" 2>/dev/null || true; }
+ # Deliberately NOT filtered by package. The icon theme is one shared tree
+ # owned by cce-icons, which has no Cargo.toml — it is an asset repo, not a
+ # crate — so crate_selected() can never match it and a filtered install
+ # would silently install no icons at all. file_crate_dir() is no help
+ # either: the dir above an icon is `apps`, not the crate. Over-installing a
+ # dozen small files on `ccebuild install cce-preview` is the cheaper wrong
+ # answer than an app whose entry points at an icon that was never shipped.
+ local i icons=0 rel
+ while read -r i; do
+ [ -n "$i" ] || continue
+ # Everything after the hicolor/ component, so the source tree's layout
+ # is reproduced verbatim under $ICONDIR.
+ rel="hicolor/${i#*/hicolor/}"
+ [ "$icons" -eq 0 ] && printf '==> installing app icons -> %s\n' "$ICONDIR"
+ install -D -m 644 "$i" "$ICONDIR/$rel"
+ icons=$((icons + 1))
+ n=$((n + 1))
+ done < <(app_icons)
+ # GTK reads the mmapped cache in preference to the directory when it is
+ # newer than the directory, so a new icon can be invisible until it is
+ # rebuilt. --ignore-theme-index because this tree deliberately ships no
+ # index.theme (see app_icons above); absent on a minimal install, so
+ # failure is ignored.
+ [ "$icons" -gt 0 ] && {
+ gtk-update-icon-cache --ignore-theme-index -q -f "$ICONDIR/hicolor" 2>/dev/null || true
+ }
+
if [ ${#missing[@]} -gt 0 ]; then
printf 'ccebuild: NOT BUILT, skipped: %s\n' "${missing[*]}" >&2
fi