#!/usr/bin/env python3 import json import sys import gi gi.require_version('Atspi', '2.0') from gi.repository import Atspi target, action = json.loads(sys.argv[1]), sys.argv[2] Atspi.init() desktop = Atspi.get_desktop(0) found = None wanted_name = target.get('name') or '' wanted_role = target.get('role') or '' wanted_rect = target.get('raw_rect') or target.get('rect') or None def rect_close(node): if not wanted_rect or len(wanted_rect) < 4: return True try: component = node.get_component() if not component: return True extents = component.get_extents(Atspi.CoordType.SCREEN) return abs(extents.x - wanted_rect[0]) < 8 and abs(extents.y - wanted_rect[1]) < 8 except Exception: return True def walk(node, depth=0): global found if found or node is None or depth > 30: return try: if (node.get_name() or '') == wanted_name and (node.get_role_name() or '') == wanted_role and rect_close(node): found = node return for i in range(node.get_child_count()): walk(node.get_child_at_index(i), depth + 1) except Exception: return if target.get('pid') is not None and target.get('atspi_path') is not None: for i in range(desktop.get_child_count()): app = desktop.get_child_at_index(i) if app.get_process_id() != target['pid']: continue candidate = app for index in target['atspi_path']: candidate = candidate.get_child_at_index(index) if candidate is None: break if candidate and (candidate.get_name() or '') == wanted_name and (candidate.get_role_name() or '') == wanted_role and rect_close(candidate): found = candidate break else: walk(desktop) if not found: raise SystemExit('AT-SPI target not found') actions = found.get_action() if actions is None: raise SystemExit(f'AT-SPI action unavailable: {action}') aliases = [action.lower()] if action.lower() in ('click', 'press', 'activate'): aliases.extend(['press', 'click', 'activate']) seen = set() for name in aliases: if name in seen: continue seen.add(name) for i in range(actions.get_n_actions()): if actions.get_action_name(i).lower() == name: if actions.do_action(i): print(json.dumps({'ok': True, 'action': name})) raise SystemExit(0) raise SystemExit(f'AT-SPI action unavailable: {action}')