Mercurial > minori
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 236:4d461ef7d424 | 237:a7d0d543b334 |
|---|---|
| 1 /* | 1 /* |
| 2 * win/quartz.cc: support for macOS (the Quartz Compositor) | 2 * win/quartz.cc: support for macOS (the Quartz Compositor) |
| 3 * | 3 * |
| 4 * This file does not require an Objective-C++ compiler, | 4 * This file does not require an Objective-C++ compiler, |
| 5 * but it *does* require an Objective-C runtime and linking | 5 * but it *does* require an Objective-C runtime. |
| 6 * with AppKit in order to receive proper window titles. | |
| 7 */ | 6 */ |
| 8 #include "animia/win/quartz.h" | 7 #include "animia/win/quartz.h" |
| 9 #include "animia/util/osx.h" | 8 #include "animia/util/osx.h" |
| 10 #include "animia.h" | 9 #include "animia.h" |
| 11 | 10 |
| 12 #include <objc/runtime.h> | 11 #include <objc/runtime.h> |
| 13 #include <objc/message.h> | 12 #include <objc/message.h> |
| 14 | 13 |
| 15 #include <CoreFoundation/CoreFoundation.h> | 14 #include <CoreFoundation/CoreFoundation.h> |
| 16 #include <CoreGraphics/CoreGraphics.h> | 15 #include <CoreGraphics/CoreGraphics.h> |
| 16 #include <ApplicationServices/ApplicationServices.h> | |
| 17 | 17 |
| 18 namespace animia::internal::quartz { | 18 namespace animia::internal::quartz { |
| 19 | 19 |
| 20 /* all of these LaunchServices things use *internal functions* that are subject | |
| 21 * to change. Granted, it's not very likely that these will change very much | |
| 22 * because I'm fairly sure Apple uses them lots in their own internal code. | |
| 23 */ | |
| 24 #if __LP64__ | |
| 25 typedef long NSInteger; | |
| 26 #else | |
| 27 typedef int NSInteger; | |
| 28 #endif | |
| 29 typedef int CGSConnection; | |
| 30 | |
| 31 typedef CGSConnection (*CGSDefaultConnectionForThreadSpec)(void); | |
| 32 typedef CGError (*CGSCopyWindowPropertySpec)(const CGSConnection, NSInteger, CFStringRef, CFStringRef*); | |
| 33 | |
| 34 static CGSDefaultConnectionForThreadSpec CGSDefaultConnectionForThread = nullptr; | |
| 35 static CGSCopyWindowPropertySpec CGSCopyWindowProperty = nullptr; | |
| 36 | |
| 37 static const CFStringRef kCoreGraphicsBundleID = CFSTR("com.apple.CoreGraphics"); | |
| 38 | |
| 39 /* Objective-C */ | |
| 20 typedef id (*object_message_send)(id, SEL, ...); | 40 typedef id (*object_message_send)(id, SEL, ...); |
| 21 typedef id (*class_message_send)(Class, SEL, ...); | 41 typedef id (*class_message_send)(Class, SEL, ...); |
| 22 | 42 |
| 23 static const object_message_send obj_send = reinterpret_cast<object_message_send>(objc_msgSend); | 43 static const object_message_send obj_send = reinterpret_cast<object_message_send>(objc_msgSend); |
| 24 static const class_message_send cls_send = reinterpret_cast<class_message_send>(objc_msgSend); | 44 static const class_message_send cls_send = reinterpret_cast<class_message_send>(objc_msgSend); |
| 25 | 45 |
| 26 static bool GetWindowTitle(unsigned int wid, std::string& result) { | 46 static bool GetCoreGraphicsPrivateSymbols() { |
| 27 // NSApplication* app = [NSApplication sharedApplication]; | 47 CFBundleRef core_graphics_bundle = CFBundleGetBundleWithIdentifier(kCoreGraphicsBundleID); |
| 28 const id app = cls_send(objc_getClass("NSApplication"), sel_getUid("sharedApplication")); | 48 if (!core_graphics_bundle) |
| 29 | 49 return false; |
| 30 // NSWindow* window = [app windowWithWindowNumber: wid]; | 50 |
| 31 const id window = obj_send(app, sel_getUid("windowWithWindowNumber:"), wid); | 51 CGSDefaultConnectionForThread = (CGSDefaultConnectionForThreadSpec)CFBundleGetFunctionPointerForName(core_graphics_bundle, CFSTR("CGSDefaultConnectionForThread")); |
| 32 if (!window) | 52 if (!CGSDefaultConnectionForThread) |
| 33 return false; | 53 return false; |
| 34 | 54 |
| 35 // NSString* title = [window title]; | 55 CGSCopyWindowProperty = (CGSCopyWindowPropertySpec)CFBundleGetFunctionPointerForName(core_graphics_bundle, CFSTR("CGSCopyWindowProperty")); |
| 36 const CFStringRef title = reinterpret_cast<CFStringRef>(obj_send(window, sel_getUid("title"))); | 56 if (!CGSCopyWindowProperty) |
| 37 if (!title) | 57 return false; |
| 38 return false; | 58 |
| 39 | 59 return true; |
| 40 // return [title UTF8String]; | 60 } |
| 41 return osx::util::StringFromCFString(title, result); | 61 |
| 62 template<typename T> | |
| 63 static bool CFDictionaryGetValue(CFDictionaryRef thedict, CFStringRef key, T& out) { | |
| 64 CFTypeRef data = nullptr; | |
| 65 if (!CFDictionaryGetValueIfPresent(thedict, key, reinterpret_cast<const void**>(&data)) || !data) | |
| 66 return false; | |
| 67 | |
| 68 if constexpr (std::is_arithmetic<T>::value) | |
| 69 osx::util::GetCFNumber(reinterpret_cast<CFNumberRef>(data), out); | |
| 70 else if constexpr (std::is_same<T, std::string>::value) | |
| 71 osx::util::StringFromCFString(reinterpret_cast<CFStringRef>(data), out); | |
| 72 else | |
| 73 return false; | |
| 74 | |
| 75 return true; | |
| 76 } | |
| 77 | |
| 78 static bool GetWindowTitleAccessibility(unsigned int wid, pid_t pid, std::string& result) { | |
| 79 CGRect bounds = {0}; | |
| 80 { | |
| 81 const CGWindowID wids[1] = {wid}; | |
| 82 CFArrayRef arr = CFArrayCreate(kCFAllocatorDefault, (CFTypeRef*)wids, 1, NULL); | |
| 83 | |
| 84 CFArrayRef dicts = CGWindowListCreateDescriptionFromArray(arr); | |
| 85 | |
| 86 CFRelease(arr); | |
| 87 | |
| 88 if (!dicts || CFArrayGetCount(dicts) < 1) | |
| 89 return false; | |
| 90 | |
| 91 CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(dicts, 0); | |
| 92 if (!dict) { | |
| 93 CFRelease(dicts); | |
| 94 return false; | |
| 95 } | |
| 96 | |
| 97 CFDictionaryRef bounds_dict = nullptr; | |
| 98 if (!CFDictionaryGetValueIfPresent(dict, kCGWindowBounds, reinterpret_cast<CFTypeRef*>(&bounds_dict)) || !bounds_dict) { | |
| 99 CFRelease(dicts); | |
| 100 return false; | |
| 101 } | |
| 102 | |
| 103 if (!CGRectMakeWithDictionaryRepresentation(bounds_dict, &bounds)) { | |
| 104 CFRelease(dicts); | |
| 105 return false; | |
| 106 } | |
| 107 | |
| 108 CFRelease(dicts); | |
| 109 } | |
| 110 | |
| 111 /* now we can actually do stuff */ | |
| 112 AXUIElementRef axapp = AXUIElementCreateApplication(pid); | |
| 113 CFArrayRef windows; | |
| 114 if ((AXUIElementCopyAttributeValue(axapp, kAXWindowsAttribute, (CFTypeRef*)&windows) != kAXErrorSuccess) || !windows) | |
| 115 return false; | |
| 116 | |
| 117 const CFIndex count = CFArrayGetCount(windows); | |
| 118 for (CFIndex i = 0; i < count; i++) { | |
| 119 const AXUIElementRef window = (AXUIElementRef)CFArrayGetValueAtIndex(windows, i); | |
| 120 | |
| 121 AXValueRef val; | |
| 122 if (AXUIElementCopyAttributeValue(window, kAXPositionAttribute, (CFTypeRef*)&val) == kAXErrorSuccess) { | |
| 123 CGPoint point; | |
| 124 if (!AXValueGetValue(val, kAXValueTypeCGPoint, (void*)&point) || (point.x != bounds.origin.x || point.y != bounds.origin.y)) | |
| 125 continue; | |
| 126 } else continue; | |
| 127 | |
| 128 if (AXUIElementCopyAttributeValue(window, kAXSizeAttribute, (CFTypeRef*)&val) == kAXErrorSuccess) { | |
| 129 CGSize size; | |
| 130 if (!AXValueGetValue(val, kAXValueTypeCGSize, (void*)&size) || (size.width != bounds.size.width || size.height != bounds.size.height)) | |
| 131 continue; | |
| 132 } else continue; | |
| 133 | |
| 134 CFStringRef title; | |
| 135 if (AXUIElementCopyAttributeValue(window, kAXTitleAttribute, (CFTypeRef*)&title) == kAXErrorSuccess) { | |
| 136 CFRelease(windows); | |
| 137 return osx::util::StringFromCFString(title, result); | |
| 138 } | |
| 139 } | |
| 140 | |
| 141 CFRelease(windows); | |
| 142 | |
| 143 return false; | |
| 144 } | |
| 145 | |
| 146 static bool GetWindowTitle(unsigned int wid, pid_t pid, std::string& result) { | |
| 147 /* private internal OS X functions */ | |
| 148 if ((CGSDefaultConnectionForThread && CGSCopyWindowProperty) || GetCoreGraphicsPrivateSymbols()) { | |
| 149 CFStringRef title = nullptr; | |
| 150 | |
| 151 CGSCopyWindowProperty(CGSDefaultConnectionForThread(), wid, CFSTR("kCGSWindowTitle"), &title); | |
| 152 if (title && CFStringGetLength(title) && osx::util::StringFromCFString(title, result)) | |
| 153 return true; | |
| 154 } | |
| 155 | |
| 156 /* don't attempt to use accessibility if we aren't trusted */ | |
| 157 return AXIsProcessTrusted() ? GetWindowTitleAccessibility(wid, pid, result) : false; | |
| 42 } | 158 } |
| 43 | 159 |
| 44 static bool GetProcessBundleIdentifier(pid_t pid, std::string& result) { | 160 static bool GetProcessBundleIdentifier(pid_t pid, std::string& result) { |
| 45 /* The Bundle ID is essentially OS X's solution to Windows' | 161 /* The Bundle ID is essentially OS X's solution to Windows' |
| 46 * "class name"; theoretically, it should be different for | 162 * "class name"; theoretically, it should be different for |
| 47 * each program, although it requires an app bundle. | 163 * each program, although it requires an app bundle. |
| 48 */ | 164 */ |
| 49 | |
| 50 // NSRunningApplication* app = [NSRunningApplication runningApplicationWithProcessIdentifier: pid]; | |
| 51 const id app = cls_send(objc_getClass("NSRunningApplication"), sel_getUid("runningApplicationWithProcessIdentifier:"), pid); | 165 const id app = cls_send(objc_getClass("NSRunningApplication"), sel_getUid("runningApplicationWithProcessIdentifier:"), pid); |
| 52 if (!app) | 166 if (!app) |
| 53 return false; | 167 return false; |
| 54 | 168 |
| 55 // NSString* bundle_id = [app bundleIdentifier]; | 169 CFStringRef bundle_id = reinterpret_cast<CFStringRef>(obj_send(app, sel_getUid("bundleIdentifier"))); |
| 56 const CFStringRef bundle_id = reinterpret_cast<CFStringRef>(obj_send(app, sel_getUid("bundleIdentifier"))); | |
| 57 if (!bundle_id) | 170 if (!bundle_id) |
| 58 return false; | 171 return false; |
| 59 | 172 |
| 60 // return [bundle_id UTF8String]; | 173 result = osx::util::StringFromCFString(bundle_id, result); |
| 61 return osx::util::StringFromCFString(bundle_id, result); | |
| 62 } | |
| 63 | |
| 64 template<typename T> | |
| 65 static bool CFDictionaryGetValue(CFDictionaryRef thedict, CFStringRef key, T& out) { | |
| 66 CFTypeRef data = nullptr; | |
| 67 if (!CFDictionaryGetValueIfPresent(thedict, key, reinterpret_cast<const void**>(&data)) || !data) | |
| 68 return false; | |
| 69 | |
| 70 if constexpr (std::is_arithmetic<T>::value) | |
| 71 osx::util::GetCFNumber(reinterpret_cast<CFNumberRef>(data), out); | |
| 72 else if constexpr (std::is_same<T, std::string>::value) | |
| 73 osx::util::StringFromCFString(reinterpret_cast<CFStringRef>(data), out); | |
| 74 else | |
| 75 return false; | |
| 76 | |
| 77 return true; | 174 return true; |
| 78 } | 175 } |
| 79 | 176 |
| 80 bool EnumerateWindows(window_proc_t window_proc) { | 177 bool EnumerateWindows(window_proc_t window_proc) { |
| 81 if (!window_proc) | 178 if (!window_proc) |
| 82 return false; | 179 return false; |
| 83 | 180 |
| 84 const CFArrayRef windows = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID); | 181 const CFArrayRef windows = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); |
| 85 if (!windows) | 182 if (!windows) |
| 86 return false; | 183 return false; |
| 87 | 184 |
| 88 const CFIndex count = CFArrayGetCount(windows); | 185 const CFIndex count = CFArrayGetCount(windows); |
| 89 for (CFIndex i = 0; i < count; i++) { | 186 for (CFIndex i = 0; i < count; i++) { |
| 100 | 197 |
| 101 Window win; | 198 Window win; |
| 102 { | 199 { |
| 103 CFDictionaryGetValue(window, CFSTR("kCGWindowNumber"), win.id); | 200 CFDictionaryGetValue(window, CFSTR("kCGWindowNumber"), win.id); |
| 104 | 201 |
| 105 if (!GetProcessBundleIdentifier(proc.pid, win.class_name)) { | 202 if (!GetProcessBundleIdentifier(proc.pid, win.class_name)) |
| 106 // Fallback to the Quartz window name, which is unlikely to be filled, but it | 203 // Fallback to the Quartz window name, which is unlikely to be filled, but it |
| 107 // *could* be. | 204 // *could* be. |
| 108 CFDictionaryGetValue(window, CFSTR("kCGWindowName"), win.class_name); | 205 CFDictionaryGetValue(window, CFSTR("kCGWindowName"), win.class_name); |
| 109 } | 206 |
| 110 | 207 GetWindowTitle(win.id, proc.pid, win.text); |
| 111 GetWindowTitle(win.id, win.text); | |
| 112 } | 208 } |
| 113 | 209 |
| 114 if (!window_proc(proc, win)) { | 210 if (!window_proc(proc, win)) { |
| 115 CFRelease(windows); | 211 CFRelease(windows); |
| 116 return false; | 212 return false; |
