Files
claude-re-setup/launch-apps.sh
T
LinusandClaude Opus 5.5 4baf0208e1 Add Ghidra 12.2 and start it alongside Claude Desktop
Install Ghidra 12.2 from the official GitHub release into /opt/ghidra,
verified against the SHA-256 in the release notes, with Ubuntu's
openjdk-25-jdk (Ghidra 12.2 requires Java 25). Version, build date and
hash are build args.

The entrypoint, renamed from launch-claude.sh to launch-apps.sh, now
starts both apps and stops both, ending the container, when either is
closed: Ghidra when its process exits (closing the project window),
Claude Desktop when none of its windows is open or minimized. Claude is
stopped via its main process so it can shut down its own helpers.

CLAUDE.md now records that Claude Desktop and Ghidra share one
container, at the user's request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 12:38:38 +01:00

69 lines
2.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# Container entrypoint: start Claude Desktop and Ghidra, and stop both (so the
# container exits) as soon as either one is closed.
#
# - Ghidra exits when its project window is closed, so its process ending is
# the signal. Closing other Ghidra windows (CodeBrowser, Help) doesn't count.
# - Closing Claude Desktop's window only hides the app (it keeps running for
# the tray icon, which GNOME doesn't show). So its X windows are watched and
# it counts as closed once none of them is open or minimized.
#
# Arguments are passed to Claude Desktop.
set -u
# Job control puts each app in its own process group, so stopping an app also
# stops everything it started (Ghidra's launch script runs java as a child).
set -m
claude-desktop "$@" &
claude=$!
"${GHIDRA_INSTALL_DIR:-/opt/ghidra}/support/launch.sh" fg jdk Ghidra "" "" ghidra.GhidraRun &
ghidra=$!
stop_all() {
trap - TERM INT HUP
echo "launch-apps: $1, stopping" >&2
# Claude's main process shuts down its own helpers; killing its whole
# group would take out the GPU process first and trigger restart loops.
# Ghidra's group is needed to reach java behind the launch script.
kill -TERM "$claude" 2>/dev/null
kill -TERM -- "-$ghidra" 2>/dev/null
# Give both a few seconds to shut down cleanly before the container ends.
for _ in {1..10}; do
kill -0 "$claude" 2>/dev/null || kill -0 -- "-$ghidra" 2>/dev/null || break
sleep 0.5
done
exit 0
}
trap 'stop_all "received stop signal"' TERM INT HUP
host="$(hostname)"
# True if any of this container's Claude windows is Normal (shown) or Iconic
# (minimized). A closed window is Withdrawn or loses WM_STATE altogether.
# WM_CLIENT_MACHINE filters out Claude windows from other containers or apps
# on the same X server.
claude_window_open() {
local w props
for w in $(xwininfo -root -tree 2>/dev/null \
| awk '/\("com\.anthropic\.claude" "com\.anthropic\.Claude"\)/ {print $1}'); do
props="$(xprop -id "$w" WM_CLIENT_MACHINE WM_STATE 2>/dev/null)" || continue
[[ "$props" == *"WM_CLIENT_MACHINE(STRING) = \"$host\""* ]] || continue
[[ "$props" =~ "window state: "(Normal|Iconic) ]] && return 0
done
return 1
}
seen=0 # only arm the window check after Claude's window has appeared
misses=0 # require a few consecutive misses to ride out brief remaps
while :; do
sleep 1 & wait $!
kill -0 "$ghidra" 2>/dev/null || stop_all "Ghidra exited"
kill -0 "$claude" 2>/dev/null || stop_all "Claude Desktop exited"
if claude_window_open; then
seen=1 misses=0
elif (( seen )) && (( ++misses >= 3 )); then
stop_all "Claude Desktop window closed"
fi
done