Mercurial > minori
annotate 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 |
| rev | line source |
|---|---|
|
191
0fc126d52de4
animia: multiple stylistic choices
Paper <mrpapersonic@gmail.com>
parents:
190
diff
changeset
|
1 /* |
|
0fc126d52de4
animia: multiple stylistic choices
Paper <mrpapersonic@gmail.com>
parents:
190
diff
changeset
|
2 * win/quartz.cc: support for macOS (the Quartz Compositor) |
|
0fc126d52de4
animia: multiple stylistic choices
Paper <mrpapersonic@gmail.com>
parents:
190
diff
changeset
|
3 * |
|
0fc126d52de4
animia: multiple stylistic choices
Paper <mrpapersonic@gmail.com>
parents:
190
diff
changeset
|
4 * This file does not require an Objective-C++ compiler, |
| 237 | 5 * but it *does* require an Objective-C runtime. |
|
191
0fc126d52de4
animia: multiple stylistic choices
Paper <mrpapersonic@gmail.com>
parents:
190
diff
changeset
|
6 */ |
| 189 | 7 #include "animia/win/quartz.h" |
|
190
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
8 #include "animia/util/osx.h" |
| 189 | 9 #include "animia.h" |
| 10 | |
| 11 #include <objc/runtime.h> | |
| 12 #include <objc/message.h> | |
| 13 | |
| 14 #include <CoreFoundation/CoreFoundation.h> | |
| 15 #include <CoreGraphics/CoreGraphics.h> | |
| 237 | 16 #include <ApplicationServices/ApplicationServices.h> |
|
190
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
17 |
| 189 | 18 namespace animia::internal::quartz { |
| 19 | |
| 237 | 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 */ | |
|
190
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
40 typedef id (*object_message_send)(id, SEL, ...); |
|
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
41 typedef id (*class_message_send)(Class, SEL, ...); |
|
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
42 |
|
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
43 static const object_message_send obj_send = reinterpret_cast<object_message_send>(objc_msgSend); |
|
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
44 static const class_message_send cls_send = reinterpret_cast<class_message_send>(objc_msgSend); |
|
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
45 |
| 237 | 46 static bool GetCoreGraphicsPrivateSymbols() { |
| 47 CFBundleRef core_graphics_bundle = CFBundleGetBundleWithIdentifier(kCoreGraphicsBundleID); | |
| 48 if (!core_graphics_bundle) | |
|
190
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
49 return false; |
|
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
50 |
| 237 | 51 CGSDefaultConnectionForThread = (CGSDefaultConnectionForThreadSpec)CFBundleGetFunctionPointerForName(core_graphics_bundle, CFSTR("CGSDefaultConnectionForThread")); |
| 52 if (!CGSDefaultConnectionForThread) | |
|
191
0fc126d52de4
animia: multiple stylistic choices
Paper <mrpapersonic@gmail.com>
parents:
190
diff
changeset
|
53 return false; |
|
0fc126d52de4
animia: multiple stylistic choices
Paper <mrpapersonic@gmail.com>
parents:
190
diff
changeset
|
54 |
| 237 | 55 CGSCopyWindowProperty = (CGSCopyWindowPropertySpec)CFBundleGetFunctionPointerForName(core_graphics_bundle, CFSTR("CGSCopyWindowProperty")); |
| 56 if (!CGSCopyWindowProperty) | |
|
191
0fc126d52de4
animia: multiple stylistic choices
Paper <mrpapersonic@gmail.com>
parents:
190
diff
changeset
|
57 return false; |
|
0fc126d52de4
animia: multiple stylistic choices
Paper <mrpapersonic@gmail.com>
parents:
190
diff
changeset
|
58 |
| 237 | 59 return true; |
|
191
0fc126d52de4
animia: multiple stylistic choices
Paper <mrpapersonic@gmail.com>
parents:
190
diff
changeset
|
60 } |
|
0fc126d52de4
animia: multiple stylistic choices
Paper <mrpapersonic@gmail.com>
parents:
190
diff
changeset
|
61 |
| 197 | 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 | |
| 237 | 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; | |
| 158 } | |
| 159 | |
| 160 static bool GetProcessBundleIdentifier(pid_t pid, std::string& result) { | |
| 161 /* The Bundle ID is essentially OS X's solution to Windows' | |
| 162 * "class name"; theoretically, it should be different for | |
| 163 * each program, although it requires an app bundle. | |
| 164 */ | |
| 165 const id app = cls_send(objc_getClass("NSRunningApplication"), sel_getUid("runningApplicationWithProcessIdentifier:"), pid); | |
| 166 if (!app) | |
| 167 return false; | |
| 168 | |
| 169 CFStringRef bundle_id = reinterpret_cast<CFStringRef>(obj_send(app, sel_getUid("bundleIdentifier"))); | |
| 170 if (!bundle_id) | |
| 171 return false; | |
| 172 | |
| 173 result = osx::util::StringFromCFString(bundle_id, result); | |
| 174 return true; | |
| 175 } | |
| 176 | |
|
198
bc1ae1810855
dep/animia: switch from using classes to global functions
Paper <mrpapersonic@gmail.com>
parents:
197
diff
changeset
|
177 bool EnumerateWindows(window_proc_t window_proc) { |
| 189 | 178 if (!window_proc) |
| 179 return false; | |
| 180 | |
| 237 | 181 const CFArrayRef windows = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); |
| 189 | 182 if (!windows) |
| 183 return false; | |
| 184 | |
|
190
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
185 const CFIndex count = CFArrayGetCount(windows); |
|
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
186 for (CFIndex i = 0; i < count; i++) { |
|
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
187 CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(windows, i)); |
| 189 | 188 if (!window) |
| 189 continue; | |
| 190 | |
| 191 Process proc; | |
| 192 { | |
| 197 | 193 CFDictionaryGetValue(window, CFSTR("kCGWindowOwnerPID"), proc.pid); |
| 194 if (!CFDictionaryGetValue(window, CFSTR("kCGWindowOwnerName"), proc.name)) | |
| 189 | 195 osx::util::GetProcessName(proc.pid, proc.name); |
| 196 } | |
| 197 | |
| 198 Window win; | |
| 199 { | |
| 197 | 200 CFDictionaryGetValue(window, CFSTR("kCGWindowNumber"), win.id); |
| 201 | |
| 237 | 202 if (!GetProcessBundleIdentifier(proc.pid, win.class_name)) |
|
191
0fc126d52de4
animia: multiple stylistic choices
Paper <mrpapersonic@gmail.com>
parents:
190
diff
changeset
|
203 // Fallback to the Quartz window name, which is unlikely to be filled, but it |
|
0fc126d52de4
animia: multiple stylistic choices
Paper <mrpapersonic@gmail.com>
parents:
190
diff
changeset
|
204 // *could* be. |
| 197 | 205 CFDictionaryGetValue(window, CFSTR("kCGWindowName"), win.class_name); |
| 206 | |
| 237 | 207 GetWindowTitle(win.id, proc.pid, win.text); |
| 189 | 208 } |
| 209 | |
|
190
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
210 if (!window_proc(proc, win)) { |
|
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
211 CFRelease(windows); |
| 189 | 212 return false; |
|
190
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
213 } |
| 189 | 214 } |
| 215 | |
|
190
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
216 CFRelease(windows); |
|
2d5823df870f
dep/animia: finalize de-objc-ifying quartz
Paper <mrpapersonic@gmail.com>
parents:
189
diff
changeset
|
217 |
| 189 | 218 return true; |
| 219 } | |
| 220 | |
| 221 } // namespace animia::win::detail |
