changeset 14:27b988a1048c

*: convert all files CRLF -> LF some files were in DOS format, others were in unix. now everything (that at least is under our control) should all be the same format
author Paper <paper@paper.us.eu.org>
date Mon, 13 May 2024 15:04:51 -0400
parents efbb8316395f
children c6351cf654e6
files LICENSE.MIT include/animone.h include/animone/fd.h include/animone/fd/proc.h include/animone/fd/win32.h include/animone/fd/xnu.h include/animone/media.h include/animone/player.h include/animone/strategies.h include/animone/types.h include/animone/util.h include/animone/util/osx.h include/animone/util/win32.h include/animone/win.h include/animone/win/win32.h include/animone/win/x11.h src/animone.cc src/fd.cc src/fd/bsd.cc src/fd/proc.cc src/fd/win32.cc src/fd/xnu.cc src/player.cc src/strategist.cc src/util.cc src/util/osx.cc src/util/win32.cc src/win.cc src/win/win32.cc src/win/x11.cc
diffstat 30 files changed, 2170 insertions(+), 2238 deletions(-) [+]
line wrap: on
line diff
--- a/LICENSE.MIT	Mon May 13 14:15:47 2024 -0400
+++ b/LICENSE.MIT	Mon May 13 15:04:51 2024 -0400
@@ -1,22 +1,22 @@
-MIT License
-
-Copyright (c) 2017 Eren Okka
-Copyright (c) 2023 Paper
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+MIT License
+
+Copyright (c) 2017 Eren Okka
+Copyright (c) 2023 Paper
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- a/include/animone.h	Mon May 13 14:15:47 2024 -0400
+++ b/include/animone.h	Mon May 13 15:04:51 2024 -0400
@@ -1,37 +1,37 @@
-#ifndef ANIMONE_ANIMONE_H_
-#define ANIMONE_ANIMONE_H_
-
-#include "animone/media.h"
-#include "animone/player.h"
-#include "animone/types.h"
-
-namespace animone {
-
-enum class ResultType {
-	Process,
-	Window
-};
-
-struct Process {
-	internal::pid_t pid = 0; /* pid_t == DWORD on Windows, from <sys/types.h> everywhere else */
-	std::string name;
-};
-
-struct Window {
-	unsigned int id = 0;
-	std::string class_name;
-	std::string text; /* title bar text */
-};
-
-struct Result {
-	Player player;
-	Process process;
-	Window window; /* has nothing under process mode */
-	std::vector<Media> media;
-};
-
-bool GetResults(const std::vector<Player>& players, std::vector<Result>& results);
-
-} // namespace animone
-
-#endif // ANIMONE_ANIMONE_H_
+#ifndef ANIMONE_ANIMONE_H_
+#define ANIMONE_ANIMONE_H_
+
+#include "animone/media.h"
+#include "animone/player.h"
+#include "animone/types.h"
+
+namespace animone {
+
+enum class ResultType {
+	Process,
+	Window
+};
+
+struct Process {
+	internal::pid_t pid = 0; /* pid_t == DWORD on Windows, from <sys/types.h> everywhere else */
+	std::string name;
+};
+
+struct Window {
+	unsigned int id = 0;
+	std::string class_name;
+	std::string text; /* title bar text */
+};
+
+struct Result {
+	Player player;
+	Process process;
+	Window window; /* has nothing under process mode */
+	std::vector<Media> media;
+};
+
+bool GetResults(const std::vector<Player>& players, std::vector<Result>& results);
+
+} // namespace animone
+
+#endif // ANIMONE_ANIMONE_H_
--- a/include/animone/fd.h	Mon May 13 14:15:47 2024 -0400
+++ b/include/animone/fd.h	Mon May 13 15:04:51 2024 -0400
@@ -1,33 +1,33 @@
-#ifndef ANIMONE_ANIMONE_FD_H_
-#define ANIMONE_ANIMONE_FD_H_
-
-#include <functional>
-#include <set>
-#include <string>
-
-#include "animone/types.h"
-
-namespace animone {
-
-struct Process;
-
-namespace internal {
-
-struct OpenFile {
-	pid_t pid = 0;
-	std::string path;
-};
-
-using process_proc_t = std::function<bool(const Process&)>;
-
-using open_file_proc_t = std::function<bool(const OpenFile&)>;
-
-bool GetProcessName(pid_t pid, std::string& name);
-bool EnumerateOpenProcesses(process_proc_t process_proc);
-bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc);
-
-} // namespace internal
-
-} // namespace animone
-
-#endif // ANIMONE_ANIMONE_FD_H_
+#ifndef ANIMONE_ANIMONE_FD_H_
+#define ANIMONE_ANIMONE_FD_H_
+
+#include <functional>
+#include <set>
+#include <string>
+
+#include "animone/types.h"
+
+namespace animone {
+
+struct Process;
+
+namespace internal {
+
+struct OpenFile {
+	pid_t pid = 0;
+	std::string path;
+};
+
+using process_proc_t = std::function<bool(const Process&)>;
+
+using open_file_proc_t = std::function<bool(const OpenFile&)>;
+
+bool GetProcessName(pid_t pid, std::string& name);
+bool EnumerateOpenProcesses(process_proc_t process_proc);
+bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc);
+
+} // namespace internal
+
+} // namespace animone
+
+#endif // ANIMONE_ANIMONE_FD_H_
--- a/include/animone/fd/proc.h	Mon May 13 14:15:47 2024 -0400
+++ b/include/animone/fd/proc.h	Mon May 13 15:04:51 2024 -0400
@@ -1,18 +1,18 @@
-#ifndef ANIMONE_ANIMONE_FD_PROC_H_
-#define ANIMONE_ANIMONE_FD_PROC_H_
-
-#include <set>
-#include <string>
-
-#include "animone/fd.h"
-#include "animone/types.h"
-
-namespace animone::internal::proc {
-
-bool EnumerateOpenProcesses(process_proc_t process_proc);
-bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc);
-bool GetProcessName(pid_t pid, std::string& result);
-
-} // namespace animone::internal::proc
-
-#endif // ANIMONE_ANIMONE_FD_PROC_H_
+#ifndef ANIMONE_ANIMONE_FD_PROC_H_
+#define ANIMONE_ANIMONE_FD_PROC_H_
+
+#include <set>
+#include <string>
+
+#include "animone/fd.h"
+#include "animone/types.h"
+
+namespace animone::internal::proc {
+
+bool EnumerateOpenProcesses(process_proc_t process_proc);
+bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc);
+bool GetProcessName(pid_t pid, std::string& result);
+
+} // namespace animone::internal::proc
+
+#endif // ANIMONE_ANIMONE_FD_PROC_H_
--- a/include/animone/fd/win32.h	Mon May 13 14:15:47 2024 -0400
+++ b/include/animone/fd/win32.h	Mon May 13 15:04:51 2024 -0400
@@ -1,18 +1,18 @@
-#ifndef ANIMONE_ANIMONE_FD_WIN32_H_
-#define ANIMONE_ANIMONE_FD_WIN32_H_
-
-#include <set>
-#include <string>
-
-#include "animone/fd.h"
-#include "animone/types.h"
-
-namespace animone::internal::win32 {
-
-bool GetProcessName(pid_t pid, std::string& name);
-bool EnumerateOpenProcesses(process_proc_t process_proc);
-bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc);
-
-} // namespace animone::internal::win32
-
-#endif // ANIMONE_ANIMONE_FD_WIN32_H_
+#ifndef ANIMONE_ANIMONE_FD_WIN32_H_
+#define ANIMONE_ANIMONE_FD_WIN32_H_
+
+#include <set>
+#include <string>
+
+#include "animone/fd.h"
+#include "animone/types.h"
+
+namespace animone::internal::win32 {
+
+bool GetProcessName(pid_t pid, std::string& name);
+bool EnumerateOpenProcesses(process_proc_t process_proc);
+bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc);
+
+} // namespace animone::internal::win32
+
+#endif // ANIMONE_ANIMONE_FD_WIN32_H_
--- a/include/animone/fd/xnu.h	Mon May 13 14:15:47 2024 -0400
+++ b/include/animone/fd/xnu.h	Mon May 13 15:04:51 2024 -0400
@@ -1,18 +1,18 @@
-#ifndef ANIMONE_ANIMONE_FD_XNU_H_
-#define ANIMONE_ANIMONE_FD_XNU_H_
-
-#include <set>
-#include <string>
-
-#include "animone/fd.h"
-#include "animone/types.h"
-
-namespace animone::internal::xnu {
-
-bool EnumerateOpenProcesses(process_proc_t process_proc);
-bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc);
-bool GetProcessName(pid_t pid, std::string& result);
-
-} // namespace animone::internal::xnu
-
-#endif // ANIMONE_ANIMONE_FD_XNU_H_
+#ifndef ANIMONE_ANIMONE_FD_XNU_H_
+#define ANIMONE_ANIMONE_FD_XNU_H_
+
+#include <set>
+#include <string>
+
+#include "animone/fd.h"
+#include "animone/types.h"
+
+namespace animone::internal::xnu {
+
+bool EnumerateOpenProcesses(process_proc_t process_proc);
+bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc);
+bool GetProcessName(pid_t pid, std::string& result);
+
+} // namespace animone::internal::xnu
+
+#endif // ANIMONE_ANIMONE_FD_XNU_H_
--- a/include/animone/media.h	Mon May 13 14:15:47 2024 -0400
+++ b/include/animone/media.h	Mon May 13 15:04:51 2024 -0400
@@ -1,32 +1,32 @@
-#ifndef ANIMONE_ANIMONE_MEDIA_H_
-#define ANIMONE_ANIMONE_MEDIA_H_
-
-#include <chrono>
-#include <functional>
-#include <string>
-#include <vector>
-
-namespace animone {
-
-using media_time_t = std::chrono::milliseconds;
-
-enum class MediaInfoType {
-	Unknown,
-	File,
-	Tab,
-	Title,
-	Url
-};
-
-struct MediaInfo {
-	MediaInfoType type = MediaInfoType::Unknown;
-	std::string value;
-};
-
-struct Media {
-	std::vector<MediaInfo> information;
-};
-
-} // namespace animone
-
-#endif // ANIMONE_ANIMONE_MEDIA_H_
+#ifndef ANIMONE_ANIMONE_MEDIA_H_
+#define ANIMONE_ANIMONE_MEDIA_H_
+
+#include <chrono>
+#include <functional>
+#include <string>
+#include <vector>
+
+namespace animone {
+
+using media_time_t = std::chrono::milliseconds;
+
+enum class MediaInfoType {
+	Unknown,
+	File,
+	Tab,
+	Title,
+	Url
+};
+
+struct MediaInfo {
+	MediaInfoType type = MediaInfoType::Unknown;
+	std::string value;
+};
+
+struct Media {
+	std::vector<MediaInfo> information;
+};
+
+} // namespace animone
+
+#endif // ANIMONE_ANIMONE_MEDIA_H_
--- a/include/animone/player.h	Mon May 13 14:15:47 2024 -0400
+++ b/include/animone/player.h	Mon May 13 15:04:51 2024 -0400
@@ -1,34 +1,34 @@
-#ifndef ANIMONE_ANIMONE_PLAYER_H_
-#define ANIMONE_ANIMONE_PLAYER_H_
-
-#include <string>
-#include <vector>
-
-namespace animone {
-
-enum class Strategy {
-	WindowTitle,
-	OpenFiles,
-	UiAutomation // unused
-};
-
-enum class PlayerType {
-	Default,
-	WebBrowser // unused
-};
-
-struct Player {
-	PlayerType type = PlayerType::Default;
-	std::string name;
-	std::string window_title_format;
-	std::vector<std::string> windows;
-	std::vector<std::string> executables;
-	std::vector<Strategy> strategies;
-};
-
-bool ParsePlayersData(const std::string& data, std::vector<Player>& players);
-bool ParsePlayersFile(const std::string& path, std::vector<Player>& players);
-
-} // namespace animone
-
+#ifndef ANIMONE_ANIMONE_PLAYER_H_
+#define ANIMONE_ANIMONE_PLAYER_H_
+
+#include <string>
+#include <vector>
+
+namespace animone {
+
+enum class Strategy {
+	WindowTitle,
+	OpenFiles,
+	UiAutomation // unused
+};
+
+enum class PlayerType {
+	Default,
+	WebBrowser // unused
+};
+
+struct Player {
+	PlayerType type = PlayerType::Default;
+	std::string name;
+	std::string window_title_format;
+	std::vector<std::string> windows;
+	std::vector<std::string> executables;
+	std::vector<Strategy> strategies;
+};
+
+bool ParsePlayersData(const std::string& data, std::vector<Player>& players);
+bool ParsePlayersFile(const std::string& path, std::vector<Player>& players);
+
+} // namespace animone
+
 #endif // ANIMONE_ANIMONE_PLAYER_H_
\ No newline at end of file
--- a/include/animone/strategies.h	Mon May 13 14:15:47 2024 -0400
+++ b/include/animone/strategies.h	Mon May 13 15:04:51 2024 -0400
@@ -1,13 +1,13 @@
-#ifndef ANIMONE_ANIMONE_STRATEGIES_H_
-#define ANIMONE_ANIMONE_STRATEGIES_H_
-
-#include "animone.h"
-#include <vector>
-
-namespace animone::internal {
-
-bool ApplyStrategies(std::vector<Result>& results);
-
-}
-
+#ifndef ANIMONE_ANIMONE_STRATEGIES_H_
+#define ANIMONE_ANIMONE_STRATEGIES_H_
+
+#include "animone.h"
+#include <vector>
+
+namespace animone::internal {
+
+bool ApplyStrategies(std::vector<Result>& results);
+
+}
+
 #endif // ANIMONE_ANIMONE_STRATEGIES_H_
\ No newline at end of file
--- a/include/animone/types.h	Mon May 13 14:15:47 2024 -0400
+++ b/include/animone/types.h	Mon May 13 15:04:51 2024 -0400
@@ -1,18 +1,18 @@
-#ifndef ANIMONE_ANIMONE_TYPES_H_
-#define ANIMONE_ANIMONE_TYPES_H_
-
-/* define this as unsigned long (DWORD) on win32 so we
-   don't force the user to include <windows.h> or <IntBase.h> */
-#ifdef _WIN32
-namespace animone::internal {
-typedef unsigned long pid_t;
-}
-#else
-/* <sys/types.h> shouldn't be that big, right? */
-#	include <sys/types.h>
-namespace animone::internal {
-typedef ::pid_t pid_t;
-}
-#endif
-
-#endif // ANIMONE_ANIMONE_TYPES_H_
+#ifndef ANIMONE_ANIMONE_TYPES_H_
+#define ANIMONE_ANIMONE_TYPES_H_
+
+/* define this as unsigned long (DWORD) on win32 so we
+   don't force the user to include <windows.h> or <IntBase.h> */
+#ifdef _WIN32
+namespace animone::internal {
+typedef unsigned long pid_t;
+}
+#else
+/* <sys/types.h> shouldn't be that big, right? */
+#	include <sys/types.h>
+namespace animone::internal {
+typedef ::pid_t pid_t;
+}
+#endif
+
+#endif // ANIMONE_ANIMONE_TYPES_H_
--- a/include/animone/util.h	Mon May 13 14:15:47 2024 -0400
+++ b/include/animone/util.h	Mon May 13 15:04:51 2024 -0400
@@ -1,25 +1,25 @@
-#ifndef ANIMONE_ANIMONE_UTIL_H_
-#define ANIMONE_ANIMONE_UTIL_H_
-
-#include <sstream>
-#include <string>
-
-namespace animone::internal::util {
-
-bool ReadFile(const std::string& path, std::string& data);
-bool EqualStrings(const std::string& str1, const std::string& str2);
-bool Stem(const std::string& filename, std::string& stem);
-bool CheckPattern(const std::string& pattern, const std::string& str);
-bool TrimLeft(std::string& str, const char* chars);
-bool TrimRight(std::string& str, const char* chars);
-
-template<typename T = int, std::enable_if_t<std::is_integral<T>::value, bool> = true>
-T StringToInt(const std::string& str, T def = 0) {
-	std::istringstream s(str);
-	s >> std::noboolalpha >> def;
-	return def;
-}
-
-} // namespace animone::internal::util
-
-#endif // ANIMONE_ANIMONE_UTIL_H_
+#ifndef ANIMONE_ANIMONE_UTIL_H_
+#define ANIMONE_ANIMONE_UTIL_H_
+
+#include <sstream>
+#include <string>
+
+namespace animone::internal::util {
+
+bool ReadFile(const std::string& path, std::string& data);
+bool EqualStrings(const std::string& str1, const std::string& str2);
+bool Stem(const std::string& filename, std::string& stem);
+bool CheckPattern(const std::string& pattern, const std::string& str);
+bool TrimLeft(std::string& str, const char* chars);
+bool TrimRight(std::string& str, const char* chars);
+
+template<typename T = int, std::enable_if_t<std::is_integral<T>::value, bool> = true>
+T StringToInt(const std::string& str, T def = 0) {
+	std::istringstream s(str);
+	s >> std::noboolalpha >> def;
+	return def;
+}
+
+} // namespace animone::internal::util
+
+#endif // ANIMONE_ANIMONE_UTIL_H_
--- a/include/animone/util/osx.h	Mon May 13 14:15:47 2024 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-#ifndef ANIMONE_ANIMONE_UTIL_OSX_H_
-#define ANIMONE_ANIMONE_UTIL_OSX_H_
-
-#include "animone/types.h"
-#include <cstdint>
-#include <string>
-#include <memory>
-
-#include <CoreFoundation/CoreFoundation.h>
-
-namespace animone::internal::osx::util {
-
-bool GetProcessName(pid_t pid, std::string& result);
-
-} // namespace animone::internal::osx::util
-
-#endif // ANIMONE_ANIMONE_UTIL_OSX_H_
--- a/include/animone/util/win32.h	Mon May 13 14:15:47 2024 -0400
+++ b/include/animone/util/win32.h	Mon May 13 15:04:51 2024 -0400
@@ -1,35 +1,35 @@
-#ifndef ANIMONE_ANIMONE_UTIL_WIN32_H_
-#define ANIMONE_ANIMONE_UTIL_WIN32_H_
-
-#include <windef.h>
-#include <subauth.h>
-#include <handleapi.h>
-
-#include <memory>
-#include <string>
-
-namespace animone::internal::win32 {
-
-struct HandleDeconstructor {
-	using pointer = HANDLE;
-	void operator()(pointer t) const { ::CloseHandle(t); };
-};
-
-using Handle = std::unique_ptr<HANDLE, HandleDeconstructor>;
-
-/* ----------------------------------------------- */
-
-std::string ToUtf8String(const std::wstring& string);
-std::string ToUtf8String(const UNICODE_STRING& string);
-std::wstring ToWstring(const std::string& string);
-
-std::wstring GetProcessPath(DWORD process_id);
-std::wstring GetFileNameFromPath(const std::wstring& path);
-std::wstring GetFileNameWithoutExtension(const std::wstring& filename);
-
-bool IsSystemDirectory(const std::string& path);
-bool IsSystemDirectory(std::wstring path);
-
-} // namespace animone::internal::win32
-
+#ifndef ANIMONE_ANIMONE_UTIL_WIN32_H_
+#define ANIMONE_ANIMONE_UTIL_WIN32_H_
+
+#include <windef.h>
+#include <subauth.h>
+#include <handleapi.h>
+
+#include <memory>
+#include <string>
+
+namespace animone::internal::win32 {
+
+struct HandleDeconstructor {
+	using pointer = HANDLE;
+	void operator()(pointer t) const { ::CloseHandle(t); };
+};
+
+using Handle = std::unique_ptr<HANDLE, HandleDeconstructor>;
+
+/* ----------------------------------------------- */
+
+std::string ToUtf8String(const std::wstring& string);
+std::string ToUtf8String(const UNICODE_STRING& string);
+std::wstring ToWstring(const std::string& string);
+
+std::wstring GetProcessPath(DWORD process_id);
+std::wstring GetFileNameFromPath(const std::wstring& path);
+std::wstring GetFileNameWithoutExtension(const std::wstring& filename);
+
+bool IsSystemDirectory(const std::string& path);
+bool IsSystemDirectory(std::wstring path);
+
+} // namespace animone::internal::win32
+
 #endif // ANIMONE_ANIMONE_UTIL_WIN32_H_
