Open the four display modes from XF86Display or the external-monitor bar icon. Reuse output reconciliation and confirmation for manual layout changes, and notify the shell when monitor state changes. Release backdrop locks before render-element queries to prevent blurred popups from freezing the compositor. Cover popup open, update and close with regression tests. Share translucent card styles between display modes and capture sources, show focus rings during keyboard navigation, and align picker buttons, label sizing and footer spacing with system modals.
86 lines
4.4 KiB
Python
86 lines
4.4 KiB
Python
"""Open, update and close blurred popups on a private nested compositor.
|
|
|
|
FERESE_TEST_BLURRED_POPUP=1 FERESE_TEST_BINARY=target/release/ferese \\
|
|
FERESE_TEST_CTL=target/release/feresectl python3 scripts/tests/test_blurred_popup_isolated.py
|
|
Requires a Wayland host, wayland-scanner and a C compiler.
|
|
"""
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
import time
|
|
import unittest
|
|
|
|
|
|
@unittest.skipUnless(os.environ.get("FERESE_TEST_BLURRED_POPUP") == "1", "opt-in nested blur test")
|
|
class BlurredPopupTest(unittest.TestCase):
|
|
def test_popup_lifecycle_keeps_presenting_frames(self):
|
|
repo = Path(__file__).resolve().parents[2]
|
|
with tempfile.TemporaryDirectory(prefix="ferese-blurred-popup-") as temporary:
|
|
root = Path(temporary)
|
|
protocols = Path(subprocess.check_output(
|
|
["pkg-config", "--variable=pkgdatadir", "wayland-protocols"], text=True
|
|
).strip())
|
|
generated = []
|
|
for name, xml in [
|
|
("xdg-shell", protocols / "stable/xdg-shell/xdg-shell.xml"),
|
|
("ferese-effects", repo / "protocols/ferese-effects-v1.xml"),
|
|
]:
|
|
source = root / f"{name}.c"
|
|
subprocess.run(["wayland-scanner", "client-header", str(xml),
|
|
str(root / f"{name}-client-protocol.h")], check=True)
|
|
subprocess.run(["wayland-scanner", "private-code", str(xml), str(source)], check=True)
|
|
generated.append(str(source))
|
|
flags = subprocess.check_output(
|
|
["pkg-config", "--cflags", "--libs", "wayland-client"], text=True
|
|
).split()
|
|
client = root / "client"
|
|
subprocess.run(["cc", "-Wall", "-Wextra", "-Werror", "-I", str(root),
|
|
str(repo / "scripts/tests/fixtures/blurred-popup.c"),
|
|
*generated, "-o", str(client), *flags], check=True)
|
|
runtime = root / "runtime"
|
|
runtime.mkdir(mode=0o700)
|
|
config = root / "config/ferese"
|
|
config.mkdir(parents=True)
|
|
(config / "config.kdl").write_text('theme { material { style "translucent"; } }\n')
|
|
host = Path(os.environ["WAYLAND_DISPLAY"])
|
|
if not host.is_absolute():
|
|
host = Path(os.environ["XDG_RUNTIME_DIR"]) / host
|
|
env = dict(os.environ, WAYLAND_DISPLAY=str(host), XDG_RUNTIME_DIR=str(runtime),
|
|
XDG_CONFIG_HOME=str(root / "config"), XDG_STATE_HOME=str(root / "state"),
|
|
XDG_CACHE_HOME=str(root / "cache"), RUST_LOG="ferese=info")
|
|
env.pop("WAYLAND_SOCKET", None)
|
|
env.pop("FERESE_SHELL_CONTROL_SOCKET", None)
|
|
binary = repo / os.environ.get("FERESE_TEST_BINARY", "target/debug/ferese")
|
|
log_path = root / "compositor.log"
|
|
with log_path.open("w") as log:
|
|
compositor = subprocess.Popen(
|
|
[str(binary), "--backend", "nested", "--grant-effects", "--", str(client)],
|
|
env=env, stdout=log, stderr=log,
|
|
)
|
|
try:
|
|
deadline = time.monotonic() + 30
|
|
while log_path.read_text().splitlines().count("close") < 3:
|
|
self.assertIsNone(compositor.poll(), log_path.read_text())
|
|
if time.monotonic() >= deadline:
|
|
self.fail("Blurred popup stopped presenting frames:\n" + log_path.read_text())
|
|
time.sleep(.025)
|
|
control = repo / os.environ.get("FERESE_TEST_CTL", "target/debug/feresectl")
|
|
subprocess.run([str(control), "get-outputs"], env=env, check=True,
|
|
stdout=subprocess.DEVNULL, timeout=5)
|
|
phases = log_path.read_text().splitlines()
|
|
for name, count in [("parent", 1), ("open", 3), ("update", 9), ("close", 3)]:
|
|
self.assertEqual(phases.count(name), count, log_path.read_text())
|
|
finally:
|
|
if compositor.poll() is None:
|
|
compositor.terminate()
|
|
try:
|
|
compositor.wait(timeout=5)
|
|
except subprocess.TimeoutExpired:
|
|
compositor.kill()
|
|
compositor.wait()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|