comparison dep/animone/src/win/quartz.cc @ 268:382b50754fe4

dep/animone: make osx code a bit less hacky it would be nice if macos actually provided a real API for getting window titles (outside of the accessibility api). the accessibility API is a real mess to work with; the user has to give permission to access it under newer versions.
author Paper <paper@paper.us.eu.org>
date Fri, 12 Apr 2024 05:21:45 -0400
parents 862d0d8619f6
children f01b6e9c8fa2
comparison
equal deleted inserted replaced
266:1a6a5d3a94cd 268:382b50754fe4
15 #include <CoreFoundation/CoreFoundation.h> 15 #include <CoreFoundation/CoreFoundation.h>
16 #include <CoreGraphics/CoreGraphics.h> 16 #include <CoreGraphics/CoreGraphics.h>
17 17
18 namespace animone::internal::quartz { 18 namespace animone::internal::quartz {
19 19
20 template<typename T>
21 using CFPtr = std::unique_ptr<T, CFDecontructor<T>>;
22
20 #if __LP64__ 23 #if __LP64__
21 typedef long NSInteger; 24 typedef long NSInteger;
22 #else 25 #else
23 typedef int NSInteger; 26 typedef int NSInteger;
24 #endif 27 #endif
56 59
57 return true; 60 return true;
58 } 61 }
59 62
60 template<typename T> 63 template<typename T>
64 static bool GetCFNumber(CFNumberRef num, T& result) {
65 if (!num)
66 return false;
67
68 int64_t res;
69 if (!CFNumberGetValue(num, static_cast<CFNumberType>(4), &res))
70 return false;
71
72 result = static_cast<T>(res);
73 return true;
74 }
75
76 static bool StringFromCFString(CFStringRef string, std::string& result) {
77 if (!string)
78 return false;
79
80 result.resize(CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8) + 1);
81 if (!CFStringGetCString(string, &result.front(), result.length(), kCFStringEncodingUTF8))
82 return false;
83
84 return true;
85 }
86
87 template<typename T>
61 static bool CFDictionaryGetValue(CFDictionaryRef thedict, CFStringRef key, T& out) { 88 static bool CFDictionaryGetValue(CFDictionaryRef thedict, CFStringRef key, T& out) {
62 CFTypeRef data = nullptr; 89 CFTypeRef data = nullptr;
63 if (!CFDictionaryGetValueIfPresent(thedict, key, reinterpret_cast<const void**>(&data)) || !data) 90 if (!CFDictionaryGetValueIfPresent(thedict, key, reinterpret_cast<const void**>(&data)) || !data)
64 return false; 91 return false;
65 92
66 if constexpr (std::is_arithmetic<T>::value) 93 if constexpr (std::is_arithmetic<T>::value)
67 osx::util::GetCFNumber(reinterpret_cast<CFNumberRef>(data), out); 94 GetCFNumber(reinterpret_cast<CFNumberRef>(data), out);
68 else if constexpr (std::is_same<T, std::string>::value) 95 else if constexpr (std::is_same<T, std::string>::value)
69 osx::util::StringFromCFString(reinterpret_cast<CFStringRef>(data), out); 96 StringFromCFString(reinterpret_cast<CFStringRef>(data), out);
70 else 97 else
71 return false; 98 return false;
72 99
73 return true; 100 return true;
74 } 101 }
102
103 template<typename T>
104 struct CFDeconstructor {
105 using pointer = T;
106 void operator()(pointer t) const { ::CFRelease(t); };
107 };
75 108
76 static bool GetWindowTitleAccessibility(unsigned int wid, pid_t pid, std::string& result) { 109 static bool GetWindowTitleAccessibility(unsigned int wid, pid_t pid, std::string& result) {
77 CGRect bounds = {0}; 110 CGRect bounds = {0};
78 { 111 {
79 const CGWindowID wids[1] = {wid}; 112 const CGWindowID wids[1] = {wid};
117 AXValueRef val; 150 AXValueRef val;
118 if (AXUIElementCopyAttributeValue(window, kAXPositionAttribute, reinterpret_cast<CFTypeRef*>(&val)) == 151 if (AXUIElementCopyAttributeValue(window, kAXPositionAttribute, reinterpret_cast<CFTypeRef*>(&val)) ==
119 kAXErrorSuccess) { 152 kAXErrorSuccess) {
120 CGPoint point; 153 CGPoint point;
121 if (!AXValueGetValue(val, kAXValueTypeCGPoint, reinterpret_cast<CFTypeRef>(&point)) || 154 if (!AXValueGetValue(val, kAXValueTypeCGPoint, reinterpret_cast<CFTypeRef>(&point)) ||
122 (point.x != bounds.origin.x || point.y != bounds.origin.y)) 155 (point.x != bounds.origin.x || point.y != bounds.origin.y)) {
156 CFRelease(val);
123 continue; 157 continue;
124 } else 158 }
159 } else {
160 CFRelease(val);
125 continue; 161 continue;
162 }
163
164 CFRelease(val);
126 165
127 if (AXUIElementCopyAttributeValue(window, kAXSizeAttribute, reinterpret_cast<CFTypeRef*>(&val)) == 166 if (AXUIElementCopyAttributeValue(window, kAXSizeAttribute, reinterpret_cast<CFTypeRef*>(&val)) ==
128 kAXErrorSuccess) { 167 kAXErrorSuccess) {
129 CGSize size; 168 CGSize size;
130 if (!AXValueGetValue(val, kAXValueTypeCGSize, reinterpret_cast<CFTypeRef>(&size)) || 169 if (!AXValueGetValue(val, kAXValueTypeCGSize, reinterpret_cast<CFTypeRef>(&size)) ||
131 (size.width != bounds.size.width || size.height != bounds.size.height)) 170 (size.width != bounds.size.width || size.height != bounds.size.height)) {
171 CFRelease(val);
132 continue; 172 continue;
133 } else 173 }
174 } else {
175 CFRelease(val);
134 continue; 176 continue;
177 }
178
179 CFRelease(val);
135 180
136 CFStringRef title; 181 CFStringRef title;
137 if (AXUIElementCopyAttributeValue(window, kAXTitleAttribute, reinterpret_cast<CFTypeRef*>(&title)) == 182 if (AXUIElementCopyAttributeValue(window, kAXTitleAttribute, reinterpret_cast<CFTypeRef*>(&title)) ==
138 kAXErrorSuccess) 183 kAXErrorSuccess) {
139 return osx::util::StringFromCFString(title, result); 184 bool success = StringFromCFString(title, result);
185 CFRelease(title);
186 return success;
187 }
140 } 188 }
141 189
142 return false; 190 return false;
143 } 191 }
144 192
150 CFStringRef t = nullptr; 198 CFStringRef t = nullptr;
151 CGSCopyWindowProperty(CGSDefaultConnectionForThread(), wid, CFSTR("kCGSWindowTitle"), &t); 199 CGSCopyWindowProperty(CGSDefaultConnectionForThread(), wid, CFSTR("kCGSWindowTitle"), &t);
152 title.reset(t); 200 title.reset(t);
153 } 201 }
154 202
155 if (title && CFStringGetLength(title.get()) && osx::util::StringFromCFString(title.get(), result)) 203 if (title && CFStringGetLength(title.get()) && StringFromCFString(title.get(), result))
156 return true; 204 return true;
157 } 205 }
158 206
159 /* then try linking to a window using the accessibility API */ 207 /* then try linking to a window using the accessibility API */
160 return AXIsProcessTrusted() ? GetWindowTitleAccessibility(wid, pid, result) : false; 208 return AXIsProcessTrusted() ? GetWindowTitleAccessibility(wid, pid, result) : false;
169 217
170 CFStringRef bundle_id = reinterpret_cast<CFStringRef>(obj_send(app, sel_getUid("bundleIdentifier"))); 218 CFStringRef bundle_id = reinterpret_cast<CFStringRef>(obj_send(app, sel_getUid("bundleIdentifier")));
171 if (!bundle_id) 219 if (!bundle_id)
172 return false; 220 return false;
173 221
174 result = osx::util::StringFromCFString(bundle_id, result); 222 return StringFromCFString(bundle_id, result);
175 return true;
176 } 223 }
177 224
178 static bool GetProcessBundleIdentifierOld(pid_t pid, std::string& result) { 225 static bool GetProcessBundleIdentifierOld(pid_t pid, std::string& result) {
179 /* OS X 10.2; deprecated in 10.9 */ 226 /* OS X 10.2; deprecated in 10.9 */
180 ProcessSerialNumber psn; 227 ProcessSerialNumber psn;
187 234
188 CFStringRef value = reinterpret_cast<CFStringRef>(CFDictionaryGetValue(dict, CFSTR("CFBundleIdentifier"))); 235 CFStringRef value = reinterpret_cast<CFStringRef>(CFDictionaryGetValue(dict, CFSTR("CFBundleIdentifier")));
189 if (!value) 236 if (!value)
190 return false; 237 return false;
191 238
192 result = osx::util::StringFromCFString(value, result); 239 return StringFromCFString(value, result);
193 return true;
194 } 240 }
195 241
196 static bool GetProcessBundleIdentifier(pid_t pid, std::string& result) { 242 static bool GetProcessBundleIdentifier(pid_t pid, std::string& result) {
197 /* The Bundle ID is essentially OS X's solution to Windows' 243 /* The Bundle ID is essentially OS X's solution to Windows'
198 * "class name"; theoretically, it should be different for 244 * "class name"; theoretically, it should be different for