249 lines
8.9 KiB
C
249 lines
8.9 KiB
C
#include <Carbon/Carbon.h>
|
|
|
|
void ax_init() {
|
|
const void *keys[] = { kAXTrustedCheckOptionPrompt };
|
|
const void *values[] = { kCFBooleanTrue };
|
|
|
|
CFDictionaryRef options;
|
|
options = CFDictionaryCreate(kCFAllocatorDefault,
|
|
keys,
|
|
values,
|
|
sizeof(keys) / sizeof(*keys),
|
|
&kCFCopyStringDictionaryKeyCallBacks,
|
|
&kCFTypeDictionaryValueCallBacks );
|
|
|
|
bool trusted = AXIsProcessTrustedWithOptions(options);
|
|
CFRelease(options);
|
|
if (!trusted) exit(1);
|
|
}
|
|
|
|
void ax_perform_click(AXUIElementRef element) {
|
|
if (!element) return;
|
|
AXUIElementPerformAction(element, kAXCancelAction);
|
|
usleep(150000);
|
|
AXUIElementPerformAction(element, kAXPressAction);
|
|
}
|
|
|
|
CFStringRef ax_get_title(AXUIElementRef element) {
|
|
CFTypeRef title = NULL;
|
|
AXError error = AXUIElementCopyAttributeValue(element,
|
|
kAXTitleAttribute,
|
|
&title );
|
|
|
|
if (error != kAXErrorSuccess) return NULL;
|
|
return title;
|
|
}
|
|
|
|
void ax_select_menu_option(AXUIElementRef app, int id) {
|
|
AXUIElementRef menubars_ref = NULL;
|
|
CFArrayRef children_ref = NULL;
|
|
|
|
AXError error = AXUIElementCopyAttributeValue(app,
|
|
kAXMenuBarAttribute,
|
|
(CFTypeRef*)&menubars_ref);
|
|
if (error == kAXErrorSuccess) {
|
|
error = AXUIElementCopyAttributeValue(menubars_ref,
|
|
kAXVisibleChildrenAttribute,
|
|
(CFTypeRef*)&children_ref );
|
|
|
|
if (error == kAXErrorSuccess) {
|
|
uint32_t count = CFArrayGetCount(children_ref);
|
|
if (id < count) {
|
|
AXUIElementRef item = CFArrayGetValueAtIndex(children_ref, id);
|
|
ax_perform_click(item);
|
|
}
|
|
if (children_ref) CFRelease(children_ref);
|
|
}
|
|
if (menubars_ref) CFRelease(menubars_ref);
|
|
}
|
|
}
|
|
|
|
void ax_print_menu_options(AXUIElementRef app) {
|
|
AXUIElementRef menubars_ref = NULL;
|
|
CFTypeRef menubar = NULL;
|
|
CFArrayRef children_ref = NULL;
|
|
|
|
AXError error = AXUIElementCopyAttributeValue(app,
|
|
kAXMenuBarAttribute,
|
|
(CFTypeRef*)&menubars_ref);
|
|
if (error == kAXErrorSuccess) {
|
|
error = AXUIElementCopyAttributeValue(menubars_ref,
|
|
kAXVisibleChildrenAttribute,
|
|
(CFTypeRef*)&children_ref );
|
|
|
|
if (error == kAXErrorSuccess) {
|
|
uint32_t count = CFArrayGetCount(children_ref);
|
|
|
|
for (int i = 1; i < count; i++) {
|
|
AXUIElementRef item = CFArrayGetValueAtIndex(children_ref, i);
|
|
CFTypeRef title = ax_get_title(item);
|
|
|
|
if (title) {
|
|
uint32_t buffer_len = 2*CFStringGetLength(title);
|
|
char buffer[2*CFStringGetLength(title)];
|
|
CFStringGetCString(title, buffer, buffer_len, kCFStringEncodingUTF8);
|
|
printf("%s\n", buffer);
|
|
CFRelease(title);
|
|
}
|
|
}
|
|
}
|
|
if (menubars_ref) CFRelease(menubars_ref);
|
|
if (children_ref) CFRelease(children_ref);
|
|
}
|
|
}
|
|
|
|
AXUIElementRef ax_get_extra_menu_item(char* alias) {
|
|
pid_t pid = 0;
|
|
CGRect bounds = CGRectNull;
|
|
CFArrayRef window_list = CGWindowListCopyWindowInfo(kCGWindowListOptionAll,
|
|
kCGNullWindowID );
|
|
char owner_buffer[256];
|
|
char name_buffer[256];
|
|
char buffer[512];
|
|
int window_count = CFArrayGetCount(window_list);
|
|
for (int i = 0; i < window_count; ++i) {
|
|
CFDictionaryRef dictionary = CFArrayGetValueAtIndex(window_list, i);
|
|
if (!dictionary) continue;
|
|
|
|
CFStringRef owner_ref = CFDictionaryGetValue(dictionary,
|
|
kCGWindowOwnerName);
|
|
|
|
CFNumberRef owner_pid_ref = CFDictionaryGetValue(dictionary,
|
|
kCGWindowOwnerPID);
|
|
|
|
CFStringRef name_ref = CFDictionaryGetValue(dictionary, kCGWindowName);
|
|
CFNumberRef layer_ref = CFDictionaryGetValue(dictionary, kCGWindowLayer);
|
|
CFDictionaryRef bounds_ref = CFDictionaryGetValue(dictionary,
|
|
kCGWindowBounds);
|
|
|
|
if (!name_ref || !owner_ref || !owner_pid_ref || !layer_ref || !bounds_ref)
|
|
continue;
|
|
|
|
long long int layer = 0;
|
|
CFNumberGetValue(layer_ref, CFNumberGetType(layer_ref), &layer);
|
|
uint64_t owner_pid = 0;
|
|
CFNumberGetValue(owner_pid_ref,
|
|
CFNumberGetType(owner_pid_ref),
|
|
&owner_pid );
|
|
|
|
if (layer != 0x19) continue;
|
|
bounds = CGRectNull;
|
|
if (!CGRectMakeWithDictionaryRepresentation(bounds_ref, &bounds)) continue;
|
|
CFStringGetCString(owner_ref,
|
|
owner_buffer,
|
|
sizeof(owner_buffer),
|
|
kCFStringEncodingUTF8);
|
|
|
|
CFStringGetCString(name_ref,
|
|
name_buffer,
|
|
sizeof(name_buffer),
|
|
kCFStringEncodingUTF8);
|
|
snprintf(buffer, sizeof(buffer), "%s,%s", owner_buffer, name_buffer);
|
|
|
|
if (strcmp(buffer, alias) == 0) {
|
|
pid = owner_pid;
|
|
break;
|
|
}
|
|
}
|
|
CFRelease(window_list);
|
|
if (!pid) return NULL;
|
|
|
|
AXUIElementRef app = AXUIElementCreateApplication(pid);
|
|
if (!app) return NULL;
|
|
AXUIElementRef result = NULL;
|
|
CFTypeRef extras = NULL;
|
|
CFArrayRef children_ref = NULL;
|
|
AXError error = AXUIElementCopyAttributeValue(app,
|
|
kAXExtrasMenuBarAttribute,
|
|
&extras );
|
|
if (error == kAXErrorSuccess) {
|
|
error = AXUIElementCopyAttributeValue(extras,
|
|
kAXVisibleChildrenAttribute,
|
|
(CFTypeRef*)&children_ref );
|
|
|
|
if (error == kAXErrorSuccess) {
|
|
uint32_t count = CFArrayGetCount(children_ref);
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
AXUIElementRef item = CFArrayGetValueAtIndex(children_ref, i);
|
|
CFTypeRef position_ref = NULL;
|
|
CFTypeRef size_ref = NULL;
|
|
AXUIElementCopyAttributeValue(item, kAXPositionAttribute,
|
|
&position_ref );
|
|
AXUIElementCopyAttributeValue(item, kAXSizeAttribute,
|
|
&size_ref );
|
|
if (!position_ref || !size_ref) continue;
|
|
|
|
CGPoint position = CGPointZero;
|
|
AXValueGetValue(position_ref, kAXValueCGPointType, &position);
|
|
CGSize size = CGSizeZero;
|
|
AXValueGetValue(size_ref, kAXValueCGSizeType, &size);
|
|
CFRelease(position_ref);
|
|
CFRelease(size_ref);
|
|
// The offset is exactly 8 on macOS Sonoma...
|
|
// printf("%f %f\n", position.x, bounds.origin.x);
|
|
if (error == kAXErrorSuccess
|
|
&& fabs(position.x - bounds.origin.x) <= 10) {
|
|
result = item;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CFRelease(app);
|
|
return result;
|
|
}
|
|
|
|
extern int SLSMainConnectionID();
|
|
extern void SLSSetMenuBarVisibilityOverrideOnDisplay(int cid, int did, bool enabled);
|
|
extern void SLSSetMenuBarVisibilityOverrideOnDisplay(int cid, int did, bool enabled);
|
|
extern void SLSSetMenuBarInsetAndAlpha(int cid, double u1, double u2, float alpha);
|
|
void ax_select_menu_extra(char* alias) {
|
|
AXUIElementRef item = ax_get_extra_menu_item(alias);
|
|
if (!item) return;
|
|
SLSSetMenuBarInsetAndAlpha(SLSMainConnectionID(), 0, 1, 0.0);
|
|
SLSSetMenuBarVisibilityOverrideOnDisplay(SLSMainConnectionID(), 0, true);
|
|
SLSSetMenuBarInsetAndAlpha(SLSMainConnectionID(), 0, 1, 0.0);
|
|
ax_perform_click(item);
|
|
SLSSetMenuBarVisibilityOverrideOnDisplay(SLSMainConnectionID(), 0, false);
|
|
SLSSetMenuBarInsetAndAlpha(SLSMainConnectionID(), 0, 1, 1.0);
|
|
CFRelease(item);
|
|
}
|
|
|
|
extern void _SLPSGetFrontProcess(ProcessSerialNumber* psn);
|
|
extern void SLSGetConnectionIDForPSN(int cid, ProcessSerialNumber* psn, int* cid_out);
|
|
extern void SLSConnectionGetPID(int cid, pid_t* pid_out);
|
|
AXUIElementRef ax_get_front_app() {
|
|
ProcessSerialNumber psn;
|
|
_SLPSGetFrontProcess(&psn);
|
|
int target_cid;
|
|
SLSGetConnectionIDForPSN(SLSMainConnectionID(), &psn, &target_cid);
|
|
|
|
pid_t pid;
|
|
SLSConnectionGetPID(target_cid, &pid);
|
|
return AXUIElementCreateApplication(pid);
|
|
}
|
|
|
|
int main (int argc, char **argv) {
|
|
if (argc == 1) {
|
|
printf("Usage: %s [-l | -s id/alias ]\n", argv[0]);
|
|
exit(0);
|
|
}
|
|
ax_init();
|
|
if (strcmp(argv[1], "-l") == 0) {
|
|
AXUIElementRef app = ax_get_front_app();
|
|
if (!app) return 1;
|
|
ax_print_menu_options(app);
|
|
CFRelease(app);
|
|
} else if (argc == 3 && strcmp(argv[1], "-s") == 0) {
|
|
int id = 0;
|
|
if (sscanf(argv[2], "%d", &id) == 1) {
|
|
AXUIElementRef app = ax_get_front_app();
|
|
if (!app) return 1;
|
|
ax_select_menu_option(app, id);
|
|
CFRelease(app);
|
|
} else ax_select_menu_extra(argv[2]);
|
|
}
|
|
return 0;
|
|
}
|