From 04204f1b9d96766fe4d6c82bd2d91d2efefbed1c Mon Sep 17 00:00:00 2001 From: Rick Peters Date: Fri, 25 Sep 2026 11:14:00 +0200 Subject: [PATCH] feat: vmcec.sh tests HDMI-CEC against a fake TV; vmwake.sh wakes the VM over QMP run.sh opens a QMP socket; the skill documents the CEC test and its gotchas. --- .claude/skills/cachyos-vm-testing/SKILL.md | 56 +++-- run.sh | 1 + scripts/vmcec.sh | 230 +++++++++++++++++++++ scripts/vmwake.sh | 11 + 4 files changed, 277 insertions(+), 21 deletions(-) create mode 100755 scripts/vmcec.sh create mode 100755 scripts/vmwake.sh diff --git a/.claude/skills/cachyos-vm-testing/SKILL.md b/.claude/skills/cachyos-vm-testing/SKILL.md index 9542f38..7ff231f 100644 --- a/.claude/skills/cachyos-vm-testing/SKILL.md +++ b/.claude/skills/cachyos-vm-testing/SKILL.md @@ -274,33 +274,47 @@ the environment through). fwupd correctly refuses the firmware in the VM A successful dry run counts as staged, so `[m]`/`[r]` and the restart question on `q` show up too; in dry-run mode "restart" only prints. -## Fake HDMI-CEC (vivid) +## Fake HDMI-CEC (vivid): `scripts/vmcec.sh` The VM has no CEC hardware; the kernel's `vivid` test driver emulates it: a -virtual HDMI input (`/dev/cec0`, plays the TV) and output (`/dev/cec1`, the -PC). Needs the HDMI-CEC item on (cecd installed); `cec-ctl` is in v4l-utils. +virtual HDMI input (`/dev/cec0`, plays the TV via `cec-ctl`/`cec-follower`) +and output (`/dev/cec1`, the PC, where cecd runs). Needs the HDMI-CEC item on; +the Steam-settings checks need Steam Machine support (steamos-manager). ```bash -ssh -p 2222 -o BatchMode=yes theupriser@localhost bash -s << 'EOF' -export XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus -sudo modprobe vivid n_devs=1 num_inputs=1 input_types=0x3 num_outputs=1 output_types=0x1 -cec-ctl -d /dev/cec0 --tv --phys-addr 0.0.0.0 --osd-name "Sony TV" >/dev/null -# plug the output into the input: the PC side gets 1.0.0.0 -v4l2-ctl -d /dev/video0 --set-ctrl hdmi_000_0_is_connected_to=2 -# the cecd service (-e) grabs every CEC device, the TV too: run it on the output only -systemctl --user stop cecd; systemd-run --user -q --unit=cecd-test /usr/bin/cecd -d /dev/cec1 -sleep 4; cec-ctl -d /dev/cec1 | grep -E "Physical Address|Logical Address " # 1.0.0.0, e.g. 8 -EOF +scripts/vmcec.sh # all checks + a real suspend/resume (~5 min) +scripts/vmcec.sh --no-sleep # without suspending the VM +scripts/vmcec.sh --sleep-only # only the suspend/resume check +scripts/vmwake.sh # wake a sleeping VM (QMP socket from run.sh) ``` -Then, with `T="cec-ctl -d /dev/cec0 -t 8"` (the PC's logical address): -`$T --give-osd-name` (the Steam Machine answers "Steam Machine"), remote keys -`$T --user-control-pressed ui-cmd=down` + `$T --user-control-released` -(arrive as KEY_DOWN on the `cecd vivid-000-vid-out0` input device; there's no -evtest, read `/dev/input/eventN` with a small python struct reader), and PC to -TV: `sudo cec-ctl -d /dev/cec0 -M` (monitor needs root) while running -`cectool -d /dev/cec1 set-active|volume-up|standby`. Afterwards -`systemctl --user stop cecd-test; sudo modprobe -r vivid; systemctl --user start cecd`. +It prints PASS/FAIL/SKIP for: identity (address 1.0.0.0, name "Steam +Machine", Valve vendor ID), every TV remote key and the key it becomes, the +Steam settings over D-Bus (remote control, WakeTv, SuspendTv, SuspendDevice +and cecd's config), PC to TV (make active, wake, volume, audio status, +standby), TV standby putting the PC to sleep (blocked by an inhibitor), and +sleep/wake turning the TV off and on. + +Gotchas it handles, learned the hard way: +- The cecd service (`-e`) grabs every CEC device, the fake TV too: the test + uses a temporary drop-in (`~/.config/systemd/user/cecd.service.d/vmcec.conf`) + with `-d /dev/cec1`, removed at the end. +- The TV's power button (KEY_POWER) and TV standby with SuspendDevice on + really suspend the VM (Steam Machine support makes the power button sleep): + block with `sudo systemd-inhibit --what=sleep:handle-power-key` (as user it's + refused). If the VM sleeps anyway: `scripts/vmwake.sh` (the QEMU window's + keyboard doesn't wake it; a VM started before run.sh had `-qmp` needs a hard restart). +- `rtcwake` skips logind, so cecd never sees the sleep: suspend with + `systemctl suspend` and wake over QMP. +- `steamosctl get/set-hdmi-cec-suspend-tv|suspend-device` don't work + (steamos-manager 26.4.1); Steam uses the D-Bus properties of + `com.steampowered.SteamOSManager1.HdmiCec2`, and so does the test. +- The kernel answers "give OSD name" itself, even without cecd: it's no proof + cecd runs. cecd logs "Putting TV in standby" only at `RUST_LOG=debug`. +- CecDevice1 methods take arguments (`VolumeUp y 0`, Daemon1 `Standby b true`). +- After the test (or a hard restart) CachyOS may put autologin back on + gamescope: set the session to plasma and restart the display manager. + The menu item is 7 (8 = Steam Machine support, 9 = kernel pin, 10 = BIOS with `--fremont`). ## Steamify shortcut diff --git a/run.sh b/run.sh index ee96192..3084c3a 100755 --- a/run.sh +++ b/run.sh @@ -49,4 +49,5 @@ exec qemu-system-x86_64 \ -nic user,model=virtio-net-pci,hostfwd=tcp::2222-:22 \ -virtfs local,path="$repo",mount_tag=repo,security_model=mapped-xattr \ -virtfs local,path="$PWD/share",mount_tag=vmtools,security_model=mapped-xattr,readonly=on \ + -qmp unix:"$PWD/qmp.sock",server=on,wait=off \ "${smbios[@]}" "${cdrom[@]}" diff --git a/scripts/vmcec.sh b/scripts/vmcec.sh new file mode 100755 index 0000000..f316c12 --- /dev/null +++ b/scripts/vmcec.sh @@ -0,0 +1,230 @@ +#!/bin/bash +# Test HDMI-CEC in the guest against a fake TV: the kernel's vivid driver +# gives a virtual HDMI input (/dev/cec0, plays the TV, driven with cec-ctl) +# and output (/dev/cec1, the PC, where cecd runs). Needs the wizard's HDMI-CEC +# item on (cecd, cec-ctl from v4l-utils); steamos-manager (Steam Machine +# support) for the Steam settings tests. Prints PASS/FAIL/SKIP per check. +# +# scripts/vmcec.sh everything, including a real suspend/resume +# (needs the QMP socket from run.sh to wake up) +# scripts/vmcec.sh --no-sleep everything except suspending the VM +# scripts/vmcec.sh --sleep-only only the real sleep/wake check (after a full run) +# +# Afterwards the normal cecd service runs again; vivid stays loaded. +# Env: VM_USER (theupriser), VM_PORT (2222), VM_HOST (localhost). +set -euo pipefail +. "$(dirname "$0")/common.sh" +SLEEP_TEST=true SLEEP_ONLY=false +[[ "${1:-}" == --no-sleep ]] && SLEEP_TEST=false +[[ "${1:-}" == --sleep-only ]] && SLEEP_ONLY=true + +# --- in the guest: helpers, setup and all checks except the real sleep --- +$SLEEP_ONLY || vm_ssh "SLEEP_TEST=$SLEEP_TEST bash -s" << 'REMOTE' +export XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus +W=~/.cache/vmcec; mkdir -p "$W" +pass=0 fail=0 skip=0 +PASS() { echo " PASS $*"; pass=$((pass + 1)); } +FAIL() { echo " FAIL $*"; fail=$((fail + 1)); } +SKIP() { echo " SKIP $*"; skip=$((skip + 1)); } +check() { local d="$1"; shift; if "$@" >/dev/null 2>&1; then PASS "$d"; else FAIL "$d"; fi; } +TV() { cec-ctl -d /dev/cec0 "$@"; } +BUS=com.steampowered.CecDaemon1 +has_smgr() { steamosctl get-hdmi-cec-state >/dev/null 2>&1; } + +# Key reader for the cecd input device: prints key names while it runs. +cat > "$W/keys.py" << 'PY' +import re, select, struct, sys, time +names = {} +for line in open('/usr/include/linux/input-event-codes.h'): + m = re.match(r'#define\s+(KEY_\w+|BTN_\w+)\s+(0x[0-9a-fA-F]+|\d+)\b', line) + if m: names.setdefault(int(m.group(2), 0), m.group(1)) +f = open(sys.argv[1], 'rb'); end = time.time() + float(sys.argv[2]) +while time.time() < end: + if select.select([f], [], [], 0.1)[0]: + _, _, t, c, v = struct.unpack('llHHi', f.read(24)) + if t == 1 and v == 1: print(names.get(c, c), flush=True) +PY + +# Monitor the TV side's CEC bus into $W/mon.txt (needs root). +mon_start() { sudo pkill -f 'cec-ctl -d /dev/cec0 -M' 2>/dev/null; : > "$W/mon.txt" + sudo sh -c "exec cec-ctl -d /dev/cec0 -M > $W/mon.txt 2>&1" & sleep 1; } +mon_stop() { sleep 1; sudo pkill -f 'cec-ctl -d /dev/cec0 -M' 2>/dev/null; wait 2>/dev/null; } +seen() { grep -q "Received from.*): $1" "$W/mon.txt"; } + +echo "== Setup: fake TV (vivid)" +lsmod | grep -q '^vivid' || sudo modprobe vivid n_devs=1 num_inputs=1 input_types=0x3 num_outputs=1 output_types=0x1 +sleep 2 +TV --tv --osd-name "Fake TV" >/dev/null 2>&1 +v4l2-ctl -d /dev/video0 --set-ctrl hdmi_000_0_is_connected_to=2 +# The cecd service (-e) takes every CEC device, the TV too: a temporary +# drop-in limits it to the PC side, so steamos-manager still controls it. +mkdir -p ~/.config/systemd/user/cecd.service.d +printf '[Service]\nExecStart=\nExecStart=/usr/bin/cecd -d /dev/cec1\n' > ~/.config/systemd/user/cecd.service.d/vmcec.conf +systemctl --user daemon-reload; systemctl --user restart cecd.service +sleep 4 +systemctl --user is-active -q steamos-manager.service && { systemctl --user restart steamos-manager.service; sleep 3; } +LA=$(cec-ctl -d /dev/cec1 | awk '/Logical Address /{print $4; exit}') +PA=$(cec-ctl -d /dev/cec1 | awk '/Physical Address/{print $4; exit}') +EV=$(grep -A4 'N: Name="cecd' /proc/bus/input/devices | grep -oE 'event[0-9]+' | head -1) +echo " PC side: physical $PA, logical $LA, input /dev/input/${EV:-none}" + +echo "== 1. Identity (TV asks the PC)" +[[ "$PA" == 1.0.0.0 ]] && PASS "physical address 1.0.0.0" || FAIL "physical address is $PA" +[[ "$LA" =~ ^(4|8|9|11)$ ]] && PASS "logical address $LA (playback device)" || FAIL "logical address is '$LA'" +name=$(TV -t "$LA" --give-osd-name 2>&1 | awk -F': ' '/name:/{print $2; exit}') +[[ "$name" == "Steam Machine" ]] && PASS "OSD name 'Steam Machine'" || FAIL "OSD name is '$name' (steamos-manager's config not applied?)" +vendor=$(TV -t "$LA" --give-device-vendor-id 2>&1 | awk '/vendor-id:/{print $2; exit}') +[[ "$vendor" == 0xe0319e* ]] && PASS "vendor ID Valve (0xe0319e)" || FAIL "vendor ID is '$vendor'" +check "answers a poll" TV -t "$LA" --poll + +echo "== 2. TV remote -> PC (key presses)" +press() { TV -t "$LA" --user-control-pressed ui-cmd="$1" >/dev/null; sleep 0.15; TV -t "$LA" --user-control-released >/dev/null; sleep 0.25; } +keys_for() { # keys_for ...: keys arriving while pressing + EV=$(grep -A4 'N: Name="cecd' /proc/bus/input/devices | grep -oE 'event[0-9]+' | head -1) + : > "$W/keys.txt"; local r= + [[ -n "$EV" ]] && { sudo timeout "$1" python3 "$W/keys.py" "/dev/input/$EV" "$1" > "$W/keys.txt" & r=$!; } + sleep 1; shift + local k; for k in "$@"; do press "$k"; done; [[ -n "$r" ]] && wait "$r" 2>/dev/null; cat "$W/keys.txt"; } +if [[ -z "$EV" ]]; then FAIL "no cecd input device" +else + for k in up down left right select exit back root-menu setup-menu contents-menu \ + play pause stop rewind fast-forward forward backward \ + volume-up volume-down mute number-entry-mode number-0-or-number-10 number-5 \ + channel-up channel-down display-information electronic-program-guide \ + f1-blue f2-red f3-green f4-yellow; do + got=$(keys_for 2 "$k" | tr '\n' ' ') + if [[ -n "$got" ]]; then PASS "$(printf '%-28s -> %s' "$k" "$got")" + else SKIP "$(printf '%-28s -> nothing (not mapped by cecd)' "$k")"; fi + done + + # The TV's power button arrives as KEY_POWER, and Steam Machine support + # makes the power button sleep: with sleep blocked, check the key only. + sudo systemd-inhibit --what=sleep:handle-power-key --mode=block --who=vmcec --why=test sleep 6 & inh=$!; sleep 1 + got=$(keys_for 2 power-toggle-function | tr '\n' ' ') + wait $inh 2>/dev/null + [[ "$got" == *KEY_POWER* ]] && PASS "$(printf '%-28s -> %s' power-toggle-function "$got") (sleeps the PC)" || + FAIL "power-toggle-function -> '$got'" +fi + +echo "== 3. Steam settings (steamos-manager, over D-Bus like Steam)" +# steamosctl's suspend-tv/suspend-device getters and setters don't work +# (steamos-manager 26.4.1); Steam uses these D-Bus properties. +SM=com.steampowered.SteamOSManager1; SMP=/com/steampowered/SteamOSManager1; CEC2=$SM.HdmiCec2 +prop() { busctl --user get-property $SM $SMP $CEC2 "$1" 2>/dev/null | awk '{print $2}'; } +setp() { busctl --user set-property $SM $SMP $CEC2 "$1" b "$2" 2>/dev/null; sleep 2; } +cfg() { grep -h "^$1 *=" ~/.config/cecd/config.d/*.toml 2>/dev/null | tail -1 | awk -F'= *' '{print $2}'; } +if ! has_smgr; then SKIP "steamos-manager has no HDMI-CEC interface (no Steam Machine support?)" +else + echo " now: $(steamosctl get-hdmi-cec-state 2>&1); EnableControl=$(prop EnableControl) WakeTv=$(prop WakeTv) SuspendTv=$(prop SuspendTv) SuspendDevice=$(prop SuspendDevice) WakeDeviceSupported=$(prop WakeDeviceSupported)" + setp EnableControl false + got=$(keys_for 2 down | tr '\n' ' ') + [[ -z "$got" && "$(cfg uinput)" == false ]] && PASS "remote control off: keys are ignored (uinput = false)" || FAIL "remote control off: keys '$got', uinput = $(cfg uinput)" + setp EnableControl true + got=$(keys_for 2 down | tr '\n' ' ') + [[ -n "$got" ]] && PASS "remote control on again: $got" || FAIL "remote control on, but no keys" + for pr in WakeTv:wake_tv SuspendTv:suspend_tv SuspendDevice:allow_standby; do + for v in false true; do + setp "${pr%%:*}" $v + [[ "$(prop "${pr%%:*}")" == "$v" && "$(cfg "${pr#*:}")" == "$v" ]] && + PASS "${pr%%:*} $v (cecd config ${pr#*:} = $v)" || FAIL "${pr%%:*} $v: property $(prop "${pr%%:*}"), config ${pr#*:} = $(cfg "${pr#*:}")" + done + done + [[ "$(prop WakeDeviceSupported)" == true ]] && PASS "waking the PC with the TV is supported" || + SKIP "waking the PC with the TV: not supported here (WakeDeviceSupported = false)" + steamosctl set-hdmi-cec-state control-and-wake >/dev/null 2>&1; sleep 3 +fi +systemctl --user is-active -q cecd.service && PASS "cecd still running" || FAIL "cecd stopped" + +echo "== 4. PC -> TV" +# A plain TV (no soundbar): volume goes to the TV itself (address 0). +TV --tv --osd-name "Fake TV" >/dev/null 2>&1; sleep 2 +echo " fake TV: $(TV | awk '/Logical Address /{printf "%s%s ", $4, $5}')" +DEV=$(busctl --user tree $BUS 2>/dev/null | grep -oE '/com/steampowered/CecDaemon1/Devices/Cec[0-9]+' | head -1) +mon_start +if has_smgr; then steamosctl hdmi-cec-make-active >/dev/null 2>&1; sleep 1; else busctl --user call $BUS "$DEV" $BUS.CecDevice1 SetActiveSource >/dev/null 2>&1; sleep 1; fi +busctl --user call $BUS /com/steampowered/CecDaemon1/Daemon $BUS.Daemon1 Wake >/dev/null 2>&1; sleep 1 +mon_stop +{ seen ACTIVE_SOURCE || seen IMAGE_VIEW_ON || seen TEXT_VIEW_ON; } && PASS "make active: TV switches to the PC ($(grep -oE '\): [A-Z_]+' "$W/mon.txt" | sort -u | tr -d '):' | tr '\n' ' '))" || FAIL "make active: TV got nothing" +seen IMAGE_VIEW_ON || seen TEXT_VIEW_ON && PASS "wake: TV is turned on (IMAGE_VIEW_ON)" || FAIL "wake: no IMAGE_VIEW_ON" +mon_start +for m in VolumeUp VolumeDown Mute; do busctl --user call $BUS "$DEV" $BUS.CecDevice1 $m y 0 >/dev/null 2>&1; sleep 0.5; done +echo " audio status: $(busctl --user --timeout=3 call $BUS "$DEV" $BUS.CecDevice1 GetAudioStatus y 0 2>&1)"; sleep 1 +mon_stop +n=$(grep -cE 'USER_CONTROL_PRESSED' "$W/mon.txt"); ops=$(grep -A1 USER_CONTROL_PRESSED "$W/mon.txt" | grep -oiE 'volume-up|volume-down|mute' | sort -u | tr '\n' ' ') +[[ $n -ge 3 ]] && PASS "volume up/down/mute sent to the TV ($ops)" || FAIL "volume: $n key presses reached the TV ($ops)" +seen GIVE_AUDIO_STATUS && PASS "asks the TV's audio status" || FAIL "no GIVE_AUDIO_STATUS" +mon_start +busctl --user call $BUS /com/steampowered/CecDaemon1/Daemon $BUS.Daemon1 Standby b true >/dev/null 2>&1; sleep 1 +mon_stop +seen STANDBY && PASS "standby: TV is turned off (STANDBY)" || FAIL "standby: no STANDBY" +systemctl --user is-active -q cec-audio-control.socket && PASS "cec-audio-control socket listening" || FAIL "cec-audio-control socket not active" + +echo "== 5. TV off -> PC to sleep (sleep blocked, so the VM stays up)" +if ! has_smgr; then SKIP "needs steamos-manager" +else + TV --tv >/dev/null 2>&1; sleep 2 + for want in true false; do + setp SuspendDevice $want; sleep 1 + since=$(date '+%Y-%m-%d %H:%M:%S') + sudo systemd-inhibit --what=sleep --mode=block --who=vmcec --why=test sleep 7 & inh=$!; sleep 1 + TV -t 15 --standby >/dev/null 2>&1; sleep 4 + wait $inh 2>/dev/null + tried=$(journalctl --user -u cecd --since "$since" --no-pager | grep -c 'Failed to standby') + if [[ $want == true ]]; then + [[ $tried -gt 0 ]] && PASS "SuspendDevice on: TV standby makes the PC go to sleep (blocked here)" || FAIL "SuspendDevice on, but no sleep attempt" + else + [[ $tried -eq 0 ]] && PASS "SuspendDevice off: TV standby doesn't touch the PC" || FAIL "SuspendDevice off, but the PC tried to sleep" + fi + done + setp SuspendDevice true +fi + +if ! $SLEEP_TEST; then rm -f ~/.config/systemd/user/cecd.service.d/vmcec.conf; systemctl --user daemon-reload; systemctl --user restart cecd.service; fi +echo "== Result: $pass passed, $fail failed, $skip skipped (before the sleep test)" +echo "$LA" > "$W/la" +REMOTE + +$SLEEP_TEST || { echo "(sleep test skipped)"; exit 0; } + +# --- real suspend/resume: the TV should go off, and on again at wake --- +# Through logind (systemctl suspend), like Steam and the power button: cecd +# acts on logind's PrepareForSleep, which rtcwake skips. cec-follower plays a +# TV that answers power status; QMP (vmwake.sh) wakes the VM. +echo "== 6. Real sleep and wake (systemctl suspend, woken over QMP)" +vm_ssh bash -s << 'REMOTE' +export XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus +W=~/.cache/vmcec +# cecd on the PC side only, with debug logging (it logs what it does at sleep). +mkdir -p ~/.config/systemd/user/cecd.service.d +printf '[Service]\nExecStart=\nExecStart=/usr/bin/cecd -d /dev/cec1\nEnvironment=RUST_LOG=debug\n' > ~/.config/systemd/user/cecd.service.d/vmcec.conf +systemctl --user daemon-reload; systemctl --user restart cecd.service; sleep 4 +SM=com.steampowered.SteamOSManager1; SMP=/com/steampowered/SteamOSManager1 +busctl --user set-property $SM $SMP $SM.HdmiCec2 SuspendTv b true 2>/dev/null +busctl --user set-property $SM $SMP $SM.HdmiCec2 WakeTv b true 2>/dev/null +sudo systemctl stop vmcec-tv vmcec-mon 2>/dev/null; sudo systemctl reset-failed vmcec-tv vmcec-mon vmcec-sleep 2>/dev/null +cec-ctl -d /dev/cec0 --tv >/dev/null; sleep 1 +sudo systemd-run -q --unit=vmcec-tv sh -c 'exec cec-follower -d /dev/cec0 > /dev/null 2>&1'; sleep 2 +steamosctl hdmi-cec-make-active >/dev/null 2>&1; sleep 3 +sudo systemd-run -q --unit=vmcec-mon sh -c "exec cec-ctl -d /dev/cec0 -M > $W/sleep-mon.txt 2>&1"; sleep 1 +sudo systemd-run -q --unit=vmcec-sleep sh -c 'sleep 2; systemctl suspend' +echo " suspending..." +REMOTE +sleep 15 +"$(dirname "$0")/vmwake.sh" || true +until vm_ssh -o ConnectTimeout=4 true 2>/dev/null; do sleep 3; done +sleep 8 +vm_ssh bash -s << 'REMOTE' +export XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus +W=~/.cache/vmcec +sudo systemctl stop vmcec-mon vmcec-tv 2>/dev/null +t=$(journalctl -b --no-pager -o short-iso | grep "PM: suspend entry" | tail -1 | awk '{print $1}') +[[ -n "$t" ]] && echo " PASS the VM really slept and woke (suspend at $t)" || echo " FAIL no suspend in the kernel log" +log=$(journalctl --user -u cecd --since "$(date -d "${t:-now} -10 sec" '+%F %T')" --no-pager) +grep -q 'Putting TV in standby' <<< "$log" && echo " PASS cecd: 'Putting TV in standby'" || echo " FAIL cecd didn't act on going to sleep" +grep -q 'Waking TV' <<< "$log" && echo " PASS cecd: 'Woke from standby. Waking TV.'" || echo " FAIL cecd didn't act on waking up" +grep -q 'Received from.*: STANDBY' "$W/sleep-mon.txt" && echo " PASS going to sleep: TV turned off (STANDBY)" || echo " FAIL going to sleep: no STANDBY to the TV" +grep -qE 'Received from.*: (IMAGE_VIEW_ON|TEXT_VIEW_ON)' "$W/sleep-mon.txt" && echo " PASS waking up: TV turned on (IMAGE_VIEW_ON)" || echo " FAIL waking up: no IMAGE_VIEW_ON" +grep -oE '^(Received|Transmitted)[^:]*: [A-Z_]+' "$W/sleep-mon.txt" | sed 's/^/ /' +# Back to the normal service. +rm -f ~/.config/systemd/user/cecd.service.d/vmcec.conf; systemctl --user daemon-reload; systemctl --user restart cecd.service +REMOTE diff --git a/scripts/vmwake.sh b/scripts/vmwake.sh new file mode 100755 index 0000000..fc9e3f3 --- /dev/null +++ b/scripts/vmwake.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# Wake the VM from sleep (suspend to RAM), e.g. after the TV's power button +# in the HDMI-CEC test. Uses the QMP socket run.sh opens in the VM directory. +# Env: VM_DIR (the repo if it holds disk.qcow2, else ~/vms/cachyos-test). +set -euo pipefail +here="$(cd "$(dirname "$0")/.." && pwd)" +VM_DIR="${VM_DIR:-$([[ -f "$here/disk.qcow2" ]] && echo "$here" || echo "$HOME/vms/cachyos-test")}" +sock="$VM_DIR/qmp.sock" +[[ -S "$sock" ]] || { echo "No QMP socket at $sock (VM not running, or started before run.sh had -qmp)." >&2; exit 1; } +printf '%s\n' '{"execute":"qmp_capabilities"}' '{"execute":"system_wakeup"}' | + socat -t 2 - UNIX-CONNECT:"$sock" 2>/dev/null | grep -c '"return"' >/dev/null && echo "Wake-up sent."