Skip to content

feat: type insertions where no text control can be observed - #68

Merged
iamnbutler merged 1 commit into
mainfrom
feat/insert-anywhere
Oct 17, 2026
Merged

iamnbutler merged 1 commit into
mainfrom
feat/insert-anywhere

Conversation

@iamnbutler

Copy link
Copy Markdown
Contributor

Closes the "Insertion outside text fields" gap. desktop_insert now has a typing route for receivers whose text can't be observed: terminals, canvases, custom views, and web editors that expose no AX text.

Why typing and not a guarded paste

  • The paste transaction restores the clipboard only after it observes consumption.
  • Until then it keeps a paste reservation, and that reservation refuses every later automated clipboard write until the receiver's process exits.
  • A receiver with no readable text can never prove consumption. Every paste into one would therefore wedge the clipboard gate for as long as the target app runs.
  • Changing that would weaken the restore rules, which this change keeps as they are.
  • Typing doesn't touch the clipboard at all.

Route choice (made before dispatch)

  • Typing can't be a fallback after a refused paste: the host consumes the snapshot when literalInsert refuses.
  • The snapshot reports a focused control → paste, unchanged.
  • No focused control → type into the window's keyboard focus (receiver: window_focus).
  • New optional route:
    • "type" types into an observed control whose text can't be read, such as a terminal's text area (receiver: observed_control).
    • "paste" refuses typing.

Admission

  • This reuses feat: send keys to windows whose focus isn't an observed control #51's window-focus admission. Typing calls upstream's typeActionsWithOutcome(expectedWindowIdentity:expectedWindowBounds:). Before every character it runs requireExactWindowKeyboardFocus: the app's focused element must be inside the window, and the window must be the app's key window.
  • With route: "type" and an observed control, typing uses the ExactWindowKeyboardTarget variant, bound to that control.
  • The runtime allowlist gains .exactWindowTargetedTypeActions. This is a host change, so an older host refuses with an "update the runtime" message. No bridge op is added, so the op count stays at 127.

Guards

  • Typed text is limited to 1,000 UTF-16 units, because focus is validated per character.
  • Newlines, tabs and other control characters are refused, since typing them presses Return or Tab. A terminal would run the line. The hint points to desktop_key.
  • Refused while IsSecureEventInputEnabled() is true (password fields, Terminal's Secure Keyboard Entry).
  • Cadence is a fixed 2 ms.

Outcome

  • A typed insertion is unknown (INSERTION_UNVERIFIED) unless the host reports confirmedChange for an observed control.

Docs: the tool description, the docs/tools.md row, a new paragraph, and the Not-supported bullet (now "Confirmed insertion outside text fields").

Checks

  • npm run check and the native build pass.
  • Pending: screen locked. Live typing into Terminal, a canvas and a contenteditable. Timing for 1,000 characters. The secure-input refusal. These also need a dev host, because the installed host doesn't allow the typing op.

@iamnbutler
iamnbutler merged commit 5eb787e into main Oct 17, 2026
2 checks passed
@iamnbutler
iamnbutler deleted the feat/insert-anywhere branch October 17, 2026 04:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant