#!/bin/bash
# clip-daemon -- OSC 52 clipboard fallback.  Listens on a Unix socket and
# relays clipboard data to the terminal via OSC 52 escape sequences.
#
# This is a FALLBACK for when the file-based clipboard bridge is not available
# (e.g. manual docker exec without the cleat wrapper).  The primary clipboard
# mechanism is the file bridge in /tmp/cleat-clip/.
#
# Uses socat in fork mode so the socket stays alive permanently.
#
# Usage (started automatically by cleat before launching Claude Code):
#   clip-daemon &

# Runtime files live in a PER-UID directory, never directly in the shared,
# sticky (1777) /tmp. If two sessions ever resolve `coder` to different uids
# (e.g. transiently, during the entrypoint UID remap) they would otherwise
# collide on the same /tmp/clip.sock, and the later one could neither unlink
# nor rebind a socket it doesn't own (EPERM in a sticky dir): the historical
# clip-error storm. A per-uid dir (mode 700) is owned by its creator, so each
# uid manages its own files and a stale foreign-owned file can't wedge a new
# daemon. The socket MUST stay in the container layer (not the /tmp/cleat-clip
# bind mount), since AF_UNIX sockets don't work on macOS virtiofs mounts.
# CLEAT_CLIP_DIR overrides the path (used by the test harness).
RUNDIR="${CLEAT_CLIP_DIR:-/tmp/cleat-run-$(id -u)}"
mkdir -p "$RUNDIR" 2>/dev/null || true
chmod 700 "$RUNDIR" 2>/dev/null || true
SOCK="$RUNDIR/clip.sock"
PIDFILE="$RUNDIR/clip-daemon.pid"
HANDLER="$RUNDIR/clip-handler"
MAX_CHILDREN=5         # cap concurrent socat forks (prevents PID exhaustion)
MAX_PAYLOAD=102400     # 100KB -- matches typical terminal OSC 52 limits
IDLE_TIMEOUT=5         # socat -T: drop a connection idle this many seconds. A
                       # client that connects but never sends data (or never
                       # closes) would otherwise leave a handler hung on
                       # `head -c` forever; accumulated hung handlers were the
                       # clip-daemon PID-exhaustion fork-storm.

cleanup() {
  [ -n "${SOCAT_PID:-}" ] && kill "$SOCAT_PID" 2>/dev/null && wait "$SOCAT_PID" 2>/dev/null
  rm -f "$SOCK" "$PIDFILE" "$HANDLER" 2>/dev/null || true
  exit 0
}
trap cleanup EXIT INT TERM HUP

# Kill any previous instance
if [ -f "$PIDFILE" ]; then
  old_pid="$(cat "$PIDFILE" 2>/dev/null)"
  if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
    # Verify the PID is actually a clip-daemon before killing
    if grep -qs clip-daemon "/proc/$old_pid/cmdline" 2>/dev/null; then
      kill "$old_pid" 2>/dev/null
      sleep 0.1
    fi
  fi
  rm -f "$SOCK" "$PIDFILE" "$HANDLER" 2>/dev/null || true
fi

echo $$ > "$PIDFILE"

# Write the per-connection handler as a standalone script.  This avoids
# fragile nested quoting inside socat's SYSTEM:"..." argument and ensures
# POSIX-compatible escape sequences (\033 not \e, \007 not \a).
# Note: unquoted heredoc so $MAX_PAYLOAD is expanded from the variable above.
cat > "$HANDLER" <<EOF
#!/bin/sh
d=\$(head -c $MAX_PAYLOAD)
[ -n "\$d" ] || exit 0
enc=\$(printf '%s' "\$d" | base64 | tr -d '\n')
printf '\033]52;c;%s\007' "\$enc" > /dev/tty 2>/dev/null
EOF
chmod +x "$HANDLER"

# socat fork mode: keeps the socket alive permanently, forks a handler for
# each connection.  max-children caps concurrent forks to prevent PID
# exhaustion under rapid-fire usage.
# unlink-early: socat removes any stale socket file before binding, so a
# leftover socket (e.g. from a hard-killed prior session) can't make the bind
# fail with "address already in use".
socat -u -T "$IDLE_TIMEOUT" "UNIX-LISTEN:$SOCK,mode=666,fork,max-children=$MAX_CHILDREN,unlink-early" "EXEC:$HANDLER" &
SOCAT_PID=$!

# Wait for socat -- if it exits unexpectedly, cleanup runs via the trap
wait "$SOCAT_PID" 2>/dev/null
