fix: Autoklicker wartet auf Ubiquitys Hintergrundpruefung statt sie zu unterbrechen

Timing-Hypothese fuer den partman-rebuild-cache-Loop: der $bootable{}-Fix
brachte live keine Aenderung (siehe ADR-0023-Nachtrag), was gegen einen
rein rezeptseitigen Fehler spricht. Stattdessen zeigt die 'Etwas
Anderes'-Seite waehrend der Klickphase ein Status-Panel 'Konfiguration
der Installation wird ueberprueft ...' - ein Klick auf 'Jetzt
installieren' WAEHREND dieser Pruefung noch laeuft koennte sie
unterbrechen/neu anstossen statt sie abschliessen zu lassen.

atspi_click.py sucht jetzt vor jedem Klickversuch aktiv nach diesem
sichtbaren Text (find_showing_text(), rollenunabhaengig) und gibt bei
Treffer Exit-Code 3 zurueck, ohne zu klicken. autoclicker.sh behandelt
das als eigenen, nicht als Fehlversuch gezaehlten Zustand.

Noch nicht live verifiziert - naechster Schritt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Thomas Stallinger 2026-08-29 17:53:10 +02:00
parent 2e68dbea71
commit 4bf1992eb9
2 changed files with 88 additions and 4 deletions

View File

