#!/usr/bin/env python3 import json import sys import gi gi.require_version('Atspi', '2.0') from gi.repository import Atspi mode, limit = sys.argv[1], int(sys.argv[2]) Atspi.init() desktop = Atspi.get_desktop(0) nodes = [] def walk(node, depth=0, route=None, pid=None, window_rect=None): route = route or [] if len(nodes) >= limit or node is None or depth > 30: return try: role = node.get_role_name() or '' name = node.get_name() or '' component = node.get_component() rect = component.get_extents(Atspi.CoordType.SCREEN) if component else None bounds = [rect.x, rect.y, rect.width, rect.height] if rect else None if window_rect is None and depth == 0: window_rect = bounds if role and (name or rect): nodes.append({'role': role, 'name': name, 'rect': [rect.x, rect.y, rect.width, rect.height] if rect else None, 'state': [str(s) for s in node.get_state_set().get_states()], 'pid': pid, 'atspi_path': route, 'window_rect': window_rect, 'raw_rect': bounds}) for i in range(node.get_child_count()): walk(node.get_child_at_index(i), depth + 1, route + [i], pid, window_rect) except Exception: return if mode == 'focused': for i in range(desktop.get_child_count()): app = desktop.get_child_at_index(i) try: for j in range(app.get_child_count()): window = app.get_child_at_index(j) if window.get_state_set().contains(Atspi.StateType.ACTIVE): walk(window, route=[j], pid=app.get_process_id()) except Exception: continue else: for i in range(desktop.get_child_count()): app = desktop.get_child_at_index(i) for j in range(app.get_child_count()): walk(app.get_child_at_index(j), route=[j], pid=app.get_process_id()) print(json.dumps(nodes, ensure_ascii=False))