#!/usr/bin/env python3 """Klickt den primaeren "Weiter"-Button der aktuell sichtbaren Ubiquity-Seite per AT-SPI (Linux-Barrierefreiheits-API) - wird von autoclicker.sh wiederholt aufgerufen (ein Prozessstart pro Klickversuch, absichtlich einfach statt dauerhaft im Hintergrund am Accessibility-Bus zu lauschen). Warum AT-SPI statt Bildschirmkoordinaten: der urspruengliche xdotool-Ansatz scheiterte daran, dass Metacity mehrere ueberlappende X11-Fenster aehnlicher Groesse erzeugt (WM-Dekorationsrahmen, Hilfsfenster) - eine "groesstes sichtbares Fenster"-Heuristik traf wiederholt das falsche Fenster (siehe ADR-0023-Nachtrag). AT-SPI identifiziert den Button stattdessen ueber seine Rolle (PUSH_BUTTON) und seinen Namen im Accessibility-Baum der Anwendung selbst - unabhaengig von Fenstergeometrie/-dekoration. Sprache ist durch "debian-installer/language=de" im Kexec-Append-Preseed fest auf de_DE gesetzt (siehe backend.sh) - die Button-Beschriftungen unten sind daher als feste deutsche Werteliste hinterlegt, nicht lokalisierungs- unabhaengig ermittelt. Exit-Codes: 0 = ein passender Button wurde gefunden und geklickt, 1 = kein passender Button gefunden (z.B. Seite gerade im Ladezustand), 2 = pyatspi/AT-SPI-Bus nicht erreichbar (Bruecke evtl. nicht aktiv). """ import sys try: import pyatspi except ImportError: print("pyatspi nicht verfuegbar", file=sys.stderr) sys.exit(2) # Bewusst nur vorwaerts-/abschliessende Aktionen - "Zurueck", "Abbrechen" # und "Beenden" (Installer-Quit) duerfen hier NIE stehen, auch nicht # versehentlich, da ein falscher Klick sonst den automatisierten Ablauf # abbricht statt ihn voranzutreiben. FORWARD_LABELS = { "Weiter", "Jetzt installieren", "Fortfahren", "Installieren", "Neu starten", "Schließen", } MAX_DEPTH = 40 def find_forward_button(node, depth=0): if node is None or depth > MAX_DEPTH: return None try: role = node.getRole() except Exception: return None if role == pyatspi.ROLE_PUSH_BUTTON: name = (node.name or "").strip() if name in FORWARD_LABELS: try: state = node.getState() if state.contains(pyatspi.STATE_SENSITIVE) and state.contains( pyatspi.STATE_SHOWING ): return node except Exception: pass return None try: count = node.childCount except Exception: return None for i in range(count): try: child = node.getChildAtIndex(i) except Exception: continue found = find_forward_button(child, depth + 1) if found is not None: return found return None def collect_button_names(node, depth=0, acc=None): """Nur fuer Diagnose-Logging: alle sichtbaren Buttonnamen einsammeln, damit ein Nicht-Treffer im Log nachvollziehbar bleibt, ohne dass man interaktiv am laufenden System nachsehen muss.""" if acc is None: acc = [] if node is None or depth > MAX_DEPTH: return acc try: if node.getRole() == pyatspi.ROLE_PUSH_BUTTON: acc.append((node.name or "").strip()) return acc count = node.childCount except Exception: return acc for i in range(count): try: child = node.getChildAtIndex(i) except Exception: continue collect_button_names(child, depth + 1, acc) return acc def main(): try: desktop = pyatspi.Registry.getDesktop(0) except Exception as exc: print(f"AT-SPI-Desktop nicht erreichbar: {exc}", file=sys.stderr) return 2 seen_buttons = [] for i in range(desktop.childCount): try: app = desktop.getChildAtIndex(i) except Exception: continue if app is None: continue btn = find_forward_button(app) if btn is not None: try: action = btn.queryAction() action.doAction(0) except Exception as exc: print(f"Klick auf '{btn.name}' fehlgeschlagen: {exc}", file=sys.stderr) return 1 print(f"geklickt: {btn.name}") return 0 seen_buttons.extend(collect_button_names(app)) print( "kein passender Button gefunden, sichtbare Buttons: " + (", ".join(repr(n) for n in seen_buttons) if seen_buttons else "(keine)"), file=sys.stderr, ) return 1 if __name__ == "__main__": sys.exit(main())