Mercurial > minori
diff dep/animia/src/win/quartz.cc @ 237:a7d0d543b334
*: make OS X builds succeed
new script: deploy_build.sh, creates the app bundle
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Fri, 19 Jan 2024 11:14:44 -0500 |
parents | bc1ae1810855 |
children |
line wrap: on
line diff
--- a/dep/animia/src/win/quartz.cc Fri Jan 19 00:24:02 2024 -0500 +++ b/dep/animia/src/win/quartz.cc Fri Jan 19 11:14:44 2024 -0500 @@ -2,8 +2,7 @@ * win/quartz.cc: support for macOS (the Quartz Compositor) * * This file does not require an Objective-C++ compiler, - * but it *does* require an Objective-C runtime and linking - * with AppKit in order to receive proper window titles. + * but it *does* require an Objective-C runtime. */ #include "animia/win/quartz.h" #include "animia/util/osx.h" @@ -14,51 +13,50 @@ #include <CoreFoundation/CoreFoundation.h> #include <CoreGraphics/CoreGraphics.h> +#include <ApplicationServices/ApplicationServices.h> namespace animia::internal::quartz { +/* all of these LaunchServices things use *internal functions* that are subject + * to change. Granted, it's not very likely that these will change very much + * because I'm fairly sure Apple uses them lots in their own internal code. +*/ +#if __LP64__ +typedef long NSInteger; +#else +typedef int NSInteger; +#endif +typedef int CGSConnection; + +typedef CGSConnection (*CGSDefaultConnectionForThreadSpec)(void); +typedef CGError (*CGSCopyWindowPropertySpec)(const CGSConnection, NSInteger, CFStringRef, CFStringRef*); + +static CGSDefaultConnectionForThreadSpec CGSDefaultConnectionForThread = nullptr; +static CGSCopyWindowPropertySpec CGSCopyWindowProperty = nullptr; + +static const CFStringRef kCoreGraphicsBundleID = CFSTR("com.apple.CoreGraphics"); + +/* Objective-C */ typedef id (*object_message_send)(id, SEL, ...); typedef id (*class_message_send)(Class, SEL, ...); static const object_message_send obj_send = reinterpret_cast<object_message_send>(objc_msgSend); static const class_message_send cls_send = reinterpret_cast<class_message_send>(objc_msgSend); -static bool GetWindowTitle(unsigned int wid, std::string& result) { - // NSApplication* app = [NSApplication sharedApplication]; - const id app = cls_send(objc_getClass("NSApplication"), sel_getUid("sharedApplication")); - - // NSWindow* window = [app windowWithWindowNumber: wid]; - const id window = obj_send(app, sel_getUid("windowWithWindowNumber:"), wid); - if (!window) - return false; - - // NSString* title = [window title]; - const CFStringRef title = reinterpret_cast<CFStringRef>(obj_send(window, sel_getUid("title"))); - if (!title) +static bool GetCoreGraphicsPrivateSymbols() { + CFBundleRef core_graphics_bundle = CFBundleGetBundleWithIdentifier(kCoreGraphicsBundleID); + if (!core_graphics_bundle) return false; - // return [title UTF8String]; - return osx::util::StringFromCFString(title, result); -} - -static bool GetProcessBundleIdentifier(pid_t pid, std::string& result) { - /* The Bundle ID is essentially OS X's solution to Windows' - * "class name"; theoretically, it should be different for - * each program, although it requires an app bundle. - */ - - // NSRunningApplication* app = [NSRunningApplication runningApplicationWithProcessIdentifier: pid]; - const id app = cls_send(objc_getClass("NSRunningApplication"), sel_getUid("runningApplicationWithProcessIdentifier:"), pid); - if (!app) + CGSDefaultConnectionForThread = (CGSDefaultConnectionForThreadSpec)CFBundleGetFunctionPointerForName(core_graphics_bundle, CFSTR("CGSDefaultConnectionForThread")); + if (!CGSDefaultConnectionForThread) return false; - // NSString* bundle_id = [app bundleIdentifier]; - const CFStringRef bundle_id = reinterpret_cast<CFStringRef>(obj_send(app, sel_getUid("bundleIdentifier"))); - if (!bundle_id) + CGSCopyWindowProperty = (CGSCopyWindowPropertySpec)CFBundleGetFunctionPointerForName(core_graphics_bundle, CFSTR("CGSCopyWindowProperty")); + if (!CGSCopyWindowProperty) return false; - // return [bundle_id UTF8String]; - return osx::util::StringFromCFString(bundle_id, result); + return true; } template<typename T> @@ -77,11 +75,110 @@ return true; } +static bool GetWindowTitleAccessibility(unsigned int wid, pid_t pid, std::string& result) { + CGRect bounds = {0}; + { + const CGWindowID wids[1] = {wid}; + CFArrayRef arr = CFArrayCreate(kCFAllocatorDefault, (CFTypeRef*)wids, 1, NULL); + + CFArrayRef dicts = CGWindowListCreateDescriptionFromArray(arr); + + CFRelease(arr); + + if (!dicts || CFArrayGetCount(dicts) < 1) + return false; + + CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(dicts, 0); + if (!dict) { + CFRelease(dicts); + return false; + } + + CFDictionaryRef bounds_dict = nullptr; + if (!CFDictionaryGetValueIfPresent(dict, kCGWindowBounds, reinterpret_cast<CFTypeRef*>(&bounds_dict)) || !bounds_dict) { + CFRelease(dicts); + return false; + } + + if (!CGRectMakeWithDictionaryRepresentation(bounds_dict, &bounds)) { + CFRelease(dicts); + return false; + } + + CFRelease(dicts); + } + + /* now we can actually do stuff */ + AXUIElementRef axapp = AXUIElementCreateApplication(pid); + CFArrayRef windows; + if ((AXUIElementCopyAttributeValue(axapp, kAXWindowsAttribute, (CFTypeRef*)&windows) != kAXErrorSuccess) || !windows) + return false; + + const CFIndex count = CFArrayGetCount(windows); + for (CFIndex i = 0; i < count; i++) { + const AXUIElementRef window = (AXUIElementRef)CFArrayGetValueAtIndex(windows, i); + + AXValueRef val; + if (AXUIElementCopyAttributeValue(window, kAXPositionAttribute, (CFTypeRef*)&val) == kAXErrorSuccess) { + CGPoint point; + if (!AXValueGetValue(val, kAXValueTypeCGPoint, (void*)&point) || (point.x != bounds.origin.x || point.y != bounds.origin.y)) + continue; + } else continue; + + if (AXUIElementCopyAttributeValue(window, kAXSizeAttribute, (CFTypeRef*)&val) == kAXErrorSuccess) { + CGSize size; + if (!AXValueGetValue(val, kAXValueTypeCGSize, (void*)&size) || (size.width != bounds.size.width || size.height != bounds.size.height)) + continue; + } else continue; + + CFStringRef title; + if (AXUIElementCopyAttributeValue(window, kAXTitleAttribute, (CFTypeRef*)&title) == kAXErrorSuccess) { + CFRelease(windows); + return osx::util::StringFromCFString(title, result); + } + } + + CFRelease(windows); + + return false; +} + +static bool GetWindowTitle(unsigned int wid, pid_t pid, std::string& result) { + /* private internal OS X functions */ + if ((CGSDefaultConnectionForThread && CGSCopyWindowProperty) || GetCoreGraphicsPrivateSymbols()) { + CFStringRef title = nullptr; + + CGSCopyWindowProperty(CGSDefaultConnectionForThread(), wid, CFSTR("kCGSWindowTitle"), &title); + if (title && CFStringGetLength(title) && osx::util::StringFromCFString(title, result)) + return true; + } + + /* don't attempt to use accessibility if we aren't trusted */ + return AXIsProcessTrusted() ? GetWindowTitleAccessibility(wid, pid, result) : false; +} + +static bool GetProcessBundleIdentifier(pid_t pid, std::string& result) { + /* The Bundle ID is essentially OS X's solution to Windows' + * "class name"; theoretically, it should be different for + * each program, although it requires an app bundle. + */ + const id app = cls_send(objc_getClass("NSRunningApplication"), sel_getUid("runningApplicationWithProcessIdentifier:"), pid); + if (!app) + return false; + + CFStringRef bundle_id = reinterpret_cast<CFStringRef>(obj_send(app, sel_getUid("bundleIdentifier"))); + if (!bundle_id) + return false; + + result = osx::util::StringFromCFString(bundle_id, result); + return true; +} + bool EnumerateWindows(window_proc_t window_proc) { if (!window_proc) return false; - const CFArrayRef windows = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID); + const CFArrayRef windows = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); if (!windows) return false; @@ -102,13 +199,12 @@ { CFDictionaryGetValue(window, CFSTR("kCGWindowNumber"), win.id); - if (!GetProcessBundleIdentifier(proc.pid, win.class_name)) { + if (!GetProcessBundleIdentifier(proc.pid, win.class_name)) // Fallback to the Quartz window name, which is unlikely to be filled, but it // *could* be. CFDictionaryGetValue(window, CFSTR("kCGWindowName"), win.class_name); - } - GetWindowTitle(win.id, win.text); + GetWindowTitle(win.id, proc.pid, win.text); } if (!window_proc(proc, win)) {