Close desktop safety gaps
This commit is contained in:
@@ -6,6 +6,10 @@ import qs.config
|
||||
import qs.services
|
||||
|
||||
ShellRoot {
|
||||
// The isolated screen model begins with both fixture outputs so changing
|
||||
// it below exercises the same reactive topology path as a real hotplug.
|
||||
Component.onCompleted: Displays.screenOverride = ["DP-2", "HDMI-A-1"]
|
||||
|
||||
IpcHandler {
|
||||
target: "displays-test"
|
||||
|
||||
@@ -42,6 +46,7 @@ ShellRoot {
|
||||
function transactionStatus(): string {
|
||||
return JSON.stringify({
|
||||
layout: Displays.currentLayout(),
|
||||
primaryFirst: Displays.primaryFirstMonitors.map(monitor => monitor.name),
|
||||
pending: Displays.pendingRequestedLayout,
|
||||
previous: Displays.pendingPreviousLayout,
|
||||
reverting: Displays.revertExpectedLayout,
|
||||
@@ -141,5 +146,8 @@ ShellRoot {
|
||||
if (monitor) Displays.forget(monitor.name);
|
||||
}
|
||||
function refresh(): void { Displays.refresh(); }
|
||||
function setScreenModel(names: string): void {
|
||||
Displays.screenOverride = JSON.parse(names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ SettingsPage {
|
||||
|
||||
property string selectedOutput: ""
|
||||
readonly property var monitor: Displays.monitorNamed(root.selectedOutput)
|
||||
?? (Displays.monitors.length > 0 ? Displays.monitors[0] : null)
|
||||
?? (Displays.primaryFirstMonitors.length > 0 ? Displays.primaryFirstMonitors[0] : null)
|
||||
readonly property string currentMode: root.monitor
|
||||
? root.monitor.mode
|
||||
: ""
|
||||
@@ -38,7 +38,8 @@ SettingsPage {
|
||||
|
||||
function syncSelectedOutput(): void {
|
||||
if (!Displays.monitorNamed(root.selectedOutput))
|
||||
root.selectedOutput = Displays.monitors.length > 0 ? Displays.monitors[0].name : "";
|
||||
root.selectedOutput = Displays.primaryFirstMonitors.length > 0
|
||||
? Displays.primaryFirstMonitors[0].name : "";
|
||||
}
|
||||
|
||||
// Probing I2C for DDC-capable monitors takes on the order of a second, so
|
||||
@@ -137,7 +138,7 @@ SettingsPage {
|
||||
ChoiceGrid {
|
||||
width: parent.width
|
||||
label: "Display"
|
||||
options: Displays.monitors.map(monitor => ({
|
||||
options: Displays.primaryFirstMonitors.map(monitor => ({
|
||||
value: monitor.name,
|
||||
label: monitor.description || monitor.name
|
||||
}))
|
||||
|
||||
@@ -18,6 +18,13 @@ Column {
|
||||
width: parent ? parent.width : 620
|
||||
spacing: 4
|
||||
|
||||
onOutputsChanged: root.syncSelectedOutput()
|
||||
|
||||
function syncSelectedOutput(): void {
|
||||
if (!root.outputs.includes(root.selectedOutput))
|
||||
root.selectedOutput = root.outputs.length > 0 ? root.outputs[0] : "";
|
||||
}
|
||||
|
||||
ChoiceGrid {
|
||||
width: parent.width
|
||||
label: "Wallpaper mode"
|
||||
|
||||
@@ -17,8 +17,10 @@ status_file="$state_dir/hyprlock-status.json"
|
||||
fallback="$config_home/hypr/hyprlock.conf"
|
||||
temporary="$generated.tmp.$$"
|
||||
status_temporary="$status_file.tmp.$$"
|
||||
shipped_wallpaper="$HOME/Pictures/Wallpapers/faroe_islands.jpg"
|
||||
|
||||
settings_valid=false
|
||||
wallpaper_warning=""
|
||||
|
||||
cleanup() {
|
||||
rm -f "$temporary" "$status_temporary" 2>/dev/null || true
|
||||
@@ -76,7 +78,23 @@ read_object() {
|
||||
}
|
||||
|
||||
valid_path() {
|
||||
[[ "$1" == /* && "$1" != *","* && "$1" != *$'\n'* ]]
|
||||
[[ "$1" == /* && "$1" != *","* && "$1" != *$'\n'* && -f "$1" && -r "$1" ]]
|
||||
}
|
||||
|
||||
resolve_wallpaper_path() {
|
||||
local candidate="$1"
|
||||
if valid_path "$candidate"; then
|
||||
resolved_wallpaper="$candidate"
|
||||
return
|
||||
fi
|
||||
if [[ -n "$candidate" ]]; then
|
||||
wallpaper_warning="One or more lock-screen wallpapers were unavailable; a safe fallback is in use."
|
||||
fi
|
||||
if valid_path "$shipped_wallpaper"; then
|
||||
resolved_wallpaper="$shipped_wallpaper"
|
||||
else
|
||||
resolved_wallpaper="screenshot"
|
||||
fi
|
||||
}
|
||||
|
||||
# Same 8 named accents as config/Theme.qml's `accents` map (kept in sync by
|
||||
@@ -134,10 +152,9 @@ load_preferences() {
|
||||
wallpaper_mode="$(read_string wallpaperMode single)"
|
||||
[[ "$wallpaper_mode" == single || "$wallpaper_mode" == slideshow || "$wallpaper_mode" == per-monitor ]] \
|
||||
|| wallpaper_mode=single
|
||||
wallpaper_path="$(read_string wallpaperPath '')"
|
||||
if ! valid_path "$wallpaper_path"; then
|
||||
wallpaper_path="$HOME/Pictures/Wallpapers/faroe_islands.jpg"
|
||||
fi
|
||||
wallpaper_warning=""
|
||||
resolve_wallpaper_path "$(read_string wallpaperPath '')"
|
||||
wallpaper_path="$resolved_wallpaper"
|
||||
wallpaper_assignments="$(read_object wallpaperPerMonitor)"
|
||||
|
||||
case "$blur_level" in
|
||||
@@ -218,8 +235,9 @@ emit_backgrounds() {
|
||||
candidate="$(jq -r --arg output "$output" \
|
||||
'if has($output) and (.[$output] | type) == "string" then .[$output] else "" end' \
|
||||
<<<"$wallpaper_assignments" 2>/dev/null || true)"
|
||||
if valid_path "$candidate"; then
|
||||
assigned="$candidate"
|
||||
if [[ -n "$candidate" ]]; then
|
||||
resolve_wallpaper_path "$candidate"
|
||||
assigned="$resolved_wallpaper"
|
||||
fi
|
||||
fi
|
||||
emit_background "$output" "$assigned"
|
||||
@@ -344,7 +362,7 @@ generate() {
|
||||
write_status false "$fallback" true "The lock-screen configuration could not be generated."
|
||||
return 1
|
||||
fi
|
||||
write_status true "$generated" false ""
|
||||
write_status true "$generated" false "$wallpaper_warning"
|
||||
}
|
||||
|
||||
status() {
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Finds wallpaper candidates from fixed roots passed as separate arguments.
|
||||
# Keeping paths as argv values avoids shell interpolation for names containing
|
||||
# quotes, spaces, or other shell syntax.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
find "$@" -maxdepth 2 -type f \
|
||||
\( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' \) \
|
||||
-printf '%T@ %p\n' 2>/dev/null \
|
||||
| sort -rn \
|
||||
| cut -d' ' -f2- \
|
||||
| head -60
|
||||
@@ -29,8 +29,24 @@ Singleton {
|
||||
// [{ name, description, width, height, refreshRate, scale, transform,
|
||||
// modes: [{ label, mode, width, height, refresh }] }]
|
||||
property var monitors: []
|
||||
// Quickshell.screens is the topology authority. The override is only the
|
||||
// isolated harness model; normal sessions always observe Quickshell.
|
||||
property var screenOverride: null
|
||||
property string lastError: ""
|
||||
|
||||
readonly property var screenModel: Array.isArray(root.screenOverride)
|
||||
? root.screenOverride : Quickshell.screens
|
||||
readonly property string screenSignature: root.screenModel
|
||||
.map(screen => typeof screen === "string" ? screen : screen.name)
|
||||
.filter(name => !!name)
|
||||
.sort()
|
||||
.join("|")
|
||||
readonly property var primaryFirstMonitors: root.monitors.slice().sort((left, right) => {
|
||||
if (left.primary !== right.primary)
|
||||
return left.primary ? -1 : 1;
|
||||
return left.name.localeCompare(right.name);
|
||||
})
|
||||
|
||||
// Set while a change is applied but not yet confirmed.
|
||||
property var pendingPreviousLayout: null
|
||||
property var pendingRequestedLayout: null
|
||||
@@ -113,6 +129,17 @@ Singleton {
|
||||
|
||||
Component.onCompleted: root.refresh()
|
||||
|
||||
onScreenSignatureChanged: root.reconcileTopology()
|
||||
|
||||
// A hotplug can invalidate the unconfirmed layout while the confirmation
|
||||
// is visible. Read the current compositor layout first; the normal queued
|
||||
// rollback then filters out any output that has disappeared.
|
||||
function reconcileTopology(): void {
|
||||
root.refresh();
|
||||
if (root.awaitingConfirmation)
|
||||
root.revertWithMessage("A display was connected or disconnected, so Panama restored the previous setting.");
|
||||
}
|
||||
|
||||
function refresh(): bool {
|
||||
if (!query.running) {
|
||||
query.generation = root.operationGeneration;
|
||||
|
||||
@@ -45,9 +45,10 @@ Singleton {
|
||||
readonly property string outputSignature: root.outputNames().slice().sort().join("|")
|
||||
|
||||
property var outputNames: function() {
|
||||
if (Array.isArray(root.outputOverride))
|
||||
return root.outputOverride.slice();
|
||||
return Quickshell.screens.map(screen => screen.name).filter(name => !!name);
|
||||
const outputs = Array.isArray(root.outputOverride)
|
||||
? root.outputOverride.slice()
|
||||
: Quickshell.screens.map(screen => screen.name).filter(name => !!name);
|
||||
return root.primaryFirstOutputs(outputs);
|
||||
}
|
||||
|
||||
readonly property var searchRoots: [
|
||||
@@ -59,10 +60,8 @@ Singleton {
|
||||
|
||||
Process {
|
||||
id: scan
|
||||
command: ["bash", "-lc",
|
||||
"find " + root.searchRoots.map(dir => `'${dir}'`).join(" ")
|
||||
+ " -maxdepth 2 -type f \\( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' \\)"
|
||||
+ " -printf '%T@ %p\\n' 2>/dev/null | sort -rn | cut -d' ' -f2- | head -60"]
|
||||
command: [Quickshell.shellDir + "/scripts/panama-wallpaper-scan"]
|
||||
.concat(root.searchRoots)
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
root.available = this.text.split("\n")
|
||||
@@ -164,6 +163,18 @@ Singleton {
|
||||
activeQuery.running = true;
|
||||
}
|
||||
|
||||
// The saved primary role anchors Panama's per-monitor choices. Keep the
|
||||
// compositor's remaining order stable so a reconnect does not reshuffle
|
||||
// the rest of the picker unnecessarily.
|
||||
function primaryFirstOutputs(outputs: var): var {
|
||||
const stored = DesktopPreferences.get("displays");
|
||||
const layouts = stored && typeof stored === "object" ? stored : {};
|
||||
const primary = outputs.find(output => layouts[output]?.primary === true);
|
||||
return primary === undefined
|
||||
? outputs
|
||||
: [primary].concat(outputs.filter(output => output !== primary));
|
||||
}
|
||||
|
||||
function candidates(extra: var): var {
|
||||
const result = [];
|
||||
const discovered = Array.isArray(root.candidateOverride)
|
||||
|
||||
@@ -7,7 +7,7 @@ import qs.services
|
||||
|
||||
ShellRoot {
|
||||
Component.onCompleted: {
|
||||
Wallpaper.outputOverride = ["DP-2", "HDMI-A-1"];
|
||||
Wallpaper.outputOverride = ["HDMI-A-1", "DP-2"];
|
||||
Wallpaper.candidateOverride = ["/images/a.jpg", "/images/b.jpg", "/images/c.jpg"];
|
||||
Wallpaper.startupRestoreEnabled = false;
|
||||
Wallpaper.slideshowIntervalOverrideMs = 60000;
|
||||
@@ -63,7 +63,9 @@ ShellRoot {
|
||||
assignments: DesktopPreferences.get("wallpaperPerMonitor"),
|
||||
slideshowPath: Wallpaper.slideshowPath,
|
||||
shuffleBag: Wallpaper.shuffleBag,
|
||||
slideshowTimerRunning: Wallpaper.slideshowTimerRunning
|
||||
slideshowTimerRunning: Wallpaper.slideshowTimerRunning,
|
||||
scanning: Wallpaper.scanning,
|
||||
available: Wallpaper.available
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,16 @@ ShellRoot {
|
||||
return JSON.stringify(root.calls);
|
||||
}
|
||||
|
||||
function selectOutput(output: string): void {
|
||||
controls.selectedOutput = output;
|
||||
}
|
||||
|
||||
function updateOutputs(outputs: string): void {
|
||||
controls.outputs = outputs === "" ? [] : outputs.split("|");
|
||||
}
|
||||
|
||||
function selectedOutput(): string { return controls.selectedOutput; }
|
||||
|
||||
function states(): string {
|
||||
picker.mode = "single";
|
||||
const single = {
|
||||
|
||||
@@ -223,16 +223,18 @@ failed_state="$(wait_for '.busy == false and .awaiting == false')"
|
||||
jq -e '.lastError | contains("rejected")' <<<"$failed_state" >/dev/null \
|
||||
|| fail "failed apply did not retain a useful recovery message: $failed_state"
|
||||
|
||||
# If an output disconnects while a change is pending, rollback sends one
|
||||
# transaction containing every output that is still connected.
|
||||
# If an output disconnects while a change is pending, the screen-model observer
|
||||
# refreshes topology and rolls back with only the still-connected output. This
|
||||
# intentionally does not call Displays.refresh() through the public harness API.
|
||||
[[ "$(run ipc --pid "$harness_pid" call displays-test applyLayoutFixture 3000 320)" == "true" ]] \
|
||||
|| fail 'disconnect fixture could not start'
|
||||
wait_for '.canConfirm == true' >/dev/null
|
||||
jq '.[0:1]' "$monitor_state" >"$fixture/connected.json"
|
||||
mv "$fixture/connected.json" "$monitor_state"
|
||||
run ipc --pid "$harness_pid" call displays-test refresh >/dev/null
|
||||
wait_for '.layout | length == 1' >/dev/null
|
||||
run ipc --pid "$harness_pid" call displays-test revertChange >/dev/null
|
||||
run ipc --pid "$harness_pid" call displays-test setScreenModel '["DP-2"]' >/dev/null
|
||||
wait_for '(.layout | length == 1) and .awaiting == false and .busy == false' >/dev/null
|
||||
[[ "$(transaction_status | jq -c .primaryFirst)" == '["DP-2"]' ]] \
|
||||
|| fail "primary-first monitor list did not reconcile after hot-unplug: $(transaction_status)"
|
||||
wait_for '.busy == false and .awaiting == false' >/dev/null
|
||||
disconnect_payload="$(tail -1 "$eval_log")"
|
||||
[[ "$(rg -o 'hl\.monitor' <<<"$disconnect_payload" | wc -l)" == "1" ]] \
|
||||
|
||||
@@ -14,6 +14,9 @@ settings="$config_home/panama/settings.json"
|
||||
generated="$state_home/panama/hyprlock.conf"
|
||||
hyprlock_log="$fixture/hyprlock.log"
|
||||
theme_helper="$repo_dir/config/dot/quickshell/scripts/panama-theme-apps"
|
||||
shipped_wallpaper="$fixture_home/Pictures/Wallpapers/faroe_islands.jpg"
|
||||
global_wallpaper="$fixture_home/Pictures/Wallpapers/global.jpg"
|
||||
portrait_wallpaper="$fixture_home/Pictures/Wallpapers/portrait.jpg"
|
||||
|
||||
fail() {
|
||||
printf 'lock screen helper contract: %s\n' "$1" >&2
|
||||
@@ -28,6 +31,9 @@ trap cleanup EXIT
|
||||
|
||||
mkdir -p "$fixture_home/Pictures/Wallpapers" "$config_home/panama" \
|
||||
"$config_home/hypr" "$state_home" "$test_bin"
|
||||
printf 'shipped wallpaper\n' >"$shipped_wallpaper"
|
||||
printf 'global wallpaper\n' >"$global_wallpaper"
|
||||
printf 'portrait wallpaper\n' >"$portrait_wallpaper"
|
||||
cp "$repo_dir/config/dot/hypr/hyprlock.conf.template" \
|
||||
"$config_home/hypr/hyprlock.conf.template"
|
||||
HOME="$fixture_home" XDG_CONFIG_HOME="$config_home" \
|
||||
@@ -131,19 +137,38 @@ write_settings '{"accentName":"not-a-real-accent","colorScheme":"dark"}'
|
||||
run_helper generate
|
||||
rg -Fq 'outer_color = rgba(130, 170, 255, 0.9)' "$generated" || fail 'an unknown accent name did not fall back to blue'
|
||||
|
||||
write_settings '{
|
||||
"lockBackgroundMode":"wallpaper",
|
||||
"wallpaperMode":"per-monitor",
|
||||
"wallpaperPath":"/images/global.jpg",
|
||||
"wallpaperPerMonitor":{"DP-2":"/images/portrait.jpg"}
|
||||
}'
|
||||
write_settings "$(jq -n \
|
||||
--arg global "$global_wallpaper" \
|
||||
--arg portrait "$portrait_wallpaper" \
|
||||
'{lockBackgroundMode:"wallpaper",wallpaperMode:"per-monitor",wallpaperPath:$global,wallpaperPerMonitor:{"DP-2":$portrait}}')"
|
||||
run_helper generate
|
||||
[[ "$(rg -c '^background \{' "$generated")" -eq 2 ]] || fail 'wallpaper mode did not create one block per output'
|
||||
awk '/^background \{/{block++} block==1 && /monitor = DP-2/{monitor=1} block==1 && /path = \/images\/portrait.jpg/{path=1} END{exit !(monitor && path)}' "$generated" \
|
||||
awk -v path="$portrait_wallpaper" '/^background \{/{block++} block==1 && /monitor = DP-2/{monitor=1} block==1 && index($0, "path = " path){path_seen=1} END{exit !(monitor && path_seen)}' "$generated" \
|
||||
|| fail 'per-monitor wallpaper was not used for DP-2'
|
||||
awk '/^background \{/{block++} block==2 && /monitor = HDMI-A-1/{monitor=1} block==2 && /path = \/images\/global.jpg/{path=1} END{exit !(monitor && path)}' "$generated" \
|
||||
awk -v path="$global_wallpaper" '/^background \{/{block++} block==2 && /monitor = HDMI-A-1/{monitor=1} block==2 && index($0, "path = " path){path_seen=1} END{exit !(monitor && path_seen)}' "$generated" \
|
||||
|| fail 'missing monitor assignment did not fall back to the global wallpaper'
|
||||
|
||||
# A selected but unreadable image never reaches hyprlock. One bounded warning
|
||||
# covers both missing global and per-monitor choices while the shipped image is
|
||||
# still readable.
|
||||
write_settings "$(jq -n \
|
||||
--arg missing "$fixture/missing.jpg" \
|
||||
'{lockBackgroundMode:"wallpaper",wallpaperMode:"per-monitor",wallpaperPath:$missing,wallpaperPerMonitor:{"DP-2":$missing}}')"
|
||||
run_helper generate
|
||||
[[ "$(rg -c "path = $shipped_wallpaper" "$generated")" -eq 2 ]] \
|
||||
|| fail 'missing selected wallpapers did not fall back to the readable shipped image'
|
||||
jq -e '.generated == true and .fallback == false
|
||||
and .error == "One or more lock-screen wallpapers were unavailable; a safe fallback is in use."' \
|
||||
<<<"$(run_helper status)" >/dev/null \
|
||||
|| fail 'missing wallpaper fallback did not report one bounded warning'
|
||||
|
||||
# If no image is readable, screenshot is the only background source that
|
||||
# hyprlock can still render safely.
|
||||
rm -f "$shipped_wallpaper"
|
||||
run_helper generate
|
||||
[[ "$(rg -c 'path = screenshot' "$generated")" -eq 2 ]] \
|
||||
|| fail 'missing wallpapers without the shipped image did not fall back to screenshots'
|
||||
|
||||
write_settings '{"lockBackgroundMode":"screenshot","lockShowClock":true,"lockShowDate":false,"lockShowUser":false,"use24Hour":true}'
|
||||
run_helper generate
|
||||
rg -Fq 'text = cmd[update:1000] date +"%H:%M"' "$generated" || fail '24-hour clock setting was ignored'
|
||||
|
||||
@@ -15,6 +15,8 @@ active_state="$fixture/active.json"
|
||||
control="$fixture/control"
|
||||
shell_log="$fixture/quickshell.log"
|
||||
harness_pid=""
|
||||
quoted_home="$fixture/home's"
|
||||
quoted_wallpaper="$quoted_home/Pictures/Wallpapers/quoted.jpg"
|
||||
|
||||
fail() {
|
||||
printf 'wallpaper service contract: %s\n' "$1" >&2
|
||||
@@ -46,9 +48,18 @@ cleanup() {
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
mkdir -p "$config_home/panama" "$state_home" "$test_bin"
|
||||
mkdir -p "$config_home/panama" "$state_home" "$test_bin" \
|
||||
"$quoted_home/Pictures/Wallpapers"
|
||||
cp -a "$repo_dir/config/dot/quickshell" "$config_path"
|
||||
printf '%s\n' '{}' >"$config_home/panama/settings.json"
|
||||
printf 'quoted wallpaper\n' >"$quoted_wallpaper"
|
||||
cat >"$config_home/panama/settings.json" <<'JSON'
|
||||
{
|
||||
"displays": {
|
||||
"DP-2": {"mode":"[email protected]","scale":1.5,"transform":0,"x":0,"y":0,"primary":true},
|
||||
"HDMI-A-1": {"mode":"[email protected]","scale":1,"transform":0,"x":3000,"y":0,"primary":false}
|
||||
}
|
||||
}
|
||||
JSON
|
||||
printf '%s\n' '{}' >"$active_state"
|
||||
: >"$command_log"
|
||||
: >"$control"
|
||||
@@ -92,6 +103,7 @@ qs_for_harness() {
|
||||
XDG_CONFIG_HOME="$config_home"
|
||||
XDG_STATE_HOME="$state_home"
|
||||
PATH="$test_bin:$PATH"
|
||||
HOME="$quoted_home"
|
||||
PANAMA_WALLPAPER_COMMAND_LOG="$command_log"
|
||||
PANAMA_WALLPAPER_ACTIVE_STATE="$active_state"
|
||||
PANAMA_WALLPAPER_CONTROL="$control"
|
||||
@@ -113,6 +125,18 @@ wait_idle() {
|
||||
fail "wallpaper transaction did not settle: $status"
|
||||
}
|
||||
|
||||
wait_for_scan() {
|
||||
local status=""
|
||||
for _ in $(seq 1 80); do
|
||||
status="$(qs_for_harness ipc call wallpaper-service-test status)"
|
||||
jq -e --arg path "$quoted_wallpaper" \
|
||||
'.scanning == false and (.available | index($path) != null)' \
|
||||
<<<"$status" >/dev/null && return
|
||||
sleep 0.1
|
||||
done
|
||||
fail "argument-safe wallpaper scan did not discover the quoted HOME image: $status"
|
||||
}
|
||||
|
||||
qs_for_harness --daemonize >"$shell_log" 2>&1 || fail 'isolated service harness did not launch'
|
||||
for _ in $(seq 1 60); do
|
||||
harness_pid="$(instances_for_harness | head -1)"
|
||||
@@ -123,6 +147,7 @@ for _ in $(seq 1 60); do
|
||||
sleep 0.1
|
||||
done
|
||||
[[ "$harness_pid" =~ ^[0-9]+$ ]] || fail 'isolated service harness process did not start'
|
||||
wait_for_scan
|
||||
wait_idle >/dev/null
|
||||
|
||||
: >"$command_log"
|
||||
|
||||
@@ -68,6 +68,15 @@ done
|
||||
[[ "$(qs_for_harness ipc call wallpaper-settings-test activate per-monitor /images/c.jpg)" \
|
||||
== '["assign:DP-2:/images/c.jpg"]' ]] || fail 'Per-display mode did not assign the selected output'
|
||||
|
||||
qs_for_harness ipc call wallpaper-settings-test selectOutput HDMI-A-1 >/dev/null
|
||||
qs_for_harness ipc call wallpaper-settings-test updateOutputs 'DP-2|HDMI-A-1' >/dev/null
|
||||
[[ "$(qs_for_harness ipc call wallpaper-settings-test selectedOutput)" == 'HDMI-A-1' ]] \
|
||||
|| fail 'a valid per-monitor selection was reset during topology reconciliation'
|
||||
qs_for_harness ipc call wallpaper-settings-test updateOutputs 'DP-2' >/dev/null
|
||||
selected_after_disconnect="$(qs_for_harness ipc call wallpaper-settings-test selectedOutput)"
|
||||
[[ "$selected_after_disconnect" == 'DP-2' ]] \
|
||||
|| fail "a disconnected per-monitor selection did not fall back to the primary output: $selected_after_disconnect"
|
||||
|
||||
states="$(qs_for_harness ipc call wallpaper-settings-test states)"
|
||||
jq -e '.selectedOutput == "DP-2"
|
||||
and .single == {"current":true,"selected":true}
|
||||
|
||||
Reference in New Issue
Block a user