Complete OWE service setup and lock feed fallback

This commit is contained in:
David Heinemeier Hansson committed 2026-09-20 20:36:19 -05:00
1 parent b0bf4876b1
commit b423f4993d
14 files changed
+289 -43

No files matched your search

+69
View File
@@ -0,0 +1,69 @@
import QtQuick
import Quickshell
ShellRoot {
id: root
property var view
property var wallpaper
property var feed
property int step: 0
Item { id: host; width: 1000; height: 700 }
function findItem(item, name) {
if (item.objectName === name) return item
for (var i = 0; i < item.children.length; i++) {
var found = findItem(item.children[i], name)
if (found) return found
}
return null
}
function check(condition, message) {
if (!condition) throw new Error(message)
}
Timer {
interval: 150
repeat: true
running: true
onTriggered: {
try {
if (root.step === 0) {
var component = Qt.createComponent("file://" + Quickshell.env("OMARCHY_PATH") + "/shell/plugins/lock/LockView.qml")
check(component.status === Component.Ready, component.errorString())
root.view = component.createObject(host, {width: 1000, height: 700, backgroundPath: "/still.png", loadBackground: false})
check(root.view !== null, component.errorString())
root.wallpaper = findItem(root.view, "lockWallpaper")
root.feed = findItem(root.view, "lockFeedLoader")
check(root.wallpaper.path === "", "hidden still must not decode")
root.view.backgroundPath = "/video.mp4"
root.view.videoPosterPath = "/poster.jpg"
} else if (root.step === 1) {
check(!root.feed.active && root.feed.item === null, "hidden video must not load a feed client")
root.view.loadBackground = true
root.view.powerSaverActive = true
} else if (root.step === 2) {
check(root.wallpaper.path === "/poster.jpg", "power saver keeps the video poster")
check(!root.feed.active, "power saver must not connect to the feed")
root.view.powerSaverActive = false
} else if (root.step === 3) {
check(root.feed.active, "visible video enables the isolated feed loader")
check(root.wallpaper.path === "/poster.jpg", "poster stays beneath the feed before its first frame")
root.view.displaysBlank = true
} else if (root.step === 4) {
check(!root.feed.active && root.feed.item === null, "blanking releases the feed client")
root.view.loadBackground = false
} else {
check(root.wallpaper.path === "", "unlock releases the poster image")
console.log("OWE_LOCK_TEST_PASS")
Qt.quit()
}
root.step++
} catch (error) {
console.error("OWE_LOCK_TEST_FAIL: " + error)
Qt.quit()
}
}
}
}
+90
View File
@@ -0,0 +1,90 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
mkdir -p "$work/bin" "$work/home"
export TEST_CALLS="$work/calls"
for command in omarchy-pkg-add omarchy-hook-install systemctl; do
cat >"$work/bin/$command" <<'SH'
#!/bin/bash
printf '%s %s\n' "${0##*/}" "$*" >>"$TEST_CALLS"
case "$*" in
'--user enable owed.service') [[ ${TEST_TTY:-0} == 0 ]] ;;
'--user is-active --quiet graphical-session.target') [[ ${TEST_TTY:-0} == 0 ]] ;;
'--user start owed.service') [[ ${TEST_START_FAIL:-0} == 0 ]] ;;
*) exit 0 ;;
esac
SH
chmod +x "$work/bin/$command"
done
migration="$ROOT/migrations/1789764927.sh"
run_migration() {
HOME="$work/home" PATH="$work/bin:$PATH" bash -euo pipefail "$migration"
}
run_migration
run_migration
grep -Fx 'omarchy-pkg-add owe owe-lockfeed' "$TEST_CALLS" >/dev/null
grep -Fx 'omarchy-hook-install theme-set /usr/share/owe/10-owe-sync' "$TEST_CALLS" >/dev/null
grep -Fx 'systemctl --user start owed.service' "$TEST_CALLS" >/dev/null
pass "migration installs OWE, its theme refresh hook, and starts the graphical session service"
: >"$TEST_CALLS"
TEST_TTY=1 run_migration
TEST_TTY=1 run_migration
[[ $(readlink "$work/home/.config/systemd/user/graphical-session.target.wants/owed.service") == /usr/lib/systemd/user/owed.service ]]
if grep -F 'systemctl --user start' "$TEST_CALLS" >/dev/null; then
fail "TTY migration must not start the renderer"
fi
pass "TTY migrations enable the next login without starting a renderer"
: >"$TEST_CALLS"
OMARCHY_UPGRADE_TO_QUATTRO_LIVE=1 run_migration
if grep -F 'systemctl --user start' "$TEST_CALLS" >/dev/null; then
fail "Quattro upgrade must defer OWE until the new graphical session"
fi
pass "Quattro upgrade enables OWE without starting it in the old shell"
if TEST_START_FAIL=1 run_migration; then
fail "a failed live service start leaves the migration pending"
fi
pass "a failed live service start fails the migration"
: >"$TEST_CALLS"
HOME="$work/home" PATH="$work/bin:$PATH" bash "$ROOT/install/user/first-run/enable-user-units.sh"
grep -E '^systemctl --user enable --now .*owed.service' "$TEST_CALLS" >/dev/null
grep -Fx 'omarchy-hook-install theme-set /usr/share/owe/10-owe-sync' "$TEST_CALLS" >/dev/null
pass "fresh installs enable OWE and install the theme refresh hook"
cat >"$work/bin/ffmpegthumbnailer" <<'SH'
#!/bin/bash
printf 'thumbnail\n' >>"$TEST_CALLS"
while (( $# )); do
case "$1" in
-o) output=$2; shift 2 ;;
*) shift ;;
esac
done
[[ ${TEST_POSTER_FAIL:-0} == 0 ]] || exit 1
printf 'poster\n' >"$output"
SH
chmod +x "$work/bin/ffmpegthumbnailer"
source_path="$work/video with 'quotes'.mp4"
printf 'source\n' >"$source_path"
poster() {
PATH="$work/bin:$PATH" XDG_CACHE_HOME="$work/cache" bash "$ROOT/shell/plugins/lock/poster.sh" "$source_path"
}
: >"$TEST_CALLS"
first=$(poster)
[[ -s $first && $(poster) == "$first" ]]
[[ $(wc -l <"$TEST_CALLS") == 1 ]]
printf 'changed source\n' >>"$source_path"
second=$(poster)
[[ -s $second && $second != "$first" && ! -e $first ]]
pass "lock posters are cached, refreshed after source changes, and old frames are evicted"
printf 'broken source\n' >>"$source_path"
if TEST_POSTER_FAIL=1 poster; then fail "failed poster generation must fail"; fi
[[ -z $(find "$work/cache" -name '.poster-*.jpg' -print -quit) ]]
pass "failed poster generation never publishes a partial image"
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
if ! command -v quickshell >/dev/null 2>&1; then
pass "quickshell unavailable; skipping lock feed QML lifecycle"
exit 0
fi
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
mkdir -p "$work/config" "$work/runtime" "$work/home"
chmod 700 "$work/runtime"
cp "$SHELL_TEST_DIR/fixtures/owe-lock/shell.qml" "$work/config/shell.qml"
ln -s "$ROOT/shell/Ui" "$work/config/Ui"
ln -s "$ROOT/shell/Commons" "$work/config/Commons"
# Offscreen rendering exercises the real QML bindings without taking a lock
# or connecting to the user's compositor.
HOME="$work/home" XDG_RUNTIME_DIR="$work/runtime" \
OMARCHY_PATH="$ROOT" QT_QPA_PLATFORM=offscreen QT_QPA_PLATFORMTHEME= \
QT_STYLE_OVERRIDE= QT_QUICK_BACKEND=software \
timeout 15 quickshell -p "$work/config" --no-color >"$work/log" 2>&1 || {
cat "$work/log" >&2
fail "lock feed QML lifecycle runs"
}
if ! grep -q 'OWE_LOCK_TEST_PASS' "$work/log" || grep -q 'OWE_LOCK_TEST_FAIL' "$work/log"; then
cat "$work/log" >&2
fail "lock feed respects visibility and keeps its poster fallback"
fi
pass "lock feed respects visibility, power saver and blanking; hidden views release images"
+4 -6
View File
@@ -49,11 +49,9 @@ assert(
assert(
/^import Owe\.LockFeed$/m.test(lockFeedQml) &&
lockFeedQml.includes('LockFeed {') &&
lockFeedQml.includes('active: root.feedEnabled') &&
lockQml.includes('source: "LockFeedSurface.qml"') &&
lockQml.includes('active: root.video') &&
/feedActive: root\.video && root\.loadBackground && !root\.displaysBlank && !root\.powerSaverActive/.test(lockQml) &&
/property: "feedEnabled"[\s\S]*?value: root\.feedActive/.test(lockQml),
lockQml.includes('active: root.feedActive') &&
/feedActive: root\.video && root\.loadBackground && !root\.displaysBlank && !root\.powerSaverActive/.test(lockQml),
'the lock screen shows video through the OWE lock feed, loaded so a missing module costs only the video'
)
assert(
@@ -61,8 +59,8 @@ assert(
'the lock view itself carries no foreign import, so the lock still loads without the feed module'
)
assert(
lockQml.includes('path: root.video ? "" : root.backgroundPath') &&
lockQml.includes('visible: !root.video') &&
lockQml.includes('path: root.loadBackground ? (root.video ? root.videoPosterPath : root.backgroundPath) : ""') &&
lockQml.indexOf('id: feedLoader') > lockQml.indexOf('MultiEffect {') &&
lockQml.includes('visible: root.video') &&
!lockQml.includes('wallpaper.video'),
'the lock screen keeps its image effect for stills and shows the feed for videos'