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 |