\ No newline at end of file
--- a/include/animone/win.h	Mon May 13 14:15:47 2024 -0400
+++ b/include/animone/win.h	Mon May 13 15:04:51 2024 -0400
@@ -1,22 +1,22 @@
-#ifndef ANIMONE_ANIMONE_WIN_H_
-#define ANIMONE_ANIMONE_WIN_H_
-
-#include <functional>
-#include <string>
-
-namespace animone {
-
-struct Process;
-struct Window;
-
-namespace internal {
-
-using window_proc_t = std::function<bool(const Process&, const Window&)>;
-
-bool EnumerateWindows(window_proc_t window_proc);
-
-} // namespace internal
-
-} // namespace animone
-
-#endif // ANIMONE_ANIMONE_WIN_H_
+#ifndef ANIMONE_ANIMONE_WIN_H_
+#define ANIMONE_ANIMONE_WIN_H_
+
+#include <functional>
+#include <string>
+
+namespace animone {
+
+struct Process;
+struct Window;
+
+namespace internal {
+
+using window_proc_t = std::function<bool(const Process&, const Window&)>;
+
+bool EnumerateWindows(window_proc_t window_proc);
+
+} // namespace internal
+
+} // namespace animone
+
+#endif // ANIMONE_ANIMONE_WIN_H_
--- a/include/animone/win/win32.h	Mon May 13 14:15:47 2024 -0400
+++ b/include/animone/win/win32.h	Mon May 13 15:04:51 2024 -0400
@@ -1,12 +1,12 @@
-#ifndef ANIMONE_ANIMONE_WIN_WIN32_H_
-#define ANIMONE_ANIMONE_WIN_WIN32_H_
-
-#include "animone/win.h"
-
-namespace animone::internal::win32 {
-
-bool EnumerateWindows(window_proc_t window_proc);
-
-} // namespace animone::internal::win32
-
-#endif // ANIMONE_ANIMONE_WIN_WIN32_H_
+#ifndef ANIMONE_ANIMONE_WIN_WIN32_H_
+#define ANIMONE_ANIMONE_WIN_WIN32_H_
+
+#include "animone/win.h"
+
+namespace animone::internal::win32 {
+
+bool EnumerateWindows(window_proc_t window_proc);
+
+} // namespace animone::internal::win32
+
+#endif // ANIMONE_ANIMONE_WIN_WIN32_H_
--- a/include/animone/win/x11.h	Mon May 13 14:15:47 2024 -0400
+++ b/include/animone/win/x11.h	Mon May 13 15:04:51 2024 -0400
@@ -1,12 +1,12 @@
-#ifndef ANIMONE_ANIMONE_WIN_X11_H_
-#define ANIMONE_ANIMONE_WIN_X11_H_
-
-#include "animone/win.h"
-
-namespace animone::internal::x11 {
-
-bool EnumerateWindows(window_proc_t window_proc);
-
-} // namespace animone::internal::x11
-
-#endif // ANIMONE_ANIMONE_WIN_X11_H_
+#ifndef ANIMONE_ANIMONE_WIN_X11_H_
+#define ANIMONE_ANIMONE_WIN_X11_H_
+
+#include "animone/win.h"
+
+namespace animone::internal::x11 {
+
+bool EnumerateWindows(window_proc_t window_proc);
+
+} // namespace animone::internal::x11
+
+#endif // ANIMONE_ANIMONE_WIN_X11_H_
--- a/src/animone.cc	Mon May 13 14:15:47 2024 -0400
+++ b/src/animone.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,71 +1,71 @@
-#include "animone.h"
-#include "animone/fd.h"
-#include "animone/strategies.h"
-#include "animone/types.h"
-#include "animone/util.h"
-#include "animone/win.h"
-
-#include <set>
-#include <string>
-#include <vector>
-
-#include <iostream>
-
-namespace animone {
-
-namespace internal {
-
-static bool IsExecutableInList(const Player& player, const Process& proc) {
-	for (const auto& pattern : player.executables)
-		if (util::CheckPattern(pattern, proc.name))
-			return true;
-
-	return false;
-}
-
-static bool IsWindowInList(const Player& player, const Window& window) {
-	for (const auto& pattern : player.windows)
-		if (util::CheckPattern(pattern, window.class_name))
-			return true;
-
-	return false;
-}
-
-static bool PlayerHasStrategy(const Player& player, const Strategy& strategy) {
-	for (const auto& pstrategy : player.strategies)
-		if (pstrategy == strategy)
-			return true;
-
-	return false;
-}
-
-} // namespace internal
-
-bool GetResults(const std::vector<Player>& players, std::vector<Result>& results) {
-	auto window_proc = [&](const Process& process, const Window& window) -> bool {
-		for (const auto& player : players)
-			if (internal::IsWindowInList(player, window) && internal::IsExecutableInList(player, process))
-				results.push_back({player, process, window, {}});
-
-		return true;
-	};
-
-	if (internal::EnumerateWindows(window_proc) && internal::ApplyStrategies(results))
-		return true;
-
-	/* fallback, enumerate over open processes instead */
-	auto process_proc = [&](const Process& process) -> bool {
-		for (const auto& player : players)
-			if (internal::IsExecutableInList(player, process))
-				results.push_back({player, process, {}, {}});
-
-		return true;
-	};
-
-	if (internal::EnumerateOpenProcesses(process_proc) && internal::ApplyStrategies(results))
-		return true;
-
-	return false;
-}
-
-} // namespace animone
+#include "animone.h"
+#include "animone/fd.h"
+#include "animone/strategies.h"
+#include "animone/types.h"
+#include "animone/util.h"
+#include "animone/win.h"
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <iostream>
+
+namespace animone {
+
+namespace internal {
+
+static bool IsExecutableInList(const Player& player, const Process& proc) {
+	for (const auto& pattern : player.executables)
+		if (util::CheckPattern(pattern, proc.name))
+			return true;
+
+	return false;
+}
+
+static bool IsWindowInList(const Player& player, const Window& window) {
+	for (const auto& pattern : player.windows)
+		if (util::CheckPattern(pattern, window.class_name))
+			return true;
+
+	return false;
+}
+
+static bool PlayerHasStrategy(const Player& player, const Strategy& strategy) {
+	for (const auto& pstrategy : player.strategies)
+		if (pstrategy == strategy)
+			return true;
+
+	return false;
+}
+
+} // namespace internal
+
+bool GetResults(const std::vector<Player>& players, std::vector<Result>& results) {
+	auto window_proc = [&](const Process& process, const Window& window) -> bool {
+		for (const auto& player : players)
+			if (internal::IsWindowInList(player, window) && internal::IsExecutableInList(player, process))
+				results.push_back({player, process, window, {}});
+
+		return true;
+	};
+
+	if (internal::EnumerateWindows(window_proc) && internal::ApplyStrategies(results))
+		return true;
+
+	/* fallback, enumerate over open processes instead */
+	auto process_proc = [&](const Process& process) -> bool {
+		for (const auto& player : players)
+			if (internal::IsExecutableInList(player, process))
+				results.push_back({player, process, {}, {}});
+
+		return true;
+	};
+
+	if (internal::EnumerateOpenProcesses(process_proc) && internal::ApplyStrategies(results))
+		return true;
+
+	return false;
+}
+
+} // namespace animone
--- a/src/fd.cc	Mon May 13 14:15:47 2024 -0400
+++ b/src/fd.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,87 +1,87 @@
-#include "animone/fd.h"
-
-#ifdef WIN32
-#	include "animone/fd/win32.h"
-#endif
-
-#ifdef LINUX
-#	include "animone/fd/proc.h"
-#endif
-
-#ifdef MACOSX
-#	include "animone/fd/xnu.h"
-#endif
-
-#ifdef BSD
-#	include "animone/fd/bsd.h"
-#endif
-
-namespace animone::internal {
-
-bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) {
-	bool success = false;
-
-#ifdef WIN32
-	success ^= win32::EnumerateOpenFiles(pids, open_file_proc);
-#endif
-
-#ifdef LINUX
-	success ^= proc::EnumerateOpenFiles(pids, open_file_proc);
-#endif
-
-#ifdef MACOSX
-	success ^= xnu::EnumerateOpenFiles(pids, open_file_proc);
-#endif
-
-#ifdef BSD
-	success ^= bsd::EnumerateOpenFiles(pids, open_file_proc);
-#endif
-
-	return success;
-}
-
-bool EnumerateOpenProcesses(process_proc_t process_proc) {
-	bool success = false;
-
-#ifdef WIN32
-	success ^= win32::EnumerateOpenProcesses(process_proc);
-#endif
-
-#ifdef LINUX
-	success ^= proc::EnumerateOpenProcesses(process_proc);
-#endif
-
-#ifdef MACOSX
-	success ^= xnu::EnumerateOpenProcesses(process_proc);
-#endif
-
-#ifdef BSD
-	success ^= bsd::EnumerateOpenProcesses(process_proc);
-#endif
-
-	return success;
-}
-
-bool GetProcessName(pid_t pid, std::string& name) {
-	bool success = false;
-
-#ifdef WIN32
-	success ^= win32::GetProcessName(pid, name);
-#endif
-
-#ifdef LINUX
-	success ^= proc::GetProcessName(pid, name);
-#endif
-
-#ifdef MACOSX
-	success ^= xnu::GetProcessName(pid, name);
-#endif
-
-#ifdef BSD
-	success ^= bsd::GetProcessName(pid, name);
-#endif
-
-	return success;
-}
-
-} // namespace animone::internal
+#include "animone/fd.h"
+
+#ifdef WIN32
+#	include "animone/fd/win32.h"
+#endif
+
+#ifdef LINUX
+#	include "animone/fd/proc.h"
+#endif
+
+#ifdef MACOSX
+#	include "animone/fd/xnu.h"
+#endif
+
+#ifdef BSD
+#	include "animone/fd/bsd.h"
+#endif
+
+namespace animone::internal {
+
+bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) {
+	bool success = false;
+
+#ifdef WIN32
+	success ^= win32::EnumerateOpenFiles(pids, open_file_proc);
+#endif
+
+#ifdef LINUX
+	success ^= proc::EnumerateOpenFiles(pids, open_file_proc);
+#endif
+
+#ifdef MACOSX
+	success ^= xnu::EnumerateOpenFiles(pids, open_file_proc);
+#endif
+
+#ifdef BSD
+	success ^= bsd::EnumerateOpenFiles(pids, open_file_proc);
+#endif
+
+	return success;
+}
+
+bool EnumerateOpenProcesses(process_proc_t process_proc) {
+	bool success = false;
+
+#ifdef WIN32
+	success ^= win32::EnumerateOpenProcesses(process_proc);
+#endif
+
+#ifdef LINUX
+	success ^= proc::EnumerateOpenProcesses(process_proc);
+#endif
+
+#ifdef MACOSX
+	success ^= xnu::EnumerateOpenProcesses(process_proc);
+#endif
+
+#ifdef BSD
+	success ^= bsd::EnumerateOpenProcesses(process_proc);
+#endif
+
+	return success;
+}
+
+bool GetProcessName(pid_t pid, std::string& name) {
+	bool success = false;
+
+#ifdef WIN32
+	success ^= win32::GetProcessName(pid, name);
+#endif
+
+#ifdef LINUX
+	success ^= proc::GetProcessName(pid, name);
+#endif
+
+#ifdef MACOSX
+	success ^= xnu::GetProcessName(pid, name);
+#endif
+
+#ifdef BSD
+	success ^= bsd::GetProcessName(pid, name);
+#endif
+
+	return success;
+}
+
+} // namespace animone::internal
--- a/src/fd/bsd.cc	Mon May 13 14:15:47 2024 -0400
+++ b/src/fd/bsd.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,225 +1,225 @@
-#include "animone/fd/bsd.h"
-#include "animone.h"
-#include "animone/fd.h"
-
-#include <sys/file.h>
-#include <sys/filedesc.h>
-#include <sys/param.h>
-#include <sys/queue.h>
-#include <sys/sysctl.h>
-#include <sys/types.h>
-#include <sys/user.h>
-#include <sys/vnode.h>
-
-#ifdef HAVE_KVM_GETFILES
-#	include <kvm.h>
-#	include <fts.h>
-#elif defined(LIBUTIL)
-#	include <libutil.h>
-#endif
-
-#include <string>
-
-namespace animone::internal::bsd {
-
-static std::string Basename(const std::string& name) {
-	size_t s = name.find_last_of('/');
-
-	if (s == std::string::npos)
-		return name;
-
-	return name.substr(s, name.size());
-}
-
-bool GetProcessName(pid_t pid, std::string& name) {
-#ifdef HAVE_KVM_GETFILES
-	char errbuf[_POSIX2_LINE_MAX];
-	kvm_t* kernel = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf);
-	if (!kernel)
-		return false;
-
-	int entries = 0;
-	struct kinfo_proc* kinfo = kvm_getprocs(kernel, KERN_PROC_PID, pid, &entries);
-	if (!kinfo) {
-		kvm_close(kernel);
-		return false;
-	}
-
-	if (entries < 1) {
-		kvm_close(kernel);
-		return false;
-	}
-
-	name = Basename(kinfo[0].ki_paddr->p_comm);
-
-	return true;
-#else
-	/* use sysctl as a fallback */
-	static const int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
-
-	struct kinfo_proc result;
-
-	size_t length = 1;
-	if (sysctl((int*)mib, (sizeof(mib) / sizeof(*mib)) - 1, &result, &length, NULL, 0) == -1)
-		return false;
-
-	name = Basename(result.ki_comm);
-
-	return true;
-#endif
-}
-
-/* Most of the BSDs share the common kvm library,
- * so accessing this information can be trivial.
- */
-bool EnumerateOpenProcesses(process_proc_t process_proc) {
-#ifdef HAVE_KVM_GETFILES
-	char errbuf[_POSIX2_LINE_MAX];
-	kvm_t* kernel = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf);
-	if (!kernel)
-		return false;
-
-	int entries = 0;
-	struct kinfo_proc* kinfo = kvm_getprocs(kernel, KERN_PROC_ALL, 0, &entries);
-	if (!kinfo) {
-		kvm_close(kernel);
-		return false;
-	}
-
-	for (int i = 0; i < entries; i++) {
-		if (!process_proc({kinfo[i].ki_paddr->p_pid, Basename(kinfo[i].ki_paddr->p_comm)})) {
-			kvm_close(kernel);
-			return false;
-		}
-	}
-
-	kvm_close(kernel);
-
-	return true;
-#else
-	/* use sysctl as a fallback */
-	static const int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
-	size_t length = 0;
-
-	sysctl((int*)mib, (sizeof(mib) / sizeof(*mib)) - 1, NULL, &length, NULL, 0);
-
-	std::unique_ptr<struct kinfo_proc[]> result;
-	result.reset(new struct kinfo_proc[length]);
-
-	if (!result.get())
-		return false;
-
-	/* actually get our results */
-	if (sysctl((const int*)mib, (sizeof(mib) / sizeof(*mib)) - 1, result.get(), &length, NULL, 0) == ENOMEM)
-		return false;
-
-	if (length < sizeof(struct kinfo_proc))
-		return false;
-
-	for (int i = 0; i < length / sizeof(result[0]); i++)
-		if (!process_proc({result[i].ki_pid, result[i].ki_comm}))
-			return false;
-
-	return true;
-#endif
-}
-
-#ifdef HAVE_KVM_GETFILES
-static bool GetOpenFileName(const struct kinfo_file& file, std::string& name) {
-	/* OpenBSD doesn't provide a native API for this, so we have
-	 * to do it ourselves */
-	static constexpr std::string_view root = "/";
-
-	FTS* file_system = fts_open(root.data(), FTS_COMFOLLOW | FTS_NOCHDIR, nullptr);
-	if (!file_system)
-		return false;
-
-	/* Search through the filesystem for a file that matches our
-	 * kinfo_file structure */
-	FTSENT* parent = nullptr;
-	while ((parent = fts_read(file_system))) {
-		FTSENT* child = fts_children(file_system, 0);
-		while (child && child->fts_link) {
-			child = child->fts_link;
-			if (!S_ISREG(child->fts_statp->st_mode) || !S_ISLNK(child->fts_statp->st_mode))
-				continue;
-
-			if (child->fts_statp->st_dev != file->va_fsid)
-				continue;
-
-			if (child->fts_statp->st_ino != file->va_fileid)
-				continue;
-
-			name = std::string(child->fts_path) + child->fts_name;
-			fts_close(file_system);
-			return true;
-		}
-	}
-
-	fts_close(filesystem);
-	return false;
-}
-#endif /* HAVE_KVM_GETFILES */
-
-bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) {
-#ifdef HAVE_KVM_GETFILES
-	char errbuf[_POSIX2_LINE_MAX];
-	kvm_t* kernel = kvm_openfiles(nullptr, nullptr, nullptr, O_RDONLY, errbuf);
-	if (!kernel)
-		return false;
-
-	for (const auto& pid : pids) {
-		int cnt;
-		struct kinfo_file* kfile = kvm_getfiles(kernel, KERN_FILE_BYPID, pid, &cnt);
-		if (!kfile) {
-			kvm_close(kernel);
-			return false;
-		}
-
-		for (int i = 0; i < cnt; i++) {
-			uint32_t oflags = kfile[i].kf_flags & O_ACCMODE;
-			if (oflags == O_WRONLY || oflags == O_RDWR)
-				continue;
-
-			std::string name;
-			if (!GetOpenFileName(kfile[i], name))
-				continue;
-
-			if (!open_file_proc({pid, name})) {
-				kvm_close(kernel);
-				return false;
-			}
-		}
-	}
-
-	kvm_close(kernel);
-	return true;
-#elif defined(LIBUTIL)
-	for (const auto& pid : pids) {
-		int cnt;
-		std::unique_ptr<struct kinfo_file[]> files(kinfo_getfile(pid, &cnt));
-		if (!files)
-			return false;
-
-		for (int i = 0; i < cnt; i++) {
-			const struct kinfo_file& current = files[i];
-			if (current.kf_vnode_type != KF_VTYPE_VREG || current.kf_vnode_type != KF_VTYPE_VLNK)
-				continue;
-
-			const int oflags = current.kf_flags & O_ACCMODE;
-			if (oflags == O_WRONLY || oflags == O_RDWR)
-				continue;
-
-			if (!open_file_proc({pid, current.kf_path}))
-				return false;
-		}
-	}
-
-	return true;
-#else
-	/* NetBSD doesn't even provide a real API for this */
-	return false;
-#endif
-}
-
-} // namespace animone::internal::kvm
+#include "animone/fd/bsd.h"
+#include "animone.h"
+#include "animone/fd.h"
+
+#include <sys/file.h>
+#include <sys/filedesc.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <sys/user.h>
+#include <sys/vnode.h>
+
+#ifdef HAVE_KVM_GETFILES
+#	include <kvm.h>
+#	include <fts.h>
+#elif defined(LIBUTIL)
+#	include <libutil.h>
+#endif
+
+#include <string>
+
+namespace animone::internal::bsd {
+
+static std::string Basename(const std::string& name) {
+	size_t s = name.find_last_of('/');
+
+	if (s == std::string::npos)
+		return name;
+
+	return name.substr(s, name.size());
+}
+
+bool GetProcessName(pid_t pid, std::string& name) {
+#ifdef HAVE_KVM_GETFILES
+	char errbuf[_POSIX2_LINE_MAX];
+	kvm_t* kernel = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf);
+	if (!kernel)
+		return false;
+
+	int entries = 0;
+	struct kinfo_proc* kinfo = kvm_getprocs(kernel, KERN_PROC_PID, pid, &entries);
+	if (!kinfo) {
+		kvm_close(kernel);
+		return false;
+	}
+
+	if (entries < 1) {
+		kvm_close(kernel);
+		return false;
+	}
+
+	name = Basename(kinfo[0].ki_paddr->p_comm);
+
+	return true;
+#else
+	/* use sysctl as a fallback */
+	static const int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
+
+	struct kinfo_proc result;
+
+	size_t length = 1;
+	if (sysctl((int*)mib, (sizeof(mib) / sizeof(*mib)) - 1, &result, &length, NULL, 0) == -1)
+		return false;
+
+	name = Basename(result.ki_comm);
+
+	return true;
+#endif
+}
+
+/* Most of the BSDs share the common kvm library,
+ * so accessing this information can be trivial.
+ */
+bool EnumerateOpenProcesses(process_proc_t process_proc) {
+#ifdef HAVE_KVM_GETFILES
+	char errbuf[_POSIX2_LINE_MAX];
+	kvm_t* kernel = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf);
+	if (!kernel)
+		return false;
+
+	int entries = 0;
+	struct kinfo_proc* kinfo = kvm_getprocs(kernel, KERN_PROC_ALL, 0, &entries);
+	if (!kinfo) {
+		kvm_close(kernel);
+		return false;
+	}
+
+	for (int i = 0; i < entries; i++) {
+		if (!process_proc({kinfo[i].ki_paddr->p_pid, Basename(kinfo[i].ki_paddr->p_comm)})) {
+			kvm_close(kernel);
+			return false;
+		}
+	}
+
+	kvm_close(kernel);
+
+	return true;
+#else
+	/* use sysctl as a fallback */
+	static const int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
+	size_t length = 0;
+
+	sysctl((int*)mib, (sizeof(mib) / sizeof(*mib)) - 1, NULL, &length, NULL, 0);
+
+	std::unique_ptr<struct kinfo_proc[]> result;
+	result.reset(new struct kinfo_proc[length]);
+
+	if (!result.get())
+		return false;
+
+	/* actually get our results */
+	if (sysctl((const int*)mib, (sizeof(mib) / sizeof(*mib)) - 1, result.get(), &length, NULL, 0) == ENOMEM)
+		return false;
+
+	if (length < sizeof(struct kinfo_proc))
+		return false;
+
+	for (int i = 0; i < length / sizeof(result[0]); i++)
+		if (!process_proc({result[i].ki_pid, result[i].ki_comm}))
+			return false;
+
+	return true;
+#endif
+}
+
+#ifdef HAVE_KVM_GETFILES
+static bool GetOpenFileName(const struct kinfo_file& file, std::string& name) {
+	/* OpenBSD doesn't provide a native API for this, so we have
+	 * to do it ourselves */
+	static constexpr std::string_view root = "/";
+
+	FTS* file_system = fts_open(root.data(), FTS_COMFOLLOW | FTS_NOCHDIR, nullptr);
+	if (!file_system)
+		return false;
+
+	/* Search through the filesystem for a file that matches our
+	 * kinfo_file structure */
+	FTSENT* parent = nullptr;
+	while ((parent = fts_read(file_system))) {
+		FTSENT* child = fts_children(file_system, 0);
+		while (child && child->fts_link) {
+			child = child->fts_link;
+			if (!S_ISREG(child->fts_statp->st_mode) || !S_ISLNK(child->fts_statp->st_mode))
+				continue;
+
+			if (child->fts_statp->st_dev != file->va_fsid)
+				continue;
+
+			if (child->fts_statp->st_ino != file->va_fileid)
+				continue;
+
+			name = std::string(child->fts_path) + child->fts_name;
+			fts_close(file_system);
+			return true;
+		}
+	}
+
+	fts_close(filesystem);
+	return false;
+}
+#endif /* HAVE_KVM_GETFILES */
+
+bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) {
+#ifdef HAVE_KVM_GETFILES
+	char errbuf[_POSIX2_LINE_MAX];
+	kvm_t* kernel = kvm_openfiles(nullptr, nullptr, nullptr, O_RDONLY, errbuf);
+	if (!kernel)
+		return false;
+
+	for (const auto& pid : pids) {
+		int cnt;
+		struct kinfo_file* kfile = kvm_getfiles(kernel, KERN_FILE_BYPID, pid, &cnt);
+		if (!kfile) {
+			kvm_close(kernel);
+			return false;
+		}
+
+		for (int i = 0; i < cnt; i++) {
+			uint32_t oflags = kfile[i].kf_flags & O_ACCMODE;
+			if (oflags == O_WRONLY || oflags == O_RDWR)
+				continue;
+
+			std::string name;
+			if (!GetOpenFileName(kfile[i], name))
+				continue;
+
+			if (!open_file_proc({pid, name})) {
+				kvm_close(kernel);
+				return false;
+			}
+		}
+	}
+
+	kvm_close(kernel);
+	return true;
+#elif defined(LIBUTIL)
+	for (const auto& pid : pids) {
+		int cnt;
+		std::unique_ptr<struct kinfo_file[]> files(kinfo_getfile(pid, &cnt));
+		if (!files)
+			return false;
+
+		for (int i = 0; i < cnt; i++) {
+			const struct kinfo_file& current = files[i];
+			if (current.kf_vnode_type != KF_VTYPE_VREG || current.kf_vnode_type != KF_VTYPE_VLNK)
+				continue;
+
+			const int oflags = current.kf_flags & O_ACCMODE;
+			if (oflags == O_WRONLY || oflags == O_RDWR)
+				continue;
+
+			if (!open_file_proc({pid, current.kf_path}))
+				return false;
+		}
+	}
+
+	return true;
+#else
+	/* NetBSD doesn't even provide a real API for this */
+	return false;
+#endif
+}
+
+} // namespace animone::internal::kvm
--- a/src/fd/proc.cc	Mon May 13 14:15:47 2024 -0400
+++ b/src/fd/proc.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,138 +1,138 @@
-#include "animone/fd/proc.h"
-#include "animone.h"
-#include "animone/util.h"
-
-#include <filesystem>
-#include <fstream>
-#include <sstream>
-#include <string>
-
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-static constexpr std::string_view PROC_LOCATION = "/proc";
-
-namespace animone::internal::proc {
-
-static bool IsRegularFile(std::string link) {
-	struct stat sb;
-	if (stat(link.c_str(), &sb) == -1)
-		return false;
-
-	return S_ISREG(sb.st_mode);
-}
-
-static bool AreFlagsOk(pid_t pid, int fd) {
-	const std::filesystem::path path =
-	    std::filesystem::path(PROC_LOCATION) / std::to_string(pid) / "fdinfo" / std::to_string(fd);
-
-	std::ifstream file(path);
-	if (!file)
-		return false;
-
-	int flags = 0;
-	for (std::string line; std::getline(file, line);)
-		if (line.find("flags:", 0) == 0)
-			flags = util::StringToInt(line.substr(line.find_last_not_of("0123456789") + 1));
-
-	/* check if the file was opened in a write mode */
-	int accflags = flags & O_ACCMODE;
-	if (accflags == O_WRONLY || accflags == O_RDWR)
-		return false;
-
-	return true;
-}
-
-static bool GetFilenameFromFd(std::string link, std::string& out) {
-	/* /proc is a "virtual filesystem", so we have to guess the path size. yippee! */
-	constexpr size_t OUT_MAX = (1ul << 15); // 32KiB
-	out.resize(32);
-
-	ssize_t exe_used = 0;
-	do {
-		out.resize(out.length() * 2);
-
-		exe_used = readlink(link.c_str(), &out.front(), out.length());
-		if (exe_used == (ssize_t)-1 || exe_used < (ssize_t)1)
-			return false; // we got a bad result. SAD!
-	} while (out.length() < OUT_MAX && exe_used >= static_cast<ssize_t>(out.length()));
-
-	out.resize(out.find('\0'));
-
-	return true;
-}
-
-static bool IsSystemFile(const std::string& path) {
-	static constexpr std::array<std::string_view, 9> invalid_paths = {"/boot", "/dev", "/bin", "/usr", "/opt",
-	                                                                  "/proc", "/var", "/etc", "/dev"};
-
-	for (const auto& invalid_path : invalid_paths)
-		if (!path.rfind(invalid_path, 0))
-			return true;
-
-	return false;
-}
-
-bool GetProcessName(pid_t pid, std::string& result) {
-	const std::filesystem::path path = std::filesystem::path(PROC_LOCATION) / std::to_string(pid) / "comm";
-
-	if (!util::ReadFile(path, result))
-		return false;
-
-	result.erase(std::remove(result.begin(), result.end(), '\n'), result.end());
-	return true;
-}
-
-bool EnumerateOpenProcesses(process_proc_t process_proc) {
-	bool success = false;
-
-	for (const auto& dir : std::filesystem::directory_iterator{PROC_LOCATION}) {
-		Process proc;
-
-		try {
-			proc.pid = util::StringToInt(dir.path().stem());
-			success = true;
-		} catch (std::invalid_argument const& ex) {
-			continue;
-		}
-
-		if (!GetProcessName(proc.pid, proc.name))
-			continue;
-
-		if (!process_proc(proc))
-			return false;
-	}
-
-	return success;
-}
-
-bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) {
-	if (!open_file_proc)
-		return false;
-
-	for (const auto& pid : pids) {
-		const std::filesystem::path path = std::filesystem::path(PROC_LOCATION) / std::to_string(pid) / "fd";
-
-		for (const auto& dir : std::filesystem::directory_iterator{path}) {
-			if (!AreFlagsOk(pid, util::StringToInt(dir.path().stem())))
-				continue;
-
-			std::string name;
-			if (!GetFilenameFromFd(dir.path(), name))
-				continue;
-
-			if (!IsRegularFile(name))
-				continue;
-
-			if (IsSystemFile(name))
-				continue;
-
-			if (!open_file_proc({pid, name}))
-				return false;
-		}
-	}
-	return true;
-}
-
-} // namespace animia::internal::proc
+#include "animone/fd/proc.h"
+#include "animone.h"
+#include "animone/util.h"
+
+#include <filesystem>
+#include <fstream>
+#include <sstream>
+#include <string>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+static constexpr std::string_view PROC_LOCATION = "/proc";
+
+namespace animone::internal::proc {
+
+static bool IsRegularFile(std::string link) {
+	struct stat sb;
+	if (stat(link.c_str(), &sb) == -1)
+		return false;
+
+	return S_ISREG(sb.st_mode);
+}
+
+static bool AreFlagsOk(pid_t pid, int fd) {
+	const std::filesystem::path path =
+	    std::filesystem::path(PROC_LOCATION) / std::to_string(pid) / "fdinfo" / std::to_string(fd);
+
+	std::ifstream file(path);
+	if (!file)
+		return false;
+
+	int flags = 0;
+	for (std::string line; std::getline(file, line);)
+		if (line.find("flags:", 0) == 0)
+			flags = util::StringToInt(line.substr(line.find_last_not_of("0123456789") + 1));
+
+	/* check if the file was opened in a write mode */
+	int accflags = flags & O_ACCMODE;
+	if (accflags == O_WRONLY || accflags == O_RDWR)
+		return false;
+
+	return true;
+}
+
+static bool GetFilenameFromFd(std::string link, std::string& out) {
+	/* /proc is a "virtual filesystem", so we have to guess the path size. yippee! */
+	constexpr size_t OUT_MAX = (1ul << 15); // 32KiB
+	out.resize(32);
+
+	ssize_t exe_used = 0;
+	do {
+		out.resize(out.length() * 2);
+
+		exe_used = readlink(link.c_str(), &out.front(), out.length());
+		if (exe_used == (ssize_t)-1 || exe_used < (ssize_t)1)
+			return false; // we got a bad result. SAD!
+	} while (out.length() < OUT_MAX && exe_used >= static_cast<ssize_t>(out.length()));
+
+	out.resize(out.find('\0'));
+
+	return true;
+}
+
+static bool IsSystemFile(const std::string& path) {
+	static constexpr std::array<std::string_view, 9> invalid_paths = {"/boot", "/dev", "/bin", "/usr", "/opt",
+	                                                                  "/proc", "/var", "/etc", "/dev"};
+
+	for (const auto& invalid_path : invalid_paths)
+		if (!path.rfind(invalid_path, 0))
+			return true;
+
+	return false;
+}
+
+bool GetProcessName(pid_t pid, std::string& result) {
+	const std::filesystem::path path = std::filesystem::path(PROC_LOCATION) / std::to_string(pid) / "comm";
+
+	if (!util::ReadFile(path, result))
+		return false;
+
+	result.erase(std::remove(result.begin(), result.end(), '\n'), result.end());
+	return true;
+}
+
+bool EnumerateOpenProcesses(process_proc_t process_proc) {
+	bool success = false;
+
+	for (const auto& dir : std::filesystem::directory_iterator{PROC_LOCATION}) {
+		Process proc;
+
+		try {
+			proc.pid = util::StringToInt(dir.path().stem());
+			success = true;
+		} catch (std::invalid_argument const& ex) {
+			continue;
+		}
+
+		if (!GetProcessName(proc.pid, proc.name))
+			continue;
+
+		if (!process_proc(proc))
+			return false;
+	}
+
+	return success;
+}
+
+bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) {
+	if (!open_file_proc)
+		return false;
+
+	for (const auto& pid : pids) {
+		const std::filesystem::path path = std::filesystem::path(PROC_LOCATION) / std::to_string(pid) / "fd";
+
+		for (const auto& dir : std::filesystem::directory_iterator{path}) {
+			if (!AreFlagsOk(pid, util::StringToInt(dir.path().stem())))
+				continue;
+
+			std::string name;
+			if (!GetFilenameFromFd(dir.path(), name))
+				continue;
+
+			if (!IsRegularFile(name))
+				continue;
+
+			if (IsSystemFile(name))
+				continue;
+
+			if (!open_file_proc({pid, name}))
+				return false;
+		}
+	}
+	return true;
+}
+
+} // namespace animia::internal::proc
--- a/src/fd/win32.cc	Mon May 13 14:15:47 2024 -0400
+++ b/src/fd/win32.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,258 +1,258 @@
-#include "animone/fd/win32.h"
-#include "animone.h"
-#include "animone/util/win32.h"
-
-#include <stdexcept>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#include <fileapi.h>
-#include <handleapi.h>
-#include <libloaderapi.h>
-#include <ntdef.h>
-#include <psapi.h>
-#include <shlobj.h>
-#include <stringapiset.h>
-#include <tlhelp32.h>
-#include <windows.h>
-#include <winternl.h>
-
-/* This file is noticably more complex than Unix and Linux, and that's because
- * there is no "simple" way to get the paths of a file. In fact, this thing requires
- * you to use *internal functions* that can't even be linked to, hence why we have to
- * use GetProcAddress and such. What a mess.
- *
- * Speaking of which, because this file uses internal functions of the OS, it is not
- * guaranteed to work far into the future. However, it has worked since NT 6.0 (Vista)
- * at least, so it's unlikely to be changed much ever.
- */
-
-/* SystemExtendedHandleInformation is only available in NT 5.1+ (XP and higher) and provides information for
- * 32-bit PIDs, unlike SystemHandleInformation
- */
-static constexpr SYSTEM_INFORMATION_CLASS SystemExtendedHandleInformation = static_cast<SYSTEM_INFORMATION_CLASS>(0x40);
-static constexpr NTSTATUS STATUS_INFO_LENGTH_MISMATCH = 0xC0000004UL;
-
-/* this is filled in at runtime because it's not guaranteed to be (and isn't)
- * constant between different versions of Windows */
-static unsigned short file_type_index = 0;
-
-struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX {
-	PVOID Object;
-	ULONG_PTR UniqueProcessId;
-	HANDLE HandleValue;
-	ACCESS_MASK GrantedAccess;
-	USHORT CreatorBackTraceIndex;
-	USHORT ObjectTypeIndex;
-	ULONG HandleAttributes;
-	ULONG Reserved;
-};
-
-struct SYSTEM_HANDLE_INFORMATION_EX {
-	ULONG_PTR NumberOfHandles;
-	ULONG_PTR Reserved;
-	SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1];
-};
-
-namespace animone::internal::win32 {
-
-class Ntdll {
-public:
-	Ntdll() {
-		ntdll = ::GetModuleHandleW(L"ntdll.dll");
-		nt_query_system_information = reinterpret_cast<decltype(::NtQuerySystemInformation)*>(
-		    ::GetProcAddress(ntdll, "NtQuerySystemInformation"));
-		nt_query_object = reinterpret_cast<decltype(::NtQueryObject)*>(::GetProcAddress(ntdll, "NtQueryObject"));
-	}
-
-	NTSTATUS QuerySystemInformation(SYSTEM_INFORMATION_CLASS cls, PVOID sysinfo, ULONG len,
-	                                PULONG retlen){
-		return nt_query_system_information(cls, sysinfo, len, retlen);
-	}
-
-	NTSTATUS QueryObject(HANDLE handle, OBJECT_INFORMATION_CLASS cls, PVOID objinf, ULONG objinflen, PULONG retlen) {
-		return nt_query_object(handle, cls, objinf, objinflen, retlen);
-	}
-
-private:
-	HMODULE ntdll;
-	decltype(::NtQuerySystemInformation)* nt_query_system_information;
-	decltype(::NtQueryObject)* nt_query_object;
-};
-
-Ntdll ntdll;
-
-static HANDLE DuplicateHandle(HANDLE process_handle, HANDLE handle) {
-	HANDLE dup_handle = nullptr;
-	const bool result =
-	    ::DuplicateHandle(process_handle, handle, ::GetCurrentProcess(), &dup_handle, 0, false, DUPLICATE_SAME_ACCESS);
-	return result ? dup_handle : nullptr;
-}
-
-static std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> GetSystemHandleInformation() {
-	/* we should really put a cap on this */
-	ULONG cb = 1 << 19;
-	NTSTATUS status = STATUS_NO_MEMORY;
-	std::unique_ptr<SYSTEM_HANDLE_INFORMATION_EX> info;
-
-	do {
-		info.reset(reinterpret_cast<SYSTEM_HANDLE_INFORMATION_EX*>(malloc(cb *= 2)));
-		if (!info)
-			continue;
-
-		status = ntdll.QuerySystemInformation(SystemExtendedHandleInformation, info.get(), cb, &cb);
-	} while (status == STATUS_INFO_LENGTH_MISMATCH);
-
-	if (!NT_SUCCESS(status))
-		return {};
-
-	std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> res;
-
-	ULONG_PTR handles = info->NumberOfHandles;
-	if (!handles)
-		return {};
-
-	res.reserve(handles);
-
-	SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX* entry = info->Handles;
-	do {
-		if (entry)
-			res.push_back(*(entry++));
-	} while (--handles);
-
-	return res;
-}
-
-static std::wstring GetHandleType(HANDLE handle) {
-	OBJECT_TYPE_INFORMATION info = {0};
-	ntdll.QueryObject(handle, ObjectTypeInformation, &info, sizeof(info), NULL);
-	return std::wstring(info.TypeName.Buffer, info.TypeName.Length);
-}
-
-static std::wstring GetFinalPathNameByHandle(HANDLE handle) {
-	std::wstring buffer;
-
-	DWORD size = ::GetFinalPathNameByHandleW(handle, NULL, 0, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
-	buffer.resize(size);
-	::GetFinalPathNameByHandleW(handle, &buffer.front(), buffer.size(), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
-
-	return buffer;
-}
-
-static bool IsFileHandle(HANDLE handle, unsigned short object_type_index) {
-	if (file_type_index)
-		return object_type_index == file_type_index;
-	else if (!handle)
-		return true;
-	else if (GetHandleType(handle) == L"File") {
-		file_type_index = object_type_index;
-		return true;
-	}
-	return false;
-}
-
-static bool IsFileMaskOk(ACCESS_MASK access_mask) {
-	if (!(access_mask & FILE_READ_DATA))
-		return false;
-
-	if ((access_mask & FILE_APPEND_DATA) || (access_mask & FILE_WRITE_EA) || (access_mask & FILE_WRITE_ATTRIBUTES))
-		return false;
-
-	return true;
-}
-
-static bool IsFilePathOk(const std::wstring& path) {
-	if (path.empty())
-		return false;
-
-	if (IsSystemDirectory(path))
-		return false;
-
-	const auto file_attributes = GetFileAttributesW(path.c_str());
-	if ((file_attributes == INVALID_FILE_ATTRIBUTES) || (file_attributes & FILE_ATTRIBUTE_DIRECTORY))
-		return false;
-
-	return true;
-}
-
-bool GetProcessName(pid_t pid, std::string& name) {
-	std::wstring wname = GetProcessPath(pid);
-	if (wname.empty())
-		return false;
-
-	name = ToUtf8String(GetFileNameWithoutExtension(GetFileNameFromPath(wname)));
-	return true;
-}
-
-bool EnumerateOpenProcesses(process_proc_t process_proc) {
-	HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
-	if (hProcessSnap == INVALID_HANDLE_VALUE)
-		return false;
-
-	PROCESSENTRY32 pe32;
-	pe32.dwSize = sizeof(PROCESSENTRY32);
-
-	if (!::Process32First(hProcessSnap, &pe32))
-		return false;
-
-	if (!process_proc({pe32.th32ProcessID, pe32.szExeFile}))
-		return false;
-
-	while (::Process32Next(hProcessSnap, &pe32))
-		if (!process_proc({pe32.th32ProcessID, pe32.szExeFile}))
-			return false;
-
-	::CloseHandle(hProcessSnap);
-
-	return true;
-}
-
-/* this could be changed to being a callback, but... I'm too lazy right now :) */
-bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) {
-	if (!open_file_proc)
-		return false;
-
-	std::unordered_map<pid_t, Handle> proc_handles;
-
-	for (const pid_t& pid : pids) {
-		const HANDLE handle = ::OpenProcess(PROCESS_DUP_HANDLE, false, pid);
-		if (handle != INVALID_HANDLE_VALUE)
-			proc_handles[pid] = Handle(handle);
-	}
-
-	if (proc_handles.empty())
-		return false;
-
-	std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> info = GetSystemHandleInformation();
-
-	for (const auto& h : info) {
-		const pid_t pid = h.UniqueProcessId;
-		if (!pids.count(pid))
-			continue;
-
-		if (!IsFileHandle(nullptr, h.ObjectTypeIndex))
-			continue;
-
-		if (!IsFileMaskOk(h.GrantedAccess))
-			continue;
-
-		Handle handle(DuplicateHandle(proc_handles[pid].get(), h.HandleValue));
-		if (handle.get() == INVALID_HANDLE_VALUE)
-			continue;
-
-		if (GetFileType(handle.get()) != FILE_TYPE_DISK)
-			continue;
-
-		const std::wstring path = GetFinalPathNameByHandle(handle.get());
-		if (!IsFilePathOk(path))
-			continue;
-
-		if (!open_file_proc({pid, ToUtf8String(path)}))
-			return false;
-	}
-
-	return true;
-}
-
-} // namespace animone::internal::win32
+#include "animone/fd/win32.h"
+#include "animone.h"
+#include "animone/util/win32.h"
+
+#include <stdexcept>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <fileapi.h>
+#include <handleapi.h>
+#include <libloaderapi.h>
+#include <ntdef.h>
+#include <psapi.h>
+#include <shlobj.h>
+#include <stringapiset.h>
+#include <tlhelp32.h>
+#include <windows.h>
+#include <winternl.h>
+
+/* This file is noticably more complex than Unix and Linux, and that's because
+ * there is no "simple" way to get the paths of a file. In fact, this thing requires
+ * you to use *internal functions* that can't even be linked to, hence why we have to
+ * use GetProcAddress and such. What a mess.
+ *
+ * Speaking of which, because this file uses internal functions of the OS, it is not
+ * guaranteed to work far into the future. However, it has worked since NT 6.0 (Vista)
+ * at least, so it's unlikely to be changed much ever.
+ */
+
+/* SystemExtendedHandleInformation is only available in NT 5.1+ (XP and higher) and provides information for
+ * 32-bit PIDs, unlike SystemHandleInformation
+ */
+static constexpr SYSTEM_INFORMATION_CLASS SystemExtendedHandleInformation = static_cast<SYSTEM_INFORMATION_CLASS>(0x40);
+static constexpr NTSTATUS STATUS_INFO_LENGTH_MISMATCH = 0xC0000004UL;
+
+/* this is filled in at runtime because it's not guaranteed to be (and isn't)
+ * constant between different versions of Windows */
+static unsigned short file_type_index = 0;
+
+struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX {
+	PVOID Object;
+	ULONG_PTR UniqueProcessId;
+	HANDLE HandleValue;
+	ACCESS_MASK GrantedAccess;
+	USHORT CreatorBackTraceIndex;
+	USHORT ObjectTypeIndex;
+	ULONG HandleAttributes;
+	ULONG Reserved;
+};
+
+struct SYSTEM_HANDLE_INFORMATION_EX {
+	ULONG_PTR NumberOfHandles;
+	ULONG_PTR Reserved;
+	SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1];
+};
+
+namespace animone::internal::win32 {
+
+class Ntdll {
+public:
+	Ntdll() {
+		ntdll = ::GetModuleHandleW(L"ntdll.dll");
+		nt_query_system_information = reinterpret_cast<decltype(::NtQuerySystemInformation)*>(
+		    ::GetProcAddress(ntdll, "NtQuerySystemInformation"));
+		nt_query_object = reinterpret_cast<decltype(::NtQueryObject)*>(::GetProcAddress(ntdll, "NtQueryObject"));
+	}
+
+	NTSTATUS QuerySystemInformation(SYSTEM_INFORMATION_CLASS cls, PVOID sysinfo, ULONG len,
+	                                PULONG retlen){
+		return nt_query_system_information(cls, sysinfo, len, retlen);
+	}
+
+	NTSTATUS QueryObject(HANDLE handle, OBJECT_INFORMATION_CLASS cls, PVOID objinf, ULONG objinflen, PULONG retlen) {
+		return nt_query_object(handle, cls, objinf, objinflen, retlen);
+	}
+
+private:
+	HMODULE ntdll;
+	decltype(::NtQuerySystemInformation)* nt_query_system_information;
+	decltype(::NtQueryObject)* nt_query_object;
+};
+
+Ntdll ntdll;
+
+static HANDLE DuplicateHandle(HANDLE process_handle, HANDLE handle) {
+	HANDLE dup_handle = nullptr;
+	const bool result =
+	    ::DuplicateHandle(process_handle, handle, ::GetCurrentProcess(), &dup_handle, 0, false, DUPLICATE_SAME_ACCESS);
+	return result ? dup_handle : nullptr;
+}
+
+static std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> GetSystemHandleInformation() {
+	/* we should really put a cap on this */
+	ULONG cb = 1 << 19;
+	NTSTATUS status = STATUS_NO_MEMORY;
+	std::unique_ptr<SYSTEM_HANDLE_INFORMATION_EX> info;
+
+	do {
+		info.reset(reinterpret_cast<SYSTEM_HANDLE_INFORMATION_EX*>(malloc(cb *= 2)));
+		if (!info)
+			continue;
+
+		status = ntdll.QuerySystemInformation(SystemExtendedHandleInformation, info.get(), cb, &cb);
+	} while (status == STATUS_INFO_LENGTH_MISMATCH);
+
+	if (!NT_SUCCESS(status))
+		return {};
+
+	std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> res;
+
+	ULONG_PTR handles = info->NumberOfHandles;
+	if (!handles)
+		return {};
+
+	res.reserve(handles);
+
+	SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX* entry = info->Handles;
+	do {
+		if (entry)
+			res.push_back(*(entry++));
+	} while (--handles);
+
+	return res;
+}
+
+static std::wstring GetHandleType(HANDLE handle) {
+	OBJECT_TYPE_INFORMATION info = {0};
+	ntdll.QueryObject(handle, ObjectTypeInformation, &info, sizeof(info), NULL);
+	return std::wstring(info.TypeName.Buffer, info.TypeName.Length);
+}
+
+static std::wstring GetFinalPathNameByHandle(HANDLE handle) {
+	std::wstring buffer;
+
+	DWORD size = ::GetFinalPathNameByHandleW(handle, NULL, 0, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
+	buffer.resize(size);
+	::GetFinalPathNameByHandleW(handle, &buffer.front(), buffer.size(), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
+
+	return buffer;
+}
+
+static bool IsFileHandle(HANDLE handle, unsigned short object_type_index) {
+	if (file_type_index)
+		return object_type_index == file_type_index;
+	else if (!handle)
+		return true;
+	else if (GetHandleType(handle) == L"File") {
+		file_type_index = object_type_index;
+		return true;
+	}
+	return false;
+}
+
+static bool IsFileMaskOk(ACCESS_MASK access_mask) {
+	if (!(access_mask & FILE_READ_DATA))
+		return false;
+
+	if ((access_mask & FILE_APPEND_DATA) || (access_mask & FILE_WRITE_EA) || (access_mask & FILE_WRITE_ATTRIBUTES))
+		return false;
+
+	return true;
+}
+
+static bool IsFilePathOk(const std::wstring& path) {
+	if (path.empty())
+		return false;
+
+	if (IsSystemDirectory(path))
+		return false;
+
+	const auto file_attributes = GetFileAttributesW(path.c_str());
+	if ((file_attributes == INVALID_FILE_ATTRIBUTES) || (file_attributes & FILE_ATTRIBUTE_DIRECTORY))
+		return false;
+
+	return true;
+}
+
+bool GetProcessName(pid_t pid, std::string& name) {
+	std::wstring wname = GetProcessPath(pid);
+	if (wname.empty())
+		return false;
+
+	name = ToUtf8String(GetFileNameWithoutExtension(GetFileNameFromPath(wname)));
+	return true;
+}
+
+bool EnumerateOpenProcesses(process_proc_t process_proc) {
+	HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+	if (hProcessSnap == INVALID_HANDLE_VALUE)
+		return false;
+
+	PROCESSENTRY32 pe32;
+	pe32.dwSize = sizeof(PROCESSENTRY32);
+
+	if (!::Process32First(hProcessSnap, &pe32))
+		return false;
+
+	if (!process_proc({pe32.th32ProcessID, pe32.szExeFile}))
+		return false;
+
+	while (::Process32Next(hProcessSnap, &pe32))
+		if (!process_proc({pe32.th32ProcessID, pe32.szExeFile}))
+			return false;
+
+	::CloseHandle(hProcessSnap);
+
+	return true;
+}
+
+/* this could be changed to being a callback, but... I'm too lazy right now :) */
+bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) {
+	if (!open_file_proc)
+		return false;
+
+	std::unordered_map<pid_t, Handle> proc_handles;
+
+	for (const pid_t& pid : pids) {
+		const HANDLE handle = ::OpenProcess(PROCESS_DUP_HANDLE, false, pid);
+		if (handle != INVALID_HANDLE_VALUE)
+			proc_handles[pid] = Handle(handle);
+	}
+
+	if (proc_handles.empty())
+		return false;
+
+	std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> info = GetSystemHandleInformation();
+
+	for (const auto& h : info) {
+		const pid_t pid = h.UniqueProcessId;
+		if (!pids.count(pid))
+			continue;
+
+		if (!IsFileHandle(nullptr, h.ObjectTypeIndex))
+			continue;
+
+		if (!IsFileMaskOk(h.GrantedAccess))
+			continue;
+
+		Handle handle(DuplicateHandle(proc_handles[pid].get(), h.HandleValue));
+		if (handle.get() == INVALID_HANDLE_VALUE)
+			continue;
+
+		if (GetFileType(handle.get()) != FILE_TYPE_DISK)
+			continue;
+
+		const std::wstring path = GetFinalPathNameByHandle(handle.get());
+		if (!IsFilePathOk(path))
+			continue;
+
+		if (!open_file_proc({pid, ToUtf8String(path)}))
+			return false;
+	}
+
+	return true;
+}
+
+} // namespace animone::internal::win32
--- a/src/fd/xnu.cc	Mon May 13 14:15:47 2024 -0400
+++ b/src/fd/xnu.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,134 +1,134 @@
-#include "animone/fd/xnu.h"
-#include "animone.h"
-#include "animone/util/osx.h"
-
-#include <cassert>
-#include <memory>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#include <fcntl.h>
-#include <libproc.h>
-#include <sys/sysctl.h>
-#include <sys/types.h>
-#include <sys/user.h>
-
-/* you may be asking: WTF is FWRITE?
- * well, from bsd/sys/fcntl.h in the XNU kernel:
- *
- *    Kernel encoding of open mode; separate read and write bits that are
- *    independently testable: 1 greater than [O_RDONLY and O_WRONLY].
- *
- * It's just how the kernel defines write mode.
-*/
-#ifndef FWRITE
-#define FWRITE	0x0002
-#endif
-
-namespace animone::internal::xnu {
-
-bool EnumerateOpenProcesses(process_proc_t process_proc) {
-	size_t pids_size = 256;
-	std::unique_ptr<pid_t[]> pids;
-
-	int returned_size = 0;
-	do {
-		pids.reset(new pid_t[pids_size *= 2]);
-		returned_size = proc_listpids(PROC_ALL_PIDS, 0, pids.get(), pids_size * sizeof(pid_t));
-		if (returned_size == -1)
-			return false;
-	} while ((pids_size * sizeof(size_t)) < returned_size);
-
-	for (int i = 0; i < pids_size; i++) {
-		std::string result;
-		osx::util::GetProcessName(pids[i], result);
-		if (!process_proc({pids[i], result}))
-			return false;
-	}
-
-	return true;
-}
-
-bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) {
-	if (!open_file_proc)
-		return false;
-
-	for (const auto& pid : pids) {
-		const int bufsz = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0);
-		if (bufsz < 0)
-			return false;
-
-		const size_t info_len = bufsz / sizeof(struct proc_fdinfo);
-		if (info_len < 1)
-			return false;
-
-		std::unique_ptr<struct proc_fdinfo[]> info(new struct proc_fdinfo[info_len]);
-		if (!info)
-			return false;
-
-		proc_pidinfo(pid, PROC_PIDLISTFDS, 0, info.get(), bufsz);
-
-		for (size_t i = 0; i < info_len; i++) {
-			if (info[i].proc_fdtype == PROX_FDTYPE_VNODE) {
-				struct vnode_fdinfowithpath vnodeInfo;
-
-				int sz = proc_pidfdinfo(pid, info[i].proc_fd, PROC_PIDFDVNODEPATHINFO, &vnodeInfo,
-				                        PROC_PIDFDVNODEPATHINFO_SIZE);
-				if (sz != PROC_PIDFDVNODEPATHINFO_SIZE)
-					return false;
-
-				/* why would a media player open a file in write mode? */
-				if (vnodeInfo.pfi.fi_openflags & FWRITE)
-					continue;
-
-				if (!open_file_proc({pid, vnodeInfo.pvip.vip_path}))
-					return false;
-			}
-		}
-	}
-
-	return true;
-}
-
-static bool GetProcessNameFromProcPidPath(pid_t pid, std::string& result) {
-	result.assign(PROC_PIDPATHINFO_MAXSIZE, '\0');
-
-	int ret = proc_pidpath(pid, result.data(), result.size() * sizeof(char));
-	if (ret <= 0)
-		return false;
-
-	/* find the last slash, if there's none, we're done here */
-	size_t last_slash = result.rfind('/');
-	if (last_slash == std::string::npos)
-		return true;
-
-	result.erase(0, last_slash + 1);
-	return true;
-}
-
-static bool GetProcessNameFromProcName(pid_t pid, std::string& result) {
-	result.assign(2 * MAXCOMLEN, '\0');
-
-	int size = proc_name(pid, &result.front(), result.length());
-
-	/* if size is MAXCOMLEN or 2 * MAXCOMLEN, assume
-	 * this method won't work and our result is truncated */
-	if (size <= 0 || size == MAXCOMLEN || size == 2 * MAXCOMLEN)
-		return false;
-
-	result.resize(size);
-	return true;
-}
-
-bool GetProcessName(pid_t pid, std::string& result) {
-	if (GetProcessNameFromProcName(pid, result))
-		return true;
-
-	if (GetProcessNameFromProcPidPath(pid, result))
-		return true;
-
-	return false;
-}
-
-} // namespace animone::internal::xnu
+#include "animone/fd/xnu.h"
+#include "animone.h"
+#include "animone/util/osx.h"
+
+#include <cassert>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <fcntl.h>
+#include <libproc.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <sys/user.h>
+
+/* you may be asking: WTF is FWRITE?
+ * well, from bsd/sys/fcntl.h in the XNU kernel:
+ *
+ *    Kernel encoding of open mode; separate read and write bits that are
+ *    independently testable: 1 greater than [O_RDONLY and O_WRONLY].
+ *
+ * It's just how the kernel defines write mode.
+*/
+#ifndef FWRITE
+#define FWRITE	0x0002
+#endif
+
+namespace animone::internal::xnu {
+
+bool EnumerateOpenProcesses(process_proc_t process_proc) {
+	size_t pids_size = 256;
+	std::unique_ptr<pid_t[]> pids;
+
+	int returned_size = 0;
+	do {
+		pids.reset(new pid_t[pids_size *= 2]);
+		returned_size = proc_listpids(PROC_ALL_PIDS, 0, pids.get(), pids_size * sizeof(pid_t));
+		if (returned_size == -1)
+			return false;
+	} while ((pids_size * sizeof(size_t)) < returned_size);
+
+	for (int i = 0; i < pids_size; i++) {
+		std::string result;
+		osx::util::GetProcessName(pids[i], result);
+		if (!process_proc({pids[i], result}))
+			return false;
+	}
+
+	return true;
+}
+
+bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) {
+	if (!open_file_proc)
+		return false;
+
+	for (const auto& pid : pids) {
+		const int bufsz = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0);
+		if (bufsz < 0)
+			return false;
+
+		const size_t info_len = bufsz / sizeof(struct proc_fdinfo);
+		if (info_len < 1)
+			return false;
+
+		std::unique_ptr<struct proc_fdinfo[]> info(new struct proc_fdinfo[info_len]);
+		if (!info)
+			return false;
+
+		proc_pidinfo(pid, PROC_PIDLISTFDS, 0, info.get(), bufsz);
+
+		for (size_t i = 0; i < info_len; i++) {
+			if (info[i].proc_fdtype == PROX_FDTYPE_VNODE) {
+				struct vnode_fdinfowithpath vnodeInfo;
+
+				int sz = proc_pidfdinfo(pid, info[i].proc_fd, PROC_PIDFDVNODEPATHINFO, &vnodeInfo,
+				                        PROC_PIDFDVNODEPATHINFO_SIZE);
+				if (sz != PROC_PIDFDVNODEPATHINFO_SIZE)
+					return false;
+
+				/* why would a media player open a file in write mode? */
+				if (vnodeInfo.pfi.fi_openflags & FWRITE)
+					continue;
+
+				if (!open_file_proc({pid, vnodeInfo.pvip.vip_path}))
+					return false;
+			}
+		}
+	}
+
+	return true;
+}
+
+static bool GetProcessNameFromProcPidPath(pid_t pid, std::string& result) {
+	result.assign(PROC_PIDPATHINFO_MAXSIZE, '\0');
+
+	int ret = proc_pidpath(pid, result.data(), result.size() * sizeof(char));
+	if (ret <= 0)
+		return false;
+
+	/* find the last slash, if there's none, we're done here */
+	size_t last_slash = result.rfind('/');
+	if (last_slash == std::string::npos)
+		return true;
+
+	result.erase(0, last_slash + 1);
+	return true;
+}
+
+static bool GetProcessNameFromProcName(pid_t pid, std::string& result) {
+	result.assign(2 * MAXCOMLEN, '\0');
+
+	int size = proc_name(pid, &result.front(), result.length());
+
+	/* if size is MAXCOMLEN or 2 * MAXCOMLEN, assume
+	 * this method won't work and our result is truncated */
+	if (size <= 0 || size == MAXCOMLEN || size == 2 * MAXCOMLEN)
+		return false;
+
+	result.resize(size);
+	return true;
+}
+
+bool GetProcessName(pid_t pid, std::string& result) {
+	if (GetProcessNameFromProcName(pid, result))
+		return true;
+
+	if (GetProcessNameFromProcPidPath(pid, result))
+		return true;
+
+	return false;
+}
+
+} // namespace animone::internal::xnu
--- a/src/player.cc	Mon May 13 14:15:47 2024 -0400
+++ b/src/player.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,181 +1,181 @@
-#include "animone/player.h"
-#include "animone/util.h"
-
-#include <map>
-#include <sstream>
-#include <string>
-#include <vector>
-
-namespace animone {
-
-namespace internal::parser {
-
-enum class State {
-	ExpectPlayerName,
-	ExpectSection,
-	ExpectWindow,
-	ExpectExecutable,
-	ExpectStrategy,
-	ExpectType,
-	ExpectWindowTitle,
-};
-
-size_t GetIndentation(const std::string& line) {
-	return line.find_first_not_of('\t');
-}
-
-bool HandleIndentation(const size_t current, const std::vector<Player>& players, State& state) {
-	// Each state has a definitive expected indentation
-	const auto expected = [&state]() -> size_t {
-		switch (state) {
-			default:
-			case State::ExpectPlayerName: return 0;
-			case State::ExpectSection: return 1;
-			case State::ExpectWindow:
-			case State::ExpectExecutable:
-			case State::ExpectStrategy:
-			case State::ExpectType: return 2;
-			case State::ExpectWindowTitle: return 3;
-		}
-	}();
-
-	if (current > expected)
-		return false; // Disallow excessive indentation
-
-	if (current < expected) {
-		auto fix_state = [&]() { state = !current ? State::ExpectPlayerName : State::ExpectSection; };
-		switch (state) {
-			case State::ExpectWindow:
-				if (players.back().windows.empty())
-					return false;
-				fix_state();
-				break;
-			case State::ExpectExecutable:
-				if (players.back().executables.empty())
-					return false;
-				fix_state();
-				break;
-			case State::ExpectStrategy:
-				if (players.back().strategies.empty())
-					return false;
-				fix_state();
-				break;
-			case State::ExpectType: fix_state(); break;
-			case State::ExpectWindowTitle: return false;
-		}
-	}
-
-	return true;
-}
-
-bool HandleState(std::string& line, std::vector<Player>& players, State& state) {
-	switch (state) {
-		case State::ExpectPlayerName:
-			players.push_back(Player());
-			players.back().name = line;
-			state = State::ExpectSection;
-			break;
-
-		case State::ExpectSection: {
-			static const std::map<std::string, State> sections = {
-			    {"windows",     State::ExpectWindow    },
-			    {"executables", State::ExpectExecutable},
-			    {"strategies",  State::ExpectStrategy  },
-			    {"type",        State::ExpectType      },
-			};
-			util::TrimRight(line, ":");
-			const auto it = sections.find(line);
-			if (it == sections.end())
-				return false;
-			state = it->second;
-			break;
-		}
-
-		case State::ExpectWindow: players.back().windows.push_back(line); break;
-
-		case State::ExpectExecutable: players.back().executables.push_back(line); break;
-
-		case State::ExpectStrategy: {
-			static const std::map<std::string, Strategy> strategies = {
-			    {"window_title",  Strategy::WindowTitle },
-			    {"open_files",    Strategy::OpenFiles   },
-			    {"ui_automation", Strategy::UiAutomation},
-			};
-			util::TrimRight(line, ":");
-			const auto it = strategies.find(line);
-			if (it == strategies.end())
-				return false;
-			const auto strategy = it->second;
-			players.back().strategies.push_back(strategy);
-			switch (strategy) {
-				case Strategy::WindowTitle: state = State::ExpectWindowTitle; break;
-			}
-			break;
-		}
-
-		case State::ExpectType: {
-			static const std::map<std::string, PlayerType> types = {
-			    {"default",     PlayerType::Default   },
-			    {"web_browser", PlayerType::WebBrowser},
-			};
-			const auto it = types.find(line);
-			if (it == types.end())
-				return false;
-			players.back().type = it->second;
-			break;
-		}
-
-		case State::ExpectWindowTitle:
-			players.back().window_title_format = line;
-			state = State::ExpectStrategy;
-			break;
-	}
-
-	return true;
-}
-
-} // namespace internal::parser
-
-////////////////////////////////////////////////////////////////////////////////
-
-bool ParsePlayersData(const std::string& data, std::vector<Player>& players) {
-	if (data.empty())
-		return false;
-
-	std::istringstream stream(data);
-	std::string line;
-	size_t indentation = 0;
-	auto state = internal::parser::State::ExpectPlayerName;
-
-	while (std::getline(stream, line, '\n')) {
-		if (line.empty())
-			continue; // Ignore empty lines
-
-		indentation = internal::parser::GetIndentation(line);
-
-		internal::util::TrimLeft(line, "\t");
-		internal::util::TrimRight(line, "\n\r");
-
-		if (line.empty() || line.front() == '#')
-			continue; // Ignore empty lines and comments
-
-		if (!internal::parser::HandleIndentation(indentation, players, state))
-			return false;
-
-		if (!internal::parser::HandleState(line, players, state))
-			return false;
-	}
-
-	return !players.empty();
-}
-
-bool ParsePlayersFile(const std::string& path, std::vector<Player>& players) {
-	std::string data;
-
-	if (!internal::util::ReadFile(path, data))
-		return false;
-
-	return ParsePlayersData(data, players);
-}
-
-} // namespace animone
+#include "animone/player.h"
+#include "animone/util.h"
+
+#include <map>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace animone {
+
+namespace internal::parser {
+
+enum class State {
+	ExpectPlayerName,
+	ExpectSection,
+	ExpectWindow,
+	ExpectExecutable,
+	ExpectStrategy,
+	ExpectType,
+	ExpectWindowTitle,
+};
+
+size_t GetIndentation(const std::string& line) {
+	return line.find_first_not_of('\t');
+}
+
+bool HandleIndentation(const size_t current, const std::vector<Player>& players, State& state) {
+	// Each state has a definitive expected indentation
+	const auto expected = [&state]() -> size_t {
+		switch (state) {
+			default:
+			case State::ExpectPlayerName: return 0;
+			case State::ExpectSection: return 1;
+			case State::ExpectWindow:
+			case State::ExpectExecutable:
+			case State::ExpectStrategy:
+			case State::ExpectType: return 2;
+			case State::ExpectWindowTitle: return 3;
+		}
+	}();
+
+	if (current > expected)
+		return false; // Disallow excessive indentation
+
+	if (current < expected) {
+		auto fix_state = [&]() { state = !current ? State::ExpectPlayerName : State::ExpectSection; };
+		switch (state) {
+			case State::ExpectWindow:
+				if (players.back().windows.empty())
+					return false;
+				fix_state();
+				break;
+			case State::ExpectExecutable:
+				if (players.back().executables.empty())
+					return false;
+				fix_state();
+				break;
+			case State::ExpectStrategy:
+				if (players.back().strategies.empty())
+					return false;
+				fix_state();
+				break;
+			case State::ExpectType: fix_state(); break;
+			case State::ExpectWindowTitle: return false;
+		}
+	}
+
+	return true;
+}
+
+bool HandleState(std::string& line, std::vector<Player>& players, State& state) {
+	switch (state) {
+		case State::ExpectPlayerName:
+			players.push_back(Player());
+			players.back().name = line;
+			state = State::ExpectSection;
+			break;
+
+		case State::ExpectSection: {
+			static const std::map<std::string, State> sections = {
+			    {"windows",     State::ExpectWindow    },
+			    {"executables", State::ExpectExecutable},
+			    {"strategies",  State::ExpectStrategy  },
+			    {"type",        State::ExpectType      },
+			};
+			util::TrimRight(line, ":");
+			const auto it = sections.find(line);
+			if (it == sections.end())
+				return false;
+			state = it->second;
+			break;
+		}
+
+		case State::ExpectWindow: players.back().windows.push_back(line); break;
+
+		case State::ExpectExecutable: players.back().executables.push_back(line); break;
+
+		case State::ExpectStrategy: {
+			static const std::map<std::string, Strategy> strategies = {
+			    {"window_title",  Strategy::WindowTitle },
+			    {"open_files",    Strategy::OpenFiles   },
+			    {"ui_automation", Strategy::UiAutomation},
+			};
+			util::TrimRight(line, ":");
+			const auto it = strategies.find(line);
+			if (it == strategies.end())
+				return false;
+			const auto strategy = it->second;
+			players.back().strategies.push_back(strategy);
+			switch (strategy) {
+				case Strategy::WindowTitle: state = State::ExpectWindowTitle; break;
+			}
+			break;
+		}
+
+		case State::ExpectType: {
+			static const std::map<std::string, PlayerType> types = {
+			    {"default",     PlayerType::Default   },
+			    {"web_browser", PlayerType::WebBrowser},
+			};
+			const auto it = types.find(line);
+			if (it == types.end())
+				return false;
+			players.back().type = it->second;
+			break;
+		}
+
+		case State::ExpectWindowTitle:
+			players.back().window_title_format = line;
+			state = State::ExpectStrategy;
+			break;
+	}
+
+	return true;
+}
+
+} // namespace internal::parser
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool ParsePlayersData(const std::string& data, std::vector<Player>& players) {
+	if (data.empty())
+		return false;
+
+	std::istringstream stream(data);
+	std::string line;
+	size_t indentation = 0;
+	auto state = internal::parser::State::ExpectPlayerName;
+
+	while (std::getline(stream, line, '\n')) {
+		if (line.empty())
+			continue; // Ignore empty lines
+
+		indentation = internal::parser::GetIndentation(line);
+
+		internal::util::TrimLeft(line, "\t");
+		internal::util::TrimRight(line, "\n\r");
+
+		if (line.empty() || line.front() == '#')
+			continue; // Ignore empty lines and comments
+
+		if (!internal::parser::HandleIndentation(indentation, players, state))
+			return false;
+
+		if (!internal::parser::HandleState(line, players, state))
+			return false;
+	}
+
+	return !players.empty();
+}
+
+bool ParsePlayersFile(const std::string& path, std::vector<Player>& players) {
+	std::string data;
+
+	if (!internal::util::ReadFile(path, data))
+		return false;
+
+	return ParsePlayersData(data, players);
+}
+
+} // namespace animone
--- a/src/strategist.cc	Mon May 13 14:15:47 2024 -0400
+++ b/src/strategist.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,108 +1,108 @@
-#include <regex>
-#include <unordered_map>
-
-#include "animone.h"
-#include "animone/fd.h"
-#include "animone/strategies.h"
-#include "animone/util.h"
-
-/* this was STUPIDLY slow in Anisthesia, oops! */
-
-namespace animone::internal {
-
-static bool ApplyWindowTitleFormat(const std::string& format, std::string& title) {
-	if (format.empty())
-		return false;
-
-	const std::regex pattern(format);
-	std::smatch match;
-	std::regex_match(title, match, pattern);
-
-	// Use the first non-empty match result, because the regular expression may
-	// contain multiple sub-expressions.
-	for (size_t i = 1; i < match.size(); ++i) {
-		if (!match.str(i).empty()) {
-			title = match.str(i);
-			return true;
-		}
-	}
-
-	// Results are empty, but the match was successful
-	if (!match.empty()) {
-		title.clear();
-		return true;
-	}
-
-	return true;
-}
-
-static MediaInfoType InferMediaInformationType(const std::string& str) {
-	const std::regex path_pattern(R"(^(?:[A-Za-z]:[/\\]|\\\\)[^<>:"/\\|?*]+)");
-	return (std::regex_search(str, path_pattern)) ? MediaInfoType::File : MediaInfoType::Unknown;
-}
-
-static bool AddMedia(Result& result, const MediaInfo media_information) {
-	if (media_information.value.empty())
-		return false;
-
-	Media media;
-	media.information.push_back(media_information);
-	result.media.push_back(std::move(media));
-
-	return true;
-}
-
-static bool ApplyWindowTitleStrategy(std::vector<Result>& results) {
-	bool success = false;
-
-	for (auto& result : results) {
-		auto title = result.window.text;
-		if (title.empty())
-			continue;
-
-		ApplyWindowTitleFormat(result.player.window_title_format, title);
-
-		success |= AddMedia(result, {InferMediaInformationType(title), title});
-	}
-
-	return success;
-}
-
-static bool ApplyOpenFilesStrategy(std::vector<Result>& results) {
-	bool success = false;
-
-	/* map pids to our results, saves time with open_file_proc */
-	std::unordered_map<pid_t, Result*> pid_map;
-	std::set<pid_t> pids;
-
-	for (Result& result : results) {
-		const pid_t pid = result.process.pid;
-		if (!pid)
-			continue;
-
-		pid_map.insert({pid, &result});
-		pids.insert(pid);
-	}
-
-	auto open_file_proc = [&](const OpenFile& file) -> bool {
-		success |= AddMedia(*pid_map[file.pid], {MediaInfoType::File, file.path});
-		return true;
-	};
-
-	EnumerateOpenFiles(pids, open_file_proc);
-
-	return success;
-}
-
-bool ApplyStrategies(std::vector<Result>& results) {
-	bool success = false;
-
-	success |= ApplyWindowTitleStrategy(results);
-	success |= ApplyOpenFilesStrategy(results);
-
-	return success;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-} // namespace animone::internal
+#include <regex>
+#include <unordered_map>
+
+#include "animone.h"
+#include "animone/fd.h"
+#include "animone/strategies.h"
+#include "animone/util.h"
+
+/* this was STUPIDLY slow in Anisthesia, oops! */
+
+namespace animone::internal {
+
+static bool ApplyWindowTitleFormat(const std::string& format, std::string& title) {
+	if (format.empty())
+		return false;
+
+	const std::regex pattern(format);
+	std::smatch match;
+	std::regex_match(title, match, pattern);
+
+	// Use the first non-empty match result, because the regular expression may
+	// contain multiple sub-expressions.
+	for (size_t i = 1; i < match.size(); ++i) {
+		if (!match.str(i).empty()) {
+			title = match.str(i);
+			return true;
+		}
+	}
+
+	// Results are empty, but the match was successful
+	if (!match.empty()) {
+		title.clear();
+		return true;
+	}
+
+	return true;
+}
+
+static MediaInfoType InferMediaInformationType(const std::string& str) {
+	const std::regex path_pattern(R"(^(?:[A-Za-z]:[/\\]|\\\\)[^<>:"/\\|?*]+)");
+	return (std::regex_search(str, path_pattern)) ? MediaInfoType::File : MediaInfoType::Unknown;
+}
+
+static bool AddMedia(Result& result, const MediaInfo media_information) {
+	if (media_information.value.empty())
+		return false;
+
+	Media media;
+	media.information.push_back(media_information);
+	result.media.push_back(std::move(media));
+
+	return true;
+}
+
+static bool ApplyWindowTitleStrategy(std::vector<Result>& results) {
+	bool success = false;
+
+	for (auto& result : results) {
+		auto title = result.window.text;
+		if (title.empty())
+			continue;
+
+		ApplyWindowTitleFormat(result.player.window_title_format, title);
+
+		success |= AddMedia(result, {InferMediaInformationType(title), title});
+	}
+
+	return success;
+}
+
+static bool ApplyOpenFilesStrategy(std::vector<Result>& results) {
+	bool success = false;
+
+	/* map pids to our results, saves time with open_file_proc */
+	std::unordered_map<pid_t, Result*> pid_map;
+	std::set<pid_t> pids;
+
+	for (Result& result : results) {
+		const pid_t pid = result.process.pid;
+		if (!pid)
+			continue;
+
+		pid_map.insert({pid, &result});
+		pids.insert(pid);
+	}
+
+	auto open_file_proc = [&](const OpenFile& file) -> bool {
+		success |= AddMedia(*pid_map[file.pid], {MediaInfoType::File, file.path});
+		return true;
+	};
+
+	EnumerateOpenFiles(pids, open_file_proc);
+
+	return success;
+}
+
+bool ApplyStrategies(std::vector<Result>& results) {
+	bool success = false;
+
+	success |= ApplyWindowTitleStrategy(results);
+	success |= ApplyOpenFilesStrategy(results);
+
+	return success;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace animone::internal
--- a/src/util.cc	Mon May 13 14:15:47 2024 -0400
+++ b/src/util.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,85 +1,85 @@
-#include <algorithm>
-#include <fstream>
-#include <regex>
-#include <sstream>
-#include <string>
-
-#include "animone/util.h"
-
-namespace animone::internal::util {
-
-bool ReadFile(const std::string& path, std::string& data) {
-	std::ifstream file(path.c_str(), std::ios::in | std::ios::binary);
-	if (!file)
-		return false;
-
-	std::ostringstream string;
-	string << file.rdbuf();
-	file.close();
-
-	data = string.str();
-
-	return true;
-}
-
-/* this assumes ASCII... which really should be the case for what we need, anyway */
-bool EqualStrings(const std::string& str1, const std::string& str2) {
-	auto tolower = [](const char c) -> char { return ('A' <= c && c <= 'Z') ? c + ('a' - 'A') : c; };
-
-	auto equal_chars = [&tolower](const char c1, const char c2) -> bool { return tolower(c1) == tolower(c2); };
-
-	return str1.length() == str2.length() && std::equal(str1.begin(), str1.end(), str2.begin(), equal_chars);
-}
-
-bool Stem(const std::string& filename, std::string& stem) {
-	unsigned long long pos = filename.find_last_of(".");
-	if (pos != std::string::npos)
-		return false;
-
-	stem = filename.substr(0, pos);
-	return true;
-}
-
-bool CheckPattern(const std::string& pattern, const std::string& str) {
-	if (pattern.empty())
-		return false;
-	if (pattern.front() == '^' && std::regex_match(str, std::regex(pattern)))
-		return true;
-	return util::EqualStrings(pattern, str);
-}
-
-bool TrimLeft(std::string& str, const char* chars) {
-	if (str.empty())
-		return false;
-
-	const auto found = str.find_first_not_of(chars);
-
-	if (found == 0)
-		return false;
-
-	if (found == std::string::npos)
-		str.clear();
-	else
-		str.erase(0, found);
-
-	return true;
-}
-
-bool TrimRight(std::string& str, const char* chars) {
-	if (str.empty())
-		return false;
-
-	const auto found = str.find_last_not_of(chars);
-
-	if (found == str.size() - 1)
-		return false;
-
-	if (found == std::string::npos)
-		str.clear();
-	else
-		str.resize(found + 1);
-
-	return true;
-}
-
-} // namespace animone::internal::util
+#include <algorithm>
+#include <fstream>
+#include <regex>
+#include <sstream>
+#include <string>
+
+#include "animone/util.h"
+
+namespace animone::internal::util {
+
+bool ReadFile(const std::string& path, std::string& data) {
+	std::ifstream file(path.c_str(), std::ios::in | std::ios::binary);
+	if (!file)
+		return false;
+
+	std::ostringstream string;
+	string << file.rdbuf();
+	file.close();
+
+	data = string.str();
+
+	return true;
+}
+
+/* this assumes ASCII... which really should be the case for what we need, anyway */
+bool EqualStrings(const std::string& str1, const std::string& str2) {
+	auto tolower = [](const char c) -> char { return ('A' <= c && c <= 'Z') ? c + ('a' - 'A') : c; };
+
+	auto equal_chars = [&tolower](const char c1, const char c2) -> bool { return tolower(c1) == tolower(c2); };
+
+	return str1.length() == str2.length() && std::equal(str1.begin(), str1.end(), str2.begin(), equal_chars);
+}
+
+bool Stem(const std::string& filename, std::string& stem) {
+	unsigned long long pos = filename.find_last_of(".");
+	if (pos != std::string::npos)
+		return false;
+
+	stem = filename.substr(0, pos);
+	return true;
+}
+
+bool CheckPattern(const std::string& pattern, const std::string& str) {
+	if (pattern.empty())
+		return false;
+	if (pattern.front() == '^' && std::regex_match(str, std::regex(pattern)))
+		return true;
+	return util::EqualStrings(pattern, str);
+}
+
+bool TrimLeft(std::string& str, const char* chars) {
+	if (str.empty())
+		return false;
+
+	const auto found = str.find_first_not_of(chars);
+
+	if (found == 0)
+		return false;
+
+	if (found == std::string::npos)
+		str.clear();
+	else
+		str.erase(0, found);
+
+	return true;
+}
+
+bool TrimRight(std::string& str, const char* chars) {
+	if (str.empty())
+		return false;
+
+	const auto found = str.find_last_not_of(chars);
+
+	if (found == str.size() - 1)
+		return false;
+
+	if (found == std::string::npos)
+		str.clear();
+	else
+		str.resize(found + 1);
+
+	return true;
+}
+
+} // namespace animone::internal::util
--- a/src/util/osx.cc	Mon May 13 14:15:47 2024 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-#include "animone/util/osx.h"
-
-#include <memory>
-#include <string>
-
-#include <libproc.h>
-#include <sys/sysctl.h>
-
-namespace animone::internal::osx::util {
-
-static bool GetProcessNameFromProcPidPath(pid_t pid, std::string& result) {
-	result.assign(PROC_PIDPATHINFO_MAXSIZE, '\0');
-
-	int ret = proc_pidpath(pid, result.data(), result.size() * sizeof(char));
-	if (ret <= 0)
-		return false;
-
-	/* find the last slash, if there's none, we're done here */
-	size_t last_slash = result.rfind('/');
-	if (last_slash == std::string::npos)
-		return true;
-
-	result.erase(0, last_slash + 1);
-	return true;
-}
-
-static bool GetProcessNameFromProcName(pid_t pid, std::string& result) {
-	result.assign(2 * MAXCOMLEN, '\0');
-
-	int size = proc_name(pid, &result.front(), result.length());
-
-	/* if size is MAXCOMLEN or 2 * MAXCOMLEN, assume
-	 * this method won't work and our result is truncated */
-	if (size <= 0 || size == MAXCOMLEN || size == 2 * MAXCOMLEN)
-		return false;
-
-	result.resize(size);
-	return true;
-}
-
-bool GetProcessName(pid_t pid, std::string& result) {
-	if (GetProcessNameFromProcName(pid, result))
-		return true;
-
-	if (GetProcessNameFromProcPidPath(pid, result))
-		return true;
-
-	return false;
-}
-
-} // namespace animone::internal::osx::util
--- a/src/util/win32.cc	Mon May 13 14:15:47 2024 -0400
+++ b/src/util/win32.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,102 +1,102 @@
-#include "animone/util/win32.h"
-
-#include <shlobj.h>  /* SHGetKnownFolderPath */
-#include <subauth.h> /* UNICODE_STRING */
-#include <windows.h>
-
-namespace animone::internal::win32 {
-
-std::string ToUtf8String(const std::wstring& string) {
-	if (string.empty())
-		return std::string();
-
-	long size = ::WideCharToMultiByte(CP_UTF8, 0, string.c_str(), string.length(), nullptr, 0, nullptr, nullptr);
-	std::string ret(size, '\0');
-	::WideCharToMultiByte(CP_UTF8, 0, string.c_str(), string.length(), &ret.front(), ret.length(), nullptr, nullptr);
-	return ret;
-}
-
-std::string ToUtf8String(const UNICODE_STRING& string) {
-	const auto wctomb = [&string](LPSTR out, int size) -> int {
-		return ::WideCharToMultiByte(CP_UTF8, 0, string.Buffer, string.Length, out, size, nullptr, nullptr);
-	};
-
-	if (string.Length <= 0)
-		return std::string();
-
-	long size = wctomb(nullptr, 0);
-	std::string ret(size, '\0');
-	wctomb(&ret.front(), ret.length());
-	return ret;
-}
-
-std::wstring ToWstring(const std::string& string) {
-	const auto mbtowc = [&string](LPWSTR out, int size) -> int {
-		return ::MultiByteToWideChar(CP_UTF8, 0, string.c_str(), string.length(), out, size);
-	};
-
-	if (string.empty())
-		return std::wstring();
-
-	long size = mbtowc(nullptr, 0);
-	std::wstring ret(size, L'\0');
-	mbtowc(&ret.front(), ret.length());
-	return ret;
-}
-
-std::wstring GetProcessPath(DWORD process_id) {
-	// If we try to open a SYSTEM process, this function fails and the last error
-	// code is ERROR_ACCESS_DENIED.
-	//
-	// Note that if we requested PROCESS_QUERY_INFORMATION access right instead
-	// of PROCESS_QUERY_LIMITED_INFORMATION, this function would fail when used
-	// to open an elevated process.
-	Handle process_handle(::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id));
-
-	if (!process_handle)
-		return std::wstring();
-
-	std::wstring buffer(MAX_PATH, L'\0');
-	DWORD buf_size = buffer.length();
-
-	// Note that this function requires Windows Vista or above. You may use
-	// GetProcessImageFileName or GetModuleFileNameEx on earlier versions.
-	if (!::QueryFullProcessImageNameW(process_handle.get(), 0, &buffer.front(), &buf_size))
-		return std::wstring();
-
-	buffer.resize(buf_size);
-	return buffer;
-}
-
-std::wstring GetFileNameFromPath(const std::wstring& path) {
-	const auto pos = path.find_last_of(L"/\\");
-	return pos != std::wstring::npos ? path.substr(pos + 1) : path;
-}
-
-std::wstring GetFileNameWithoutExtension(const std::wstring& filename) {
-	const auto pos = filename.find_last_of(L".");
-	return pos != std::wstring::npos ? filename.substr(0, pos) : filename;
-}
-
-static std::wstring GetSystemDirectory() {
-	PWSTR path_wch;
-	SHGetKnownFolderPath(FOLDERID_Windows, 0, NULL, &path_wch);
-	std::wstring path_wstr(path_wch);
-	CoTaskMemFree(path_wch);
-	return path_wstr;
-}
-
-bool IsSystemDirectory(const std::string& path) {
-	return IsSystemDirectory(ToWstring(path));
-}
-
-bool IsSystemDirectory(std::wstring path) {
-	::CharUpperBuffW(&path.front(), path.length());
-
-	std::wstring windir = GetSystemDirectory();
-	::CharUpperBuffW(&windir.front(), windir.length());
-
-	return path.find(windir) == 4;
-}
-
-} // namespace animone::internal::win32
+#include "animone/util/win32.h"
+
+#include <shlobj.h>  /* SHGetKnownFolderPath */
+#include <subauth.h> /* UNICODE_STRING */
+#include <windows.h>
+
+namespace animone::internal::win32 {
+
+std::string ToUtf8String(const std::wstring& string) {
+	if (string.empty())
+		return std::string();
+
+	long size = ::WideCharToMultiByte(CP_UTF8, 0, string.c_str(), string.length(), nullptr, 0, nullptr, nullptr);
+	std::string ret(size, '\0');
+	::WideCharToMultiByte(CP_UTF8, 0, string.c_str(), string.length(), &ret.front(), ret.length(), nullptr, nullptr);
+	return ret;
+}
+
+std::string ToUtf8String(const UNICODE_STRING& string) {
+	const auto wctomb = [&string](LPSTR out, int size) -> int {
+		return ::WideCharToMultiByte(CP_UTF8, 0, string.Buffer, string.Length, out, size, nullptr, nullptr);
+	};
+
+	if (string.Length <= 0)
+		return std::string();
+
+	long size = wctomb(nullptr, 0);
+	std::string ret(size, '\0');
+	wctomb(&ret.front(), ret.length());
+	return ret;
+}
+
+std::wstring ToWstring(const std::string& string) {
+	const auto mbtowc = [&string](LPWSTR out, int size) -> int {
+		return ::MultiByteToWideChar(CP_UTF8, 0, string.c_str(), string.length(), out, size);
+	};
+
+	if (string.empty())
+		return std::wstring();
+
+	long size = mbtowc(nullptr, 0);
+	std::wstring ret(size, L'\0');
+	mbtowc(&ret.front(), ret.length());
+	return ret;
+}
+
+std::wstring GetProcessPath(DWORD process_id) {
+	// If we try to open a SYSTEM process, this function fails and the last error
+	// code is ERROR_ACCESS_DENIED.
+	//
+	// Note that if we requested PROCESS_QUERY_INFORMATION access right instead
+	// of PROCESS_QUERY_LIMITED_INFORMATION, this function would fail when used
+	// to open an elevated process.
+	Handle process_handle(::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id));
+
+	if (!process_handle)
+		return std::wstring();
+
+	std::wstring buffer(MAX_PATH, L'\0');
+	DWORD buf_size = buffer.length();
+
+	// Note that this function requires Windows Vista or above. You may use
+	// GetProcessImageFileName or GetModuleFileNameEx on earlier versions.
+	if (!::QueryFullProcessImageNameW(process_handle.get(), 0, &buffer.front(), &buf_size))
+		return std::wstring();
+
+	buffer.resize(buf_size);
+	return buffer;
+}
+
+std::wstring GetFileNameFromPath(const std::wstring& path) {
+	const auto pos = path.find_last_of(L"/\\");
+	return pos != std::wstring::npos ? path.substr(pos + 1) : path;
+}
+
+std::wstring GetFileNameWithoutExtension(const std::wstring& filename) {
+	const auto pos = filename.find_last_of(L".");
+	return pos != std::wstring::npos ? filename.substr(0, pos) : filename;
+}
+
+static std::wstring GetSystemDirectory() {
+	PWSTR path_wch;
+	SHGetKnownFolderPath(FOLDERID_Windows, 0, NULL, &path_wch);
+	std::wstring path_wstr(path_wch);
+	CoTaskMemFree(path_wch);
+	return path_wstr;
+}
+
+bool IsSystemDirectory(const std::string& path) {
+	return IsSystemDirectory(ToWstring(path));
+}
+
+bool IsSystemDirectory(std::wstring path) {
+	::CharUpperBuffW(&path.front(), path.length());
+
+	std::wstring windir = GetSystemDirectory();
+	::CharUpperBuffW(&windir.front(), windir.length());
+
+	return path.find(windir) == 4;
+}
+
+} // namespace animone::internal::win32
--- a/src/win.cc	Mon May 13 14:15:47 2024 -0400
+++ b/src/win.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,35 +1,35 @@
-#include "animone/win.h"
-
-#ifdef WIN32
-#	include "animone/win/win32.h"
-#endif
-
-#ifdef MACOSX
-#	include "animone/win/quartz.h"
-#endif
-
-#ifdef X11
-#	include "animone/win/x11.h"
-#endif
-
-namespace animone::internal {
-
-bool EnumerateWindows(window_proc_t window_proc) {
-	bool success = false;
-
-#ifdef WIN32
-	success |= win32::EnumerateWindows(window_proc);
-#endif
-
-#ifdef MACOSX
-	success |= quartz::EnumerateWindows(window_proc);
-#endif
-
-#ifdef X11
-	success |= x11::EnumerateWindows(window_proc);
-#endif
-
-	return success;
-}
-
-} // namespace animone::internal
+#include "animone/win.h"
+
+#ifdef WIN32
+#	include "animone/win/win32.h"
+#endif
+
+#ifdef MACOSX
+#	include "animone/win/quartz.h"
+#endif
+
+#ifdef X11
+#	include "animone/win/x11.h"
+#endif
+
+namespace animone::internal {
+
+bool EnumerateWindows(window_proc_t window_proc) {
+	bool success = false;
+
+#ifdef WIN32
+	success |= win32::EnumerateWindows(window_proc);
+#endif
+
+#ifdef MACOSX
+	success |= quartz::EnumerateWindows(window_proc);
+#endif
+
+#ifdef X11
+	success |= x11::EnumerateWindows(window_proc);
+#endif
+
+	return success;
+}
+
+} // namespace animone::internal
--- a/src/win/win32.cc	Mon May 13 14:15:47 2024 -0400
+++ b/src/win/win32.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,144 +1,144 @@
-/*
- * win/win32.cc: support for Windows
- *
- * Surprisingly, this is the one time where Microsoft actually
- * does it fairly OK. Everything has a pretty simple API, despite
- * the stupid wide string stuff.
- */
-#include "animone/win/win32.h"
-#include "animone.h"
-#include "animone/util/win32.h"
-#include "animone/win.h"
-#include "animone/fd.h"
-
-#include <set>
-#include <string>
-
-#include <windows.h>
-
-namespace animone::internal::win32 {
-
-static std::wstring GetWindowClassName(HWND hwnd) {
-	static constexpr int kMaxSize = 256;
-
-	std::wstring buffer(kMaxSize, L'\0');
-	const auto size = ::GetClassNameW(hwnd, &buffer.front(), buffer.length());
-	buffer.resize(size);
-	return buffer;
-}
-
-static std::wstring GetWindowText(HWND hwnd) {
-	const auto estimated_size = ::GetWindowTextLengthW(hwnd);
-	std::wstring buffer(estimated_size + 1, L'\0');
-
-	const auto size = ::GetWindowTextW(hwnd, &buffer.front(), buffer.length());
-	/* GetWindowTextLength docs:
-	 * "Under certain conditions, the GetWindowTextLength function may return a value
-	 *  that is larger than the actual length of the text." */
-	buffer.resize(size);
-	return buffer;
-}
-
-static DWORD GetWindowProcessId(HWND hwnd) {
-	DWORD process_id = 0;
-	::GetWindowThreadProcessId(hwnd, &process_id);
-	return process_id;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-static bool VerifyWindowStyle(HWND hwnd) {
-	const auto window_style = ::GetWindowLong(hwnd, GWL_STYLE);
-	const auto window_ex_style = ::GetWindowLong(hwnd, GWL_EXSTYLE);
-
-	auto has_style = [&window_style](DWORD style) { return (window_style & style) != 0; };
-	auto has_ex_style = [&window_ex_style](DWORD ex_style) { return (window_ex_style & ex_style) != 0; };
-
-	// Toolbars, tooltips and similar topmost windows
-	if (has_style(WS_POPUP) && has_ex_style(WS_EX_TOOLWINDOW))
-		return false;
-	if (has_ex_style(WS_EX_TOPMOST) && has_ex_style(WS_EX_TOOLWINDOW))
-		return false;
-
-	return true;
-}
-
-static bool VerifyClassName(const std::wstring& name) {
-	static const std::set<std::wstring> invalid_names = {
-	    // System classes
-	    L"#32770",        // Dialog box
-	    L"CabinetWClass", // Windows Explorer
-	    L"ComboLBox",
-	    L"DDEMLEvent",
-	    L"DDEMLMom",
-	    L"DirectUIHWND",
-	    L"GDI+ Hook Window Class",
-	    L"IME",
-	    L"Internet Explorer_Hidden",
-	    L"MSCTFIME UI",
-	    L"tooltips_class32",
-	};
-
-	return !name.empty() && !invalid_names.count(name);
-}
-
-static bool VerifyProcessPath(const std::wstring& path) {
-	return !path.empty() && !IsSystemDirectory(path);
-}
-
-static bool VerifyProcessFileName(const std::wstring& name) {
-	static const std::set<std::wstring> invalid_names = {
-	    // System files
-	    L"explorer",   // Windows Explorer
-	    L"taskeng",    // Task Scheduler Engine
-	    L"taskhost",   // Host Process for Windows Tasks
-	    L"taskhostex", // Host Process for Windows Tasks
-	    L"Taskmgr",    // Task Manager
-	};
-
-	return !name.empty() && !invalid_names.count(name);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM param) {
-	if (!::IsWindowVisible(hwnd))
-		return TRUE;
-
-	if (!VerifyWindowStyle(hwnd))
-		return TRUE;
-
-	Window window;
-	window.id = static_cast<unsigned int>(reinterpret_cast<ULONG_PTR>(hwnd));
-	window.text = ToUtf8String(GetWindowText(hwnd));
-
-	{
-		std::wstring class_name = GetWindowClassName(hwnd);
-		window.class_name = ToUtf8String(class_name);
-		if (!VerifyClassName(class_name))
-			return TRUE;
-	}
-
-	Process process;
-	process.pid = GetWindowProcessId(hwnd);
-	GetProcessName(process.pid, process.name);
-
-	auto& window_proc = *reinterpret_cast<window_proc_t*>(param);
-	if (!window_proc(process, window))
-		return FALSE;
-
-	return TRUE;
-}
-
-bool EnumerateWindows(window_proc_t window_proc) {
-	if (!window_proc)
-		return false;
-
-	const auto param = reinterpret_cast<LPARAM>(&window_proc);
-
-	// Note that EnumWindows enumerates only top-level windows of desktop apps
-	// (as opposed to UWP apps) on Windows 8 and above.
-	return ::EnumWindows(EnumWindowsProc, param) != FALSE;
-}
-
-} // namespace animone::internal::win32
+/*
+ * win/win32.cc: support for Windows
+ *
+ * Surprisingly, this is the one time where Microsoft actually
+ * does it fairly OK. Everything has a pretty simple API, despite
+ * the stupid wide string stuff.
+ */
+#include "animone/win/win32.h"
+#include "animone.h"
+#include "animone/util/win32.h"
+#include "animone/win.h"
+#include "animone/fd.h"
+
+#include <set>
+#include <string>
+
+#include <windows.h>
+
+namespace animone::internal::win32 {
+
+static std::wstring GetWindowClassName(HWND hwnd) {
+	static constexpr int kMaxSize = 256;
+
+	std::wstring buffer(kMaxSize, L'\0');
+	const auto size = ::GetClassNameW(hwnd, &buffer.front(), buffer.length());
+	buffer.resize(size);
+	return buffer;
+}
+
+static std::wstring GetWindowText(HWND hwnd) {
+	const auto estimated_size = ::GetWindowTextLengthW(hwnd);
+	std::wstring buffer(estimated_size + 1, L'\0');
+
+	const auto size = ::GetWindowTextW(hwnd, &buffer.front(), buffer.length());
+	/* GetWindowTextLength docs:
+	 * "Under certain conditions, the GetWindowTextLength function may return a value
+	 *  that is larger than the actual length of the text." */
+	buffer.resize(size);
+	return buffer;
+}
+
+static DWORD GetWindowProcessId(HWND hwnd) {
+	DWORD process_id = 0;
+	::GetWindowThreadProcessId(hwnd, &process_id);
+	return process_id;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static bool VerifyWindowStyle(HWND hwnd) {
+	const auto window_style = ::GetWindowLong(hwnd, GWL_STYLE);
+	const auto window_ex_style = ::GetWindowLong(hwnd, GWL_EXSTYLE);
+
+	auto has_style = [&window_style](DWORD style) { return (window_style & style) != 0; };
+	auto has_ex_style = [&window_ex_style](DWORD ex_style) { return (window_ex_style & ex_style) != 0; };
+
+	// Toolbars, tooltips and similar topmost windows
+	if (has_style(WS_POPUP) && has_ex_style(WS_EX_TOOLWINDOW))
+		return false;
+	if (has_ex_style(WS_EX_TOPMOST) && has_ex_style(WS_EX_TOOLWINDOW))
+		return false;
+
+	return true;
+}
+
+static bool VerifyClassName(const std::wstring& name) {
+	static const std::set<std::wstring> invalid_names = {
+	    // System classes
+	    L"#32770",        // Dialog box
+	    L"CabinetWClass", // Windows Explorer
+	    L"ComboLBox",
+	    L"DDEMLEvent",
+	    L"DDEMLMom",
+	    L"DirectUIHWND",
+	    L"GDI+ Hook Window Class",
+	    L"IME",
+	    L"Internet Explorer_Hidden",
+	    L"MSCTFIME UI",
+	    L"tooltips_class32",
+	};
+
+	return !name.empty() && !invalid_names.count(name);
+}
+
+static bool VerifyProcessPath(const std::wstring& path) {
+	return !path.empty() && !IsSystemDirectory(path);
+}
+
+static bool VerifyProcessFileName(const std::wstring& name) {
+	static const std::set<std::wstring> invalid_names = {
+	    // System files
+	    L"explorer",   // Windows Explorer
+	    L"taskeng",    // Task Scheduler Engine
+	    L"taskhost",   // Host Process for Windows Tasks
+	    L"taskhostex", // Host Process for Windows Tasks
+	    L"Taskmgr",    // Task Manager
+	};
+
+	return !name.empty() && !invalid_names.count(name);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM param) {
+	if (!::IsWindowVisible(hwnd))
+		return TRUE;
+
+	if (!VerifyWindowStyle(hwnd))
+		return TRUE;
+
+	Window window;
+	window.id = static_cast<unsigned int>(reinterpret_cast<ULONG_PTR>(hwnd));
+	window.text = ToUtf8String(GetWindowText(hwnd));
+
+	{
+		std::wstring class_name = GetWindowClassName(hwnd);
+		window.class_name = ToUtf8String(class_name);
+		if (!VerifyClassName(class_name))
+			return TRUE;
+	}
+
+	Process process;
+	process.pid = GetWindowProcessId(hwnd);
+	GetProcessName(process.pid, process.name);
+
+	auto& window_proc = *reinterpret_cast<window_proc_t*>(param);
+	if (!window_proc(process, window))
+		return FALSE;
+
+	return TRUE;
+}
+
+bool EnumerateWindows(window_proc_t window_proc) {
+	if (!window_proc)
+		return false;
+
+	const auto param = reinterpret_cast<LPARAM>(&window_proc);
+
+	// Note that EnumWindows enumerates only top-level windows of desktop apps
+	// (as opposed to UWP apps) on Windows 8 and above.
+	return ::EnumWindows(EnumWindowsProc, param) != FALSE;
+}
+
+} // namespace animone::internal::win32
--- a/src/win/x11.cc	Mon May 13 14:15:47 2024 -0400
+++ b/src/win/x11.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,256 +1,256 @@
-#include "animone/win/x11.h"
-#include "animone.h"
-#include "animone/fd.h" /* GetProcessName() */
-#include "animone/win.h"
-
-#include <xcb/res.h>
-#include <xcb/xcb.h>
-
-#include <climits>
-#include <cstdint>
-#include <cstring>
-#include <set>
-#include <string>
-#include <memory>
-
-#include <chrono>
-
-#include <iostream>
-
-/* This uses XCB (and it uses it *right*), so it should be plenty fast */
-
-static size_t str_nlen(const char* s, size_t len) {
-	size_t i = 0;
-	for (; i < len && s[i]; i++)
-		;
-	return i;
-}
-
-namespace animone::internal::x11 {
-
-static bool GetAllTopLevelWindowsEWMH(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots,
-                                      std::set<xcb_window_t>& result) {
-	const xcb_atom_t Atom__NET_CLIENT_LIST = [connection] {
-		static constexpr std::string_view name = "_NET_CLIENT_LIST";
-		xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data());
-		std::unique_ptr<xcb_intern_atom_reply_t> reply(::xcb_intern_atom_reply(connection, cookie, NULL));
-
-		xcb_atom_t atom = reply->atom;
-
-		return atom;
-	}();
-	if (Atom__NET_CLIENT_LIST == XCB_ATOM_NONE)
-		return false; // BTFO
-
-	bool success = false;
-
-	std::vector<xcb_get_property_cookie_t> cookies;
-	cookies.reserve(roots.size());
-
-	for (const auto& root : roots)
-		cookies.push_back(::xcb_get_property(connection, 0, root, Atom__NET_CLIENT_LIST, XCB_ATOM_ANY, 0L, UINT_MAX));
-
-	for (const auto& cookie : cookies) {
-		std::unique_ptr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, cookie, NULL));
-
-		if (reply) {
-			xcb_window_t* value = reinterpret_cast<xcb_window_t*>(::xcb_get_property_value(reply.get()));
-			int len = ::xcb_get_property_value_length(reply.get());
-
-			for (size_t i = 0; i < len; i++)
-				result.insert(value[i]);
-
-			success |= true;
-		}
-	}
-
-	return success;
-}
-
-/* This is called on every window. What this does is:
- * 1. Gets the tree of children
- * 2. Searches all children recursively for a WM_STATE property
- * 3. If that failed... return the original window
- */
-static bool WalkWindows(xcb_connection_t* connection, int depth, xcb_atom_t Atom_WM_STATE, const xcb_window_t* windows,
-                        int windows_len, std::set<xcb_window_t>& result) {
-	/* The depth we should start returning at. */
-	static constexpr int CUTOFF = 1;
-
-	bool success = false;
-
-	std::vector<xcb_query_tree_cookie_t> cookies;
-	cookies.reserve(windows_len);
-
-	for (int i = 0; i < windows_len; i++)
-		cookies.push_back(::xcb_query_tree(connection, windows[i]));
-
-	for (const auto& cookie : cookies) {
-		std::unique_ptr<xcb_query_tree_reply_t> query_tree_reply(::xcb_query_tree_reply(connection, cookie, NULL));
-
-		xcb_window_t* tree_children = ::xcb_query_tree_children(query_tree_reply.get());
-		int tree_children_len = ::xcb_query_tree_children_length(query_tree_reply.get());
-
-		std::vector<xcb_get_property_cookie_t> state_property_cookies;
-		state_property_cookies.reserve(tree_children_len);
-
-		for (int i = 0; i < tree_children_len; i++)
-			state_property_cookies.push_back(
-			    ::xcb_get_property(connection, 0, tree_children[i], Atom_WM_STATE, Atom_WM_STATE, 0, 0));
-
-		for (int i = 0; i < tree_children_len; i++) {
-			std::unique_ptr<xcb_get_property_reply_t> get_property_reply(
-			    ::xcb_get_property_reply(connection, state_property_cookies[i], NULL));
-
-			/* X11 is unfriendly here. what this means is "did the property exist?" */
-			if (get_property_reply->format || get_property_reply->type || get_property_reply->length) {
-				result.insert(tree_children[i]);
-				if (depth >= CUTOFF)
-					return true;
-
-				success |= true;
-				continue;
-			}
-		}
-
-		if (WalkWindows(connection, depth + 1, Atom_WM_STATE, tree_children, tree_children_len, result)) {
-			success |= true;
-			if (depth >= CUTOFF)
-				return true;
-			continue;
-		}
-	}
-
-	return success;
-}
-
-static bool GetAllTopLevelWindowsICCCM(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots,
-                                       std::set<xcb_window_t>& result) {
-	bool success = false;
-
-	xcb_atom_t Atom_WM_STATE = [connection] {
-		static constexpr std::string_view name = "WM_STATE";
-		xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data());
-		xcb_intern_atom_reply_t* reply = ::xcb_intern_atom_reply(connection, cookie, NULL);
-
-		xcb_atom_t atom = reply->atom;
-		free(reply);
-		return atom;
-	}();
-	if (Atom_WM_STATE == XCB_ATOM_NONE)
-		return success;
-
-	std::vector<xcb_query_tree_cookie_t> cookies;
-	cookies.reserve(roots.size());
-
-	for (const auto& root : roots)
-		cookies.push_back(::xcb_query_tree(connection, root));
-
-	for (const auto& cookie : cookies)
-		success |= WalkWindows(connection, 0, Atom_WM_STATE, roots.data(), roots.size(), result);
-
-	return success;
-}
-
-bool EnumerateWindows(window_proc_t window_proc) {
-	if (!window_proc)
-		return false;
-
-	xcb_connection_t* connection = ::xcb_connect(NULL, NULL);
-	if (xcb_connection_has_error(connection))
-		return false;
-
-	std::set<xcb_window_t> windows;
-	{
-		std::vector<xcb_window_t> roots;
-		{
-			xcb_screen_iterator_t iter = ::xcb_setup_roots_iterator(::xcb_get_setup(connection));
-			for (; iter.rem; ::xcb_screen_next(&iter))
-				roots.push_back(iter.data->root);
-		}
-
-		if (!GetAllTopLevelWindowsEWMH(connection, roots, windows))
-			GetAllTopLevelWindowsICCCM(connection, roots, windows);
-	}
-
-	struct WindowCookies {
-		xcb_window_t window;
-		xcb_get_property_cookie_t class_property_cookie;
-		xcb_get_property_cookie_t name_property_cookie;
-		xcb_res_query_client_ids_cookie_t pid_property_cookie;
-	};
-
-	std::vector<WindowCookies> window_cookies;
-	window_cookies.reserve(windows.size());
-
-	for (const auto& window : windows) {
-		xcb_res_client_id_spec_t spec = {.client = window, .mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID};
-
-		WindowCookies window_cookie = {
-		    window, ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0L, 2048L),
-		    ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0L, UINT_MAX),
-		    ::xcb_res_query_client_ids(connection, 1, &spec)};
-
-		window_cookies.push_back(window_cookie);
-	}
-
-	for (const auto& window_cookie : window_cookies) {
-		Window win = {0};
-		win.id = window_cookie.window;
-		{
-			/* Class name */
-			std::unique_ptr<xcb_get_property_reply_t> reply(
-			    ::xcb_get_property_reply(connection, window_cookie.class_property_cookie, NULL));
-
-			if (reply && reply->format == 8) {
-				const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply.get()));
-				const int data_len = ::xcb_get_property_value_length(reply.get());
-
-				int instance_len = str_nlen(data, data_len);
-				const char* class_name = data + instance_len + 1;
-
-				win.class_name = std::string(class_name, str_nlen(class_name, data_len - (instance_len + 1)));
-			}
-		}
-		{
-			/* Title text */
-			std::unique_ptr<xcb_get_property_reply_t> reply(
-			    ::xcb_get_property_reply(connection, window_cookie.name_property_cookie, NULL));
-
-			if (reply) {
-				const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply.get()));
-				int len = ::xcb_get_property_value_length(reply.get());
-
-				win.text = std::string(data, len);
-			}
-		}
-		Process proc = {0};
-		{
-			/* PID */
-			std::unique_ptr<xcb_res_query_client_ids_reply_t> reply(
-			    ::xcb_res_query_client_ids_reply(connection, window_cookie.pid_property_cookie, NULL));
-
-			if (reply) {
-				xcb_res_client_id_value_iterator_t it = ::xcb_res_query_client_ids_ids_iterator(reply.get());
-				for (; it.rem; ::xcb_res_client_id_value_next(&it)) {
-					if (it.data->spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) {
-						proc.pid = *::xcb_res_client_id_value_value(it.data);
-						GetProcessName(proc.pid, proc.name); /* fill this in if we can */
-						break;
-					}
-				}
-			}
-		}
-
-		if (!window_proc(proc, win)) {
-			::xcb_disconnect(connection);
-			return false;
-		}
-	}
-
-	::xcb_disconnect(connection);
-
-	return true;
-}
-
-} // namespace animone::internal::x11
+#include "animone/win/x11.h"
+#include "animone.h"
+#include "animone/fd.h" /* GetProcessName() */
+#include "animone/win.h"
+
+#include <xcb/res.h>
+#include <xcb/xcb.h>
+
+#include <climits>
+#include <cstdint>
+#include <cstring>
+#include <set>
+#include <string>
+#include <memory>
+
+#include <chrono>
+
+#include <iostream>
+
+/* This uses XCB (and it uses it *right*), so it should be plenty fast */
+
+static size_t str_nlen(const char* s, size_t len) {
+	size_t i = 0;
+	for (; i < len && s[i]; i++)
+		;
+	return i;
+}
+
+namespace animone::internal::x11 {
+
+static bool GetAllTopLevelWindowsEWMH(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots,
+                                      std::set<xcb_window_t>& result) {
+	const xcb_atom_t Atom__NET_CLIENT_LIST = [connection] {
+		static constexpr std::string_view name = "_NET_CLIENT_LIST";
+		xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data());
+		std::unique_ptr<xcb_intern_atom_reply_t> reply(::xcb_intern_atom_reply(connection, cookie, NULL));
+
+		xcb_atom_t atom = reply->atom;
+
+		return atom;
+	}();
+	if (Atom__NET_CLIENT_LIST == XCB_ATOM_NONE)
+		return false; // BTFO
+
+	bool success = false;
+
+	std::vector<xcb_get_property_cookie_t> cookies;
+	cookies.reserve(roots.size());
+
+	for (const auto& root : roots)
+		cookies.push_back(::xcb_get_property(connection, 0, root, Atom__NET_CLIENT_LIST, XCB_ATOM_ANY, 0L, UINT_MAX));
+
+	for (const auto& cookie : cookies) {
+		std::unique_ptr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, cookie, NULL));
+
+		if (reply) {
+			xcb_window_t* value = reinterpret_cast<xcb_window_t*>(::xcb_get_property_value(reply.get()));
+			int len = ::xcb_get_property_value_length(reply.get());
+
+			for (size_t i = 0; i < len; i++)
+				result.insert(value[i]);
+
+			success |= true;
+		}
+	}
+
+	return success;
+}
+
+/* This is called on every window. What this does is:
+ * 1. Gets the tree of children
+ * 2. Searches all children recursively for a WM_STATE property
+ * 3. If that failed... return the original window
+ */
+static bool WalkWindows(xcb_connection_t* connection, int depth, xcb_atom_t Atom_WM_STATE, const xcb_window_t* windows,
+                        int windows_len, std::set<xcb_window_t>& result) {
+	/* The depth we should start returning at. */
+	static constexpr int CUTOFF = 1;
+
+	bool success = false;
+
+	std::vector<xcb_query_tree_cookie_t> cookies;
+	cookies.reserve(windows_len);
+
+	for (int i = 0; i < windows_len; i++)
+		cookies.push_back(::xcb_query_tree(connection, windows[i]));
+
+	for (const auto& cookie : cookies) {
+		std::unique_ptr<xcb_query_tree_reply_t> query_tree_reply(::xcb_query_tree_reply(connection, cookie, NULL));
+
+		xcb_window_t* tree_children = ::xcb_query_tree_children(query_tree_reply.get());
+		int tree_children_len = ::xcb_query_tree_children_length(query_tree_reply.get());
+
+		std::vector<xcb_get_property_cookie_t> state_property_cookies;
+		state_property_cookies.reserve(tree_children_len);
+
+		for (int i = 0; i < tree_children_len; i++)
+			state_property_cookies.push_back(
+			    ::xcb_get_property(connection, 0, tree_children[i], Atom_WM_STATE, Atom_WM_STATE, 0, 0));
+
+		for (int i = 0; i < tree_children_len; i++) {
+			std::unique_ptr<xcb_get_property_reply_t> get_property_reply(
+			    ::xcb_get_property_reply(connection, state_property_cookies[i], NULL));
+
+			/* X11 is unfriendly here. what this means is "did the property exist?" */
+			if (get_property_reply->format || get_property_reply->type || get_property_reply->length) {
+				result.insert(tree_children[i]);
+				if (depth >= CUTOFF)
+					return true;
+
+				success |= true;
+				continue;
+			}
+		}
+
+		if (WalkWindows(connection, depth + 1, Atom_WM_STATE, tree_children, tree_children_len, result)) {
+			success |= true;
+			if (depth >= CUTOFF)
+				return true;
+			continue;
+		}
+	}
+
+	return success;
+}
+
+static bool GetAllTopLevelWindowsICCCM(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots,
+                                       std::set<xcb_window_t>& result) {
+	bool success = false;
+
+	xcb_atom_t Atom_WM_STATE = [connection] {
+		static constexpr std::string_view name = "WM_STATE";
+		xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data());
+		xcb_intern_atom_reply_t* reply = ::xcb_intern_atom_reply(connection, cookie, NULL);
+
+		xcb_atom_t atom = reply->atom;
+		free(reply);
+		return atom;
+	}();
+	if (Atom_WM_STATE == XCB_ATOM_NONE)
+		return success;
+
+	std::vector<xcb_query_tree_cookie_t> cookies;
+	cookies.reserve(roots.size());
+
+	for (const auto& root : roots)
+		cookies.push_back(::xcb_query_tree(connection, root));
+
+	for (const auto& cookie : cookies)
+		success |= WalkWindows(connection, 0, Atom_WM_STATE, roots.data(), roots.size(), result);
+
+	return success;
+}
+
+bool EnumerateWindows(window_proc_t window_proc) {
+	if (!window_proc)
+		return false;
+
+	xcb_connection_t* connection = ::xcb_connect(NULL, NULL);
+	if (xcb_connection_has_error(connection))
+		return false;
+
+	std::set<xcb_window_t> windows;
+	{
+		std::vector<xcb_window_t> roots;
+		{
+			xcb_screen_iterator_t iter = ::xcb_setup_roots_iterator(::xcb_get_setup(connection));
+			for (; iter.rem; ::xcb_screen_next(&iter))
+				roots.push_back(iter.data->root);
+		}
+
+		if (!GetAllTopLevelWindowsEWMH(connection, roots, windows))
+			GetAllTopLevelWindowsICCCM(connection, roots, windows);
+	}
+
+	struct WindowCookies {
+		xcb_window_t window;
+		xcb_get_property_cookie_t class_property_cookie;
+		xcb_get_property_cookie_t name_property_cookie;
+		xcb_res_query_client_ids_cookie_t pid_property_cookie;
+	};
+
+	std::vector<WindowCookies> window_cookies;
+	window_cookies.reserve(windows.size());
+
+	for (const auto& window : windows) {
+		xcb_res_client_id_spec_t spec = {.client = window, .mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID};
+
+		WindowCookies window_cookie = {
+		    window, ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0L, 2048L),
+		    ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0L, UINT_MAX),
+		    ::xcb_res_query_client_ids(connection, 1, &spec)};
+
+		window_cookies.push_back(window_cookie);
+	}
+
+	for (const auto& window_cookie : window_cookies) {
+		Window win = {0};
+		win.id = window_cookie.window;
+		{
+			/* Class name */
+			std::unique_ptr<xcb_get_property_reply_t> reply(
+			    ::xcb_get_property_reply(connection, window_cookie.class_property_cookie, NULL));
+
+			if (reply && reply->format == 8) {
+				const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply.get()));
+				const int data_len = ::xcb_get_property_value_length(reply.get());
+
+				int instance_len = str_nlen(data, data_len);
+				const char* class_name = data + instance_len + 1;
+
+				win.class_name = std::string(class_name, str_nlen(class_name, data_len - (instance_len + 1)));
+			}
+		}
+		{
+			/* Title text */
+			std::unique_ptr<xcb_get_property_reply_t> reply(
+			    ::xcb_get_property_reply(connection, window_cookie.name_property_cookie, NULL));
+
+			if (reply) {
+				const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply.get()));
+				int len = ::xcb_get_property_value_length(reply.get());
+
+				win.text = std::string(data, len);
+			}
+		}
+		Process proc = {0};
+		{
+			/* PID */
+			std::unique_ptr<xcb_res_query_client_ids_reply_t> reply(
+			    ::xcb_res_query_client_ids_reply(connection, window_cookie.pid_property_cookie, NULL));
+
+			if (reply) {
+				xcb_res_client_id_value_iterator_t it = ::xcb_res_query_client_ids_ids_iterator(reply.get());
+				for (; it.rem; ::xcb_res_client_id_value_next(&it)) {
+					if (it.data->spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) {
+						proc.pid = *::xcb_res_client_id_value_value(it.data);
+						GetProcessName(proc.pid, proc.name); /* fill this in if we can */
+						break;
+					}
+				}
+			}
+		}
+
+		if (!window_proc(proc, win)) {
+			::xcb_disconnect(connection);
+			return false;
+		}
+	}
+
+	::xcb_disconnect(connection);
+
+	return true;
+}
+
+} // namespace animone::internal::x11