#!/usr/bin/env bash
#
# Erzeugt eine KVM/libvirt VM, die dem im Beitrag beschriebenen Laptop
# entspricht, und dient dazu, die Screenshots reproduzierbar zu erzeugen:
#
#   * UEFI Firmware (OVMF) ohne Secure Boot
#   * eine emulierte NVMe Platte, im Gast also /dev/nvme0n1
#   * das Manjaro Live ISO als Bootmedium
#
# Die VM laeuft in der Session-Instanz von libvirt (qemu:///session), es sind
# also keine root-Rechte noetig. Screenshots und Eingaben laufen ueber libvirt
# bzw. den QEMU Monitor, ein Viewer-Fenster wird dafuer nicht gebraucht.
#
# Verwendung:
#   ./manjaro-luks-vm.sh create              # Platte anlegen, VM definieren und starten
#   ./manjaro-luks-vm.sh shot 01-gdisk       # Screenshot nach ../images/01-gdisk.png
#   ./manjaro-luks-vm.sh type 'lsblk -f'     # Text in den Gast tippen (US-Layout)
#   ./manjaro-luks-vm.sh key KEY_ENTER       # einzelne Tasten senden
#   ./manjaro-luks-vm.sh click 640 400       # Mausklick auf Bildschirmkoordinate
#   ./manjaro-luks-vm.sh viewer              # zum Zuschauen/Nacharbeiten von Hand
#   ./manjaro-luks-vm.sh destroy             # VM und Platte wieder entfernen
#
# Der Tastatur-Code geht vom US-Layout im Gast aus, das das Live ISO ohne
# weitere Auswahl im Bootmenue verwendet.

set -euo pipefail

VM_NAME="${VM_NAME:-manjaro-luks-demo}"
DISK_SIZE="${DISK_SIZE:-40G}"
RAM_MB="${RAM_MB:-6144}"
VCPUS="${VCPUS:-4}"
SCREEN_W="${SCREEN_W:-1280}"
SCREEN_H="${SCREEN_H:-800}"

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SHOT_DIR="${SHOT_DIR:-$HERE/../images}"
VM_DIR="${VM_DIR:-$HOME/.local/share/libvirt/images}"
DISK="$VM_DIR/$VM_NAME.qcow2"

# Neuestes Manjaro ISO in ~/Downloads, per ISO=... ueberschreibbar.
find_iso() {
  local candidate
  candidate="$(ls -t "$HOME"/Downloads/manjaro-*.iso 2>/dev/null | head -1 || true)"
  printf '%s' "${ISO:-$candidate}"
}

domain_xml() {
  local iso="$1"
  cat <<XML
<domain type='kvm'>
  <name>$VM_NAME</name>
  <memory unit='MiB'>$RAM_MB</memory>
  <vcpu>$VCPUS</vcpu>
  <os firmware='efi'>
    <type arch='x86_64' machine='q35'>hvm</type>
    <firmware>
      <feature enabled='no' name='secure-boot'/>
    </firmware>
    <boot dev='cdrom'/>
    <boot dev='hd'/>
  </os>
  <features>
    <acpi/>
    <apic/>
  </features>
  <cpu mode='host-passthrough'/>
  <clock offset='utc'/>
  <devices>
    <emulator>/usr/bin/qemu-system-x86_64</emulator>
    <!-- NVMe statt virtio, damit der Gast /dev/nvme0n1 sieht -->
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2' discard='unmap'/>
      <source file='$DISK'/>
      <target dev='nvme0n1' bus='nvme'/>
      <serial>MANJARO-DEMO</serial>
    </disk>
    <disk type='file' device='cdrom'>
      <driver name='qemu' type='raw'/>
      <source file='$iso'/>
      <target dev='sda' bus='sata'/>
      <readonly/>
    </disk>
    <interface type='user'>
      <model type='virtio'/>
    </interface>
    <graphics type='vnc' port='-1' listen='127.0.0.1'/>
    <video>
      <model type='virtio' heads='1'>
        <resolution x='$SCREEN_W' y='$SCREEN_H'/>
      </model>
    </video>
    <input type='tablet' bus='usb'/>
    <input type='keyboard' bus='usb'/>
    <memballoon model='virtio'/>
  </devices>
</domain>
XML
}

cmd_create() {
  local iso
  iso="$(find_iso)"
  [ -n "$iso" ] && [ -r "$iso" ] || {
    echo "Kein Manjaro ISO gefunden. Bitte ISO=/pfad/zum.iso setzen." >&2
    exit 1
  }
  echo "ISO:    $iso"
  echo "Platte: $DISK ($DISK_SIZE)"

  mkdir -p "$VM_DIR"
  [ -e "$DISK" ] || qemu-img create -f qcow2 "$DISK" "$DISK_SIZE" >/dev/null

  virsh destroy "$VM_NAME" >/dev/null 2>&1 || true
  virsh undefine --nvram "$VM_NAME" >/dev/null 2>&1 || true
  domain_xml "$iso" | virsh define /dev/stdin >/dev/null
  virsh start "$VM_NAME" >/dev/null
  echo "VM '$VM_NAME' laeuft. Anzeige: $(virsh vncdisplay "$VM_NAME" 2>/dev/null || echo '-')"
}

cmd_shot() {
  local name="${1:?Name des Screenshots fehlt}"
  local tmp
  tmp="$(mktemp --suffix=.ppm)"
  mkdir -p "$SHOT_DIR"
  virsh screenshot "$VM_NAME" --file "$tmp" >/dev/null
  magick "$tmp" "$SHOT_DIR/$name.png"
  rm -f "$tmp"
  echo "$SHOT_DIR/$name.png"
}

