* Cache workspace layouts instead of running a script on every switch Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Test the workspace layout cache by running its logic, not matching its source Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the fake layout read running while its exit handler runs Addresses Codex review: clearing running first let an undeferred, broken retry pass the cache tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
527 lines
22 KiB
Bash
Executable File
527 lines
22 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
source "$(dirname "${BASH_SOURCE[0]}")/base-test.sh"
|
|
|
|
require_command lua
|
|
require_command jq
|
|
|
|
tmpdir=$(mktemp -d)
|
|
trap 'rm -rf "$tmpdir"' EXIT
|
|
|
|
stub_dir="$tmpdir/bin"
|
|
fake="$tmpdir/hyprland"
|
|
home_dir="$tmpdir/home"
|
|
state_home="$home_dir/.local/state"
|
|
runtime_dir="$tmpdir/runtime"
|
|
modes_dir="$state_home/omarchy/workspace-layouts"
|
|
mkdir -p "$stub_dir" "$fake" "$home_dir" "$runtime_dir"
|
|
|
|
# Answers come from files the cases write, and everything else is logged.
|
|
cat >"$stub_dir/hyprctl" <<'EOF'
|
|
#!/bin/bash
|
|
|
|
case "$1" in
|
|
activeworkspace) cat "$FAKE/activeworkspace.json" ;;
|
|
activewindow) cat "$FAKE/activewindow.json" ;;
|
|
clients) cat "$FAKE/clients.json" ;;
|
|
monitors) cat "$FAKE/monitors.json" ;;
|
|
workspaces) cat "$FAKE/workspaces.json" ;;
|
|
plugin) printf '[]\n' ;;
|
|
getoption) printf '{"option": "%s", "str": "%s"}\n' "$2" "${HYPRCTL_LAYOUT:-dwindle}" ;;
|
|
*) printf '%s\n' "$*" >>"$FAKE/hyprctl.log" ;;
|
|
esac
|
|
EOF
|
|
|
|
for command in omarchy-notification-send omarchy-bar omarchy-shell; do
|
|
printf '#!/bin/bash\nprintf "%%s\\n" "%s $*" >>"$FAKE/commands.log"\n' "$command" >"$stub_dir/$command"
|
|
done
|
|
chmod +x "$stub_dir"/*
|
|
|
|
run() {
|
|
env HOME="$home_dir" XDG_STATE_HOME="$state_home" XDG_RUNTIME_DIR="$runtime_dir" FAKE="$fake" \
|
|
PATH="$stub_dir:$ROOT/bin:$PATH" "$@"
|
|
}
|
|
|
|
dispatched() {
|
|
grep -F "dispatch hl.dsp.$1" "$fake/hyprctl.log" >/dev/null
|
|
}
|
|
|
|
reset_logs() {
|
|
: >"$fake/hyprctl.log"
|
|
: >"$fake/commands.log"
|
|
}
|
|
|
|
cat >"$fake/monitors.json" <<'EOF'
|
|
[{ "id": 0, "name": "DP-1", "x": 0, "y": 0, "width": 2560, "height": 1440, "scale": 1, "transform": 0, "reserved": [0, 30, 0, 0] }]
|
|
EOF
|
|
printf '{ "id": 2, "name": "2", "tiledLayout": "dwindle" }\n' >"$fake/activeworkspace.json"
|
|
printf '[{ "id": 2, "name": "2", "tiledLayout": "dwindle" }, { "id": 4, "name": "4", "tiledLayout": "scrolling" }]\n' >"$fake/workspaces.json"
|
|
reset_logs
|
|
|
|
# --- Which layout a workspace is in -------------------------------------------
|
|
|
|
[[ $(run omarchy-hyprland-workspace-layout-current) == "dwindle" ]] ||
|
|
fail "a workspace with nothing saved reports Hyprland's tiled layout"
|
|
[[ $(run omarchy-hyprland-workspace-layout-current 4) == "scrolling" ]] ||
|
|
fail "another workspace reports its own tiled layout"
|
|
|
|
mkdir -p "$modes_dir"
|
|
printf 'o.workspace_mode({ workspace = "4", mode = "floating" })\n' >"$modes_dir/4.lua"
|
|
[[ $(run omarchy-hyprland-workspace-layout-current 4) == "floating" ]] ||
|
|
fail "a saved floating mode is reported, though Hyprland cannot report it"
|
|
pass "the layout of a workspace comes from its saved mode before Hyprland's"
|
|
|
|
# --- The default layout -------------------------------------------------------
|
|
|
|
[[ $(run omarchy-hyprland-workspace-layout-default) == "dwindle" ]] || fail "the default layout starts as tiling"
|
|
[[ $(HYPRCTL_LAYOUT=scrolling run omarchy-hyprland-workspace-layout-default) == "scrolling" ]] ||
|
|
fail "without a saved default, Hyprland's own layout is the default"
|
|
run omarchy-hyprland-workspace-layout-default scrolling
|
|
grep -qxF 'o.workspace_mode({ default = "scrolling" })' "$modes_dir/default.lua" ||
|
|
fail "the default layout is saved" "$(cat "$modes_dir/default.lua" 2>&1)"
|
|
[[ $(run omarchy-hyprland-workspace-layout-default) == "scrolling" ]] || fail "the saved default is reported"
|
|
grep -qx 'reload' "$fake/hyprctl.log" || fail "a new default reloads Hyprland"
|
|
grep -qF 'omarchy-shell -q omarchy.workspaces refresh' "$fake/commands.log" || fail "a new default refreshes the bar"
|
|
pass "the default layout is saved, applied, and shown"
|
|
|
|
run omarchy-hyprland-workspace-layout-default floating
|
|
[[ $(run omarchy-hyprland-workspace-layout-current 2) == "floating" ]] ||
|
|
fail "a workspace without a layout of its own follows the default"
|
|
[[ $(run omarchy-hyprland-workspace-layout-current 4) == "floating" ]] ||
|
|
fail "a saved floating workspace still reports floating"
|
|
printf 'o.workspace_mode({ workspace = "4", mode = "dwindle" })\n' >"$modes_dir/4.lua"
|
|
[[ $(run omarchy-hyprland-workspace-layout-current 4) == "dwindle" ]] ||
|
|
fail "a workspace given its own layout keeps it under a floating default"
|
|
pass "workspaces without a layout of their own follow the default, others keep theirs"
|
|
|
|
if run omarchy-hyprland-workspace-layout-default sideways 2>/dev/null; then
|
|
fail "an unknown default layout is refused"
|
|
fi
|
|
[[ $(run omarchy-hyprland-workspace-layout-default) == "floating" ]] || fail "a refused default leaves the saved one alone"
|
|
pass "an unknown default layout is refused"
|
|
rm -f "$modes_dir"/*.lua
|
|
|
|
# --- The workspace mode in Hyprland --------------------------------------------
|
|
|
|
mkdir -p "$state_home/omarchy/current/theme"
|
|
cat >"$state_home/omarchy/current/theme/colors.toml" <<'EOF'
|
|
background = "#112233"
|
|
lighter_background = "#223344"
|
|
selection = "#334455"
|
|
foreground = "#ddeeff"
|
|
accent = "#ff8800"
|
|
EOF
|
|
|
|
lua_harness="$tmpdir/harness.lua"
|
|
cat >"$lua_harness" <<'LUA'
|
|
local all = {}
|
|
local subscriptions = {}
|
|
local plugin_loads = 0
|
|
local titlebar_config = nil
|
|
local buttons = {}
|
|
local config = { ["input.follow_mouse"] = 1, ["decoration.shadow.enabled"] = false }
|
|
local rules = {}
|
|
local placed = {}
|
|
local active = { id = 1 }
|
|
local titlebars_installed = os.getenv("TITLEBARS_INSTALLED") == "1"
|
|
|
|
local monitor = { width = 2560, height = 1440, scale = 1, transform = 0, reserved = { left = 0, right = 0, top = 30, bottom = 0 } }
|
|
|
|
function window(address, workspace, options)
|
|
options = options or {}
|
|
local created = {
|
|
address = address,
|
|
floating = options.floating or false,
|
|
fullscreen = options.fullscreen or 0,
|
|
pinned = false,
|
|
tags = options.tags or {},
|
|
workspace = { id = workspace },
|
|
monitor = monitor,
|
|
}
|
|
table.insert(all, created)
|
|
return created
|
|
end
|
|
|
|
function has_tag(target, tag)
|
|
for _, held in ipairs(target.tags) do
|
|
if held == tag then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local real_open = io.open
|
|
io.open = function(path, mode)
|
|
if path == "/usr/lib/omarchy-hyprland-titlebars/titlebars.so" then
|
|
return titlebars_installed and { close = function() end } or nil
|
|
end
|
|
return real_open(path, mode)
|
|
end
|
|
|
|
hl = {
|
|
workspace_rule = function() end,
|
|
window_rule = function(rule) table.insert(rules, rule) end,
|
|
get_workspace_windows = function(selector)
|
|
local found = {}
|
|
for _, candidate in ipairs(all) do
|
|
if tostring(candidate.workspace.id) == tostring(selector) then
|
|
table.insert(found, candidate)
|
|
end
|
|
end
|
|
return found
|
|
end,
|
|
get_windows = function(filter)
|
|
local found = {}
|
|
for _, candidate in ipairs(all) do
|
|
if not filter or (filter.tag and has_tag(candidate, filter.tag)) then
|
|
table.insert(found, candidate)
|
|
end
|
|
end
|
|
return found
|
|
end,
|
|
dispatch = function(action)
|
|
local target = action.window
|
|
if action.kind == "place" then
|
|
placed[target.address] = true
|
|
elseif action.tag then
|
|
local name = action.tag:sub(2)
|
|
if action.tag:sub(1, 1) == "+" then
|
|
if not has_tag(target, name) then
|
|
table.insert(target.tags, name)
|
|
end
|
|
else
|
|
for index, held in ipairs(target.tags) do
|
|
if held == name then
|
|
table.remove(target.tags, index)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
else
|
|
target.floating = action.action == "on"
|
|
end
|
|
end,
|
|
dsp = {
|
|
window = {
|
|
float = function(spec) return spec end,
|
|
tag = function(spec) return spec end,
|
|
resize = function(spec) spec.kind = "place" return spec end,
|
|
center = function(spec) spec.kind = "place" return spec end,
|
|
move = function(spec) spec.kind = "place" return spec end,
|
|
},
|
|
},
|
|
on = function(event, callback)
|
|
subscriptions[event] = subscriptions[event] or {}
|
|
table.insert(subscriptions[event], callback)
|
|
end,
|
|
timer = function(callback) callback() end,
|
|
get_active_workspace = function() return active end,
|
|
get_config = function(key) return config[key] end,
|
|
config = function(values)
|
|
if values.plugin then
|
|
titlebar_config = values.plugin.hyprbars
|
|
return
|
|
end
|
|
local function flatten(prefix, options)
|
|
for key, value in pairs(options) do
|
|
if type(value) == "table" then
|
|
flatten(prefix .. key .. ".", value)
|
|
else
|
|
config[prefix .. key] = value
|
|
end
|
|
end
|
|
end
|
|
flatten("", values)
|
|
end,
|
|
plugin = {
|
|
load = function(path)
|
|
plugin_loads = plugin_loads + 1
|
|
assert(path == "/usr/lib/omarchy-hyprland-titlebars/titlebars.so", "the titlebars were loaded from the wrong place")
|
|
if os.getenv("TITLEBARS_LOADED") == "1" then
|
|
hl.plugin.hyprbars = { add_button = function(button) table.insert(buttons, button) end }
|
|
end
|
|
end,
|
|
},
|
|
}
|
|
|
|
function state()
|
|
return {
|
|
plugin_loads = plugin_loads,
|
|
titlebar_config = titlebar_config,
|
|
buttons = buttons,
|
|
config = config,
|
|
rules = rules,
|
|
placed = placed,
|
|
subscriptions = subscriptions,
|
|
active = active,
|
|
}
|
|
end
|
|
|
|
dofile(os.getenv("OMARCHY_PATH") .. "/default/hypr/bootstrap.lua")
|
|
require("default.hypr.helpers")
|
|
LUA
|
|
|
|
run_lua() {
|
|
local script=$1
|
|
shift
|
|
printf 'dofile("%s")\n%s\n' "$lua_harness" "$script" >"$tmpdir/case.lua"
|
|
env HOME="$home_dir" XDG_STATE_HOME="$state_home" OMARCHY_PATH="$ROOT" "$@" lua "$tmpdir/case.lua"
|
|
}
|
|
|
|
rm -rf "$modes_dir"
|
|
|
|
# Nothing floats: the titlebar plugin stays out and nothing changes.
|
|
if ! run_lua '
|
|
local tiled = window("0x1", 1)
|
|
require("default.hypr.workspace-layouts")
|
|
local s = state()
|
|
assert(s.plugin_loads == 0, "the titlebar plugin was loaded with nothing floating")
|
|
assert(not tiled.floating, "a window floated with nothing floating")
|
|
assert(#s.rules == 0, "window rules were added with nothing floating")
|
|
' TITLEBARS_INSTALLED=1; then
|
|
fail "nothing changes for a session where no workspace floats"
|
|
fi
|
|
pass "nothing changes for a session where no workspace floats"
|
|
|
|
# A floating default floats every regular workspace without a mode of its own,
|
|
# and leaves special workspaces and those with their own layout alone.
|
|
mkdir -p "$modes_dir"
|
|
printf 'o.workspace_mode({ default = "floating" })\n' >"$modes_dir/default.lua"
|
|
printf 'o.workspace_mode({ workspace = "2", mode = "dwindle" })\n' >"$modes_dir/2.lua"
|
|
|
|
if ! run_lua '
|
|
local first = window("0x1", 1)
|
|
local dialog = window("0x2", 1, { floating = true })
|
|
local own_layout = window("0x3", 2)
|
|
local scratch = window("0x4", -98)
|
|
local fullscreen = window("0x5", 3, { fullscreen = 1 })
|
|
require("default.hypr.workspace-layouts")
|
|
local s = state()
|
|
|
|
assert(first.floating and has_tag(first, "omarchy-mode-floated"), "a window on an unsaved workspace was not floated")
|
|
assert(s.placed["0x1"], "a window the mode floated was not given a place")
|
|
assert(has_tag(first, "omarchy-floating-workspace"), "a floated window got no titlebar tag")
|
|
assert(has_tag(dialog, "omarchy-floating-workspace"), "a window floating already got no titlebar tag")
|
|
assert(not has_tag(dialog, "omarchy-mode-floated"), "a window floating already was claimed")
|
|
assert(not s.placed["0x2"], "a window floating already was moved")
|
|
assert(not own_layout.floating, "a workspace with its own layout was floated")
|
|
assert(not has_tag(own_layout, "omarchy-floating-workspace"), "a tiled window got a titlebar tag")
|
|
assert(not scratch.floating and #scratch.tags == 0, "a window on a special workspace was touched")
|
|
assert(not fullscreen.floating, "a fullscreen window was floated")
|
|
assert(has_tag(fullscreen, "omarchy-floating-workspace"), "a fullscreen window on a floating workspace got no titlebar tag")
|
|
|
|
-- Pointer focus follows the active workspace.
|
|
assert(s.config["input.follow_mouse"] == 0, "a floating workspace focused on hover")
|
|
s.active.id = 2
|
|
s.subscriptions["workspace.active"][1]()
|
|
assert(s.config["input.follow_mouse"] == 1, "a tiled workspace lost the user focus setting")
|
|
|
|
-- Shadows only for windows on floating workspaces, when the user has none.
|
|
assert(s.config["decoration.shadow.enabled"] == true, "floating windows got no shadows to tell them apart")
|
|
assert(#s.rules >= 3, "the floating window rules were not added")
|
|
' TITLEBARS_INSTALLED=1; then
|
|
fail "a floating default floats every regular workspace without a layout of its own"
|
|
fi
|
|
pass "a floating default floats every regular workspace without a layout of its own"
|
|
|
|
# A tiled default is Hyprland's own layout for workspaces without a rule.
|
|
rm -f "$modes_dir"/*.lua
|
|
printf 'o.workspace_mode({ default = "scrolling" })\n' >"$modes_dir/default.lua"
|
|
if ! run_lua '
|
|
require("default.hypr.workspace-layouts")
|
|
assert(state().config["general.layout"] == "scrolling", "a scrolling default did not set the layout: " .. tostring(state().config["general.layout"]))
|
|
'; then
|
|
fail "a scrolling default is Hyprland's default layout"
|
|
fi
|
|
pass "a scrolling default is Hyprland's default layout"
|
|
|
|
# Moving the default away from floating is a fresh start, and the windows it floated
|
|
# have to be given back even though nothing names their workspace any more. With
|
|
# no workspace given a layout of its own, no saved mode is left to start the mode.
|
|
rm -f "$modes_dir"/*.lua
|
|
if ! run_lua '
|
|
local floated = window("0x1", 1, { floating = true, tags = { "omarchy-mode-floated", "omarchy-floating-workspace" } })
|
|
local dialog = window("0x2", 1, { floating = true, tags = { "omarchy-floating-workspace" } })
|
|
local scratch = window("0x3", -98, { floating = true, tags = { "omarchy-mode-floated" } })
|
|
require("default.hypr.workspace-layouts")
|
|
assert(not floated.floating, "a window the mode floated stayed floating once floating was off")
|
|
assert(not has_tag(floated, "omarchy-mode-floated"), "a window given back is still claimed")
|
|
assert(not has_tag(floated, "omarchy-floating-workspace"), "a tiled window kept its titlebar tag")
|
|
assert(dialog.floating, "a dialog was tiled when floating was turned off")
|
|
assert(not has_tag(dialog, "omarchy-floating-workspace"), "a dialog on a tiled workspace kept its titlebar tag")
|
|
assert(scratch.floating and has_tag(scratch, "omarchy-mode-floated"), "a window on the scratchpad was tiled")
|
|
'; then
|
|
fail "turning floating off gives back what it floated, but not what is on the scratchpad"
|
|
fi
|
|
pass "turning floating off gives back what it floated, but not what is on the scratchpad"
|
|
|
|
# A tiled window that was fullscreen when its workspace started floating is
|
|
# left as it is, and floats once it comes out of fullscreen.
|
|
printf 'o.workspace_mode({ workspace = "1", mode = "floating" })\n' >"$modes_dir/1.lua"
|
|
if ! run_lua '
|
|
local video = window("0x1", 1, { fullscreen = 2 })
|
|
require("default.hypr.workspace-layouts")
|
|
assert(not video.floating, "a fullscreen window was floated")
|
|
video.fullscreen = 0
|
|
state().subscriptions["window.fullscreen"][1](video)
|
|
assert(video.floating and has_tag(video, "omarchy-mode-floated"), "a window leaving fullscreen on a floating workspace stayed tiled")
|
|
'; then
|
|
fail "a window leaving fullscreen on a floating workspace floats"
|
|
fi
|
|
pass "a window leaving fullscreen on a floating workspace floats"
|
|
|
|
# A window moved to a special workspace, like the scratchpad, is left alone.
|
|
printf 'o.workspace_mode({ workspace = "1", mode = "floating" })\n' >"$modes_dir/1.lua"
|
|
if ! run_lua '
|
|
local moving = window("0x1", 1)
|
|
require("default.hypr.workspace-layouts")
|
|
local moved = state().subscriptions["window.move_to_workspace"][1]
|
|
assert(moving.floating, "the window was not floated to begin with")
|
|
|
|
moved(moving, { id = -98 })
|
|
assert(moving.floating and has_tag(moving, "omarchy-mode-floated"), "a window was tiled on its way to the scratchpad")
|
|
|
|
moved(moving, { id = 2 })
|
|
assert(not moving.floating, "a window carried to a tiled workspace kept floating")
|
|
'; then
|
|
fail "the mode leaves windows on their way to a special workspace alone"
|
|
fi
|
|
pass "the mode leaves windows on their way to a special workspace alone"
|
|
|
|
# Titlebars: asked for only when installed, and themed once loaded.
|
|
if ! run_lua '
|
|
window("0x1", 1)
|
|
require("default.hypr.workspace-layouts")
|
|
assert(state().plugin_loads == 0, "titlebars were asked for without being installed")
|
|
'; then
|
|
fail "a missing titlebar package leaves floating without titlebars"
|
|
fi
|
|
|
|
if ! run_lua '
|
|
window("0x1", 1)
|
|
require("default.hypr.workspace-layouts")
|
|
local s = state()
|
|
assert(s.plugin_loads == 1, "the titlebar plugin was not loaded with a workspace floating")
|
|
local bar = s.titlebar_config
|
|
assert(bar, "the titlebars were not configured once loaded")
|
|
assert(bar.workspace_tag == "omarchy-floating-workspace", "titlebars are not limited to floating workspaces")
|
|
assert(bar.bar_color == "rgb(223344)", "titlebars do not stand out from the window background: " .. tostring(bar.bar_color))
|
|
assert(bar.inactive_button_color == "rgb(223344)", "titlebar buttons do not match the titlebar")
|
|
for _, button in ipairs(s.buttons) do
|
|
assert(button.bg_color == "rgb(223344)", "a titlebar button does not match the titlebar")
|
|
end
|
|
assert(bar.col.text == "rgb(ddeeff)", "titlebar text does not follow the theme foreground")
|
|
assert(not bar.edge_snap, "dragged windows snap to screen edges")
|
|
assert(#s.buttons == 2, "the titlebars do not have exactly close and maximize")
|
|
local actions = s.buttons[1].action .. s.buttons[2].action
|
|
assert(actions:find("window.close", 1, true), "no button closes the window")
|
|
assert(actions:find("maximized", 1, true), "no button maximizes the window")
|
|
assert(bar.on_double_click:find("maximized", 1, true), "double-clicking a titlebar does not maximize")
|
|
' TITLEBARS_INSTALLED=1 TITLEBARS_LOADED=1; then
|
|
fail "titlebars load when a workspace floats and follow the theme"
|
|
fi
|
|
pass "titlebars load when a workspace floats and follow the theme"
|
|
|
|
# A theme whose raised surface is its background would hide the titlebar again,
|
|
# so it takes the selection color instead.
|
|
theme="$state_home/omarchy/current/theme/colors.toml"
|
|
cp "$theme" "$theme.saved"
|
|
sed -i 's/^lighter_background = .*/lighter_background = "#112233"/' "$theme"
|
|
if ! run_lua '
|
|
window("0x1", 1)
|
|
require("default.hypr.workspace-layouts")
|
|
local bar = state().titlebar_config
|
|
assert(bar and bar.bar_color == "rgb(334455)", "a flat theme does not fall back to its selection color: " .. tostring(bar and bar.bar_color))
|
|
' TITLEBARS_INSTALLED=1 TITLEBARS_LOADED=1; then
|
|
fail "titlebars stand out on a theme whose raised surface is its background"
|
|
fi
|
|
mv "$theme.saved" "$theme"
|
|
pass "titlebars stand out on a theme whose raised surface is its background"
|
|
|
|
# omarchy-theme-set writes the current theme under ~/.local/state even when
|
|
# XDG_STATE_HOME points elsewhere, so that is where the titlebars find it.
|
|
other_state="$tmpdir/other-state"
|
|
mkdir -p "$other_state/omarchy/workspace-layouts"
|
|
cp "$modes_dir/1.lua" "$other_state/omarchy/workspace-layouts/"
|
|
if ! run_lua '
|
|
window("0x1", 1)
|
|
require("default.hypr.workspace-layouts")
|
|
local bar = state().titlebar_config
|
|
assert(bar and bar.bar_color == "rgb(223344)", "titlebars missed the theme with XDG_STATE_HOME elsewhere: " .. tostring(bar and bar.bar_color))
|
|
' TITLEBARS_INSTALLED=1 TITLEBARS_LOADED=1 XDG_STATE_HOME="$other_state"; then
|
|
fail "titlebars follow the theme when XDG_STATE_HOME points elsewhere"
|
|
fi
|
|
pass "titlebars follow the theme when XDG_STATE_HOME points elsewhere"
|
|
|
|
run_node_test <<'JS'
|
|
const source = require('fs').readFileSync(path.join(root, 'shell/plugins/bar/widgets/Workspaces.qml'), 'utf8')
|
|
assert(source.includes('function onFocusedWorkspaceChanged() { root.showFocusedMode() }'), 'a workspace switch shows the cached layout instead of running the script')
|
|
assert(source.includes('String(event.name) === "configreloaded") root.refresh()'), 'a config reload drops every cached layout')
|
|
|
|
// Run the widget's own cache logic against a fake Hyprland and process.
|
|
function body(pattern) {
|
|
const match = source.match(pattern)
|
|
if (!match) fail('Workspaces.qml cache logic can be extracted', String(pattern))
|
|
return match[1]
|
|
}
|
|
// QML resolves bare calls like readMode() on the widget, so it is passed in too.
|
|
const args = ['root', 'Hyprland', 'modeProc', 'Qt', 'readMode']
|
|
const fn = name => new Function(...args, body(new RegExp(`\\n function ${name}\\(\\) \\{([\\s\\S]*?)\\n \\}`)))
|
|
const streamFinished = new Function(...args, 'text', body(/onStreamFinished: \{([\s\S]*?)\n \}/))
|
|
const exited = new Function(...args, body(/onExited: \{([\s\S]*?)\n \}/))
|
|
|
|
const hyprland = { focusedWorkspace: { id: 1 } }
|
|
const proc = { running: false, command: null, runs: [] }
|
|
const deferred = []
|
|
const qt = { callLater: callback => deferred.push(callback) }
|
|
const widget = { focusedMode: 'dwindle', modeStale: false, modeCache: {}, modeGeneration: 0, modeProcWorkspace: -1, modeProcGeneration: 0 }
|
|
for (const name of ['focusedWorkspaceId', 'showFocusedMode', 'readMode', 'refresh']) {
|
|
const call = fn(name)
|
|
widget[name] = () => call(widget, hyprland, proc, qt, widget.readMode)
|
|
}
|
|
const start = () => { if (proc.running) proc.runs.push(proc.command[1]) }
|
|
// Quickshell still reports the process running while it signals exit, so a
|
|
// retry has to be deferred until after the handler returns.
|
|
const finish = output => {
|
|
streamFinished(widget, hyprland, proc, qt, widget.readMode, output)
|
|
exited(widget, hyprland, proc, qt, widget.readMode)
|
|
proc.running = false
|
|
while (deferred.length) deferred.shift()()
|
|
}
|
|
const focus = id => { hyprland.focusedWorkspace = { id }; widget.showFocusedMode(); start() }
|
|
|
|
widget.refresh(); start()
|
|
finish('scrolling\n')
|
|
assertEqual(widget.focusedMode, 'scrolling', 'the first read shows the focused layout')
|
|
focus(2); finish('floating\n')
|
|
focus(1)
|
|
assertEqual(widget.focusedMode, 'scrolling', 'returning to a read workspace shows its cached layout')
|
|
assertDeepEqual(proc.runs, ['1', '2'], 'a cached workspace switch runs no script')
|
|
|
|
widget.refresh(); start()
|
|
assert(proc.running && proc.runs.length === 3, 'a refresh reads the focused layout again')
|
|
assertDeepEqual(widget.modeCache, {}, 'a refresh drops every cached layout')
|
|
widget.refresh()
|
|
assert(widget.modeStale, 'a refresh during a read marks it stale')
|
|
finish('dwindle\n')
|
|
assert(widget.modeCache[1] === undefined, 'a read that predates a refresh is not cached')
|
|
start()
|
|
assertDeepEqual(proc.runs, ['1', '2', '1', '1'], 'a stale read is retried once it exits')
|
|
finish('floating\n')
|
|
assertEqual(widget.focusedMode, 'floating', 'the retried read shows the current layout')
|
|
|
|
widget.refresh(); start()
|
|
focus(2)
|
|
assert(widget.modeStale, 'switching to an uncached workspace during a read marks it pending')
|
|
finish('scrolling\n')
|
|
assertEqual(widget.modeCache[1], 'scrolling', 'the in-flight read still caches its own workspace')
|
|
assertEqual(widget.focusedMode, 'floating', 'a read for a workspace no longer focused leaves the shown layout alone')
|
|
start()
|
|
assertEqual(proc.runs[proc.runs.length - 1], '2', 'the pending read follows the newly focused workspace')
|
|
finish('dwindle\n')
|
|
assertEqual(widget.focusedMode, 'dwindle', 'the newly focused workspace shows its own layout')
|
|
JS
|