Files
SilentWolf75 2b08e0c6a7 AppImage: refresh the icon cache so the taskbar shows the mark (#10673) (#10858)
* claim: #10673

Co-authored-by: Cursor <cursoragent@cursor.com>

* AppImage: point the taskbar at the installed icon PNG (#10673)

Install hicolor icons before writing the desktop entry, refresh the icon cache, and set Icon= to the absolute 256×256 path so Cinnamon and similar panels show the mark instead of a thin strip.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-02 01:50:44 +00:00

167 lines
7.5 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
# SolidCraft AppImage entry point (packaging/linux/AppRun; installed by package.sh).
# Adapted from PhotoCraft's AppRun (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
#
# On Wayland the compositor resolves a window's dock icon by taking the window's
# app_id (io.tangencylabs.solidcraft) and looking up an installed .desktop file.
# An AppImage installs nothing, so every AppImage run would otherwise show the
# generic "Wayland" icon. Best-effort desktop integration: on first run
# (and whenever the AppImage's path changes) install the .desktop entry and the
# hicolor icons into $XDG_DATA_HOME, refresh the icon cache, point Icon= at the
# installed PNG (panels that skip the theme cache still show the mark; #10673),
# then launch. Every failure here must leave the launch untouched. Opt out with
# SOLIDCRAFT_NO_DESKTOP_INTEGRATION=1 (the pre-rename SOLVECRAFT_NO_DESKTOP_INTEGRATION
# still works: compatibility shim, docs/compatibility.md), or for good with an empty
# file $XDG_DATA_HOME/solidcraft/no-desktop-integration. Nothing is installed either
# when appimaged (go-appimage) already made an entry for this AppImage, or when a
# launcher that manages AppImages (Gear Lever, AppImageLauncher) started it: it sets
# DESKTOPINTEGRATION and owns the menu entry.
#
# The same step puts `solidcraft-cli` on PATH: a small wrapper script in ~/.local/bin that starts this
# AppImage in command-line mode (SOLIDCRAFT_APPIMAGE_CLI=1 makes AppRun run the bundled
# solidcraft-cli), so MCP clients and shells find it by name. It is rewritten when the AppImage
# moves, and a file there that this script did not write is never touched. The same opt-outs apply.
set -u
APPDIR="$(dirname "$(readlink -f "$0")")"
DESK_ID=io.tangencylabs.solidcraft
CLI_MARKER="# solidcraft-cli wrapper written by the SolidCraft AppImage"
# Compatibility shim (docs/compatibility.md): the desktop ids of earlier releases (0.8 until the
# move to Tangency Labs, and before 0.8).
OLD_DESK_IDS="ai.storyteller.solidcraft ai.storyteller.solvecraft"
# The wrapper in ~/.local/bin: $1 is the AppImage's path, $2 the home folder.
install_cli() {
bin="${XDG_BIN_HOME:-$2/.local/bin}"
wrapper="$bin/solidcraft-cli"
# Anything there that is not our wrapper (a file, a link, a directory) is the user's.
if [ -e "$wrapper" ] || [ -L "$wrapper" ]; then
[ -f "$wrapper" ] && [ ! -L "$wrapper" ] && grep -qF "$CLI_MARKER" "$wrapper" 2>/dev/null || return 0
fi
mkdir -p "$bin" 2>/dev/null || return 0
tmp="$bin/.solidcraft-cli.tmp.$$"
printf '#!/bin/sh\n%s\nSOLIDCRAFT_APPIMAGE_CLI=1 exec "%s" "$@"\n' "$CLI_MARKER" "$1" >"$tmp" 2>/dev/null || { rm -f "$tmp"; return 0; }
chmod 755 "$tmp" 2>/dev/null
if cmp -s "$tmp" "$wrapper" 2>/dev/null; then
rm -f "$tmp"
else
mv -f "$tmp" "$wrapper" 2>/dev/null || rm -f "$tmp"
fi
}
integrate() {
case "${SOLIDCRAFT_NO_DESKTOP_INTEGRATION:-${SOLVECRAFT_NO_DESKTOP_INTEGRATION:-}}" in
1 | true | yes) return 0 ;;
esac
# The AppImage runtime exports $APPIMAGE; without it (an extracted AppDir) there
# is no path to register and nothing to do.
appimage="${APPIMAGE:-}"
[ -n "$appimage" ] && [ -f "$appimage" ] || return 0
appimage="$(readlink -f "$appimage")" || return 0
# Exec= is parsed by the desktop: a path holding a quote, backquote, dollar, backslash,
# percent or a line break would need escaping (or could add lines), so skip integration.
case "$appimage" in
*[\"\`\$\\%]* | *'
'*) return 0 ;;
esac
home="${HOME:-}"
[ -n "$home" ] || return 0
data="${XDG_DATA_HOME:-$home/.local/share}"
[ -e "$data/solidcraft/no-desktop-integration" ] && return 0
install_cli "$appimage" "$home"
src_desktop="$APPDIR/usr/share/applications/$DESK_ID.desktop"
[ -f "$src_desktop" ] || return 0
apps="$data/applications"
# Gear Lever and AppImageLauncher write their own entry and start the AppImage with
# DESKTOPINTEGRATION set; treat that like appimaged: no entry from here, and drop the one
# this script wrote earlier (it would otherwise come back after the launcher's is removed).
if [ -n "${DESKTOPINTEGRATION:-}" ]; then
mine="$apps/$DESK_ID.desktop"
if [ -f "$mine" ] && grep -q '^Exec="' "$mine"; then
rm -f "$mine"
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$apps" >/dev/null 2>&1
fi
return 0
fi
# appimaged integrates AppImages itself (appimagekit_<hash>-<name>.desktop, Exec= this file):
# a second entry from here would show the app twice, so drop an entry this script wrote earlier
# (only one it wrote: it points Exec at a path in quotes) and leave the rest to appimaged.
for other in "$apps"/appimagekit_*.desktop; do
if ! { [ -f "$other" ] && grep -qF "$appimage" "$other" 2>/dev/null; }; then
continue
fi
mine="$apps/$DESK_ID.desktop"
if [ -f "$mine" ] && grep -q '^Exec="' "$mine"; then
rm -f "$mine"
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$apps" >/dev/null 2>&1
fi
return 0
done
# Icons first: the desktop entry's Icon= points at the installed 256×256 PNG so panels
# that skip the theme cache still show the mark (#10673: Mint Cinnamon AppImage taskbar).
src_icons="$APPDIR/usr/share/icons/hicolor"
icons="$data/icons/hicolor"
if [ -d "$src_icons" ]; then
(cd "$src_icons" && find . -type f) | while IFS= read -r rel; do
mkdir -p "$icons/$(dirname "$rel")" 2>/dev/null || continue
if ! cmp -s "$src_icons/$rel" "$icons/$rel"; then
cp "$src_icons/$rel" "$icons/$rel" 2>/dev/null || true
fi
done
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
gtk-update-icon-cache -q -t -f "$icons" 2>/dev/null || true
elif command -v update-icon-caches >/dev/null 2>&1; then
update-icon-caches "$icons" 2>/dev/null || true
fi
fi
# Rewrite Exec to this AppImage (the binary is not on the host PATH), drop TryExec
# (which would hide the entry), and point Icon at the installed PNG when we have one.
mkdir -p "$apps" 2>/dev/null || return 0
tmp="$apps/.$DESK_ID.tmp.$$"
esc="$(printf '%s' "$appimage" | sed 's/[\\&|]/\\&/g')"
icon_png="$icons/256x256/apps/$DESK_ID.png"
if [ -f "$icon_png" ]; then
icon_esc="$(printf '%s' "$icon_png" | sed 's/[\\&|]/\\&/g')"
icon_sed="s|^Icon=.*|Icon=$icon_esc|"
else
icon_sed='s|^Icon=.*|Icon='"$DESK_ID"'|'
fi
if sed -e "s|^Exec=.*|Exec=\"$esc\" %F|" -e '/^TryExec=/d' -e "$icon_sed" "$src_desktop" 2>/dev/null >"$tmp"; then
if cmp -s "$tmp" "$apps/$DESK_ID.desktop"; then
rm -f "$tmp"
else
if mv -f "$tmp" "$apps/$DESK_ID.desktop" 2>/dev/null; then
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$apps" >/dev/null 2>&1
else
rm -f "$tmp"
fi
fi
else
rm -f "$tmp"
return 0
fi
# An entry and icons that an earlier AppImage installed under an old id would show a second
# menu entry; remove them (only an entry this script wrote: it points Exec at a path).
for old_id in $OLD_DESK_IDS; do
old="$apps/$old_id.desktop"
if [ -f "$old" ] && grep -q "^Icon=$old_id\$" "$old" && grep -q '^Exec="' "$old"; then
rm -f "$old" "$data"/icons/hicolor/*/apps/"$old_id".png 2>/dev/null
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$apps" >/dev/null 2>&1
fi
done
}
# Started through the wrapper `integrate` installs: the command-line tool, with no integration.
if [ "${SOLIDCRAFT_APPIMAGE_CLI:-}" = 1 ]; then
unset SOLIDCRAFT_APPIMAGE_CLI
exec "$APPDIR/usr/bin/solidcraft-cli" "$@"
fi
integrate
exec "$APPDIR/usr/bin/solidcraft" "$@"