* Let the workspace layout test's Lua assertions fail the suite
Lua 5.5's standalone interpreter exits 0 when the script that raised the error came in on stdin, and exits 1 for the same error in a script file. The block here was piped in through a heredoc, so every assertion in it reported as passing however wrong the value was: changing the expected layout to one the toggle never writes still left the file green.
* Add a floating mode to the workspace layout cycle
Super + L cycled a workspace between dwindle and scrolling. It now carries on into floating, where every window on the workspace floats.
Floating cannot be a third `layout` on the workspace rule. Hyprland has no layout by that name and accepts one anyway: `hl.workspace_rule({ layout = "floating" })` returns ok and silently leaves the workspace on dwindle, exactly as a misspelt name does. So the mode floats the windows themselves, through the same `float` dispatcher SUPER + T uses.
Ending the mode has to tile the windows the mode floated and only those, so each one is tagged with `omarchy-mode-floated`. A window already floating when the mode reaches it -- an app with a float rule of its own, a dialog, one popped out with SUPER + T -- is left unclaimed and is still floating when the mode ends. The tag lives on the window rather than in a table here: it dies with the window, follows it between workspaces, and survives the config reload that plugging in a monitor performs, none of which a table on this side would do. A pinned window is left alone entirely, because tiling one unpins it as well.
Windows arrive on a workspace three ways and a window rule only covers one of them, so the mode watches events: window.open for what opens there, window.move_to_workspace for what is carried in, and the same event for what is carried back out, which gives up the floating the mode gave it rather than staying floating on a workspace that tiles. Every tiled window is claimed before any of them is floated, because floating one member of a group floats the rest, and claiming as it went would find those already floating and leave them stranded.
The mode is read back from the saved file rather than from the compositor. Hyprland reports a workspace's tiled layout and floating is not one, so a floating workspace still answers dwindle or scrolling; trusting that answer would skip a step of the cycle. A file saved before floating existed names a layout rather than a mode and falls through to what Hyprland reports, so the cycle keeps working and the next press rewrites it.
Two faults in the old script had to go first, because saving the mode makes both persistent. Named workspaces report a negative id, which passed the `^-?[0-9]+$` guard, and Hyprland reads a leading "-" as a selector relative to the current workspace: pressing Super + L on a named workspace reconfigured workspace 1 instead, and would now save a file that did it again at every login. And the saved state was written under $HOME/.local/state while the Hyprland side reads XDG_STATE_HOME, which also had to reach package.path: the loader lists that directory but loads the files by module name, so a state home anywhere else listed the saved layouts and then loaded same-named ones from the home directory.
Saving is checked too. The mode reaching the compositor and the mode reaching disk are separate things that can fail separately, and a save that failed silently would leave the next press reading a file that never got written, cycling between two modes and never reaching the third.
The guard against named workspaces stops new files being written, but an earlier version already wrote them, and they go on being loaded at every login. A migration removes the saved layouts named for a negative id.
The keyword fallback is gone. `hyprctl keyword` refuses a Lua configuration outright -- "keyword can't work with non-legacy parsers. Use eval." -- and exits 0 while doing so, so as a fallback it neither worked nor reported that it had not. A failed apply now says so and saves nothing, rather than announcing a layout the screen does not show and restoring it at the next login.
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Add titlebars, edge snapping, and a window Shelf to floating workspaces
Floating workspaces become a traditional desktop that works with every
theme. Windows get themed titlebars from the omarchy-hyprland-titlebars
plugin, which is loaded only while something floats. Dragging a titlebar
to the left or right edge snaps the window to that half, and dragging it
to the top or double-clicking maximizes it. The window floating mode
places is sized, centered, and cascaded, and floating workspaces focus
on click.
Super + M, or the minus button, sets a window aside on the Shelf, which
works from any workspace and restores the window onto the current one
in that workspace's layout. Float All Workspaces makes floating the
default while Super + L still sets a single workspace's layout.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Bring resize on border to tiling too
* Fix floating mode cleanup, fullscreen windows, and failed Shelf notes
Turning Float All Workspaces off with no workspace layouts left behind
started nothing to give the floated windows back. A window fullscreen
when its workspace began floating now floats once it leaves fullscreen.
A window whose Shelf note cannot be saved stays where it is, and the
titlebars read the theme from where omarchy-theme-set writes it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Place the Shelf on the bar once, instead of on every minimize
Putting it back on each minimize kept a Shelf the user moved where they
put it, but re-added one they had removed. A migration places it once for
existing bars; Super + Alt + M opens the Shelf either way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Raise titlebars off the window background
A titlebar in the theme's background color ran into the window under it.
It takes the theme's lighter background, the raised surface every theme
defines, and the selection color on themes that make the two the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop minimizing and edge snapping from floating workspaces
A floating workspace is now titlebars with maximize and close, and nothing
else. The Shelf (its bar widget, panel, commands, notes, Super + M and
Super + Alt + M) goes with the minimize button, and the titlebar plugin's
edge snapping is left off, along with the shell's snap preview.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Stop describing the titlebar package as snapping
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Name web app windows in their launchers
Chromium names a web app window after its URL, and titles it with the page
title or just the domain. StartupWMClass ties the window to its launcher,
so the titlebar plugin can show the launcher's Name instead: WhatsApp
rather than web.whatsapp.com. New web apps, the shipped ones, and existing
launchers (through a migration) all carry it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop the titlebar package comment from the base list
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Show the workspace layout in the bar and cycle it on click
The focused workspace's mark is a square for dwindle, a triangle for
scrolling, and a circle for floating, and clicking it cycles the layout
as Super + L does. Both layout toggles ask the bar to read the new mode.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Show scrolling as a circle and floating as a triangle
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Move the look of the desktop to Trigger > Appearance
Menu Bar, Battery Percentage, Workspace Layout, Window Gaps, Animations,
and 1-Window Ratio change how the desktop looks rather than switching a
behavior, so they leave Trigger > Toggle for their own menu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Choose a default workspace layout instead of floating everything
Trigger > Appearance > Workspace Layout sets the layout of every workspace
without one of its own: Tiling, Scrolling, or Floating. A tiled default
is Hyprland's own layout, and a floating one is the floating mode. It
replaces Float All Workspaces, which also overrode and set aside the
layouts workspaces had been given with Super + L.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Title the workspace layout menu Default Workspace Layout
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Use the animation icon for Appearance > Animations
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Open clipboard history from Trigger
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Leave edge resizing to looknfeel on floating workspaces
looknfeel now resizes from borders everywhere, so floating workspaces
only need to switch pointer focus to click.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Convert saved workspace layout rules to modes
Released versions saved a workspace's layout as a plain workspace rule,
which names no mode, so a floating default would override a workspace
set to scrolling, and the bar and Super + L would misread it. A migration
rewrites those files as modes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Cycle workspace layouts backwards with Super + Alt + L
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Test the Trigger > Appearance split and Clipboard History placement
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Decode escaped web app URLs before naming their windows
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Describe the workspace widget's layout mark and click
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Let a workspace cycled onto the default follow it again
Once Super + L gave a workspace a layout of its own, it kept it for good,
so a later change of the default never reached it. Cycling onto the
default layout now forgets the workspace's own instead of saving it.
Without a saved default, the default is Hyprland's own layout, which
the user's looknfeel may have set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
458 lines
19 KiB
Bash
Executable File
458 lines
19 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"
|