@ -30,10 +30,29 @@ Sprache ist durch "debian-installer/language=de" im Kexec-Append-Preseed
grundsaetzlich auf de_DE gesetzt (siehe backend.sh), s.o. aber nicht grundsaetzlich auf de_DE gesetzt (siehe backend.sh), s.o. aber nicht
verlaesslich fuer jede einzelne Seite. verlaesslich fuer jede einzelne Seite.
NACHTRAG (real beim Testen entdeckt, 29.08.2026): auf der manuellen
Partitionierungsseite ("Etwas Anderes") lief der Klick auf "Jetzt
installieren" nachweislich zuverlaessig (AT-SPI meldete jedes Mal Erfolg),
aber die Seite kam nie voran - stattdessen loeste jeder Klick erneut die
Debconf-Frage "ubiquity/partman-rebuild-cache" aus
(/lib/partman/update.d/99signal_ubiquity), was ubi-partman.py zurueck auf
dieselbe Seite wirft (siehe ADR-0023-Nachtrag). Ein gezielter Fix am
Partitionsrezept (kein zusaetzliches $bootable{ } auf der Root-Partition
unter UEFI) aenderte NICHTS - das deutet auf einen zeitlichen statt
strukturellen Fehler hin: die Seite zeigt waehrend dieser Phase ein
Status-Panel "Konfiguration der Installation wird ueberprueft ..." - klickt
der Autoklicker WAEHREND dieser Hintergrundpruefung noch laeuft, koennte
er sie unterbrechen/neu anstossen, statt ihr Zeit zum Abschliessen zu
geben. Dieses Skript wartet deshalb jetzt aktiv, solange dieser Text
sichtbar ist (siehe VALIDATION_TEXT_MARKERS/find_showing_text()), und
klickt erst, wenn er verschwunden ist.
Exit-Codes: 0 = ein Button wurde gefunden und geklickt, Exit-Codes: 0 = ein Button wurde gefunden und geklickt,
1 = kein eindeutiger Button gefunden (z.B. Seite im Ladezustand, oder echte 1 = kein eindeutiger Button gefunden (z.B. Seite im Ladezustand, oder echte
Mehrdeutigkeit - dann bewusst NICHT geklickt, siehe select_target()), Mehrdeutigkeit - dann bewusst NICHT geklickt, siehe select_target()),
2 = pyatspi/AT-SPI-Bus nicht erreichbar (Bruecke evtl. nicht aktiv). 2 = pyatspi/AT-SPI-Bus nicht erreichbar (Bruecke evtl. nicht aktiv),
3 = Ubiquitys eigene Hintergrundpruefung laeuft noch sichtbar - bewusst
nicht geklickt, siehe Nachtrag oben.
""" """
import sys import sys
@ -88,6 +107,52 @@ PREFER_LABELS = {
MAX_DEPTH = 40 MAX_DEPTH = 40
# Zweisprachig vorsichtshalber (siehe Multimedia-Codecs-Erfahrung oben),
# auch wenn dieser konkrete Text bisher immer deutsch beobachtet wurde.
VALIDATION_TEXT_MARKERS = (
"wird überprüft",
"wird ueberprueft",
"being checked",
"checking the install",
)
def find_showing_text(node, needle, depth=0):
"""Sucht ein sichtbares Accessible, dessen Name ODER Beschreibung den
Suchtext enthaelt - unabhaengig von seiner Rolle (Label/Expander/...),
da Ubiquitys Status-Panel-Text nicht zuverlaessig unter derselben Rolle
exponiert sein muss. Bei Unsicherheit ueber den Sichtbarkeitsstatus
bewusst konservativ (lieber einen Zyklus zu viel warten als einen
Klick mitten in die Pruefung setzen)."""
if node is None or depth > MAX_DEPTH:
return False
try:
name = (node.name or "").strip().lower()
except Exception:
name = ""
try:
desc = (node.description or "").strip().lower()
except Exception:
desc = ""
if needle in name or needle in desc:
try:
state = node.getState()
return state.contains(pyatspi.STATE_SHOWING)
except Exception:
return True
try:
count = node.childCount
except Exception:
return False
for i in range(count):
try:
child = node.getChildAtIndex(i)
except Exception:
continue
if find_showing_text(child, needle, depth + 1):
return True
return False
def collect_buttons(node, depth=0, acc=None): def collect_buttons(node, depth=0, acc=None):
"""Alle sichtbaren+aktivierten Push-Buttons im Baum einsammeln (Rolle, """Alle sichtbaren+aktivierten Push-Buttons im Baum einsammeln (Rolle,
@ -171,14 +236,26 @@ def main():
print(f"AT-SPI-Desktop nicht erreichbar: {exc}", file=sys.stderr) print(f"AT-SPI-Desktop nicht erreichbar: {exc}", file=sys.stderr)
return 2 return 2
all_buttons = [] apps = []
for i in range(desktop.childCount): for i in range(desktop.childCount):
try: try:
app = desktop.getChildAtIndex(i) app = desktop.getChildAtIndex(i)
except Exception: except Exception:
continue continue
if app is None: if app is not None:
continue apps.append(app)
for marker in VALIDATION_TEXT_MARKERS:
for app in apps:
if find_showing_text(app, marker):
print(
f"Hintergrundpruefung noch sichtbar ('{marker}'), warte statt zu klicken.",
file=sys.stderr,
)
return 3
all_buttons = []
for app in apps:
collect_buttons(app, 0, all_buttons) collect_buttons(app, 0, all_buttons)
target = select_target(all_buttons) target = select_target(all_buttons)

View File

@ -97,6 +97,13 @@ while [ "${SECONDS}" -lt "${end}" ]; do
elif [ "${rc}" -eq 2 ]; then elif [ "${rc}" -eq 2 ]; then
log "AT-SPI-Bus nicht erreichbar, breche ab: ${out}" log "AT-SPI-Bus nicht erreichbar, breche ab: ${out}"
break break
elif [ "${rc}" -eq 3 ]; then
# Ubiquitys eigene Hintergrundpruefung laeuft noch sichtbar (siehe
# atspi_click.py-Nachtrag) - bewusst NICHT als Fehlversuch zaehlen,
# nur abwarten. Real beim Testen entdeckt: ein Klick auf "Jetzt
# installieren" WAEHREND dieser Pruefung fuehrte wiederholt in die
# partman-rebuild-cache-Schleife statt zum eigentlichen Commit.
log "wartet auf Hintergrundpruefung: ${out}"
else else
misses=$((misses + 1)) misses=$((misses + 1))
log "kein Treffer (${misses}): ${out}" log "kein Treffer (${misses}): ${out}"