changeset 199:9f3534f6b8c4

dep/animia: initial Wayland support, drop non-working kvm fd plugin
author Paper <mrpapersonic@gmail.com>
date Tue, 02 Jan 2024 02:34:27 -0500
parents bc1ae1810855
children da91af31ae73
files dep/animia/CMakeLists.txt dep/animia/README.md dep/animia/data/ext-foreign-toplevel-list-v1.xml dep/animia/include/animia/win/wayland.h dep/animia/include/animia/win/wayland/ext-foreign-toplevel-list-v1.h dep/animia/src/fd/kvm.cc dep/animia/src/win.cc dep/animia/src/win/wayland.cc dep/animia/src/win/wayland/ext-foreign-toplevel-list-v1.c dep/animia/src/win/x11.cc
diffstat 10 files changed, 1049 insertions(+), 193 deletions(-) [+]
line wrap: on
line diff
--- a/dep/animia/CMakeLists.txt	Sun Dec 24 02:59:42 2023 -0500
+++ b/dep/animia/CMakeLists.txt	Tue Jan 02 02:34:27 2024 -0500
@@ -60,9 +60,7 @@
 
 	if(LINUX)
 		list(APPEND DEFINES LINUX)
-		list(APPEND SRC_FILES
-			src/fd/proc.cc
-		)
+		list(APPEND SRC_FILES src/fd/proc.cc)
 	elseif(LIBUTIL_LIBRARY) # FreeBSD's libutil
 		get_filename_component(LIBUTIL_DIR ${LIBUTIL_LIBRARY} DIRECTORY)
 
@@ -88,18 +86,20 @@
 	endif() # LINUX
 
 	# X11
-	find_package(X11 COMPONENTS X11)
+	find_package(X11 COMPONENTS X11 XRes)
+
 	if(X11_FOUND)
+		# Getting PIDs from windows...
+		if (X11_XRes_FOUND)
+			list(APPEND DEFINES HAVE_XRES)
+		else() # NOT X11_XRes_FOUND
+			message(WARNING "libXRes could not be found! Finding PIDs in X11 windows may not work correctly!")
+		endif() # X11_XRes_FOUND
+
 		list(APPEND DEFINES X11)
-		list(APPEND SRC_FILES
-			src/win/x11.cc
-		)
-		list(APPEND INCLUDE_DIRS
-			${X11_INCLUDE_DIRS}
-		)
-		list(APPEND LIBRARIES
-			${X11_LIBRARIES}
-		)
+		list(APPEND SRC_FILES src/win/x11.cc)
+		list(APPEND INCLUDE_DIRS ${X11_INCLUDE_DIRS})
+		list(APPEND LIBRARIES ${X11_LIBRARIES}) # This will include Xres, I think..
 	else() # NOT X11_FOUND
 		# For some systems, i.e. Debian, FindX11 fails to find X11, so we have
 		# to use pkg_config as a fallback
@@ -107,19 +107,40 @@
 		if(PKG_CONFIG_FOUND)
 			pkg_check_modules(X11 x11)
 			if(X11_FOUND)
+				# Check for XRes the hard way
+				find_path(X11_XRes_HEADER "extensions/XRes.h" PATHS ${X11_INCLUDE_DIRS})
+				find_library(X11_XRes_LIB XRes ${X11_LIBRARY_DIRS})
+
+				if(X11_XRes_HEADER AND X11_XRes_LIB)
+					# TODO: We should REALLY check for XResQueryClientIds here...
+					list(APPEND DEFINES HAVE_XRES)
+					list(APPEND LIBRARIES ${X11_XRes_LIB})
+				else()
+					message(WARNING "libXRes could not be found! Finding PIDs in X11 windows may not work correctly!")
+				endif()
+
 				list(APPEND DEFINES X11)
-				list(APPEND SRC_FILES
-					src/win/x11.cc
-				)
-				list(APPEND INCLUDE_DIRS
-					${X11_INCLUDE_DIRS}
-				)
-				list(APPEND LIBRARIES
-					${X11_LINK_LIBRARIES}
-				)
+				list(APPEND SRC_FILES src/win/x11.cc)
+				list(APPEND INCLUDE_DIRS ${X11_INCLUDE_DIRS})
+				list(APPEND LIBRARIES ${X11_LINK_LIBRARIES})
 			endif() # X11_FOUND
 		endif() # PKG_CONFIG_FOUND
 	endif() # X11_FOUND
+
+	find_package(PkgConfig)
+	if(PKG_CONFIG_FOUND)
+		pkg_check_modules(WAYLAND wayland-client)
+		if(WAYLAND_FOUND)
+			enable_language(C)
+			list(APPEND DEFINES WAYLAND)
+			list(APPEND SRC_FILES
+				src/win/wayland.cc
+				src/win/wayland/ext-foreign-toplevel-list-v1.c
+			)
+			list(APPEND INCLUDE_DIRS ${WAYLAND_INCLUDE_DIRS})
+			list(APPEND LIBRARIES ${WAYLAND_LINK_LIBRARIES})
+		endif() # WAYLAND_FOUND
+	endif() # PKG_CONFIG_FOUND
 endif() # WIN32 AND APPLE
 
 add_library(animia SHARED ${SRC_FILES})
@@ -128,6 +149,6 @@
 	CXX_STANDARD 17
 )
 
-target_compile_definitions(animia PUBLIC ${DEFINES})
+target_compile_definitions(animia PRIVATE ${DEFINES})
 target_include_directories(animia PRIVATE include PUBLIC ${INCLUDE_DIRS})
 target_link_libraries(animia PUBLIC ${LIBRARIES})
--- a/dep/animia/README.md	Sun Dec 24 02:59:42 2023 -0500
+++ b/dep/animia/README.md	Tue Jan 02 02:34:27 2024 -0500
@@ -1,27 +1,43 @@
 # Animia
-Animia is a work-in-progress cross-platform hard fork of Anisthesia and part of Minori.
+Animia is a work-in-progress cross-platform hard fork of Anisthesia and part of
+Minori.
 
-Most (if not all) Anisthesia configs should also work in this library as well (at least on Windows).
+Most (if not all) Anisthesia configs should also work in this library as well
+(at least on Windows).
 
 ## Support
-Animia supports Windows, macOS, and Linux when dealing with file descriptors. When enumerating
-windows, it supports Windows, macOS (Quartz), and X11. I'd love to be able to support Wayland, but
-there's nothing I can do to provide an API that literally does not exist.
+Animia supports Windows, macOS, and Linux when dealing with file descriptors.
+When enumerating windows, it supports Windows, macOS (Quartz), X11, and
+Wayland (only via the `ext_foreign_toplevel_handle_v1` interface).
 
-Unlike Anisthesia, Animia currently does not support UI automation, i.e., some web browsers will not
-work properly, if at all.
+Unlike Anisthesia, Animia currently does not support UI automation, i.e., most
+web browsers will not work properly, if at all.
 
 ## Platform-specific quirks
 
+### Windows
+To get the currently opened file handles on Windows, Animia has to use internal
+kernel functions. However, these functions aren't likely to change anytime soon.
+
 ### macOS
