Mercurial > minori
view dep/animia/src/win/quartz.cc @ 254:d14f8e0e40c3
[UNFINISHED] *: update anime button
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Wed, 07 Feb 2024 07:57:37 -0500 |
parents | a7d0d543b334 |
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 "animia/win/quartz.h" #include "animia/util/osx.h" #include "animia.h" #include <objc/runtime.h> #include <objc/message.h> #include <CoreFoundation/CoreFoundation.h> #include <CoreGraphics/CoreGraphics.h> #include <ApplicationServices/ApplicationServices.h> namespace animia::internal::quartz { /* all of these LaunchServices things use *internal functions* that are subject * to change. Granted, it's not very likely that these will change very much * because I'm fairly sure Apple uses them lots in their own internal code. */ #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}; CFArrayRef arr = CFArrayCreate(kCFAllocatorDefault, (CFTypeRef*)wids, 1, NULL); CFArrayRef dicts = CGWindowListCreateDescriptionFromArray(arr); CFRelease(arr); if (!dicts || CFArrayGetCount(dicts) < 1) return false; CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(dicts, 0); if (!dict) { CFRelease(dicts); return false; } CFDictionaryRef bounds_dict = nullptr; if (!CFDictionaryGetValueIfPresent(dict, kCGWindowBounds, reinterpret_cast<CFTypeRef*>(&bounds_dict)) || !bounds_dict) { CFRelease(dicts); return false; } if (!CGRectMakeWithDictionaryRepresentation(bounds_dict, &bounds)) { CFRelease(dicts); return false; } CFRelease(dicts); } /* now we can actually do stuff */ AXUIElementRef axapp = AXUIElementCreateApplication(pid); CFArrayRef windows; if ((AXUIElementCopyAttributeValue(axapp, kAXWindowsAttribute, (CFTypeRef*)&windows) != kAXErrorSuccess) || !windows) return false; const CFIndex count = CFArrayGetCount(windows); for (CFIndex i = 0; i < count; i++) { const AXUIElementRef window = (AXUIElementRef)CFArrayGetValueAtIndex(windows, i); AXValueRef val; if (AXUIElementCopyAttributeValue(window, kAXPositionAttribute, (CFTypeRef*)&val) == kAXErrorSuccess) { CGPoint point; if (!AXValueGetValue(val, kAXValueTypeCGPoint, (void*)&point) || (point.x != bounds.origin.x || point.y != bounds.origin.y)) continue; } else continue; if (AXUIElementCopyAttributeValue(window, kAXSizeAttribute, (CFTypeRef*)&val) == kAXErrorSuccess) { CGSize size; if (!AXValueGetValue(val, kAXValueTypeCGSize, (void*)&size) || (size.width != bounds.size.width || size.height != bounds.size.height)) continue; } else continue; CFStringRef title; if (AXUIElementCopyAttributeValue(window, kAXTitleAttribute, (CFTypeRef*)&title) == kAXErrorSuccess) { CFRelease(windows); return osx::util::StringFromCFString(title, result); } } CFRelease(windows); return false; } static bool GetWindowTitle(unsigned int wid, pid_t pid, std::string& result) { /* private internal OS X functions */ if ((CGSDefaultConnectionForThread && CGSCopyWindowProperty) || GetCoreGraphicsPrivateSymbols()) { CFStringRef title = nullptr; CGSCopyWindowProperty(CGSDefaultConnectionForThread(), wid, CFSTR("kCGSWindowTitle"), &title); if (title && CFStringGetLength(title) && osx::util::StringFromCFString(title, result)) return true; } /* don't attempt to use accessibility if we aren't trusted */ return AXIsProcessTrusted() ? GetWindowTitleAccessibility(wid, pid, result) : false; } 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. */ 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; } 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 animia::win::detail