xdotool/Enter-Taste-Ansatz war nicht zuverlaessig genug (Metacity erzeugt mehrere ueberlappende X11-Fenster aehnlicher Groesse, 'groesstes Fenster'- Heuristik traf wiederholt das falsche; Enter aktiviert auf der Partitionierungs-Uebersicht einen Ausklapp-Pfeil statt 'Jetzt installieren' - siehe ADR-0023-Nachtrag). - live-updates/usr/share/ubiquity/tuxflotte-atspi-env.sh (neu): aktiviert GTK_MODULES=gail:atk-bridge + Session-/A11y-Bus VOR dem GTK-Start von Ubiquity - muss vor dem Init passieren, eine Bruecke wird von einer bereits laufenden GTK-App nicht nachgeladen. - initrd-hooks/casper-bottom/99casperboot: bindet obige Datei per Ein-Zeilen-Sed-Patch in start-ubiquity-dm ein (dry-run gegen den echten Datei-Inhalt verifiziert). - live-updates/opt/tuxflotte/scripts/atspi_click.py (neu): findet+klickt den passenden 'Weiter'-Button ueber Rolle+Name im Accessibility-Baum, feste deutsche Werteliste (Sprache per Preseed fest de_DE), niemals Zurueck/Abbrechen/Beenden. - autoclicker.sh: ruft atspi_click.py in Schleife statt xdotool/Enter auf, wartet auf den ubiquity-Prozess statt auf den X-Socket. Noch nicht live verifiziert - naechster Schritt: QEMU-Testlauf auf lenny. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
147 lines
4.5 KiB
Python
Executable File
147 lines
4.5 KiB
Python
Executable File
#!/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())
|