Macotron

macotron.event

Send clicks, key presses, typed text, and scrolls as if they came from the hardware, and watch keyboard, mouse, and trackpad input from every app. A tap can also swallow an event so that no app sees it.

// Option-Tab opens a window switcher instead of reaching the app.
const TAB = 48;
macotron.event.tap(["keyDown", "keyUp"], (e) => {
  if (e.keyCode === TAB && e.flags.includes("opt")) {
    if (e.type === "keyDown") showSwitcher();
    return false;
  }
});

Posting events

post(event): boolean

Posts one event to the system, which sends it to the app under the pointer or the app with keyboard focus. Returns false for an unknown type, a key name that does not parse, or an empty string. Events you post are marked, so your own tap() listeners never see them.

typeFieldsWhat it does
"click"button?: "left" (default), "right", or "middle". x?, y?A press and a release at that point.
"key"key: a key name from the combo list. flags?: any of "cmd", "shift", "ctrl", "opt", "fn", "caps"A key down and a key up, with those modifiers held. key can also be a whole combo such as "cmd+v"; flags, when given, replaces its modifiers.
"unicode"stringTypes the string as one key press, whatever the keyboard layout. Use it for emoji and accented letters.
"scroll"dy?, dx?, pixel?One scroll. dy is vertical and dx is horizontal, in lines, or in pixels when pixel is true. Values are rounded to whole numbers.

Click coordinates are global screen points with the origin at the top-left of the main display and y going down. That is the same space as macotron.window frames, and not the space of macotron.mouse.

Without x and y, the click lands where the pointer is.

// Paste after the launcher closes and focus returns to the app.
setTimeout(() => macotron.event.post({ type: "unicode", string: "🎉" }), 150);
macotron.event.post({ type: "key", key: "v", flags: ["cmd"] });

Posting events needs Accessibility permission. Declare permissions: ["accessibility"] in macotron.plugin().

Watching input

tap(types: string | string[], callback: (e) => boolean | void): void

Calls callback for each matching event from any app. Return false to swallow the event. Any other return value lets it through. Unknown type names are ignored, and if no name is valid the call does nothing. Taps last until plugins reload; there is no way to remove one sooner.

Type namesCan swallow
keyDown, keyUp, flagsChanged (a modifier key goes down or up)Yes
leftMouseDown, leftMouseUp, rightMouseDown, rightMouseUp, mouseMoved, leftMouseDraggedYes
scroll (or scrollWheel)Yes
swipe, magnify, rotate (trackpad gestures)No. The return value is ignored

The callback receives one object:

FieldPresent forMeaning
typeAllThe type name. Scroll events report "scroll".
flagsAllModifiers held: any of "cmd", "shift", "ctrl", "opt", "fn", "caps".
x, yKeyboard, mouse, scrollPointer position, with the origin at the top-left of the main display.
keyCodekeyDown, keyUpThe macOS virtual key code, for example 48 for Tab.
dx, dyscrollScroll amount in lines.
button, downLeft and right mouse down and up"left" or "right", and whether this is the press.
fingersGesturesThe number of fingers.
directionswipe"left", "right", "up", or "down".
deltamagnify, rotateThe zoom amount, or the rotation in degrees.
macotron.event.tap("swipe", (e) => {
  if (e.fingers !== 3) return;
  const win = macotron.window.focused();
  if (!win) return;
  const x = e.direction === "left" ? 0 : 0.5;
  macotron.window.moveToFraction(win.id, { x, y: 0, w: 0.5, h: 1 });
});

Every key press and click on the Mac waits for your callback, so keep it short. A tap callback has 250 ms before Macotron interrupts it, and an interrupted callback lets the event through. Do slow work in a setTimeout.

Taps need Accessibility and Input Monitoring permission. Declare permissions: ["accessibility", "inputMonitoring"] in macotron.plugin().

See also