// postmouse.c — post synthesized mouse events to a specific macOS process. // // Build: // clang -framework CoreFoundation -framework CoreGraphics \ // -o scripts/postmouse scripts/postmouse.c // // Usage: // ./scripts/postmouse [cmd] // move | down | drag | up (kCGEventMouseMoved / LeftMouseDown / // LeftMouseDragged / LeftMouseUp) // global display coordinates (CGEventGetLocation space) // [cmd] pass "cmd" to hold the Command modifier (pane-drag gesture) // // The press carries the command modifier; drag/up continue the gesture with // the button already down. CGEventPostToPid is the only injection channel // that works on a locked macOS console (system-wide posting is dropped when // loginwindow is frontmost). #include #include #include #include #include #include int main(int argc, char **argv) { if (argc < 5) { fprintf(stderr, "usage: postmouse [delta|cmd]\n"); return 2; } pid_t pid = (pid_t)atoi(argv[1]); const char *action = argv[2]; double x = atof(argv[3]); double y = atof(argv[4]); bool withCmd = argc > 5 && strstr(argv[5], "cmd") != NULL; // Scroll wheel: are atof-ignored; [delta] is the wheel // delta (positive scrolls up/back). CGEventPostToPid delivers it to // the frontmost process's wheel handler. if (strcmp(action, "scroll") == 0) { double delta = (argc > 5) ? atof(argv[5]) : 3.0; if (delta == 0.0) delta = 3.0; CGEventRef ev = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, (int32_t)delta); CGEventSetLocation(ev, CGPointMake(x, y)); CGEventPostToPid(pid, ev); CFRelease(ev); return 0; } CGEventType type; if (strcmp(action, "move") == 0) type = kCGEventMouseMoved; else if (strcmp(action, "down") == 0) type = kCGEventLeftMouseDown; else if (strcmp(action, "drag") == 0) type = kCGEventLeftMouseDragged; else if (strcmp(action, "up") == 0) type = kCGEventLeftMouseUp; else { fprintf(stderr, "unknown action: %s\n", action); return 2; } CGEventRef ev = CGEventCreateMouseEvent(NULL, type, CGPointMake(x, y), kCGMouseButtonLeft); if (withCmd) { CGEventSetFlags(ev, kCGEventFlagMaskCommand); } CGEventPostToPid(pid, ev); CFRelease(ev); return 0; }