Held Modifiers Across an Action Group
hotkey presses a set of keys and releases them immediately — fine for a
one-shot chord, but there was no way to hold ctrl (or shift) down across
several independent actions (range-select with shift-clicks, ctrl-clicks to
multi-select) and be sure the modifiers are released even if one of those actions
raises.
plan_with_modifiers() wraps an op-step list with press / release steps and
is pure / unit-testable; hold_modifiers() is a context manager that presses
on enter and releases (in reverse) on exit — including on exception —
dispatching through an injectable sink. Imports no PySide6.
Headless API
from je_auto_control import hold_modifiers, plan_with_modifiers
from je_auto_control import click_mouse
# shift-held range select: every click happens with shift down
with hold_modifiers(["shift"]):
click_mouse("mouse_left", 100, 100)
click_mouse("mouse_left", 100, 300)
# shift is released here — even if a click raised
plan_with_modifiers([{"op": "click"}], ["ctrl", "shift"])
# press ctrl, press shift, click, release shift, release ctrl
Modifiers are pressed in order on entry and released in reverse order on exit,
in a finally block, so a stuck modifier can never leak. plan_with_modifiers
is the pure plan for any op-step list.
Executor commands
AC_with_modifiers runs a nested JSON action list while modifiers (e.g.
["ctrl"] or "ctrl+shift") are held, releasing them even if an action
fails. It is exposed as the MCP tool ac_with_modifiers and as a Script
Builder command under Keyboard.