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; |