1// Posts input to the logged-in session as the HID system would, for driving the app
2// over ssh: `input click X Y`, `input double X Y`, `input drag X Y X2 Y2`,
3// `input scroll X Y LINES`, `input type TEXT`,
4// `input key KEYCODE [command] [shift] [option] [control]`.
5// Points are the display's, from its top left.
6#include <ApplicationServices/ApplicationServices.h>
7#include <stdlib.h>
8#include <string.h>
9#include <unistd.h>
10
11static void post(CGEventRef event) {
12 CGEventPost(kCGHIDEventTap, event);
13 CFRelease(event);
14 usleep(20000);
15}
16
17static void mouse(CGEventType type, CGPoint at, int clicks) {
18 CGEventRef event = CGEventCreateMouseEvent(NULL, type, at, kCGMouseButtonLeft);
19 CGEventSetIntegerValueField(event, kCGMouseEventClickState, clicks);
20 post(event);
21}
22
23static void click(CGPoint at, int clicks) {
24 mouse(kCGEventLeftMouseDown, at, clicks);
25 mouse(kCGEventLeftMouseUp, at, clicks);
26}
27
28static void key(CGKeyCode code, CGEventFlags flags, const UniChar *text, UniCharCount length) {
29 for (int down = 1; down >= 0; down--) {
30 CGEventRef event = CGEventCreateKeyboardEvent(NULL, code, down);
31 CGEventSetFlags(event, flags);
32 if (length) CGEventKeyboardSetUnicodeString(event, length, text);
33 post(event);
34 }
35}
36
37int main(int argc, char **argv) {
38 if ((argc == 4 && !strcmp(argv[1], "click")) || (argc == 4 && !strcmp(argv[1], "double"))) {
39 CGPoint at = CGPointMake(atof(argv[2]), atof(argv[3]));
40 mouse(kCGEventMouseMoved, at, 0);
41 click(at, 1);
42 if (!strcmp(argv[1], "double")) click(at, 2);
43 } else if (argc == 6 && !strcmp(argv[1], "drag")) {
44 CGPoint from = CGPointMake(atof(argv[2]), atof(argv[3]));
45 CGPoint to = CGPointMake(atof(argv[4]), atof(argv[5]));
46 mouse(kCGEventMouseMoved, from, 0);
47 mouse(kCGEventLeftMouseDown, from, 1);
48 for (int step = 1; step <= 10; step++) {
49 CGPoint at = CGPointMake(from.x + (to.x - from.x) * step / 10, from.y + (to.y - from.y) * step / 10);
50 mouse(kCGEventLeftMouseDragged, at, 1);
51 }
52 mouse(kCGEventLeftMouseUp, to, 1);
53 } else if (argc == 5 && !strcmp(argv[1], "scroll")) {
54 CGPoint at = CGPointMake(atof(argv[2]), atof(argv[3]));
55 mouse(kCGEventMouseMoved, at, 0);
56 CGEventRef event = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, atoi(argv[4]));
57 CGEventSetLocation(event, at);
58 post(event);
59 } else if (argc == 3 && !strcmp(argv[1], "type")) {
60 CFStringRef text = CFStringCreateWithCString(NULL, argv[2], kCFStringEncodingUTF8);
61 for (CFIndex i = 0; i < CFStringGetLength(text); i++) {
62 UniChar c = CFStringGetCharacterAtIndex(text, i);
63 key(0, 0, &c, 1);
64 }
65 CFRelease(text);
66 } else if (argc >= 3 && !strcmp(argv[1], "key")) {
67 CGEventFlags flags = 0;
68 for (int i = 3; i < argc; i++) {
69 if (!strcmp(argv[i], "command")) flags |= kCGEventFlagMaskCommand;
70 else if (!strcmp(argv[i], "shift")) flags |= kCGEventFlagMaskShift;
71 else if (!strcmp(argv[i], "option")) flags |= kCGEventFlagMaskAlternate;
72 else if (!strcmp(argv[i], "control")) flags |= kCGEventFlagMaskControl;
73 }
74 key(atoi(argv[2]), flags, NULL, 0);
75 } else {
76 fprintf(stderr, "usage: input click|double X Y | drag X Y X2 Y2 | scroll X Y LINES | type TEXT | key KEYCODE [command] [shift] [option] [control]\n");
77 return 2;
78 }
79 return 0;
80}