Mercurial > libanimone
comparison src/win/quartz.cc @ 4:cce3a81b03bf
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 | a76fa32bdc92 |
| children | f80b3c3ec7f0 |
comparison
equal
deleted
inserted
replaced
| 2:97ea6a3e1954 | 4:cce3a81b03bf |
|---|---|
| 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 |
