Omarchy never sets XCURSOR_THEME, so asking GSettings on every cold boot replaced the "default" alias with whatever GSettings held, which is the schema default Adwaita when nothing was ever set. That overrode a theme chosen through ~/.icons/default and exported the substitute to every application. The saved theme and GSettings are now consulted only when recovering from the blank startup theme or an interrupted reveal. Co-Authored-By: Codex XHigh <codex@openai.com>
137 lines
7.2 KiB
Bash
Executable File
137 lines
7.2 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
source "$(dirname "$0")/base-test.sh"
|
|
require_command lua
|
|
|
|
lua - <<'LUA'
|
|
package.path = os.getenv("ROOT") .. "/?.lua;" .. package.path
|
|
local events, recovery, command, imported = {}, nil, nil, false
|
|
local config = { invisible = false, enable_hyprcursor = true, sync_gsettings_theme = true }
|
|
local env = { XCURSOR_THEME = "my-xcursor", HYPRCURSOR_THEME = "my-hyprcursor", XCURSOR_PATH = "/my/icons", HYPRLAND_INSTANCE_SIGNATURE = "test-session", OMARCHY_PATH = os.getenv("ROOT") }
|
|
local getenv = os.getenv
|
|
os.getenv = function(name) return env[name] or getenv(name) end
|
|
local monitors = {}
|
|
hl = {
|
|
on = function(event, callback) events[event] = callback end,
|
|
get_monitors = function() return monitors end,
|
|
get_config = function(key) return config[key:match("%.(.+)")] end,
|
|
env = function(name, value) env[name] = value end,
|
|
config = function(values)
|
|
for name, value in pairs(values.cursor) do config[name] = value end
|
|
end,
|
|
timer = function(callback, options)
|
|
assert(options.timeout == 15000 and options.type == "oneshot")
|
|
recovery = callback
|
|
end,
|
|
exec_cmd = function(value)
|
|
if value == "dbus-update-activation-environment --systemd XCURSOR_PATH XCURSOR_THEME" then
|
|
assert(env.XCURSOR_THEME ~= "omarchy-startup" and not env.XCURSOR_PATH:match("/default/hypr/cursors"))
|
|
imported = true
|
|
end
|
|
if value == "omarchy-launch-shell" then
|
|
assert(config.invisible and env.XCURSOR_THEME == "my-xcursor", "hide the compositor cursor while restoring application settings before launch")
|
|
end
|
|
command = value
|
|
end,
|
|
}
|
|
o = { shell_quote = function(value) return "'" .. value .. "'" end, launch = function(value) return value end }
|
|
|
|
require("default.hypr.autostart")
|
|
assert(not config.invisible, "loading the module must wait for user configuration")
|
|
events["config.reloaded"]()
|
|
assert(config.invisible and not config.enable_hyprcursor and not config.sync_gsettings_theme)
|
|
assert(env.XCURSOR_THEME == "omarchy-startup" and env.XCURSOR_PATH:match("/default/hypr/cursors:/my/icons$"))
|
|
assert(omarchy_startup_cursor_pending, "the first compositor frame must use the blank cursor")
|
|
events["hyprland.start"]()
|
|
assert(env.XCURSOR_THEME == "my-xcursor" and env.XCURSOR_PATH == "/my/icons", "applications must inherit the user's cursor")
|
|
assert(config.invisible, "starting applications must not reveal the cursor")
|
|
|
|
local previous_recovery = recovery
|
|
-- Hyprland reloads the configuration in a fresh Lua state and discards timers.
|
|
omarchy_startup_cursor_pending = nil
|
|
omarchy_startup_cursor = nil
|
|
for name in pairs(config) do config[name] = true end
|
|
config.invisible = false
|
|
monitors = { {} }
|
|
dofile(os.getenv("ROOT") .. "/default/hypr/startup-cursor.lua")
|
|
events["config.reloaded"]()
|
|
assert(omarchy_startup_cursor_pending and omarchy_startup_cursor.xcursor == "my-xcursor", "a fresh Lua state must retain the pending reveal and the user's cursor")
|
|
assert(recovery ~= previous_recovery and config.invisible, "a config reload must retain startup hiding and rearm recovery")
|
|
recovery()
|
|
assert(command:match("setcursor 'my%-xcursor'"), "restore the Xcursor fallback before revealing the pointer")
|
|
assert(config.invisible, "wait for the normal theme before restoring visibility")
|
|
omarchy_startup_cursor_restore(true)
|
|
assert(not config.invisible and config.enable_hyprcursor and config.sync_gsettings_theme)
|
|
assert(command:match("setcursor 'my%-hyprcursor'"), "restore the user's Hyprcursor theme")
|
|
local previous_command = command
|
|
previous_recovery()
|
|
assert(command == previous_command, "recovery must not change a revealed cursor")
|
|
events["config.reloaded"]()
|
|
assert(not config.invisible and env.XCURSOR_THEME == "my-xcursor", "ordinary config reloads must not hide the cursor")
|
|
|
|
omarchy_startup_cursor_pending = nil
|
|
monitors = { {} }
|
|
events["config.reloaded"]()
|
|
assert(not omarchy_startup_cursor_pending and not config.invisible, "installing the fix in a running compositor must leave its cursor alone")
|
|
|
|
local popen = io.popen
|
|
local gsettings = "'custom-cursor'\n"
|
|
io.popen = function(value)
|
|
assert(value:match("gsettings get org.gnome.desktop.interface cursor%-theme"))
|
|
return { read = function() return gsettings end, close = function() return true end }
|
|
end
|
|
local function orphan(theme)
|
|
imported = false
|
|
omarchy_startup_cursor_pending, omarchy_startup_cursor = nil, nil
|
|
env.XCURSOR_THEME = "omarchy-startup"
|
|
env.OMARCHY_STARTUP_CURSOR_THEME = theme
|
|
env.OMARCHY_STARTUP_CURSOR_SESSION = "old-session"
|
|
env.XCURSOR_PATH = "/old/worktree/default/hypr/cursors:/my/icons"
|
|
env.OMARCHY_STARTUP_CURSOR_PATH = nil
|
|
config.invisible, config.enable_hyprcursor, config.sync_gsettings_theme = false, false, true
|
|
dofile(os.getenv("ROOT") .. "/default/hypr/startup-cursor.lua")
|
|
events["config.reloaded"]()
|
|
assert(omarchy_startup_cursor_pending, "an orphan blank theme must be recovered with live monitors")
|
|
assert(env.XCURSOR_THEME ~= "omarchy-startup" and env.XCURSOR_PATH == "/my/icons", "recovery must remove the blank theme and old worktree search path")
|
|
return omarchy_startup_cursor.xcursor
|
|
end
|
|
assert(orphan("saved-custom") == "saved-custom", "saved settings take precedence over GSettings")
|
|
omarchy_startup_cursor_restore()
|
|
assert(command:match("setcursor 'saved%-custom'") and imported, "orphan recovery must also repair activation environments")
|
|
local stalled = command
|
|
command = nil
|
|
recovery()
|
|
assert(command == stalled, "the timeout must retry an unfinished restore")
|
|
local completion = assert(command:match("hyprctl eval '(.*)'$"))
|
|
omarchy_startup_cursor.xcursor = "changed-during-restore"
|
|
assert(load(completion))()
|
|
assert(omarchy_startup_cursor_pending, "a stale completion must not reveal a different cursor after reload")
|
|
omarchy_startup_cursor.xcursor = "saved-custom"
|
|
local old_size = omarchy_startup_cursor.size
|
|
omarchy_startup_cursor.size = old_size + 8
|
|
assert(load(completion))()
|
|
assert(omarchy_startup_cursor_pending, "a stale completion must not acknowledge a different cursor size")
|
|
omarchy_startup_cursor.size = old_size
|
|
omarchy_startup_cursor_restore(true)
|
|
assert(not config.invisible and config.sync_gsettings_theme)
|
|
assert(orphan(nil) == "custom-cursor", "recover a nonblank user GSettings theme when no snapshot survives")
|
|
omarchy_startup_cursor_restore(true)
|
|
gsettings = "'omarchy-startup'\n"
|
|
assert(orphan(nil) == "default", "a destroyed user preference must fall back to the default alias, never the blank theme")
|
|
omarchy_startup_cursor_restore(true)
|
|
omarchy_startup_cursor_pending, omarchy_startup_cursor = nil, nil
|
|
monitors = {}
|
|
env.XCURSOR_THEME, env.XCURSOR_PATH = "next-theme", "/next/icons"
|
|
env.OMARCHY_STARTUP_CURSOR_PATH = "/stale/icons"
|
|
env.OMARCHY_STARTUP_CURSOR_SESSION = "old-session"
|
|
events["config.reloaded"]()
|
|
assert(omarchy_startup_cursor.xcursor == "next-theme" and omarchy_startup_cursor.path == "/next/icons", "a new session must honor changed cursor settings instead of inherited snapshots")
|
|
omarchy_startup_cursor_pending, omarchy_startup_cursor = nil, nil
|
|
env.XCURSOR_THEME, env.OMARCHY_STARTUP_CURSOR_SESSION, gsettings = "", "old-session", "'custom-cursor'\n"
|
|
events["config.reloaded"]()
|
|
assert(omarchy_startup_cursor.xcursor == "default", "an ordinary boot without a cursor theme keeps the default alias instead of adopting GSettings")
|
|
io.popen = popen
|
|
LUA
|
|
pass "the first compositor frame starts blank and the reveal restores user cursor settings"
|