# Ein Zeichen als Tastendruck senden (US-Layout im Gast).
send_char() {
  local c="$1" keys=()
  case "$c" in
    [a-z])   keys=("KEY_$(printf '%s' "$c" | tr '[:lower:]' '[:upper:]')") ;;
    [A-Z])   keys=(KEY_LEFTSHIFT "KEY_$c") ;;
    [0-9])   keys=("KEY_$c") ;;
    ' ')     keys=(KEY_SPACE) ;;
    '-')     keys=(KEY_MINUS) ;;
    '=')     keys=(KEY_EQUAL) ;;
    '[')     keys=(KEY_LEFTBRACE) ;;
    ']')     keys=(KEY_RIGHTBRACE) ;;
    ';')     keys=(KEY_SEMICOLON) ;;
    "'")     keys=(KEY_APOSTROPHE) ;;
    '`')     keys=(KEY_GRAVE) ;;
    '\')     keys=(KEY_BACKSLASH) ;;
    ',')     keys=(KEY_COMMA) ;;
    '.')     keys=(KEY_DOT) ;;
    '/')     keys=(KEY_SLASH) ;;
    '!')     keys=(KEY_LEFTSHIFT KEY_1) ;;
    '@')     keys=(KEY_LEFTSHIFT KEY_2) ;;
    '#')     keys=(KEY_LEFTSHIFT KEY_3) ;;
    '$')     keys=(KEY_LEFTSHIFT KEY_4) ;;
    '%')     keys=(KEY_LEFTSHIFT KEY_5) ;;
    '^')     keys=(KEY_LEFTSHIFT KEY_6) ;;
    '&')     keys=(KEY_LEFTSHIFT KEY_7) ;;
    '*')     keys=(KEY_LEFTSHIFT KEY_8) ;;
    '(')     keys=(KEY_LEFTSHIFT KEY_9) ;;
    ')')     keys=(KEY_LEFTSHIFT KEY_0) ;;
    '_')     keys=(KEY_LEFTSHIFT KEY_MINUS) ;;
    '+')     keys=(KEY_LEFTSHIFT KEY_EQUAL) ;;
    '{')     keys=(KEY_LEFTSHIFT KEY_LEFTBRACE) ;;
    '}')     keys=(KEY_LEFTSHIFT KEY_RIGHTBRACE) ;;
    ':')     keys=(KEY_LEFTSHIFT KEY_SEMICOLON) ;;
    '"')     keys=(KEY_LEFTSHIFT KEY_APOSTROPHE) ;;
    '~')     keys=(KEY_LEFTSHIFT KEY_GRAVE) ;;
    '|')     keys=(KEY_LEFTSHIFT KEY_BACKSLASH) ;;
    '<')     keys=(KEY_LEFTSHIFT KEY_COMMA) ;;
    '>')     keys=(KEY_LEFTSHIFT KEY_DOT) ;;
    '?')     keys=(KEY_LEFTSHIFT KEY_SLASH) ;;
    *)       echo "Zeichen '$c' nicht abgebildet." >&2; return 1 ;;
  esac
  virsh send-key "$VM_NAME" --holdtime 30 "${keys[@]}" >/dev/null
}

cmd_type() {
  local text="${1?Text fehlt}" i
  for (( i = 0; i < ${#text}; i++ )); do
    send_char "${text:i:1}"
  done
}

cmd_key() {
  virsh send-key "$VM_NAME" --holdtime 50 "$@" >/dev/null
}

# Absolute Mausposition ueber das USB-Tablet: QEMU erwartet 0..32767 je Achse.
qmp() {
  virsh qemu-monitor-command "$VM_NAME" "$1" >/dev/null
}

cmd_move() {
  local x="${1:?x fehlt}" y="${2:?y fehlt}" ax ay
  ax=$(( x * 32767 / SCREEN_W ))
  ay=$(( y * 32767 / SCREEN_H ))
  qmp "{\"execute\":\"input-send-event\",\"arguments\":{\"events\":[
        {\"type\":\"abs\",\"data\":{\"axis\":\"x\",\"value\":$ax}},
        {\"type\":\"abs\",\"data\":{\"axis\":\"y\",\"value\":$ay}}]}}"
}

cmd_click() {
  cmd_move "$@"
  qmp '{"execute":"input-send-event","arguments":{"events":[
        {"type":"btn","data":{"down":true,"button":"left"}}]}}'
  qmp '{"execute":"input-send-event","arguments":{"events":[
        {"type":"btn","data":{"down":false,"button":"left"}}]}}'
}

cmd_viewer() {
  virt-viewer --connect qemu:///session "$VM_NAME" &
}

cmd_destroy() {
  virsh destroy "$VM_NAME" >/dev/null 2>&1 || true
  virsh undefine --nvram "$VM_NAME" >/dev/null 2>&1 || true
  rm -f "$DISK"
  echo "VM '$VM_NAME' und $DISK entfernt."
}

case "${1:-}" in
  create)  shift; cmd_create "$@" ;;
  shot)    shift; cmd_shot "$@" ;;
  type)    shift; cmd_type "$@" ;;
  key)     shift; cmd_key "$@" ;;
  move)    shift; cmd_move "$@" ;;
  click)   shift; cmd_click "$@" ;;
  viewer)  shift; cmd_viewer "$@" ;;
  destroy) shift; cmd_destroy "$@" ;;
  *)
    sed -n '3,30p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
    exit 1
    ;;
esac
