Mercurial > minori
view dep/animone/src/win/quartz.cc @ 367:8d45d892be88 default tip
*: instead of pugixml, use Qt XML features
this means we have one extra Qt dependency though...
author | Paper <paper@tflc.us> |
---|---|
date | Sun, 17 Nov 2024 22:55:47 -0500 |
parents | adb79bdde329 |
children |
line wrap: on
line source
/* * win/quartz.cc: support for macOS (the Quartz Compositor) * * This file does not require an Objective-C++ compiler, * but it *does* require an Objective-C runtime. */ #include "animone/win/quartz.h" #include "animone/fd.h" #include "animone.h" #include <objc/message.h> #include <objc/runtime.h> #include <ApplicationServices/ApplicationServices.h> #include <CoreFoundation/CoreFoundation.h> #include <CoreGraphics/CoreGraphics.h> namespace animone::internal::quartz { template<typename T> struct CFDeconstructor { using pointer = T; void operator()(pointer t) const { ::CFRelease(t); }; }; template<typename T> using CFPtr = std::unique_ptr<T, CFDeconstructor<T>>; #if __LP64__ typedef long NSInteger; #else typedef int NSInteger; #endif typedef int CGSConnection; typedef CGSConnection (*CGSDefaultConnectionForThreadSpec)(void); typedef CGError (*CGSCopyWindowPropertySpec)(const CGSConnection, NSInteger, CFStringRef, CFStringRef*); static CGSDefaultConnectionForThreadSpec CGSDefaultConnectionForThread = nullptr; static CGSCopyWindowPropertySpec CGSCopyWindowProperty = nullptr; static const CFStringRef kCoreGraphicsBundleID = CFSTR("com.apple.CoreGraphics"); /* Objective-C */ typedef id (*object_message_send)(id, SEL, ...); typedef id (*class_message_send)(Class, SEL, ...); static const object_message_send obj_send = reinterpret_cast<object_message_send>(objc_msgSend); static const class_message_send cls_send = reinterpret_cast<class_message_send>(objc_msgSend); static bool GetCoreGraphicsPrivateSymbols() { CFBundleRef core_graphics_bundle = CFBundleGetBundleWithIdentifier(kCoreGraphicsBundleID); if (!core_graphics_bundle) return false; CGSDefaultConnectionForThread = (CGSDefaultConnectionForThreadSpec)CFBundleGetFunctionPointerForName( core_graphics_bundle, CFSTR("CGSDefaultConnectionForThread")); if (!CGSDefaultConnectionForThread) return false; CGSCopyWindowProperty = (CGSCopyWindowPropertySpec)CFBundleGetFunctionPointerForName( core_graphics_bundle, CFSTR("CGSCopyWindowProperty")); if (!CGSCopyWindowProperty) return false; return true; } template<typename T> static bool GetCFNumber(CFNumberRef num, T& result) { if (!num) return false; int64_t res; if (!CFNumberGetValue(num, static_cast<CFNumberType>(4), &res)) return false; result = static_cast<T>(res); return true; } static bool StringFromCFString(CFStringRef string, std::string& result) { if (!string) return false; result.resize(CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8) + 1); if (!CFStringGetCString(string, &result.front(), result.length(), kCFStringEncodingUTF8)) return false; return true; } template<typename T> static bool CFDictionaryGetValue(CFDictionaryRef thedict, CFStringRef key, T& out) { CFTypeRef data = nullptr; if (!CFDictionaryGetValueIfPresent(thedict, key, reinterpret_cast<const void**>(&data)) || !data) return false; if constexpr (std::is_arithmetic<T>::value) GetCFNumber(reinterpret_cast<CFNumberRef>(data), out); else if constexpr (std::is_same<T, std::string>::value) StringFromCFString(reinterpret_cast<CFStringRef>(data), out); else return false; return true; } static bool GetWindowTitleAccessibility(unsigned int wid, pid_t pid, std::string& result) { CGRect bounds = {0}; { const CGWindowID wids[1] = {wid}; CFPtr<CFArrayRef> arr(CFArrayCreate(kCFAllocatorDefault, (CFTypeRef*)wids, 1, NULL)); CFPtr<CFArrayRef> dicts(CGWindowListCreateDescriptionFromArray(arr.get())); if (!dicts.get() || CFArrayGetCount(dicts.get()) < 1) return false; CFDictionaryRef dict = reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(dicts.get(), 0)); if (!dict) return false; CFDictionaryRef bounds_dict = nullptr; if (!CFDictionaryGetValueIfPresent(dict, kCGWindowBounds, reinterpret_cast<CFTypeRef*>(&bounds_dict)) || !bounds_dict) return false; if (!CGRectMakeWithDictionaryRepresentation(bounds_dict, &bounds)) return false; } /* now we can actually do stuff */ AXUIElementRef axapp = AXUIElementCreateApplication(pid); CFPtr<CFArrayRef> windows; { CFArrayRef ref; if ((AXUIElementCopyAttributeValue(axapp, kAXWindowsAttribute, reinterpret_cast<CFTypeRef*>(&ref)) != kAXErrorSuccess) || !windows) return false; windows.reset(ref); } const CFIndex count = CFArrayGetCount(windows.get()); for (CFIndex i = 0; i < count; i++) { const AXUIElementRef window = reinterpret_cast<AXUIElementRef>(CFArrayGetValueAtIndex(windows.get(), i)); /* does this leak memory? probably. */ AXValueRef val; if (AXUIElementCopyAttributeValue(window, kAXPositionAttribute, reinterpret_cast<CFTypeRef*>(&val)) == kAXErrorSuccess) { CGPoint point; if (!AXValueGetValue(val, static_cast<AXValueType>(kAXValueCGPointType), reinterpret_cast<void*>(&point)) || (point.x != bounds.origin.x || point.y != bounds.origin.y)) { CFRelease(val); continue; } } else { CFRelease(val); continue; } CFRelease(val); if (AXUIElementCopyAttributeValue(window, kAXSizeAttribute, reinterpret_cast<CFTypeRef*>(&val)) == kAXErrorSuccess) { CGSize size; if (!AXValueGetValue(val, static_cast<AXValueType>(kAXValueCGSizeType), reinterpret_cast<void*>(&size)) || (size.width != bounds.size.width || size.height != bounds.size.height)) { CFRelease(val); continue; } } else { CFRelease(val); continue; } CFRelease(val); CFStringRef title; if (AXUIElementCopyAttributeValue(window, kAXTitleAttribute, reinterpret_cast<CFTypeRef*>(&title)) == kAXErrorSuccess) { bool success = StringFromCFString(title, result); CFRelease(title); return success; } } return false; } static bool GetWindowTitle(unsigned int wid, pid_t pid, std::string& result) { /* try using CoreGraphics (only usable on old versions of OS X) */ if ((CGSDefaultConnectionForThread && CGSCopyWindowProperty) || GetCoreGraphicsPrivateSymbols()) { CFPtr<CFStringRef> title; { CFStringRef t = nullptr; CGSCopyWindowProperty(CGSDefaultConnectionForThread(), wid, CFSTR("kCGSWindowTitle"), &t); title.reset(t); } if (title && CFStringGetLength(title.get()) && StringFromCFString(title.get(), result)) return true; } /* then try linking to a window using the accessibility API */ return AXIsProcessTrusted() ? GetWindowTitleAccessibility(wid, pid, result) : false; } static bool GetProcessBundleIdentifierNew(pid_t pid, std::string& result) { /* 10.6 and higher */ const id app = cls_send((Class)objc_getClass("NSRunningApplication"), sel_getUid("runningApplicationWithProcessIdentifier:"), pid); if (!app) return false; CFStringRef bundle_id = reinterpret_cast<CFStringRef>(obj_send(app, sel_getUid("bundleIdentifier"))); if (!bundle_id) return false; return StringFromCFString(bundle_id, result); } static bool GetProcessBundleIdentifierOld(pid_t pid, std::string& result) { /* OS X 10.2; deprecated in 10.9 */ ProcessSerialNumber psn; if (GetProcessForPID(pid, &psn)) return false; CFPtr<CFDictionaryRef> info(ProcessInformationCopyDictionary(&psn, kProcessDictionaryIncludeAllInformationMask)); if (!info) return false; CFStringRef value = reinterpret_cast<CFStringRef>(CFDictionaryGetValue(info.get(), CFSTR("CFBundleIdentifier"))); if (!value) return false; return StringFromCFString(value, result); } static bool GetProcessBundleIdentifier(pid_t pid, std::string& result) { /* The Bundle ID is essentially OS X's solution to Windows' * "class name"; theoretically, it should be different for * each program, although it requires an app bundle. */ if (GetProcessBundleIdentifierNew(pid, result)) return true; return GetProcessBundleIdentifierOld(pid, result); } bool EnumerateWindows(window_proc_t window_proc) { if (!window_proc) return false; const CFArrayRef windows = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); if (!windows) return false; const CFIndex count = CFArrayGetCount(windows); for (CFIndex i = 0; i < count; i++) { CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(windows, i)); if (!window) continue; Process proc; proc.platform = ExecutablePlatform::Xnu; CFDictionaryGetValue(window, CFSTR("kCGWindowOwnerPID"), proc.pid); if (!CFDictionaryGetValue(window, CFSTR("kCGWindowOwnerName"), proc.comm)) fd::GetProcessName(proc.pid, proc.comm); Window win; win.platform = WindowPlatform::Quartz; CFDictionaryGetValue(window, CFSTR("kCGWindowNumber"), win.id.quartz); GetProcessBundleIdentifier(proc.pid, win.class_name); GetWindowTitle(win.id, proc.pid, win.text); if (!window_proc(proc, win)) { CFRelease(windows); return false; } } CFRelease(windows); return true; } } // namespace animone::internal::quartz