From 4f93522f7d07120ddd474c2c912a615a0f638751 Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Mon, 17 Aug 2026 16:59:15 -0400 Subject: [PATCH] Fix fixture process transitions --- .../dot/quickshell/services/HomeAssistant.qml | 67 ++++++++++++++++++- .../control-center-services-contract.sh | 36 ++++++++-- 2 files changed, 94 insertions(+), 9 deletions(-) diff --git a/config/dot/quickshell/services/HomeAssistant.qml b/config/dot/quickshell/services/HomeAssistant.qml index 0213e7d..2959294 100644 --- a/config/dot/quickshell/services/HomeAssistant.qml +++ b/config/dot/quickshell/services/HomeAssistant.qml @@ -32,6 +32,9 @@ Singleton { property bool actionStreamFinished: false property bool actionExited: false property int actionExitCode: 0 + property bool fixtureTransitionDraining: false + property bool fixtureTransitionStreamFinished: false + property bool fixtureTransitionExited: false readonly property var visibleEntities: root.selectedEntities.slice(0, 4) readonly property int discoveredCount: root.catalog.length @@ -193,8 +196,12 @@ Singleton { } function startNextAction(): void { - if (actionProc.running || root.activeAction !== null || root.actionQueue.length === 0) + if (root.fixtureTransitionDraining + || actionProc.running + || root.activeAction !== null + || root.actionQueue.length === 0) { return; + } root.activeAction = root.actionQueue[0]; root.actionResponseText = ""; @@ -220,6 +227,11 @@ Singleton { } function handleActionStreamFinished(text: string): void { + if (root.fixtureTransitionDraining) { + root.fixtureTransitionStreamFinished = true; + fixtureTransitionTimer.restart(); + return; + } if (root.activeAction === null || root.actionStreamFinished) return; root.actionResponseText = text; @@ -228,6 +240,11 @@ Singleton { } function handleActionExited(exitCode: int): void { + if (root.fixtureTransitionDraining) { + root.fixtureTransitionExited = true; + fixtureTransitionTimer.restart(); + return; + } if (root.activeAction === null || root.actionExited) return; root.actionExited = true; @@ -350,13 +367,49 @@ Singleton { root.actionExitCode = 0; } + function beginFixtureTransition(): void { + const shouldDrain = root.fixtureMode + && root.fixtureProcessMode !== "" + && (actionProc.running || root.activeAction !== null); + const streamAlreadyFinished = root.actionStreamFinished; + const exitAlreadyObserved = root.actionExited; + + fixtureTransitionTimer.stop(); + root.fixtureTransitionDraining = shouldDrain; + root.fixtureTransitionStreamFinished = shouldDrain && streamAlreadyFinished; + root.fixtureTransitionExited = shouldDrain && exitAlreadyObserved; + root.resetActionState(); + + if (!shouldDrain) + return; + if (actionProc.running) + actionProc.running = false; + fixtureTransitionTimer.restart(); + } + + function tryFinishFixtureTransition(): void { + if (!root.fixtureTransitionDraining) + return; + if (actionProc.running) { + fixtureTransitionTimer.restart(); + return; + } + if (!root.fixtureTransitionStreamFinished || !root.fixtureTransitionExited) + return; + + root.fixtureTransitionDraining = false; + root.fixtureTransitionStreamFinished = false; + root.fixtureTransitionExited = false; + queueAdvanceTimer.restart(); + } + function applyFixture(name: string): void { if (["ready", "stale", "unavailable", "missing-selected", "action-error", "process-actions", "process-no-output"].indexOf(name) < 0) return; + root.beginFixtureTransition(); root.fixtureMode = true; - root.resetActionState(); root.fixtureProcessMode = name === "process-actions" ? "success" : (name === "process-no-output" ? "no-output" : ""); @@ -389,6 +442,7 @@ Singleton { } function clearFixture(): void { + root.beginFixtureTransition(); root.fixtureMode = false; root.fixtureProcessMode = ""; root.phase = "loading"; @@ -397,7 +451,6 @@ Singleton { root.fixtureFavorites = []; root.stale = false; root.lastError = ""; - root.resetActionState(); root.refresh(); } @@ -427,6 +480,8 @@ Singleton { id: queueAdvanceTimer interval: 0 onTriggered: { + if (root.fixtureTransitionDraining) + return; if (actionProc.running) { queueAdvanceTimer.restart(); return; @@ -435,6 +490,12 @@ Singleton { } } + Timer { + id: fixtureTransitionTimer + interval: 0 + onTriggered: root.tryFinishFixtureTransition() + } + Timer { interval: 60000 repeat: true diff --git a/tests/quickshell/control-center-services-contract.sh b/tests/quickshell/control-center-services-contract.sh index 00ecd9a..9c2b473 100755 --- a/tests/quickshell/control-center-services-contract.sh +++ b/tests/quickshell/control-center-services-contract.sh @@ -45,12 +45,15 @@ trap cleanup EXIT start_test_shell() { stop_test_shell || fail 'pre-existing branch shell did not stop cleanly' - qs_for_test --daemonize >"$shell_log" 2>&1 - for _ in $(seq 1 80); do - if qs_for_test ipc show 2>/dev/null | rg '^target home-assistant$' >/dev/null; then - return - fi - sleep 0.1 + for _attempt in 1 2; do + qs_for_test --daemonize >"$shell_log" 2>&1 + for _ in $(seq 1 80); do + if qs_for_test ipc show 2>/dev/null | rg '^target home-assistant$' >/dev/null; then + return + fi + sleep 0.1 + done + stop_test_shell || fail 'failed branch-shell attempt did not stop cleanly' done sed -n '1,200p' "$shell_log" >&2 fail 'isolated branch shell did not start' @@ -185,6 +188,27 @@ wait_for_home_status ' (.entities[] | select(.id == "light.fixture_hall") | .active == true) ' 'nonzero no-output Home action did not fail safely and continue the queue' +qs_for_test ipc call home-assistant fixture process-actions >/dev/null +qs_for_test ipc call home-assistant brightness light.fixture_living 88 >/dev/null +jq -e ' + .busyEntityIds == ["light.fixture_living"] and + .pendingBrightness["light.fixture_living"] == 88 +' <<<"$(qs_for_test ipc call home-assistant status)" >/dev/null \ + || fail 'fixture transition setup did not start an in-flight action' +qs_for_test ipc call home-assistant fixture process-no-output >/dev/null +qs_for_test ipc call home-assistant toggle light.fixture_hall >/dev/null +wait_for_home_status ' + .phase == "ready" and + .busy == false and + .busyEntityIds == [] and + .pendingBrightness == {} and + .entityErrors == {} and + (.entities[] | + select(.id == "light.fixture_living") | + .active == false and .brightnessPct == 36) and + (.entities[] | select(.id == "light.fixture_hall") | .active == true) +' 'switching process fixtures stranded or misattributed the new action' + qs_for_test ipc call home-assistant fixture missing-selected >/dev/null jq -e ' .fixture == true and