-The code to executable names on macOS uses internal functions. However, if these functions
-cannot be found for whatever reason, it falls back to parsing the arguments, and then to calling
-the kernel.
+The code to retrieve executable names on macOS uses internal functions. However,
+if these functions cannot be found for whatever reason, it falls back to parsing
+the arguments, and then to calling the kernel.
 
-macOS doesn't have the concept of "class names", rather, it has bundle identifiers, which are
-a suitable replacement for most use cases.
+Additionally, macOS does not have the concept of class names, rather, it has
+bundle identifiers, which are a suitable replacement in most use cases, and are
+what Animia will try to grab before falling back to the Quartz window name.
 
 ### X11
-X11 has no idea what PID started your window. As a result, we can't provide it. Eventually,
-there'll be support for the XRes extension which provides this possibility. For now, PIDs are
-received using the untrustworthy `_NET_WM_PID` resource.
+If your X server has the XRes extension installed, Animia will use it to get
+PIDs. Otherwise, X11 has no idea what PID started your window. As a result,
+what Animia will give you is from the `_NET_WM_PID` resource, which is
+[very](https://stackoverflow.com/a/49970490)
+[unreliable](https://stackoverflow.com/a/49970271).
+
+### Wayland
+Only Wayland servers that implement the `ext_foreign_toplevel_handle_v1`
+interface will work with Animia. As of 2 January 2024, this means there are no
+server implementations that will work. **However**, it is possible to implement
+support for the wlroots-specific `wlr_foreign_toplevel_management_unstable_v1`
+protocol, which will at least give support to window managers based off of it.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dep/animia/data/ext-foreign-toplevel-list-v1.xml	Tue Jan 02 02:34:27 2024 -0500
@@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="ext_foreign_toplevel_list_v1">
+  <copyright>
+    Copyright © 2018 Ilia Bozhinov
+    Copyright © 2020 Isaac Freund
+    Copyright © 2022 wb9688
+    Copyright © 2023 i509VCB
+
+    Permission to use, copy, modify, distribute, and sell this
+    software and its documentation for any purpose is hereby granted
+    without fee, provided that the above copyright notice appear in
+    all copies and that both that copyright notice and this permission
+    notice appear in supporting documentation, and that the name of
+    the copyright holders not be used in advertising or publicity
+    pertaining to distribution of the software without specific,
+    written prior permission.  The copyright holders make no
+    representations about the suitability of this software for any
+    purpose.  It is provided "as is" without express or implied
+    warranty.
+
+    THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+    SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+    FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+    AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+    ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+    THIS SOFTWARE.
+  </copyright>
+
+  <description summary="list toplevels">
+    The purpose of this protocol is to provide protocol object handles for
+    toplevels, possibly originating from another client.
+
+    This protocol is intentionally minimalistic and expects additional
+    functionality (e.g. creating a screencopy source from a toplevel handle,
+    getting information about the state of the toplevel) to be implemented
+    in extension protocols.
+
+    The compositor may choose to restrict this protocol to a special client
+    launched by the compositor itself or expose it to all clients,
+    this is compositor policy.
+
+    The key words "must", "must not", "required", "shall", "shall not",
+    "should", "should not", "recommended",  "may", and "optional" in this
+    document are to be interpreted as described in IETF RFC 2119.
+
+    Warning! The protocol described in this file is currently in the testing
+    phase. Backward compatible changes may be added together with the
+    corresponding interface version bump. Backward incompatible changes can
+    only be done by creating a new major version of the extension.
+  </description>
+
+  <interface name="ext_foreign_toplevel_list_v1" version="1">
+    <description summary="list toplevels">
+      A toplevel is defined as a surface with a role similar to xdg_toplevel.
+      XWayland surfaces may be treated like toplevels in this protocol.
+
+      After a client binds the ext_foreign_toplevel_list_v1, each mapped
+      toplevel window will be sent using the ext_foreign_toplevel_list_v1.toplevel
+      event.
+
+      Clients which only care about the current state can perform a roundtrip after
+      binding this global.
+
+      For each instance of ext_foreign_toplevel_list_v1, the compositor must
+      create a new ext_foreign_toplevel_handle_v1 object for each mapped toplevel.
+
+      If a compositor implementation sends the ext_foreign_toplevel_list_v1.finished
+      event after the global is bound, the compositor must not send any
+      ext_foreign_toplevel_list_v1.toplevel events.
+    </description>
+
+    <event name="toplevel">
+      <description summary="a toplevel has been created">
+        This event is emitted whenever a new toplevel window is created. It is
+        emitted for all toplevels, regardless of the app that has created them.
+
+        All initial properties of the toplevel (identifier, title, app_id) will be sent
+        immediately after this event using the corresponding events for
+        ext_foreign_toplevel_handle_v1. The compositor will use the
+        ext_foreign_toplevel_handle_v1.done event to indicate when all data has
+        been sent.
+      </description>
+      <arg name="toplevel" type="new_id" interface="ext_foreign_toplevel_handle_v1"/>
+    </event>
+
+    <event name="finished">
+      <description summary="the compositor has finished with the toplevel manager">
+        This event indicates that the compositor is done sending events
+        to this object. The client should should destroy the object.
+        See ext_foreign_toplevel_list_v1.destroy for more information.
+
+        The compositor must not send any more toplevel events after this event.
+      </description>
+    </event>
+
+    <request name="stop">
+      <description summary="stop sending events">
+        This request indicates that the client no longer wishes to receive
+        events for new toplevels.
+
+        The Wayland protocol is asynchronous, meaning the compositor may send
+        further toplevel events until the stop request is processed.
+        The client should wait for a ext_foreign_toplevel_list_v1.finished
+        event before destroying this object.
+      </description>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the ext_foreign_toplevel_list_v1 object">
+        This request should be called either when the client will no longer
+        use the ext_foreign_toplevel_list_v1 or after the finished event
+        has been received to allow destruction of the object.
+
+        If a client wishes to destroy this object it should send a
+        ext_foreign_toplevel_list_v1.stop request and wait for a ext_foreign_toplevel_list_v1.finished
+        event, then destroy the handles and then this object.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="ext_foreign_toplevel_handle_v1" version="1">
+    <description summary="a mapped toplevel">
+      A ext_foreign_toplevel_handle_v1 object represents a mapped toplevel
+      window. A single app may have multiple mapped toplevels.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the ext_foreign_toplevel_handle_v1 object">
+        This request should be used when the client will no longer use the handle
+        or after the closed event has been received to allow destruction of the
+        object.
+
+        When a handle is destroyed, a new handle may not be created by the server
+        until the toplevel is unmapped and then remapped. Destroying a toplevel handle
+        is not recommended unless the client is cleaning up child objects
+        before destroying the ext_foreign_toplevel_list_v1 object, the toplevel
+        was closed or the toplevel handle will not be used in the future.
+
+        Other protocols which extend the ext_foreign_toplevel_handle_v1
+        interface should require destructors for extension interfaces be
+        called before allowing the toplevel handle to be destroyed.
+      </description>
+    </request>
+
+    <event name="closed">
+      <description summary="the toplevel has been closed">
+        The server will emit no further events on the ext_foreign_toplevel_handle_v1
+        after this event. Any requests received aside from the destroy request must
+        be ignored. Upon receiving this event, the client should destroy the handle.
+
+        Other protocols which extend the ext_foreign_toplevel_handle_v1
+        interface must also ignore requests other than destructors.
+      </description>
+    </event>
+
+    <event name="done">
+      <description summary="all information about the toplevel has been sent">
+        This event is sent after all changes in the toplevel state have
+        been sent.
+
+        This allows changes to the ext_foreign_toplevel_handle_v1 properties
+        to be atomically applied. Other protocols which extend the
+        ext_foreign_toplevel_handle_v1 interface may use this event to also
+        atomically apply any pending state.
+
+        This event must not be sent after the ext_foreign_toplevel_handle_v1.closed
+        event.
+      </description>
+    </event>
+
+    <event name="title">
+      <description summary="title change">
+        The title of the toplevel has changed.
+
+        The configured state must not be applied immediately. See
+        ext_foreign_toplevel_handle_v1.done for details.
+      </description>
+      <arg name="title" type="string"/>
+    </event>
+
+    <event name="app_id">
+      <description summary="app_id change">
+        The app id of the toplevel has changed.
+
+        The configured state must not be applied immediately. See
+        ext_foreign_toplevel_handle_v1.done for details.
+      </description>
+      <arg name="app_id" type="string"/>
+    </event>
+
+    <event name="identifier">
+      <description summary="a stable identifier for a toplevel">
+        This identifier is used to check if two or more toplevel handles belong
+        to the same toplevel.
+
+        The identifier is useful for command line tools or privileged clients
+        which may need to reference an exact toplevel across processes or
+        instances of the ext_foreign_toplevel_list_v1 global.
+
+        The compositor must only send this event when the handle is created.
+
+        The identifier must be unique per toplevel and it's handles. Two different
+        toplevels must not have the same identifier. The identifier is only valid
+        as long as the toplevel is mapped. If the toplevel is unmapped the identifier
+        must not be reused. An identifier must not be reused by the compositor to
+        ensure there are no races when sharing identifiers between processes.
+
+        An identifier is a string that contains up to 32 printable ASCII bytes.
+        An identifier must not be an empty string. It is recommended that a
+        compositor includes an opaque generation value in identifiers. How the
+        generation value is used when generating the identifier is implementation
+        dependent.
+      </description>
+      <arg name="identifier" type="string"/>
+    </event>
+  </interface>
+</protocol>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dep/animia/include/animia/win/wayland.h	Tue Jan 02 02:34:27 2024 -0500
@@ -0,0 +1,12 @@
+#ifndef __animia__animia__win__wayland_h
+#define __animia__animia__win__wayland_h
+
+#include "animia/win.h"
+
+namespace animia::internal::wayland {
+
+bool EnumerateWindows(window_proc_t window_proc);
+
+} // namespace animia::internal::wayland
+
+#endif // __animia__animia__win__wayland_h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dep/animia/include/animia/win/wayland/ext-foreign-toplevel-list-v1.h	Tue Jan 02 02:34:27 2024 -0500
@@ -0,0 +1,443 @@
+/* Generated by wayland-scanner 1.21.0 */
+
+#ifndef EXT_FOREIGN_TOPLEVEL_LIST_V1_CLIENT_PROTOCOL_H
+#define EXT_FOREIGN_TOPLEVEL_LIST_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_ext_foreign_toplevel_list_v1 The ext_foreign_toplevel_list_v1 protocol
+ * list toplevels
+ *
+ * @section page_desc_ext_foreign_toplevel_list_v1 Description
+ *
+ * The purpose of this protocol is to provide protocol object handles for
+ * toplevels, possibly originating from another client.
+ *
+ * This protocol is intentionally minimalistic and expects additional
+ * functionality (e.g. creating a screencopy source from a toplevel handle,
+ * getting information about the state of the toplevel) to be implemented
+ * in extension protocols.
+ *
+ * The compositor may choose to restrict this protocol to a special client
+ * launched by the compositor itself or expose it to all clients,
+ * this is compositor policy.
+ *
+ * The key words "must", "must not", "required", "shall", "shall not",
+ * "should", "should not", "recommended",  "may", and "optional" in this
+ * document are to be interpreted as described in IETF RFC 2119.
+ *
+ * Warning! The protocol described in this file is currently in the testing
+ * phase. Backward compatible changes may be added together with the
+ * corresponding interface version bump. Backward incompatible changes can
+ * only be done by creating a new major version of the extension.
+ *
+ * @section page_ifaces_ext_foreign_toplevel_list_v1 Interfaces
+ * - @subpage page_iface_ext_foreign_toplevel_list_v1 - list toplevels
+ * - @subpage page_iface_ext_foreign_toplevel_handle_v1 - a mapped toplevel
+ * @section page_copyright_ext_foreign_toplevel_list_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2018 Ilia Bozhinov
+ * Copyright © 2020 Isaac Freund
+ * Copyright © 2022 wb9688
+ * Copyright © 2023 i509VCB
+ *
+ * Permission to use, copy, modify, distribute, and sell this
+ * software and its documentation for any purpose is hereby granted
+ * without fee, provided that the above copyright notice appear in
+ * all copies and that both that copyright notice and this permission
+ * notice appear in supporting documentation, and that the name of
+ * the copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ * </pre>
+ */
+struct ext_foreign_toplevel_handle_v1;
+struct ext_foreign_toplevel_list_v1;
+
+#ifndef EXT_FOREIGN_TOPLEVEL_LIST_V1_INTERFACE
+#define EXT_FOREIGN_TOPLEVEL_LIST_V1_INTERFACE
+/**
+ * @page page_iface_ext_foreign_toplevel_list_v1 ext_foreign_toplevel_list_v1
+ * @section page_iface_ext_foreign_toplevel_list_v1_desc Description
+ *
+ * A toplevel is defined as a surface with a role similar to xdg_toplevel.
+ * XWayland surfaces may be treated like toplevels in this protocol.
+ *
+ * After a client binds the ext_foreign_toplevel_list_v1, each mapped
+ * toplevel window will be sent using the ext_foreign_toplevel_list_v1.toplevel
+ * event.
+ *
+ * Clients which only care about the current state can perform a roundtrip after
+ * binding this global.
+ *
+ * For each instance of ext_foreign_toplevel_list_v1, the compositor must
+ * create a new ext_foreign_toplevel_handle_v1 object for each mapped toplevel.
+ *
+ * If a compositor implementation sends the ext_foreign_toplevel_list_v1.finished
+ * event after the global is bound, the compositor must not send any
+ * ext_foreign_toplevel_list_v1.toplevel events.
+ * @section page_iface_ext_foreign_toplevel_list_v1_api API
+ * See @ref iface_ext_foreign_toplevel_list_v1.
+ */
+/**
+ * @defgroup iface_ext_foreign_toplevel_list_v1 The ext_foreign_toplevel_list_v1 interface
+ *
+ * A toplevel is defined as a surface with a role similar to xdg_toplevel.
+ * XWayland surfaces may be treated like toplevels in this protocol.
+ *
+ * After a client binds the ext_foreign_toplevel_list_v1, each mapped
+ * toplevel window will be sent using the ext_foreign_toplevel_list_v1.toplevel
+ * event.
+ *
+ * Clients which only care about the current state can perform a roundtrip after
+ * binding this global.
+ *
+ * For each instance of ext_foreign_toplevel_list_v1, the compositor must
+ * create a new ext_foreign_toplevel_handle_v1 object for each mapped toplevel.
+ *
+ * If a compositor implementation sends the ext_foreign_toplevel_list_v1.finished
+ * event after the global is bound, the compositor must not send any
+ * ext_foreign_toplevel_list_v1.toplevel events.
+ */
+extern const struct wl_interface ext_foreign_toplevel_list_v1_interface;
+#endif
+#ifndef EXT_FOREIGN_TOPLEVEL_HANDLE_V1_INTERFACE
+#define EXT_FOREIGN_TOPLEVEL_HANDLE_V1_INTERFACE
+/**
+ * @page page_iface_ext_foreign_toplevel_handle_v1 ext_foreign_toplevel_handle_v1
+ * @section page_iface_ext_foreign_toplevel_handle_v1_desc Description
+ *
+ * A ext_foreign_toplevel_handle_v1 object represents a mapped toplevel
+ * window. A single app may have multiple mapped toplevels.
+ * @section page_iface_ext_foreign_toplevel_handle_v1_api API
+ * See @ref iface_ext_foreign_toplevel_handle_v1.
+ */
+/**
+ * @defgroup iface_ext_foreign_toplevel_handle_v1 The ext_foreign_toplevel_handle_v1 interface
+ *
+ * A ext_foreign_toplevel_handle_v1 object represents a mapped toplevel
+ * window. A single app may have multiple mapped toplevels.
+ */
+extern const struct wl_interface ext_foreign_toplevel_handle_v1_interface;
+#endif
+
+/**
+ * @ingroup iface_ext_foreign_toplevel_list_v1
+ * @struct ext_foreign_toplevel_list_v1_listener
+ */
+struct ext_foreign_toplevel_list_v1_listener {
+	/**
+	 * a toplevel has been created
+	 *
+	 * This event is emitted whenever a new toplevel window is
+	 * created. It is emitted for all toplevels, regardless of the app
+	 * that has created them.
+	 *
+	 * All initial properties of the toplevel (identifier, title,
+	 * app_id) will be sent immediately after this event using the
+	 * corresponding events for ext_foreign_toplevel_handle_v1. The
+	 * compositor will use the ext_foreign_toplevel_handle_v1.done
+	 * event to indicate when all data has been sent.
+	 */
+	void (*toplevel)(void *data,
+			 struct ext_foreign_toplevel_list_v1 *ext_foreign_toplevel_list_v1,
+			 struct ext_foreign_toplevel_handle_v1 *toplevel);
+	/**
+	 * the compositor has finished with the toplevel manager
+	 *
+	 * This event indicates that the compositor is done sending
+	 * events to this object. The client should should destroy the
+	 * object. See ext_foreign_toplevel_list_v1.destroy for more
+	 * information.
+	 *
+	 * The compositor must not send any more toplevel events after this
+	 * event.
+	 */
+	void (*finished)(void *data,
+			 struct ext_foreign_toplevel_list_v1 *ext_foreign_toplevel_list_v1);
+};
+
+/**
+ * @ingroup iface_ext_foreign_toplevel_list_v1
+ */
+static inline int
+ext_foreign_toplevel_list_v1_add_listener(struct ext_foreign_toplevel_list_v1 *ext_foreign_toplevel_list_v1,
+					  const struct ext_foreign_toplevel_list_v1_listener *listener, void *data)
+{
+	return wl_proxy_add_listener((struct wl_proxy *) ext_foreign_toplevel_list_v1,
+				     (void (**)(void)) listener, data);
+}
+
+#define EXT_FOREIGN_TOPLEVEL_LIST_V1_STOP 0
+#define EXT_FOREIGN_TOPLEVEL_LIST_V1_DESTROY 1
+
+/**
+ * @ingroup iface_ext_foreign_toplevel_list_v1
+ */
+#define EXT_FOREIGN_TOPLEVEL_LIST_V1_TOPLEVEL_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_foreign_toplevel_list_v1
+ */
+#define EXT_FOREIGN_TOPLEVEL_LIST_V1_FINISHED_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_ext_foreign_toplevel_list_v1
+ */
+#define EXT_FOREIGN_TOPLEVEL_LIST_V1_STOP_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_foreign_toplevel_list_v1
+ */
+#define EXT_FOREIGN_TOPLEVEL_LIST_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_ext_foreign_toplevel_list_v1 */
+static inline void
+ext_foreign_toplevel_list_v1_set_user_data(struct ext_foreign_toplevel_list_v1 *ext_foreign_toplevel_list_v1, void *user_data)
+{
+	wl_proxy_set_user_data((struct wl_proxy *) ext_foreign_toplevel_list_v1, user_data);
+}
+
+/** @ingroup iface_ext_foreign_toplevel_list_v1 */
+static inline void *
+ext_foreign_toplevel_list_v1_get_user_data(struct ext_foreign_toplevel_list_v1 *ext_foreign_toplevel_list_v1)
+{
+	return wl_proxy_get_user_data((struct wl_proxy *) ext_foreign_toplevel_list_v1);
+}
+
+static inline uint32_t
+ext_foreign_toplevel_list_v1_get_version(struct ext_foreign_toplevel_list_v1 *ext_foreign_toplevel_list_v1)
+{
+	return wl_proxy_get_version((struct wl_proxy *) ext_foreign_toplevel_list_v1);
+}
+
+/**
+ * @ingroup iface_ext_foreign_toplevel_list_v1
+ *
+ * This request indicates that the client no longer wishes to receive
+ * events for new toplevels.
+ *
+ * The Wayland protocol is asynchronous, meaning the compositor may send
+ * further toplevel events until the stop request is processed.
+ * The client should wait for a ext_foreign_toplevel_list_v1.finished
+ * event before destroying this object.
+ */
+static inline void
+ext_foreign_toplevel_list_v1_stop(struct ext_foreign_toplevel_list_v1 *ext_foreign_toplevel_list_v1)
+{
+	wl_proxy_marshal_flags((struct wl_proxy *) ext_foreign_toplevel_list_v1,
+			 EXT_FOREIGN_TOPLEVEL_LIST_V1_STOP, NULL, wl_proxy_get_version((struct wl_proxy *) ext_foreign_toplevel_list_v1), 0);
+}
+
+/**
+ * @ingroup iface_ext_foreign_toplevel_list_v1
+ *
+ * This request should be called either when the client will no longer
+ * use the ext_foreign_toplevel_list_v1 or after the finished event
+ * has been received to allow destruction of the object.
+ *
+ * If a client wishes to destroy this object it should send a
+ * ext_foreign_toplevel_list_v1.stop request and wait for a ext_foreign_toplevel_list_v1.finished
+ * event, then destroy the handles and then this object.
+ */
+static inline void
+ext_foreign_toplevel_list_v1_destroy(struct ext_foreign_toplevel_list_v1 *ext_foreign_toplevel_list_v1)
+{
+	wl_proxy_marshal_flags((struct wl_proxy *) ext_foreign_toplevel_list_v1,
+			 EXT_FOREIGN_TOPLEVEL_LIST_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) ext_foreign_toplevel_list_v1), WL_MARSHAL_FLAG_DESTROY);
+}
+
+/**
+ * @ingroup iface_ext_foreign_toplevel_handle_v1
+ * @struct ext_foreign_toplevel_handle_v1_listener
+ */
+struct ext_foreign_toplevel_handle_v1_listener {
+	/**
+	 * the toplevel has been closed
+	 *
+	 * The server will emit no further events on the
+	 * ext_foreign_toplevel_handle_v1 after this event. Any requests
+	 * received aside from the destroy request must be ignored. Upon
+	 * receiving this event, the client should destroy the handle.
+	 *
+	 * Other protocols which extend the ext_foreign_toplevel_handle_v1
+	 * interface must also ignore requests other than destructors.
+	 */
+	void (*closed)(void *data,
+		       struct ext_foreign_toplevel_handle_v1 *ext_foreign_toplevel_handle_v1);
+	/**
+	 * all information about the toplevel has been sent
+	 *
+	 * This event is sent after all changes in the toplevel state
+	 * have been sent.
+	 *
+	 * This allows changes to the ext_foreign_toplevel_handle_v1
+	 * properties to be atomically applied. Other protocols which
+	 * extend the ext_foreign_toplevel_handle_v1 interface may use this
+	 * event to also atomically apply any pending state.
+	 *
+	 * This event must not be sent after the
+	 * ext_foreign_toplevel_handle_v1.closed event.
+	 */
+	void (*done)(void *data,
+		     struct ext_foreign_toplevel_handle_v1 *ext_foreign_toplevel_handle_v1);
+	/**
+	 * title change
+	 *
+	 * The title of the toplevel has changed.
+	 *
+	 * The configured state must not be applied immediately. See
+	 * ext_foreign_toplevel_handle_v1.done for details.
+	 */
+	void (*title)(void *data,
+		      struct ext_foreign_toplevel_handle_v1 *ext_foreign_toplevel_handle_v1,
+		      const char *title);
+	/**
+	 * app_id change
+	 *
+	 * The app id of the toplevel has changed.
+	 *
+	 * The configured state must not be applied immediately. See
+	 * ext_foreign_toplevel_handle_v1.done for details.
+	 */
+	void (*app_id)(void *data,
+		       struct ext_foreign_toplevel_handle_v1 *ext_foreign_toplevel_handle_v1,
+		       const char *app_id);
+	/**
+	 * a stable identifier for a toplevel
+	 *
+	 * This identifier is used to check if two or more toplevel
+	 * handles belong to the same toplevel.
+	 *
+	 * The identifier is useful for command line tools or privileged
+	 * clients which may need to reference an exact toplevel across
+	 * processes or instances of the ext_foreign_toplevel_list_v1
+	 * global.
+	 *
+	 * The compositor must only send this event when the handle is
+	 * created.
+	 *
+	 * The identifier must be unique per toplevel and it's handles. Two
+	 * different toplevels must not have the same identifier. The
+	 * identifier is only valid as long as the toplevel is mapped. If
+	 * the toplevel is unmapped the identifier must not be reused. An
+	 * identifier must not be reused by the compositor to ensure there
+	 * are no races when sharing identifiers between processes.
+	 *
+	 * An identifier is a string that contains up to 32 printable ASCII
+	 * bytes. An identifier must not be an empty string. It is
+	 * recommended that a compositor includes an opaque generation
+	 * value in identifiers. How the generation value is used when
+	 * generating the identifier is implementation dependent.
+	 */
+	void (*identifier)(void *data,
+			   struct ext_foreign_toplevel_handle_v1 *ext_foreign_toplevel_handle_v1,
+			   const char *identifier);
+};
+
+/**
+ * @ingroup iface_ext_foreign_toplevel_handle_v1
+ */
+static inline int
+ext_foreign_toplevel_handle_v1_add_listener(struct ext_foreign_toplevel_handle_v1 *ext_foreign_toplevel_handle_v1,
+					    const struct ext_foreign_toplevel_handle_v1_listener *listener, void *data)
+{
+	return wl_proxy_add_listener((struct wl_proxy *) ext_foreign_toplevel_handle_v1,
+				     (void (**)(void)) listener, data);
+}
+
+#define EXT_FOREIGN_TOPLEVEL_HANDLE_V1_DESTROY 0
+
+/**
+ * @ingroup iface_ext_foreign_toplevel_handle_v1
+ */
+#define EXT_FOREIGN_TOPLEVEL_HANDLE_V1_CLOSED_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_foreign_toplevel_handle_v1
+ */
+#define EXT_FOREIGN_TOPLEVEL_HANDLE_V1_DONE_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_foreign_toplevel_handle_v1
+ */
+#define EXT_FOREIGN_TOPLEVEL_HANDLE_V1_TITLE_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_foreign_toplevel_handle_v1
+ */
+#define EXT_FOREIGN_TOPLEVEL_HANDLE_V1_APP_ID_SINCE_VERSION 1
+/**
+ * @ingroup iface_ext_foreign_toplevel_handle_v1
+ */
+#define EXT_FOREIGN_TOPLEVEL_HANDLE_V1_IDENTIFIER_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_ext_foreign_toplevel_handle_v1
+ */
+#define EXT_FOREIGN_TOPLEVEL_HANDLE_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_ext_foreign_toplevel_handle_v1 */
+static inline void
+ext_foreign_toplevel_handle_v1_set_user_data(struct ext_foreign_toplevel_handle_v1 *ext_foreign_toplevel_handle_v1, void *user_data)
+{
+	wl_proxy_set_user_data((struct wl_proxy *) ext_foreign_toplevel_handle_v1, user_data);
+}
+
+/** @ingroup iface_ext_foreign_toplevel_handle_v1 */
+static inline void *
+ext_foreign_toplevel_handle_v1_get_user_data(struct ext_foreign_toplevel_handle_v1 *ext_foreign_toplevel_handle_v1)
+{
+	return wl_proxy_get_user_data((struct wl_proxy *) ext_foreign_toplevel_handle_v1);
+}
+
+static inline uint32_t
+ext_foreign_toplevel_handle_v1_get_version(struct ext_foreign_toplevel_handle_v1 *ext_foreign_toplevel_handle_v1)
+{
+	return wl_proxy_get_version((struct wl_proxy *) ext_foreign_toplevel_handle_v1);
+}
+
+/**
+ * @ingroup iface_ext_foreign_toplevel_handle_v1
+ *
+ * This request should be used when the client will no longer use the handle
+ * or after the closed event has been received to allow destruction of the
+ * object.
+ *
+ * When a handle is destroyed, a new handle may not be created by the server
+ * until the toplevel is unmapped and then remapped. Destroying a toplevel handle
+ * is not recommended unless the client is cleaning up child objects
+ * before destroying the ext_foreign_toplevel_list_v1 object, the toplevel
+ * was closed or the toplevel handle will not be used in the future.
+ *
+ * Other protocols which extend the ext_foreign_toplevel_handle_v1
+ * interface should require destructors for extension interfaces be
+ * called before allowing the toplevel handle to be destroyed.
+ */
+static inline void
+ext_foreign_toplevel_handle_v1_destroy(struct ext_foreign_toplevel_handle_v1 *ext_foreign_toplevel_handle_v1)
+{
+	wl_proxy_marshal_flags((struct wl_proxy *) ext_foreign_toplevel_handle_v1,
+			 EXT_FOREIGN_TOPLEVEL_HANDLE_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) ext_foreign_toplevel_handle_v1), WL_MARSHAL_FLAG_DESTROY);
+}
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif
--- a/dep/animia/src/fd/kvm.cc	Sun Dec 24 02:59:42 2023 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,134 +0,0 @@
-/* kvm.cc: provides support for libkvm in multiple BSDs
-**
-** this is really the only way to get a thing that works on
-** OpenBSD AND NetBSD.
-**
-** Much of this file is taken from the fstat source code in
-** NetBSD.
-*/
-
-#include <kvm.h>
-
-namespace animia::internal::kvm {
-
-static bool GetFilename(kvm_t* kvm, struct vnode *vp, std::string& name) {
-	struct vnode vn;
-	if (!kvm_read(kvm, vp, &vn, sizeof(*vn)))
-		return 0;
-
-	struct filestat fst;
-	const char* type = vfilestat(vn, &fst);
-	if (type == dead)
-		return false;
-
-	for (DEVS* d = devs; d != NULL; d = d->next) {
-		if (d->fsid == fst->fsid && d->ino == fst->fileid) {
-			name = d->name;
-			break;
-		}
-	}
-
-	return true;
-}
-
-static bool GetFilePath(kvm_t* kvm, fdfile_t* fp, std::string& path) {
-	struct file file;
-	fdfile_t fdfile;
-
-	if (!kvm_read(kvm, fp, &fdfile, sizeof(fdfile)))
-		return false;
-
-	if (!fdfile.ff_file)
-		return false;
-
-	if (!kvm_read(fdfile.ff_file, &file, sizeof(file)))
-		return false;
-
-	if (file.f_type != DTYPE_VNODE)
-		return false;
-
-	return GetFilename(kvm, file.f_data, path);
-}
-
-static bool OpenFiles(kvm_t* kvm, struct kinfo_proc* p, open_file_proc_t open_file_proc) {
-	if (p->proc->p_fd == 0 || p->proc->p_cwdi == 0)
-		return false;
-
-	struct filedesc filed;
-	if (!kvm_read(kvm, p->proc->p_fd, &filed, sizeof(filed)))
-		return false;
-
-	if (filed.fd_lastfile == -1)
-		return false;
-
-	struct cwdinfo cwdi;
-	if (!kvm_read(kvm, p->proc->p_cwdi, &cwdi, sizeof(cwdi)))
-		return false;
-
-	struct fdtab dt;
-	if (!kvm_read(kvm, filed.fd_dt, &dt, sizeof(dt)))
-		return false;
-
-	/* check for corrupted files? */
-	if ((unsigned)filed.fd_lastfile >= dt.dt_nfiles || filed.fd_freefile > filed.fd_lastfile + 1)
-		return false;
-
-	/* open files */
-	std::unique_ptr<fdfile_t*[]> ofiles = nullptr;
-	{
-		ofiles.reset(malloc((filed.fd_lastfile + 1) * sizeof(fdfile_t*)));
-		if (!ofiles.get())
-			return false;
-	}
-
-	if (!kvm_read(kvm, &filed.fd_dt->dt_ff, ofiles.get(), filed.fd_lastfile + 1 * (sizeof(fdfile_t*))))
-		return false;
-
-	for (int i = 0; i <= filed.fd_lastfile; i++) {
-		if (!ofiles[i])
-			continue;
-		std::string name;
-		GetFilePath(kvm, ofiles[i], name);
-		if (!open_file_proc(p->proc->p_pid, name))
-			return false;
-	}
-
-	return true;
-}
-
-bool EnumerateOpenProcesses(process_proc_t process_proc) {
-	char errbuf[_POSIX2_LINE_MAX];
-	kvm_t* kernel = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
-	if (!kernel)
-		return false;
-
-	int entries = 0;
-	struct kinfo_proc* kinfo = kvm_getprocs(kernel, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc), &nentries);
-	if (!kinfo)
-		return false;
-
-	for (int i = 0; i < entries; i++)
-		if (!process_proc({kinfo[i].p_pid, kinfo[i].p_comm}))
-			return false;
-
-	return true;
-}
-
-bool EnumerateOpenFiles(std::set<pid_t>& pids, open_file_proc_t open_file_proc) {
-	kvm_t* kernel = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
-	if (!kernel)
-		return false;
-
-	for (const auto& pid : pids) {
-		int cnt;
-		struct kinfo_proc* kinfo = kvm_getprocs(kernel, KERN_PROC_PID, pid, &cnt);
-		if (!kinfo)
-			return false;
-
-		for (int i = 0; i < cnt; i++) {
-			OpenFiles(kernel, kinfo, open_file_proc);
-		}
-	}
-}
-
-}
--- a/dep/animia/src/win.cc	Sun Dec 24 02:59:42 2023 -0500
+++ b/dep/animia/src/win.cc	Tue Jan 02 02:34:27 2024 -0500
@@ -12,21 +12,29 @@
 #	include "animia/win/x11.h"
 #endif
 
