Files
omarchy/test/shell.d/hyprland-startup-cursor-cold-test.sh
T
Spencer BullandGPT-6.1-Sol XHigh f5170dd070 Recover the startup cursor across Hyprland reloads
Hyprland discards Lua globals and timers during reload, so the pending startup reveal must survive outside Lua state. Preserve the normal cursor theme for this compositor session, recover orphaned blank settings, and repair activation environments before future applications inherit them.

🤖 Generated by GPT-6.1-Sol High in Codex through T3 Code. Reviewed by GPT-6.1-Sol XHigh in Codex through T3 Code.

Co-Authored-By: GPT-6.1-Sol XHigh <codex@openai.com>
2026-10-10 17:17:28 -05:00

93 lines
4.2 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
source "$(dirname "$0")/base-test.sh"
require_compositor "cold compositor cursor startup"
require_command Hyprland
require_command quickshell
require_command grim
require_command magick
require_command python3
python3 <<'PY'
import os
import pathlib
import signal
import subprocess
import tempfile
import time
root = pathlib.Path(os.environ["ROOT"])
with tempfile.TemporaryDirectory() as directory:
stage = pathlib.Path(directory)
home = stage / "home"
home.mkdir()
(stage / "release").write_text("")
(stage / "bin").mkdir()
launcher = stage / "bin/omarchy-theme-bg-boot-intro"
launcher.write_text("#!/bin/bash\nexit 0\n")
launcher.chmod(0o755)
for name in ("Commons", "services"):
(stage / name).symlink_to(root / "shell" / name)
fixture = root / "test/shell.d/fixtures/startup-cursor"
(stage / "shell.qml").write_text((fixture / "shell.qml").read_text())
env = os.environ.copy()
env.update(HOME=str(home), OMARCHY_PATH=str(root), CURSOR_TEST_STAGE=str(stage),
PATH=str(stage / "bin") + ":" + env["PATH"], AQ_BACKEND="wayland",
HYPRLAND_NO_SD_VARS="1", HYPRLAND_NO_SD_NOTIFY="1", GSETTINGS_BACKEND="memory",
XCURSOR_THEME="Adwaita", XCURSOR_PATH="/usr/share/icons:/usr/share/pixmaps", XCURSOR_SIZE="24", HYPRCURSOR_SIZE="24")
env.pop("HYPRLAND_INSTANCE_SIGNATURE", None)
env.pop("HYPRCURSOR_THEME", None)
log = open(stage / "compositor.log", "w")
compositor = subprocess.Popen(["Hyprland", "--config", str(fixture / "hyprland.lua")],
env=env, stdout=log, stderr=log, start_new_session=True)
try:
deadline = time.monotonic() + 5
while not (stage / "session").exists():
assert compositor.poll() is None, "cold compositor exited before startup"
assert time.monotonic() < deadline, "cold compositor never started"
time.sleep(0.005)
display, signature = (stage / "session").read_text().splitlines()
env.update(WAYLAND_DISPLAY=display, HYPRLAND_INSTANCE_SIGNATURE=signature)
# Capture immediately, including before Quickshell loads its cover. The
# fixture cannot reveal while grim or magick is busy taking these frames.
deadline = time.monotonic() + 5
for sample in range(3):
assert time.monotonic() < deadline, "initial compositor captures timed out"
screenshot = stage / "frame.png"
subprocess.run(["grim", "-c", str(screenshot)], env=env, check=True,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=5)
maximum = subprocess.check_output(["magick", str(screenshot), "-format", "%[fx:maxima]", "info:"], text=True, timeout=5)
assert maximum == "0", "the cursor appeared in the first compositor frames"
def wait_phase(expected):
deadline = time.monotonic() + 5
while not (stage / "phase").exists() or (stage / "phase").read_text() != expected:
assert compositor.poll() is None and time.monotonic() < deadline, "startup never reached " + expected
time.sleep(0.01)
wait_phase("holding")
subprocess.run(["hyprctl", "reload"], env=env, check=True, capture_output=True, timeout=5)
subprocess.run(["hyprctl", "eval", "assert(omarchy_startup_cursor_pending, 'reload lost the pending cursor reveal')"],
env=env, check=True, capture_output=True, timeout=5)
(stage / "release").write_text("reveal")
wait_phase("revealed")
# Cursor visibility is polled by the compositor independently of the fade.
deadline = time.monotonic() + 5
while True:
subprocess.run(["grim", "-c", str(screenshot)], env=env, check=True,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=5)
green = subprocess.check_output(["magick", str(screenshot), "-channel", "G", "-separate", "-threshold", "0", "-format", "%[fx:mean*w*h]", "info:"], text=True, timeout=5)
if float(green) > 20:
break
assert time.monotonic() < deadline, "the normal cursor did not return with the desktop"
except Exception:
print((stage / "compositor.log").read_text())
raise
finally:
os.killpg(compositor.pid, signal.SIGTERM)
compositor.wait(timeout=10)
log.close()
PY
pass "a fresh compositor never exposes its cursor before the desktop reveal"