#!/bin/sh
# Show a desktop app that is running on the PC's own screen on the Brain, without restarting it
# (running tasks keep going).
#   share.sh start claude|chatgpt   size that app's window for the Brain, keep it on top and serve
#                                   that rectangle of the screen with x11vnc (localhost:5906)
#   share.sh stop                   stop serving and put the window back as it was
# One app at a time. While it runs it watches the screen lock: when the PC is locked, the served
# rectangle moves to the middle of the primary monitor, where the lock dialog is, so the PC
# password can be typed from the Brain into the real lock screen. Nothing else unlocks the PC.
DIR="$HOME/.config/lex01-vnc"
OUT=786x446   # what the Brain's viewer window can show
export DISPLAY=:0
export XAUTHORITY="${XAUTHORITY:-$HOME/.Xauthority}"
log() { echo "$(date '+%F %T') share: $*" >>"$DIR/agent.log"; }

app() {  # window class pattern (wmctrl -lx) and the size to capture
    case "$1" in
        claude)  PAT='com\.anthropic'; W=786; H=446 ;;
        # the ChatGPT window cannot be lower than 600 px: capture 1058x600 and scale it to OUT
        chatgpt) PAT='\.Chatgpt '; W=1058; H=600 ;;
        *) return 1 ;;
    esac
}
win() { wmctrl -lx | grep -E "$PAT" | head -1 | cut -d' ' -f1; }
vnc() { x11vnc -display :0 -R "$1" >/dev/null 2>&1; }
serving() { pgrep -x x11vnc >/dev/null; }
locked() { cinnamon-screensaver-command -q 2>/dev/null | grep -q "is active"; }
extents() {  # sets l r t b: the invisible shadow border GTK apps draw around their window
    set -- $(xprop -id "$1" _GTK_FRAME_EXTENTS 2>/dev/null | sed -n 's/.*= *//p' | tr -d ',')
    l=${1:-0}; r=${2:-0}; t=${3:-0}; b=${4:-0}
}
primary() {  # sets PW PH PX PY
    set -- $(xrandr --query | sed -n 's/.* connected primary \([0-9]*\)x\([0-9]*\)+\([0-9]*\)+\([0-9]*\).*/\1 \2 \3 \4/p')
    PW=$1; PH=$2; PX=$3; PY=$4
}
app_clip() {
    id=$(win); [ -n "$id" ] || return 1
    eval "$(xdotool getwindowgeometry --shell "$id")"
    extents "$id"
    echo "${W}x${H}+$((X + l))+$((Y + t))"
}
lock_clip() { primary; echo "${W}x${H}+$((PX + (PW - W) / 2))+$((PY + (PH - H) / 2))"; }
place() {  # move the window near the top left of the primary monitor at capture size
    id=$(win); [ -n "$id" ] || return 1
    primary
    extents "$id"
    wmctrl -i -r "$id" -b remove,maximized_vert,maximized_horz
    wmctrl -i -r "$id" -b remove,fullscreen
    wmctrl -i -a "$id"
    wmctrl -i -r "$id" -e "0,$((PX + 60 - l)),$((PY + 60 - t)),$((W + l + r)),$((H + t + b))"
    wmctrl -i -r "$id" -b add,above
    sleep 1
}
restore() {
    [ -s "$DIR/saved-geometry" ] || return 0
    read NAME X Y WIDTH HEIGHT MAX OP <"$DIR/saved-geometry"
    rm -f "$DIR/saved-geometry"
    app "$NAME" || return 0
    id=$(win); [ -n "$id" ] || return 0
    wmctrl -i -r "$id" -b remove,above
    wmctrl -i -r "$id" -e "0,$X,$Y,$WIDTH,$HEIGHT"
    # MAX is two digits: maximized vertically, horizontally (a window tiled to one side has only one)
    case "$MAX" in 1?) wmctrl -i -r "$id" -b add,maximized_vert ;; esac
    case "$MAX" in ?1) wmctrl -i -r "$id" -b add,maximized_horz ;; esac
    case "$OP" in
        ""|none) xprop -id "$id" -remove _NET_WM_WINDOW_OPACITY ;;
        *) xprop -id "$id" -f _NET_WM_WINDOW_OPACITY 32c -set _NET_WM_WINDOW_OPACITY "$OP" ;;
    esac
    log "$NAME window restored"
}
stop() {
    if serving; then vnc stop; sleep 1; pkill -x x11vnc; sleep 1; fi
    restore
}

case "$1" in
    start)
        app "$2" || { echo "usage: $0 start claude|chatgpt" >&2; exit 2; }
        name=$2
        [ -n "$(win)" ] || { log "no $name window on the PC screen"; exit 1; }
        if serving; then
            [ "$(cut -d' ' -f1 "$DIR/saved-geometry" 2>/dev/null)" = "$name" ] && { log "already sharing $name"; exit 0; }
            stop
            app "$name"
        fi
        id=$(win)
        eval "$(xdotool getwindowgeometry --shell "$id")"
        # a translucent window would show what is behind it: make it opaque while it is shared
        op=$(xprop -id "$id" _NET_WM_WINDOW_OPACITY | sed -n 's/.*= *//p')
        echo "$name $X $Y $WIDTH $HEIGHT $(xprop -id "$id" _NET_WM_STATE | grep -c MAXIMIZED_VERT)$(xprop -id "$id" _NET_WM_STATE | grep -c MAXIMIZED_HORZ) ${op:-none}" >"$DIR/saved-geometry"
        xprop -id "$id" -f _NET_WM_WINDOW_OPACITY 32c -set _NET_WM_WINDOW_OPACITY 4294967295
        locked || place
        clip=$(if locked; then lock_clip; else app_clip; fi)
        scale=""; [ "${W}x${H}" = "$OUT" ] || scale="-scale $OUT"
        log "start $name, clip $clip $scale"
        x11vnc -display :0 -localhost -rfbport 5906 -nopw -shared -forever -clip "$clip" $scale \
            -noxdamage -repeat -quiet -bg -o "$DIR/x11vnc.log" >/dev/null 2>&1
        # follow the lock state until x11vnc is stopped
        state=$(locked && echo locked || echo open)
        while serving; do
            sleep 2
            now=$(locked && echo locked || echo open)
            [ "$now" = "$state" ] && continue
            state=$now
            if [ "$now" = locked ]; then
                primary
                xdotool mousemove $((PX + PW / 2)) $((PY + PH / 2))  # the dialog follows the pointer
                vnc "clip:$(lock_clip)"; log "locked: showing the lock screen"
            else
                place; vnc "clip:$(app_clip)"; log "unlocked: showing the $name window"
            fi
        done
        ;;
    stop) stop ;;
    *) echo "usage: $0 start claude|chatgpt | stop" >&2; exit 2 ;;
esac
