Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/crutchcorn/hearthspace. A spatial Linux desktop for persistent, local-first computing.
Something went wrong. Try again.
8.0 kB · 229 lines
Python
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230"""Compact shell acceptance shared by headless and owned native sessions."""
import argparseimport jsonimport shleximport subprocessfrom pathlib import Path
from . import daily_checks, shell_dock, shell_keyboard, shell_presentation, shell_searchfrom . import shell_checks as checksfrom .control import ControlErrorfrom .daily import DailySmokefrom .input import Input, keycode
def keyboard_search(smoke): leaked = smoke.artifacts / "terminal-leaked-search.txt" quoted = shlex.quote(str(leaked)) with Input(smoke.control) as inputs: inputs.type( f"stty -echo; : > {quoted}; " f"while IFS= read -r -n1 key; do printf '%s' \"$key\" >> {quoted}; done\n" ) smoke.until("terminal input guard ready", leaked.exists) with Input(smoke.control) as inputs: inputs.down("key", keycode("super")) if checks.panel(smoke, "search"): raise ControlError("Meta press opened search before release") inputs.up("key", keycode("super")) # Intentionally no render/readiness wait between release and typing. inputs.type("settings") checks.opened(smoke, "search") smoke.until( "complete immediately typed search query", lambda: checks.query_text(smoke) == "settings", ) smoke.until( "Settings remains discoverable in search", lambda: checks.elements(smoke, "Open: Hearthspace Settings"), ) if leaked.read_bytes(): raise ControlError("Meta search leaked typed characters to the prior terminal") smoke.capture("search-settings") smoke.record("meta-immediate-search", query="settings", terminal_received_bytes=0) with Input(smoke.control) as inputs: inputs.chord("super") checks.closed(smoke) with Input(smoke.control) as inputs: inputs.chord("super+space") checks.opened(smoke, "search") with Input(smoke.control) as inputs: inputs.chord("esc") checks.closed(smoke) # The existing daily selector opens a second terminal through Super+Enter, # then queries and focuses the original window using a bare Meta tap. daily_checks.selector(smoke) checks.closed(smoke)
def launch_settings(smoke): before = {item["id"] for item in smoke.control.status()["windows"]} checks.click(smoke, "Apps List") checks.opened(smoke, "search") with Input(smoke.control) as inputs: inputs.type("Hearthspace Settings") shell_search.selected(smoke, "Open: Hearthspace Settings") with Input(smoke.control) as inputs: inputs.chord("enter") checks.closed(smoke) window = smoke.until( "Settings launched from search", lambda: next( ( item for item in smoke.control.status()["windows"] if item["id"] not in before and item.get("mapped") and item.get("focused") and item.get("title") == "Hearthspace Settings" ), None, ), ) smoke.capture("settings-launched-from-search") smoke.control.request(f"close-window {window['id']}") smoke.until( "owned Settings window closed", lambda: smoke.window(window["id"]) is None ) smoke.until( "application focus restored after Settings", lambda: (smoke.window(smoke.target) or {}).get("focused"), ) smoke.record("settings-launched-from-search", window=window, closed=True)
def pointer_panels(smoke): checks.click(smoke, "Apps List") checks.opened(smoke, "search") checks.click(smoke, "Apps List") checks.closed(smoke) smoke.until( "search icon toggle restores application keyboard focus", lambda: smoke.window(smoke.target)["focused"], ) checks.click(smoke, "Apps List") checks.opened(smoke, "search") if checks.elements(smoke, f"Window: {smoke.marker}"): raise ControlError( "empty search still exposes the removed unfiltered window list" ) with Input(smoke.control) as inputs: inputs.type(smoke.marker) smoke.until( "pointer-opened search result", lambda: checks.elements(smoke, f"Window: {smoke.marker}"), ) checks.click(smoke, f"Window: {smoke.marker}", "search") checks.closed(smoke) smoke.until( "pointer result restores application focus", lambda: smoke.window(smoke.target)["focused"], ) checks.click(smoke, "Apps List") checks.opened(smoke, "search") checks.click(smoke, "Close apps list", "search") checks.closed(smoke) checks.click(smoke, "Notifications") checks.opened(smoke, "notifications") smoke.capture("bell-notifications") checks.click(smoke, "Power") # Mapping the replacement and destroying the previous Wayland surface are # separate events. Require both to finish within the existing deadline. checks.opened(smoke, "power", exclusive=True) smoke.until( "nested Lock screen option", lambda: checks.elements(smoke, "Lock screen") ) checks.tab_to(smoke, "Lock screen") smoke.capture("power-options") checks.click(smoke, "Log off", "power") checks.opened(smoke, "power-confirm", exclusive=True) smoke.until( "log off confirmation", lambda: checks.elements(smoke, "Log off?", role="dialog"), ) smoke.capture("power-logout-confirmation") checks.tab_to(smoke, "Cancel") with Input(smoke.control) as inputs: inputs.chord("enter") checks.closed(smoke) checks.click(smoke, "Power") checks.opened(smoke, "power", exclusive=True) with Input(smoke.control) as inputs: inputs.chord("esc") checks.closed(smoke) if smoke.control.status().get("locked"): raise ControlError("cancelled power menu unexpectedly locked the session") smoke.record( "power-menu-verified", confirmation_cancelled=True, lock_option_present=True, keyboard_focus=["Lock screen", "Cancel"], keyboard_cancel=True, escape_closes_menu=True, )
def run(smoke): checks.bar(smoke) smoke.capture("compact-shell-bar") shell_presentation.run(smoke) daily_checks.terminal(smoke) if checks.panel(smoke, "search"): raise ControlError("Super+Enter incorrectly toggled search on Meta release") shell_keyboard.run(smoke) keyboard_search(smoke) shell_dock.run(smoke) shell_search.run(smoke) launch_settings(smoke) pointer_panels(smoke) daily_checks.notifications(smoke) checks.closed(smoke) checks.bar(smoke) smoke.capture("shell-workflow-complete") smoke.record( "shell-workflow-verified", meta_search=True, running_window_search=True, running_app_dock=True, settings_search=True, bell_notifications=True, power_cancel=True, )
class ShellSmoke(DailySmoke): def __init__(self, args): args.captures, args.notifications = 1, True super().__init__(args)
def workflow(self): self.manifest = self.connect() self.step("ready", DailySmoke.ready) try: self.step("compact-shell", run) except BaseException: self.failure_evidence("compact-shell-failed") raise
def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--session", type=Path, required=True) parser.add_argument("--timeout", type=float, default=120) parser.add_argument("--total-timeout", type=float, default=240) parser.add_argument("--log-level", default="info") args = parser.parse_args() try: ShellSmoke(args).run() return 0 except ( ControlError, OSError, ValueError, KeyError, subprocess.SubprocessError, KeyboardInterrupt, ) as error: print(json.dumps({"ok": False, "error": str(error)}), flush=True) return 1