Mercurial > minori
diff dep/animone/src/win/quartz.cc @ 258:862d0d8619f6
*: HUUUGE changes
animia has been renamed to animone, so instead of thinking of a
health condition, you think of a beautiful flower :)
I've also edited some of the code for animone, but I have no idea
if it even works or not because I don't have a mac or windows
machine lying around. whoops!
... anyway, all of the changes divergent from Anisthesia are now
licensed under BSD. it's possible that I could even rewrite most
of the code to where I don't even have to keep the MIT license,
but that's thinking too far into the future
I've been slacking off on implementing the anime seasons page,
mostly out of laziness. I think I'd have to create another db file
specifically for the seasons
anyway, this code is being pushed *primarily* because the hard drive
it's on is failing! yay :)
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Mon, 01 Apr 2024 02:43:44 -0400 |
parents | |
children | 382b50754fe4 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dep/animone/src/win/quartz.cc Mon Apr 01 02:43:44 2024 -0400 @@ -0,0 +1,251 @@ +/* + * 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.h" +#include "animone/util/osx.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 { + +#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 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) + osx::util::GetCFNumber(reinterpret_cast<CFNumberRef>(data), out); + else if constexpr (std::is_same<T, std::string>::value) + osx::util::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)); + + if (!dicts.get() || CFArrayGetCount(dicts.get()) < 1) + return false; + + CFDictionaryRef dict = reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(dicts, 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, kAXValueTypeCGPoint, reinterpret_cast<CFTypeRef>(&point)) || + (point.x != bounds.origin.x || point.y != bounds.origin.y)) + continue; + } else + continue; + + if (AXUIElementCopyAttributeValue(window, kAXSizeAttribute, reinterpret_cast<CFTypeRef*>(&val)) == + kAXErrorSuccess) { + CGSize size; + if (!AXValueGetValue(val, kAXValueTypeCGSize, reinterpret_cast<CFTypeRef>(&size)) || + (size.width != bounds.size.width || size.height != bounds.size.height)) + continue; + } else + continue; + + CFStringRef title; + if (AXUIElementCopyAttributeValue(window, kAXTitleAttribute, reinterpret_cast<CFTypeRef*>(&title)) == + kAXErrorSuccess) + return osx::util::StringFromCFString(title, result); + } + + 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()) && osx::util::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(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; + + result = osx::util::StringFromCFString(bundle_id, result); + return true; +} + +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(dict, CFSTR("CFBundleIdentifier"))); + if (!value) + return false; + + result = osx::util::StringFromCFString(value, result); + return true; +} + +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(); +} + +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; + { + CFDictionaryGetValue(window, CFSTR("kCGWindowOwnerPID"), proc.pid); + if (!CFDictionaryGetValue(window, CFSTR("kCGWindowOwnerName"), proc.name)) + osx::util::GetProcessName(proc.pid, proc.name); + } + + Window win; + { + CFDictionaryGetValue(window, CFSTR("kCGWindowNumber"), win.id); + + if (!GetProcessBundleIdentifier(proc.pid, win.class_name)) + // Fallback to the Quartz window name, which is unlikely to be filled, but it + // *could* be. + CFDictionaryGetValue(window, CFSTR("kCGWindowName"), 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