workflow-osx-0.0.0: cbits/objc_workflow.m
#import <Cocoa/Cocoa.h>
#import "objc_workflow.h"
/* appInfo
// NSLog(@"%@", appInfo);
NSDictionary: {
NSApplicationBundleIdentifier = "org.gnu.Emacs";
NSApplicationName = Emacs;
NSApplicationPath = "/Applications/Notes.app";
NSApplicationProcessIdentifier = 40831;
NSApplicationProcessSerialNumberHigh = 0;
NSApplicationProcessSerialNumberLow = 4195328;
NSWorkspaceApplicationKey = "<NSRunningApplication: 0x7fe3c0e08b30 (org.gnu.Emacs - 40831)>";
}
*/
/* ProcessSerialNumber
https: //developer.apple.com/legacy/library/documentation/Carbon/Reference/Process_Manager/index.html#//apple_ref/doc/c_ref/ProcessSerialNumber
struct ProcessSerialNumber { unsigned long highLongOfPSN; unsigned long lowLongOfPSN; };
typedef struct ProcessSerialNumber ProcessSerialNumber;
typedef ProcessSerialNumber* ProcessSerialNumberPtr;
*/
/* CGEventPostToPSN
https://developer.apple.com/library/mac/documentation/Carbon/Reference/QuartzEventServicesRef/index.html
void CGEventPostToPSN ( void *processSerialNumber, CGEventRef event );
*/
// private helpers
NSString* fromUTF8(const char* s) {
return [[NSString alloc]
initWithCString:s encoding:NSUTF8StringEncoding];
}
const char* toUTF8(NSString* s) {
return [s cStringUsingEncoding:NSUTF8StringEncoding];
}
void printPSN(ProcessSerialNumber psn) {
NSLog(@"ProcessSerialNumber: {%d,%d}", psn.highLongOfPSN, psn.lowLongOfPSN);
}
void printApplications() {
NSLog(@"launchedApplications: %@", [[NSWorkspace sharedWorkspace] launchedApplications]);
}
// I think that uninitialized ProcessSerialNumber is always {0,0}.
// I *hope* it doesn't crash CGEventPostToPSN (hasn't yet) and that no application has both fields coinciding.
ProcessSerialNumber nullProcessSerialNumber = {0,0};
// returns pseudo-Just a pointer to a heap-allocated structure, must be freed.
// pseudo-Nothing if no application by that path is running
// TODO or pointer to a pointer to a structure (without a pointer can be null)?
// from full path or from name
ProcessSerialNumber* getApplicationPSN(const char* s) {
// no maybe type...
ProcessSerialNumber* psn = (ProcessSerialNumber*)malloc(sizeof(ProcessSerialNumber));
*psn = nullProcessSerialNumber; // http://stackoverflow.com/questions/9127246/copy-struct-to-struct-in-c
// existentially-quantified arrays...
NSArray* appInfos = [[NSWorkspace sharedWorkspace] launchedApplications]; // runningApplications
for (NSDictionary* appInfo in appInfos) {
if (0 == strcmp(s, toUTF8([appInfo objectForKey:@"NSApplicationPath"]))) {
psn->highLongOfPSN = [[appInfo objectForKey:@"NSApplicationProcessSerialNumberHigh"] unsignedIntValue];
psn->lowLongOfPSN = [[appInfo objectForKey:@"NSApplicationProcessSerialNumberLow"] unsignedIntValue];
return psn;
}
}
for (NSDictionary* appInfo in appInfos) {
if (0 == strcmp(s, toUTF8([appInfo objectForKey:@"NSApplicationName"]))) {
psn->highLongOfPSN = [[appInfo objectForKey:@"NSApplicationProcessSerialNumberHigh"] unsignedIntValue];
psn->lowLongOfPSN = [[appInfo objectForKey:@"NSApplicationProcessSerialNumberLow"] unsignedIntValue];
return psn;
}
}
return psn;
}
ProcessSerialNumber currentApplicationPSN() {
NSDictionary *appInfo = [[NSWorkspace sharedWorkspace] activeApplication]; // frontmostApplication
ProcessSerialNumber psn;
psn.highLongOfPSN = [[appInfo objectForKey:@"NSApplicationProcessSerialNumberHigh"] unsignedIntValue];
psn.lowLongOfPSN = [[appInfo objectForKey:@"NSApplicationProcessSerialNumberLow"] unsignedIntValue];
return psn;
}
// public
const char* currentApplicationPath() {
return toUTF8([[[NSWorkspace sharedWorkspace]
activeApplication] // frontmostApplication
objectForKey:@"NSApplicationPath"]);
}
// can be full path (e.g. "/Applications/Work.app"), or just the name (e.g. "Work")
void openApplication(const char* s) {
[[NSWorkspace sharedWorkspace] launchApplication:fromUTF8(s)];
}
// simulates a key press from the keyboard. works in pop-ups like Alfred.
void pressKey(CGEventFlags modifiers, CGKeyCode key) {
// NSLog(@"pressKey"); // debug
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); // kCGEventSourceStateCombinedSessionState
// events to press a key
CGEventRef event1 = CGEventCreateKeyboardEvent(source, key, true); // key down
CGEventRef event2 = CGEventCreateKeyboardEvent(source, key, false); // key up
// add modifiers to event
CGEventSetFlags(event1, modifiers);
CGEventSetFlags(event2, modifiers);
// send a keyboard event (a quartz event) "globally"
CGEventPost(kCGHIDEventTap, event1); // kCGSessionEventTap
CGEventPost(kCGHIDEventTap, event2); // kCGSessionEventTap
// free memory
CFRelease(event1);
CFRelease(event2);
CFRelease(source);
}
// may take up to 15s (!) when the keypresses sent to and after besides the one that's running the server
void pressKeyToCurrentApplication(CGEventFlags modifiers, CGKeyCode key) {
pressKeyTo(modifiers, key, currentApplicationPSN());
}
// send a keypress to a specific process/application
void pressKeyTo(CGEventFlags modifiers, CGKeyCode key, ProcessSerialNumber psn) {
// events to press a key
CGEventRef event1 = CGEventCreateKeyboardEvent(NULL, key, true); // key down
CGEventRef event2 = CGEventCreateKeyboardEvent(NULL, key, false); // key up
// add modifiers to event
CGEventSetFlags(event1, modifiers);
CGEventSetFlags(event2, modifiers);
// send keyboard event to application process (a quartz event)
CGEventPostToPSN(&psn, event1);
CGEventPostToPSN(&psn, event2);
// free memory
CFRelease(event1);
CFRelease(event2);
// psn is a struct, that's been passed by value
}
const char* getClipboard() {
return [[[NSPasteboard generalPasteboard]
stringForType:NSStringPboardType]
cStringUsingEncoding:NSUTF8StringEncoding];
}
void setClipboard(const char* contents) {
[[NSPasteboard generalPasteboard] clearContents];
[[NSPasteboard generalPasteboard] setString:fromUTF8(contents) forType:NSStringPboardType];
}
void openURL(const char* url) {
[[NSWorkspace sharedWorkspace]
openURL:[NSURL URLWithString: fromUTF8(url)]];
}
// CGEventRef CGEventCreateMouseEvent ( CGEventSourceRef source, CGEventType mouseType, CGPoint mouseCursorPosition, CGMouseButton mouseButton );
// http://stackoverflow.com/questions/1117065/cocoa-getting-the-current-mouse-position-on-the-screen
void clickMouse(CGEventFlags modifiers, CGMouseButton mouseButton, CGEventType mouseDown, CGEventType mouseUp, UInt32 numClicks) {
CGPoint currentPosition = CGEventGetLocation(CGEventCreate(NULL));
CGEventRef eventDown = CGEventCreateMouseEvent(NULL, kCGEventOtherMouseDown, currentPosition, mouseButton);
CGEventRef eventUp = CGEventCreateMouseEvent(NULL, kCGEventOtherMouseUp, currentPosition, mouseButton);
// hold down modifiers
CGEventSetFlags(eventDown, modifiers);
CGEventSetFlags(eventUp, modifiers);
// click numClicks times. each click must say which it is (1st, 2nd, 3rd, etc)
for (int nthClick=1; nthClick<=numClicks; nthClick++) {
CGEventSetIntegerValueField(eventDown, kCGMouseEventClickState, nthClick);
CGEventSetIntegerValueField(eventUp, kCGMouseEventClickState, nthClick);
CGEventPost(kCGHIDEventTap, eventDown);
CGEventPost(kCGHIDEventTap, eventUp);
}
// free memory
CFRelease(eventDown);
CFRelease(eventUp);
}