+#ifdef WAYLAND
+#	include "animia/win/wayland.h"
+#endif
+
 namespace animia::internal {
 
 bool EnumerateWindows(window_proc_t window_proc) {
 	bool success = false;
 
 #ifdef WIN32
-	success ^= win32::EnumerateWindows(window_proc);
+	success |= win32::EnumerateWindows(window_proc);
 #endif
 
 #ifdef MACOSX
-	success ^= quartz::EnumerateWindows(window_proc);
+	success |= quartz::EnumerateWindows(window_proc);
 #endif
 
 #ifdef X11
-	success ^= x11::EnumerateWindows(window_proc);
+	success |= x11::EnumerateWindows(window_proc);
+#endif
+
+#ifdef WAYLAND
+	success |= wayland::EnumerateWindows(window_proc);
 #endif
 
 	return success;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dep/animia/src/win/wayland.cc	Tue Jan 02 02:34:27 2024 -0500
@@ -0,0 +1,155 @@
+#include "animia/win/wayland.h"
+#include "animia/win.h"
+#include "animia.h"
+
+#include <iostream>
+#include <cstring>
+
+#include "animia/win/wayland/ext-foreign-toplevel-list-v1.h"
+#include <wayland-client.h>
+
+namespace animia::internal::wayland {
+
+static void noop() {}
+
+/* ext_foreign_handle stuff */
+static void ext_foreign_handle_handle_app_id(void* data, struct ext_foreign_toplevel_handle_v1* handle, const char* app_id) {
+	if (app_id)
+		reinterpret_cast<Window*>(data)->class_name = app_id;
+}
+
+static void ext_foreign_handle_handle_title(void* data, struct ext_foreign_toplevel_handle_v1* handle, const char* title) {
+	if (title)
+		reinterpret_cast<Window*>(data)->text = title;
+}
+
+static void ext_foreign_handle_handle_done(void* data, struct ext_foreign_toplevel_handle_v1* handle) {
+	if (handle)
+		ext_foreign_toplevel_handle_v1_destroy(handle);
+}
+
+static void ext_foreign_handle_handle_closed(void*, struct ext_foreign_toplevel_handle_v1*) {}
+static void ext_foreign_handle_handle_identifier(void*, ext_foreign_toplevel_handle_v1*, const char*) {}
+
+static const struct ext_foreign_toplevel_handle_v1_listener ext_handle_listener = {
+	.closed     = ext_foreign_handle_handle_closed,
+	.done       = ext_foreign_handle_handle_done,
+	.title      = ext_foreign_handle_handle_title,
+	.app_id     = ext_foreign_handle_handle_app_id,
+	.identifier = ext_foreign_handle_handle_identifier // for now
+};
+
+static void ext_toplevel_list_handle_toplevel(void* data, struct ext_foreign_toplevel_list_v1* list, struct ext_foreign_toplevel_handle_v1* handle) {
+	std::vector<Window>* windows = reinterpret_cast<std::vector<Window>*>(data);
+	if (!windows)
+		return;
+
+	windows->push_back({0});
+	ext_foreign_toplevel_handle_v1_add_listener(handle, &ext_handle_listener, &*windows->end());
+}
+
+static void ext_toplevel_list_handle_finished(void*, ext_foreign_toplevel_list_v1*) {}
+
+static const struct ext_foreign_toplevel_list_v1_listener ext_toplevel_list_listener = {
+	.toplevel = ext_toplevel_list_handle_toplevel,
+	.finished = ext_toplevel_list_handle_finished
+};
+
+/* -- Global data -- */
+struct global_data {
+	struct ext_foreign_toplevel_list_v1* ext_toplevel_list;
+	std::vector<Window> windows;
+};
+
+static void registry_handle_global(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) {
+	struct global_data* global = reinterpret_cast<struct global_data*>(data);
+
+	if (!std::strcmp(interface, ext_foreign_toplevel_list_v1_interface.name)) {
+		// bind to ext-foreign-toplevel-list-v1
+		global->ext_toplevel_list = reinterpret_cast<struct ext_foreign_toplevel_list_v1*>(wl_registry_bind(registry, name, &ext_foreign_toplevel_list_v1_interface, 1));
+		ext_foreign_toplevel_list_v1_add_listener(global->ext_toplevel_list, &ext_toplevel_list_listener, &global->windows);
+	}
+}
+
+static void registry_handle_global_remove(void*, wl_registry*, uint32_t) {}
+
+static const struct wl_registry_listener registry_listener = {
+	.global        = registry_handle_global,
+	.global_remove = registry_handle_global_remove
+};
+
+/* BAH! humbug... */
+struct sync_data {
+	bool loop = true;
+	int sync = 0;
+
+	struct global_data global = {0};
+
+	struct wl_callback* callback = nullptr;
+	struct wl_display* display = nullptr;
+};
+
+static void sync_handle_done (void* data, struct wl_callback* callback, uint32_t other_data);
+static const struct wl_callback_listener sync_callback_listener = {
+	.done = sync_handle_done,
+};
+
+static void sync_handle_done (void* data, struct wl_callback* callback, uint32_t other_data) {
+	struct sync_data* sync = reinterpret_cast<struct sync_data*>(data);
+
+	wl_callback_destroy(callback);
+	sync->callback = nullptr;
+
+	if (sync->sync == 0) {
+		if (!sync->global.ext_toplevel_list) {
+			std::cerr << "animia/wayland: Wayland server doesn't support ext-foreign-toplevel-list-v1!" << std::endl;
+			sync->loop = false;
+			return;
+		}
+
+		sync->sync++;
+		sync->callback = wl_display_sync(sync->display);
+		wl_callback_add_listener(sync->callback, &sync_callback_listener, sync);
+
+		/* we may need another sync here if there are protocol extensions for
+		 * ext_foreign_toplevel_list.
+		 *
+		 * more info: https://git.sr.ht/~leon_plickat/lswt/tree/toplevel-info/item/lswt.c
+		*/
+	} else {
+		/* we've received everything we need! */
+		sync->loop = false;
+	}
+}
+
+/* here comes the actual function we can use */
+bool EnumerateWindows(window_proc_t window_proc) {
+	struct sync_data sync = {
+		.display = wl_display_connect(NULL)
+	};
+
+	if (!sync.display) {
+		std::cerr << "animia/wayland: Can't connect to display" << std::endl;
+		return false;
+	}
+
+	struct global_data global;
+
+	struct wl_registry* registry = wl_display_get_registry(sync.display);
+	wl_registry_add_listener(registry, &registry_listener, &sync.global);
+
+	sync.callback = wl_display_sync(sync.display);
+	wl_callback_add_listener(sync.callback, &sync_callback_listener, &sync);
+
+	while (sync.loop && wl_display_dispatch(sync.display));
+
+	for (const auto& window : global.windows)
+		if (!window_proc({0}, window))
+			return false;
+
+	wl_display_disconnect(sync.display);
+
+	return true;
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dep/animia/src/win/wayland/ext-foreign-toplevel-list-v1.c	Tue Jan 02 02:34:27 2024 -0500
@@ -0,0 +1,85 @@
+/* Generated by wayland-scanner 1.21.0 */
+
+/*
+ * Copyright © 2018 Ilia Bozhinov
+ * Copyright © 2020 Isaac Freund
+ * Copyright © 2022 wb9688
+ * Copyright © 2023 i509VCB
+ *
+ * Permission to use, copy, modify, distribute, and sell this
+ * software and its documentation for any purpose is hereby granted
+ * without fee, provided that the above copyright notice appear in
+ * all copies and that both that copyright notice and this permission
+ * notice appear in supporting documentation, and that the name of
+ * the copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include "wayland-util.h"
+
+#ifndef __has_attribute
+# define __has_attribute(x) 0  /* Compatibility with non-clang compilers. */
+#endif
+
+#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
+#define WL_PRIVATE __attribute__ ((visibility("hidden")))
+#else
+#define WL_PRIVATE
+#endif
+
+extern const struct wl_interface ext_foreign_toplevel_handle_v1_interface;
+
+static const struct wl_interface *ext_foreign_toplevel_list_v1_types[] = {
+	NULL,
+	&ext_foreign_toplevel_handle_v1_interface,
+};
+
+static const struct wl_message ext_foreign_toplevel_list_v1_requests[] = {
+	{ "stop", "", ext_foreign_toplevel_list_v1_types + 0 },
+	{ "destroy", "", ext_foreign_toplevel_list_v1_types + 0 },
+};
+
+static const struct wl_message ext_foreign_toplevel_list_v1_events[] = {
+	{ "toplevel", "n", ext_foreign_toplevel_list_v1_types + 1 },
+	{ "finished", "", ext_foreign_toplevel_list_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface ext_foreign_toplevel_list_v1_interface = {
+	"ext_foreign_toplevel_list_v1", 1,
+	2, ext_foreign_toplevel_list_v1_requests,
+	2, ext_foreign_toplevel_list_v1_events,
+};
+
+static const struct wl_message ext_foreign_toplevel_handle_v1_requests[] = {
+	{ "destroy", "", ext_foreign_toplevel_list_v1_types + 0 },
+};
+
+static const struct wl_message ext_foreign_toplevel_handle_v1_events[] = {
+	{ "closed", "", ext_foreign_toplevel_list_v1_types + 0 },
+	{ "done", "", ext_foreign_toplevel_list_v1_types + 0 },
+	{ "title", "s", ext_foreign_toplevel_list_v1_types + 0 },
+	{ "app_id", "s", ext_foreign_toplevel_list_v1_types + 0 },
+	{ "identifier", "s", ext_foreign_toplevel_list_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface ext_foreign_toplevel_handle_v1_interface = {
+	"ext_foreign_toplevel_handle_v1", 1,
+	1, ext_foreign_toplevel_handle_v1_requests,
+	5, ext_foreign_toplevel_handle_v1_events,
+};
+
--- a/dep/animia/src/win/x11.cc	Sun Dec 24 02:59:42 2023 -0500
+++ b/dep/animia/src/win/x11.cc	Tue Jan 02 02:34:27 2024 -0500
@@ -5,6 +5,9 @@
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
 #include <X11/Xatom.h> // XA_*
+#ifdef HAVE_XRES
+#include <X11/extensions/XRes.h>
+#endif
 
 #include <cstdint>
 #include <string>
@@ -16,11 +19,16 @@
 
 namespace animia::internal::x11 {
 
+/* specify that these are X types. */
+typedef ::Window XWindow;
+typedef ::Display XDisplay;
+typedef ::Atom XAtom;
+
 /* should return UTF8_STRING or STRING */
-static bool GetWindowPropertyAsString(::Display* display, ::Window window, ::Atom atom, std::string& result, ::Atom reqtype = AnyPropertyType) {
+static bool GetWindowPropertyAsString(XDisplay* display, XWindow window, XAtom atom, std::string& result, XAtom reqtype = AnyPropertyType) {
 	int format;
 	unsigned long leftover_bytes, num_of_items;
-	::Atom type;
+	XAtom type;
 	unsigned char* data;
 
 	int status = ::XGetWindowProperty(display, window, atom, 0L, (~0L), False, reqtype,
@@ -36,10 +44,35 @@
 }
 
 /* this should return CARDINAL, a 32-bit integer */
-static bool GetWindowPID(::Display* display, ::Window window, pid_t& result) {
+static bool GetWindowPID(XDisplay* display, XWindow window, pid_t& result) {
+#ifdef HAVE_XRES
+	{
+		long num_ids;
+		XResClientIdValue *client_ids;
+		XResClientIdSpec spec = {
+			.client = window,
+			.mask = XRES_CLIENT_ID_PID_MASK
+		};
+
+		::XResQueryClientIds(display, 1, &spec, &num_ids, &client_ids);
+
+		for (long i = 0; i < num_ids; i++) {
+			if (client_ids[i].spec.mask == XRES_CLIENT_ID_PID_MASK) {
+				result = ::XResGetClientPid(&client_ids[i]);
+				::XResClientIdsDestroy(num_ids, client_ids);
+				return true;
+			}
+		}
+
+		::XResClientIdsDestroy(num_ids, client_ids);
+
+		return false;
+	}
+#endif
+
 	int format;
 	unsigned long leftover_bytes, num_of_items;
-	::Atom atom = ::XInternAtom(display, "_NET_WM_PID", False), type;
+	XAtom atom = ::XInternAtom(display, "_NET_WM_PID", False), type;
 	unsigned char* data;
 
 	int status = ::XGetWindowProperty(display, window, atom, 0L, (~0L), False, XA_CARDINAL,
@@ -47,14 +80,14 @@
 	if (status != Success || type != XA_CARDINAL || num_of_items < 1)
 		return false;
 
-	result = static_cast<pid_t>(*(uint32_t*)data);
+	result = static_cast<pid_t>(*reinterpret_cast<uint32_t*>(data));
 
 	::XFree(data);
 
 	return true;
 }
 
-static bool FetchName(::Display* display, ::Window window, std::string& result) {
+static bool FetchName(XDisplay* display, XWindow window, std::string& result) {
 	/* TODO: Check if XInternAtom created None or not... */
 	if (GetWindowPropertyAsString(display, window, ::XInternAtom(display, "_NET_WM_NAME", False),
 		                          result, ::XInternAtom(display, "UTF8_STRING", False)))
@@ -92,19 +125,17 @@
 	return true;
 }
 
-static bool WalkWindows(::Display* display, std::set<::Window>& children, const std::set<::Window>& windows) {
-	/* This sucks. It takes waaaay too long to finish.
-	 * TODO: Look at the code for xwininfo to see what they do.
-	*/
+static bool WalkWindows(XDisplay* display, std::set<XWindow>& children, const std::set<XWindow>& windows) {
+	/* This can take a VERY long time if many windows are open. */
 	if (windows.empty())
 		return false;
 
-	for (const ::Window& window : windows) {
+	for (const XWindow& window : windows) {
 		unsigned int num_children = 0;
-		::Window* children_arr = nullptr;
+		XWindow* children_arr = nullptr;
 
-		::Window root_return;
-		::Window parent_return;
+		XWindow root_return;
+		XWindow parent_return;
 
 		int status = ::XQueryTree(display, window, &root_return, &parent_return, &children_arr, &num_children);
 		if (!status || !children_arr)
@@ -121,7 +152,7 @@
 
 		::XFree(children_arr);
 
-		std::set<::Window> children_children;
+		std::set<XWindow> children_children;
 
 		if (WalkWindows(display, children_children, children))
 			children.insert(children_children.begin(), children_children.end());
@@ -134,13 +165,13 @@
 	if (!window_proc)
 		return false;
 
-	::Display* display = ::XOpenDisplay(nullptr);
+	XDisplay* display = ::XOpenDisplay(nullptr);
 	if (!display)
 		return false;
 
-	::Window root = DefaultRootWindow(display);
+	XWindow root = ::XDefaultRootWindow(display);
 
-	std::set<::Window> windows;
+	std::set<XWindow> windows;
 	WalkWindows(display, windows, {root});
 
 	for (const auto& window : windows) {