75 lines
2.5 KiB
Python
75 lines
2.5 KiB
Python
#!/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}')
|