diff dep/animia/src/win/quartz.cc @ 202:71832ffe425a

animia: re-add kvm fd source this is all being merged from my wildly out-of-date laptop. SORRY! in other news, I edited the CI file to install the wayland client as well, so the linux CI build might finally get wayland stuff.
author Paper <paper@paper.us.eu.org>
date Tue, 02 Jan 2024 06:05:06 -0500
parents bc1ae1810855
children a7d0d543b334
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dep/animia/src/win/quartz.cc	Tue Jan 02 06:05:06 2024 -0500
@@ -0,0 +1,125 @@
+/*
+ * 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 and linking
+ * with AppKit in order to receive proper window titles.
+*/
+#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>
+
+namespace animia::internal::quartz {
+
+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 GetWindowTitle(unsigned int wid, std::string& result) {
+	// NSApplication* app = [NSApplication sharedApplication];
+	const id app = cls_send(objc_getClass("NSApplication"), sel_getUid("sharedApplication"));
+
+	// NSWindow* window = [app windowWithWindowNumber: wid];
+	const id window = obj_send(app, sel_getUid("windowWithWindowNumber:"), wid);
+	if (!window)
+		return false;
+
+	// NSString* title = [window title];
+	const CFStringRef title = reinterpret_cast<CFStringRef>(obj_send(window, sel_getUid("title")));
+	if (!title)
+		return false;
+
+	// return [title UTF8String];
+	return osx::util::StringFromCFString(title, 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.
+	*/
+
+	// NSRunningApplication* app = [NSRunningApplication runningApplicationWithProcessIdentifier: pid];
+	const id app = cls_send(objc_getClass("NSRunningApplication"), sel_getUid("runningApplicationWithProcessIdentifier:"), pid);
+	if (!app)
+		return false;
+
+	// NSString* bundle_id = [app bundleIdentifier];
+	const CFStringRef bundle_id = reinterpret_cast<CFStringRef>(obj_send(app, sel_getUid("bundleIdentifier")));
+	if (!bundle_id)
+		return false;
+
+	// return [bundle_id UTF8String];
+	return osx::util::StringFromCFString(bundle_id, result);
+}
+
+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;
+}
+
+bool EnumerateWindows(window_proc_t window_proc) {
+	if (!window_proc)
+		return false;
+
+	const CFArrayRef windows = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, 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, win.text);
+		}
+
+		if (!window_proc(proc, win)) {
+			CFRelease(windows);
+			return false;
+		}
+	}
+
+	CFRelease(windows);
+
+	return true;
+}
+
+} // namespace animia::win::detail