changeset 301:b1f625b0227c

*: 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 8eb0cfe59992
children 675865737a23
files dep/animone/LICENSE.MIT dep/animone/include/animone.h dep/animone/include/animone/fd.h dep/animone/include/animone/fd/proc.h dep/animone/include/animone/fd/win32.h dep/animone/include/animone/fd/xnu.h dep/animone/include/animone/media.h dep/animone/include/animone/player.h dep/animone/include/animone/strategies.h dep/animone/include/animone/types.h dep/animone/include/animone/util.h dep/animone/include/animone/util/osx.h dep/animone/include/animone/util/win32.h dep/animone/include/animone/win.h dep/animone/include/animone/win/win32.h dep/animone/include/animone/win/x11.h dep/animone/src/animone.cc dep/animone/src/fd.cc dep/animone/src/fd/bsd.cc dep/animone/src/fd/proc.cc dep/animone/src/fd/win32.cc dep/animone/src/fd/xnu.cc dep/animone/src/player.cc dep/animone/src/strategist.cc dep/animone/src/util.cc dep/animone/src/util/osx.cc dep/animone/src/util/win32.cc dep/animone/src/win.cc dep/animone/src/win/win32.cc dep/animone/src/win/x11.cc include/core/http.h include/core/ini.h include/core/torrent.h include/gui/locale.h include/gui/translate/anilist.h include/gui/translate/config.h include/gui/widgets/anime_info.h include/gui/widgets/clickable_label.h include/gui/widgets/poster.h include/track/media.h rc/animone.qrc rc/sys/win32/resource.rc rc/sys/win32/version.rc src/core/http.cc src/gui/dialog/settings/recognition.cc src/gui/dialog/settings/torrents.cc src/gui/locale.cc src/gui/translate/anilist.cc src/gui/translate/config.cc src/gui/widgets/anime_info.cc src/gui/widgets/clickable_label.cc src/gui/widgets/poster.cc src/track/media.cc
diffstat 53 files changed, 3240 insertions(+), 3308 deletions(-) [+]
line wrap: on
line diff
--- a/dep/animone/LICENSE.MIT	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/include/animone.h	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/include/animone/fd.h	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/include/animone/fd/proc.h	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/include/animone/fd/win32.h	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/include/animone/fd/xnu.h	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/include/animone/media.h	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/include/animone/player.h	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/include/animone/strategies.h	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/include/animone/types.h	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/include/animone/util.h	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/include/animone/util/osx.h	Mon May 13 14:56:37 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/dep/animone/include/animone/util/win32.h	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/include/animone/win.h	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/include/animone/win/win32.h	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/include/animone/win/x11.h	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/src/animone.cc	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/src/fd.cc	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/src/fd/bsd.cc	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/src/fd/proc.cc	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/src/fd/win32.cc	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/src/fd/xnu.cc	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/src/player.cc	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/src/strategist.cc	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/src/util.cc	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/src/util/osx.cc	Mon May 13 14:56:37 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/dep/animone/src/util/win32.cc	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/src/win.cc	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/src/win/win32.cc	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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/dep/animone/src/win/x11.cc	Mon May 13 14:56:37 2024 -0400
+++ b/dep/animone/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
--- a/include/core/http.h	Mon May 13 14:56:37 2024 -0400
+++ b/include/core/http.h	Mon May 13 15:04:51 2024 -0400
@@ -1,58 +1,58 @@
-#ifndef MINORI_CORE_HTTP_H_
-#define MINORI_CORE_HTTP_H_
-
-#include <QByteArray>
-#include <QThread>
-
-#include <string>
-#include <vector>
-#include <mutex>
-
-namespace HTTP {
-
-enum class Type {
-	Get,
-	Post
-};
-
-QByteArray Request(const std::string& url, const std::vector<std::string>& headers = {}, const std::string& data = "", Type type = Type::Get);
-
-class RequestThread final : public QThread {
-	Q_OBJECT
-
-public:
-	RequestThread(Type type = Type::Get, QObject* parent = nullptr);
-	RequestThread(const std::string& url, const std::vector<std::string>& headers = {},
-		          const std::string& data = "", Type type = Type::Get, QObject* parent = nullptr);
-	~RequestThread();
-
-	void SetUrl(const std::string& url);
-	void SetHeaders(const std::vector<std::string>& headers);
-	void SetData(const std::string& data);
-	void SetType(Type type);
-
-	void Stop();
-
-signals:
-	void ReceivedData(const QByteArray& ba);
-
-protected:
-	void run() override;
-	static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userdata);
-
-	std::string url_;
-	std::string data_;
-	std::vector<std::string> headers_;
-	Type type_;
-
-	/* these are passed to the write callback */
-	QByteArray array_;
-	bool cancelled_ = false;
-
-	/* don't fuck this up */
-	std::mutex callback_data_mutex_;
-};
-
-} // namespace HTTP
-
-#endif // MINORI_CORE_HTTP_H_
+#ifndef MINORI_CORE_HTTP_H_
+#define MINORI_CORE_HTTP_H_
+
+#include <QByteArray>
+#include <QThread>
+
+#include <string>
+#include <vector>
+#include <mutex>
+
+namespace HTTP {
+
+enum class Type {
+	Get,
+	Post
+};
+
+QByteArray Request(const std::string& url, const std::vector<std::string>& headers = {}, const std::string& data = "", Type type = Type::Get);
+
+class RequestThread final : public QThread {
+	Q_OBJECT
+
+public:
+	RequestThread(Type type = Type::Get, QObject* parent = nullptr);
+	RequestThread(const std::string& url, const std::vector<std::string>& headers = {},
+		          const std::string& data = "", Type type = Type::Get, QObject* parent = nullptr);
+	~RequestThread();
+
+	void SetUrl(const std::string& url);
+	void SetHeaders(const std::vector<std::string>& headers);
+	void SetData(const std::string& data);
+	void SetType(Type type);
+
+	void Stop();
+
+signals:
+	void ReceivedData(const QByteArray& ba);
+
+protected:
+	void run() override;
+	static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userdata);
+
+	std::string url_;
+	std::string data_;
+	std::vector<std::string> headers_;
+	Type type_;
+
+	/* these are passed to the write callback */
+	QByteArray array_;
+	bool cancelled_ = false;
+
+	/* don't fuck this up */
+	std::mutex callback_data_mutex_;
+};
+
+} // namespace HTTP
+
+#endif // MINORI_CORE_HTTP_H_
--- a/include/core/ini.h	Mon May 13 14:56:37 2024 -0400
+++ b/include/core/ini.h	Mon May 13 15:04:51 2024 -0400
@@ -1,21 +1,21 @@
-#ifndef MINORI_CORE_INI_H_
-#define MINORI_CORE_INI_H_
-
-#define MINI_CASE_SENSITIVE
-#include "core/strings.h"
-#include "mini/ini.h"
-#include <string>
-
-namespace INI {
-
-std::string GetIniString(const mINI::INIStructure& ini, const std::string& section, const std::string& key, const std::string& def);
-bool GetIniBool(const mINI::INIStructure& ini, const std::string& section, const std::string& key, bool def);
-
-template<typename T>
-T GetIniInteger(const mINI::INIStructure& ini, const std::string& section, const std::string& key, T def) {
-	return Strings::ToInt<T>(GetIniString(ini, section, key, ""), def);
-}
-
-} // namespace INI
-
-#endif
+#ifndef MINORI_CORE_INI_H_
+#define MINORI_CORE_INI_H_
+
+#define MINI_CASE_SENSITIVE
+#include "core/strings.h"
+#include "mini/ini.h"
+#include <string>
+
+namespace INI {
+
+std::string GetIniString(const mINI::INIStructure& ini, const std::string& section, const std::string& key, const std::string& def);
+bool GetIniBool(const mINI::INIStructure& ini, const std::string& section, const std::string& key, bool def);
+
+template<typename T>
+T GetIniInteger(const mINI::INIStructure& ini, const std::string& section, const std::string& key, T def) {
+	return Strings::ToInt<T>(GetIniString(ini, section, key, ""), def);
+}
+
+} // namespace INI
+
+#endif
--- a/include/core/torrent.h	Mon May 13 14:56:37 2024 -0400
+++ b/include/core/torrent.h	Mon May 13 15:04:51 2024 -0400
@@ -1,62 +1,62 @@
-#ifndef MINORI_CORE_TORRENT_H_
-#define MINORI_CORE_TORRENT_H_
-
-#include <QDateTime>
-#include <string>
-
-/* this is really just a fancy struct...
- *
- * this will be moved into its own namespace if
- * it's deemed necessary
- */
-class Torrent {
-public:
-	std::string GetTitle() const { return _title; };
-	std::string GetCategory() const { return _category; };
-	std::string GetEpisode() const { return _episode; };
-	std::string GetGroup() const { return _group; };
-	size_t GetSize() const { return _size; };
-	std::string GetResolution() const { return _resolution; };
-	int GetSeeders() const { return _seeders; };
-	int GetLeechers() const { return _leechers; };
-	int GetDownloads() const { return _downloads; };
-	std::string GetDescription() const { return _description; };
-	std::string GetFilename() const { return _filename; };
-	std::string GetLink() const { return _link; };
-	std::string GetGuid() const { return _guid; };
-	QDateTime GetDate() const { return _date; };
-
-	void SetTitle(const std::string& title) { _title = title; };
-	void SetCategory(const std::string& category) { _category = category; };
-	void SetEpisode(const std::string& episode) { _episode = episode; };
-	void SetGroup(const std::string& group) { _group = group; };
-	void SetSize(const size_t size) { _size = size; };
-	void SetResolution(const std::string& resolution) { _resolution = resolution; };
-	void SetSeeders(const int seeders) { _seeders = seeders; };
-	void SetLeechers(const int leechers) { _leechers = leechers; };
-	void SetDownloads(const int downloads) { _downloads = downloads; };
-	void SetDescription(const std::string& description) { _description = description; };
-	void SetFilename(const std::string& filename) { _filename = filename; };
-	void SetLink(const std::string& link) { _link = link; };
-	void SetGuid(const std::string& guid) { _guid = guid; };
-	void SetDate(const QDateTime& date) { _date = date; };
-
-private:
-	std::string _title;
-	std::string _category;
-	std::string _episode;
-	std::string _group;
-	size_t _size = 0;
-	std::string _resolution; /* technically should be an int,
-	                            but std::string is more useful */
-	int _seeders = 0;
-	int _leechers = 0;
-	int _downloads = 0;
-	std::string _description;
-	std::string _filename;
-	std::string _link;
-	std::string _guid;
-	QDateTime _date;
-};
-
-#endif // MINORI_CORE_TORRENT_H_
+#ifndef MINORI_CORE_TORRENT_H_
+#define MINORI_CORE_TORRENT_H_
+
+#include <QDateTime>
+#include <string>
+
+/* this is really just a fancy struct...
+ *
+ * this will be moved into its own namespace if
+ * it's deemed necessary
+ */
+class Torrent {
+public:
+	std::string GetTitle() const { return _title; };
+	std::string GetCategory() const { return _category; };
+	std::string GetEpisode() const { return _episode; };
+	std::string GetGroup() const { return _group; };
+	size_t GetSize() const { return _size; };
+	std::string GetResolution() const { return _resolution; };
+	int GetSeeders() const { return _seeders; };
+	int GetLeechers() const { return _leechers; };
+	int GetDownloads() const { return _downloads; };
+	std::string GetDescription() const { return _description; };
+	std::string GetFilename() const { return _filename; };
+	std::string GetLink() const { return _link; };
+	std::string GetGuid() const { return _guid; };
+	QDateTime GetDate() const { return _date; };
+
+	void SetTitle(const std::string& title) { _title = title; };
+	void SetCategory(const std::string& category) { _category = category; };
+	void SetEpisode(const std::string& episode) { _episode = episode; };
+	void SetGroup(const std::string& group) { _group = group; };
+	void SetSize(const size_t size) { _size = size; };
+	void SetResolution(const std::string& resolution) { _resolution = resolution; };
+	void SetSeeders(const int seeders) { _seeders = seeders; };
+	void SetLeechers(const int leechers) { _leechers = leechers; };
+	void SetDownloads(const int downloads) { _downloads = downloads; };
+	void SetDescription(const std::string& description) { _description = description; };
+	void SetFilename(const std::string& filename) { _filename = filename; };
+	void SetLink(const std::string& link) { _link = link; };
+	void SetGuid(const std::string& guid) { _guid = guid; };
+	void SetDate(const QDateTime& date) { _date = date; };
+
+private:
+	std::string _title;
+	std::string _category;
+	std::string _episode;
+	std::string _group;
+	size_t _size = 0;
+	std::string _resolution; /* technically should be an int,
+	                            but std::string is more useful */
+	int _seeders = 0;
+	int _leechers = 0;
+	int _downloads = 0;
+	std::string _description;
+	std::string _filename;
+	std::string _link;
+	std::string _guid;
+	QDateTime _date;
+};
+
+#endif // MINORI_CORE_TORRENT_H_
--- a/include/gui/locale.h	Mon May 13 14:56:37 2024 -0400
+++ b/include/gui/locale.h	Mon May 13 15:04:51 2024 -0400
@@ -1,35 +1,35 @@
-#ifndef MINORI_GUI_LOCALE_H_
-#define MINORI_GUI_LOCALE_H_
-
-#include <QLocale>
-#include <QTranslator>
-#include <memory>
-#include <string>
-#include <vector>
-
-namespace Locale {
-
-std::string GetLocaleFullName(const QLocale& locale);
-
-class Locale {
-public:
-	Locale();
-	Locale(const std::string& name);
-	QLocale GetLocale() const;
-	std::vector<QLocale> GetAvailableLocales() const;
-	void RefreshAvailableLocales(); // why would this ever be called?
-	bool IsLocaleAvailable(const QLocale& locale) const;
-	bool SetActiveLocale(const QLocale& locale);
-
-private:
-	bool SwitchTranslator(QTranslator& translator, const QString& name);
-
-	QTranslator _translator;
-	QTranslator _translator_qt;
-	QLocale _locale;
-	std::vector<QLocale> _available_translations = {};
-};
-
-} // namespace Locale
-
-#endif // MINORI_GUI_LOCALE_H_
+#ifndef MINORI_GUI_LOCALE_H_
+#define MINORI_GUI_LOCALE_H_
+
+#include <QLocale>
+#include <QTranslator>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace Locale {
+
+std::string GetLocaleFullName(const QLocale& locale);
+
+class Locale {
+public:
+	Locale();
+	Locale(const std::string& name);
+	QLocale GetLocale() const;
+	std::vector<QLocale> GetAvailableLocales() const;
+	void RefreshAvailableLocales(); // why would this ever be called?
+	bool IsLocaleAvailable(const QLocale& locale) const;
+	bool SetActiveLocale(const QLocale& locale);
+
+private:
+	bool SwitchTranslator(QTranslator& translator, const QString& name);
+
+	QTranslator _translator;
+	QTranslator _translator_qt;
+	QLocale _locale;
+	std::vector<QLocale> _available_translations = {};
+};
+
+} // namespace Locale
+
+#endif // MINORI_GUI_LOCALE_H_
--- a/include/gui/translate/anilist.h	Mon May 13 14:56:37 2024 -0400
+++ b/include/gui/translate/anilist.h	Mon May 13 15:04:51 2024 -0400
@@ -1,16 +1,16 @@
-#ifndef MINORI_GUI_TRANSLATE_ANILIST_H_
-#define MINORI_GUI_TRANSLATE_ANILIST_H_
-
-#include "core/anime.h"
-
-namespace Translate {
-namespace AniList {
-
-Anime::SeriesStatus ToSeriesStatus(const std::string& status);
-Anime::SeriesSeason ToSeriesSeason(const std::string& season);
-Anime::SeriesFormat ToSeriesFormat(const std::string& format);
-
-} // namespace AniList
-} // namespace Translate
-
-#endif // MINORI_GUI_TRANSLATE_ANILIST_H_
+#ifndef MINORI_GUI_TRANSLATE_ANILIST_H_
+#define MINORI_GUI_TRANSLATE_ANILIST_H_
+
+#include "core/anime.h"
+
+namespace Translate {
+namespace AniList {
+
+Anime::SeriesStatus ToSeriesStatus(const std::string& status);
+Anime::SeriesSeason ToSeriesSeason(const std::string& season);
+Anime::SeriesFormat ToSeriesFormat(const std::string& format);
+
+} // namespace AniList
+} // namespace Translate
+
+#endif // MINORI_GUI_TRANSLATE_ANILIST_H_
--- a/include/gui/translate/config.h	Mon May 13 14:56:37 2024 -0400
+++ b/include/gui/translate/config.h	Mon May 13 15:04:51 2024 -0400
@@ -1,14 +1,14 @@
-#ifndef MINORI_GUI_TRANSLATE_CONFIG_H_
-#define MINORI_GUI_TRANSLATE_CONFIG_H_
-
-#include "gui/theme.h"
-
-namespace Translate {
-
-Theme::Theme ToTheme(const std::string& theme);
-std::string ToString(const Theme::Theme& theme);
-std::string ToLocalString(const Theme::Theme& theme);
-
-} // namespace Translate
-
-#endif // MINORI_GUI_TRANSLATE_CONFIG_H_
+#ifndef MINORI_GUI_TRANSLATE_CONFIG_H_
+#define MINORI_GUI_TRANSLATE_CONFIG_H_
+
+#include "gui/theme.h"
+
+namespace Translate {
+
+Theme::Theme ToTheme(const std::string& theme);
+std::string ToString(const Theme::Theme& theme);
+std::string ToLocalString(const Theme::Theme& theme);
+
+} // namespace Translate
+
+#endif // MINORI_GUI_TRANSLATE_CONFIG_H_
--- a/include/gui/widgets/anime_info.h	Mon May 13 14:56:37 2024 -0400
+++ b/include/gui/widgets/anime_info.h	Mon May 13 15:04:51 2024 -0400
@@ -1,25 +1,25 @@
-#ifndef MINORI_GUI_WIDGETS_ANIME_INFO_H_
-#define MINORI_GUI_WIDGETS_ANIME_INFO_H_
-
-#include <QWidget>
-#include "gui/widgets/text.h"
-
-namespace Anime {
-class Anime;
-}
-
-class AnimeInfoWidget final : public QWidget {
-	Q_OBJECT
-
-public:
-	AnimeInfoWidget(QWidget* parent = nullptr);
-	AnimeInfoWidget(const Anime::Anime& anime, QWidget* parent = nullptr);
-	void SetAnime(const Anime::Anime& anime);
-
-private:
-	TextWidgets::OneLineSection _title;
-	TextWidgets::LabelledSection _details;
-	TextWidgets::SelectableSection _synopsis;
-};
-
-#endif // MINORI_GUI_WIDGETS_ANIME_INFO_H_
+#ifndef MINORI_GUI_WIDGETS_ANIME_INFO_H_
+#define MINORI_GUI_WIDGETS_ANIME_INFO_H_
+
+#include <QWidget>
+#include "gui/widgets/text.h"
+
+namespace Anime {
+class Anime;
+}
+
+class AnimeInfoWidget final : public QWidget {
+	Q_OBJECT
+
+public:
+	AnimeInfoWidget(QWidget* parent = nullptr);
+	AnimeInfoWidget(const Anime::Anime& anime, QWidget* parent = nullptr);
+	void SetAnime(const Anime::Anime& anime);
+
+private:
+	TextWidgets::OneLineSection _title;
+	TextWidgets::LabelledSection _details;
+	TextWidgets::SelectableSection _synopsis;
+};
+
+#endif // MINORI_GUI_WIDGETS_ANIME_INFO_H_
--- a/include/gui/widgets/clickable_label.h	Mon May 13 14:56:37 2024 -0400
+++ b/include/gui/widgets/clickable_label.h	Mon May 13 15:04:51 2024 -0400
@@ -1,20 +1,20 @@
-#ifndef MINORI_GUI_WIDGETS_CLICKABLE_LABEL_H_
-#define MINORI_GUI_WIDGETS_CLICKABLE_LABEL_H_
-
-#include <QLabel>
-
-class ClickableLabel final : public QLabel {
-	Q_OBJECT
-
-public:
-	explicit ClickableLabel(QWidget* parent = nullptr);
-	~ClickableLabel();
-
-signals:
-	void clicked();
-
-protected:
-	void mousePressEvent(QMouseEvent*) override;
-};
-
-#endif // MINORI_GUI_WIDGETS_CLICKABLE_LABEL_H_
+#ifndef MINORI_GUI_WIDGETS_CLICKABLE_LABEL_H_
+#define MINORI_GUI_WIDGETS_CLICKABLE_LABEL_H_
+
+#include <QLabel>
+
+class ClickableLabel final : public QLabel {
+	Q_OBJECT
+
+public:
+	explicit ClickableLabel(QWidget* parent = nullptr);
+	~ClickableLabel();
+
+signals:
+	void clicked();
+
+protected:
+	void mousePressEvent(QMouseEvent*) override;
+};
+
+#endif // MINORI_GUI_WIDGETS_CLICKABLE_LABEL_H_
--- a/include/gui/widgets/poster.h	Mon May 13 14:56:37 2024 -0400
+++ b/include/gui/widgets/poster.h	Mon May 13 15:04:51 2024 -0400
@@ -1,43 +1,43 @@
-#ifndef MINORI_GUI_WIDGETS_POSTER_H_
-#define MINORI_GUI_WIDGETS_POSTER_H_
-#include <QFrame>
-#include <QImage>
-
-#include "gui/widgets/clickable_label.h"
-#include "core/http.h"
-
-namespace Anime {
-class Anime;
-}
-
-class Poster final : public QFrame {
-	Q_OBJECT
-
-public:
-	Poster(QWidget* parent = nullptr);
-	Poster(const Anime::Anime& anime, QWidget* parent = nullptr);
-	~Poster();
-	void SetAnime(const Anime::Anime& anime);
-	void SetClickable(bool clickable);
-
-protected:
-	void showEvent(QShowEvent*) override;
-	void resizeEvent(QResizeEvent*) override;
-	void ImageDownloadFinished(const QByteArray& arr);
-	void RenderToLabel();
-	void DownloadPoster();
-
-private:
-	/* stored as a pointer to prevent blocking */
-	HTTP::RequestThread* get_thread_;
-
-	QImage img_;
-	QString service_url_;
-	std::string poster_url_;
-	ClickableLabel label_;
-
-	bool clickable_ = true;
-	bool need_refresh_ = false;
-};
-
-#endif // MINORI_GUI_WIDGETS_POSTER_H_
+#ifndef MINORI_GUI_WIDGETS_POSTER_H_
+#define MINORI_GUI_WIDGETS_POSTER_H_
+#include <QFrame>
+#include <QImage>
+
+#include "gui/widgets/clickable_label.h"
+#include "core/http.h"
+
+namespace Anime {
+class Anime;
+}
+
+class Poster final : public QFrame {
+	Q_OBJECT
+
+public:
+	Poster(QWidget* parent = nullptr);
+	Poster(const Anime::Anime& anime, QWidget* parent = nullptr);
+	~Poster();
+	void SetAnime(const Anime::Anime& anime);
+	void SetClickable(bool clickable);
+
+protected:
+	void showEvent(QShowEvent*) override;
+	void resizeEvent(QResizeEvent*) override;
+	void ImageDownloadFinished(const QByteArray& arr);
+	void RenderToLabel();
+	void DownloadPoster();
+
+private:
+	/* stored as a pointer to prevent blocking */
+	HTTP::RequestThread* get_thread_;
+
+	QImage img_;
+	QString service_url_;
+	std::string poster_url_;
+	ClickableLabel label_;
+
+	bool clickable_ = true;
+	bool need_refresh_ = false;
+};
+
+#endif // MINORI_GUI_WIDGETS_POSTER_H_
--- a/include/track/media.h	Mon May 13 14:56:37 2024 -0400
+++ b/include/track/media.h	Mon May 13 15:04:51 2024 -0400
@@ -1,15 +1,15 @@
-#ifndef MINORI_TRACK_MEDIA_H_
-#define MINORI_TRACK_MEDIA_H_
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-namespace Track {
-namespace Media {
-
-bool GetCurrentlyPlaying(std::vector<std::string>& vec);
-
-} // namespace Media
-} // namespace Track
-
-#endif // MINORI_TRACK_MEDIA_H_
+#ifndef MINORI_TRACK_MEDIA_H_
+#define MINORI_TRACK_MEDIA_H_
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace Track {
+namespace Media {
+
+bool GetCurrentlyPlaying(std::vector<std::string>& vec);
+
+} // namespace Media
+} // namespace Track
+
+#endif // MINORI_TRACK_MEDIA_H_
--- a/rc/animone.qrc	Mon May 13 14:56:37 2024 -0400
+++ b/rc/animone.qrc	Mon May 13 15:04:51 2024 -0400
@@ -1,5 +1,5 @@
-<!DOCTYPE rcc><RCC version="1.0">
-	<qresource>
-		<file alias="players.anisthesia">../dep/animone/data/players.anisthesia</file>
-	</qresource>
-</RCC>
+<!DOCTYPE rcc><RCC version="1.0">
+	<qresource>
+		<file alias="players.anisthesia">../dep/animone/data/players.anisthesia</file>
+	</qresource>
+</RCC>
--- a/rc/sys/win32/resource.rc	Mon May 13 14:56:37 2024 -0400
+++ b/rc/sys/win32/resource.rc	Mon May 13 15:04:51 2024 -0400
@@ -1,3 +1,3 @@
-#include "winver.h"
-
-IDI_ICON1	ICON	"favicon.ico"
+#include "winver.h"
+
+IDI_ICON1	ICON	"favicon.ico"
--- a/rc/sys/win32/version.rc	Mon May 13 14:56:37 2024 -0400
+++ b/rc/sys/win32/version.rc	Mon May 13 15:04:51 2024 -0400
@@ -1,37 +1,37 @@
-#include "winver.h"
-
-#ifndef WRC_VERSION
-#   define WRC_VERSION 0,0,0,0
-#endif
-
-#ifndef PACKAGE_VERSION
-#	define PACKAGE_VERSION "0.0.0"
-#endif
-
-VS_VERSION_INFO VERSIONINFO
-	FILEVERSION WRC_VERSION
-	PRODUCTVERSION WRC_VERSION
-	FILEFLAGS 0x0L
-	FILEFLAGSMASK 0x3fL
-	FILEOS 0x00040004L
-	FILETYPE 0x1L
-	FILESUBTYPE 0x0L
-BEGIN
-    BLOCK "StringFileInfo"
-    BEGIN
-        BLOCK "000004b0"
-        BEGIN
-            VALUE "CompanyName", "Paper"
-            VALUE "FileDescription", "A lightweight anime tracker built with Qt."
-            VALUE "FileVersion", PACKAGE_VERSION
-            VALUE "InternalName", "minori"
-            VALUE "OriginalFilename", "minori.exe"
-            VALUE "ProductName", "Minori"
-            VALUE "ProductVersion", PACKAGE_VERSION
-        END
-    END
-    BLOCK "VarFileInfo"
-    BEGIN
-        VALUE "Translation", 0x409, 1252
-    END
-END
+#include "winver.h"
+
+#ifndef WRC_VERSION
+#   define WRC_VERSION 0,0,0,0
+#endif
+
+#ifndef PACKAGE_VERSION
+#	define PACKAGE_VERSION "0.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+	FILEVERSION WRC_VERSION
+	PRODUCTVERSION WRC_VERSION
+	FILEFLAGS 0x0L
+	FILEFLAGSMASK 0x3fL
+	FILEOS 0x00040004L
+	FILETYPE 0x1L
+	FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "000004b0"
+        BEGIN
+            VALUE "CompanyName", "Paper"
+            VALUE "FileDescription", "A lightweight anime tracker built with Qt."
+            VALUE "FileVersion", PACKAGE_VERSION
+            VALUE "InternalName", "minori"
+            VALUE "OriginalFilename", "minori.exe"
+            VALUE "ProductName", "Minori"
+            VALUE "ProductVersion", PACKAGE_VERSION
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1252
+    END
+END
--- a/src/core/http.cc	Mon May 13 14:56:37 2024 -0400
+++ b/src/core/http.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,138 +1,138 @@
-#include "core/http.h"
-#include "core/session.h"
-#include <QByteArray>
-#include <curl/curl.h>
-#include <iostream>
-#include <string>
-#include <vector>
-
-namespace HTTP {
-
-static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userdata) {
-	reinterpret_cast<QByteArray*>(userdata)->append(reinterpret_cast<char*>(contents), size * nmemb);
-	return size * nmemb;
-}
-
-QByteArray Request(const std::string& url, const std::vector<std::string>& headers, const std::string& data, Type type) {
-	struct curl_slist* list = NULL;
-	QByteArray userdata;
-
-	CURL* curl = curl_easy_init();
-	if (curl) {
-		for (const std::string& h : headers)
-			list = curl_slist_append(list, h.c_str());
-
-		curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
-		if (type == Type::Post)
-			curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
-		curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
-		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &userdata);
-		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback);
-		/* Use system certs... useful on Windows. */
-		curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
-		curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); // threading
-		CURLcode res = curl_easy_perform(curl);
-		session.IncrementRequests();
-		curl_easy_cleanup(curl);
-		if (res != CURLE_OK)
-			std::cerr << "curl_easy_perform(curl) failed!: " << curl_easy_strerror(res) << std::endl;
-	}
-	return userdata;
-}
-
-/* this function is static */
-size_t RequestThread::WriteCallback(void* contents, size_t size, size_t nmemb, void* userdata) {
-	RequestThread* thread = reinterpret_cast<RequestThread*>(userdata);
-
-	const std::lock_guard<std::mutex> lock(thread->callback_data_mutex_);
-
-	/* stop writing, then! */
-	if (thread->cancelled_)
-		return CURL_WRITEFUNC_ERROR;
-
-	/* else, continue on as normal */
-	thread->array_.append(reinterpret_cast<char*>(contents), size * nmemb);
-	return size * nmemb;
-}
-
-RequestThread::RequestThread(Type type, QObject* parent) : QThread(parent) {
-	SetType(type);
-}
-
-RequestThread::RequestThread(const std::string& url, const std::vector<std::string>& headers,
-	                         const std::string& data, Type type, QObject* parent)
-	: QThread(parent) {
-	SetUrl(url);
-	SetData(data);
-	SetHeaders(headers);
-	SetType(type);
-}
-
-RequestThread::~RequestThread() {
-	/* block until the function can safely exit.
-	 *
-	 * this sucks. find out a better way to do this, which will probably
-	 * be to put all of the threads in a pool */
-	Stop();
-	wait();
-}
-
-void RequestThread::SetUrl(const std::string& url) {
-	url_ = url;
-}
-
-void RequestThread::SetHeaders(const std::vector<std::string>& headers) {
-	headers_ = headers;
-}
-
-void RequestThread::SetData(const std::string& data) {
-	data_ = data;
-}
-
-void RequestThread::SetType(Type type) {
-	type_ = type;
-}
-
-void RequestThread::run() {
-	struct curl_slist* list = NULL;
-
-	CURL* curl = curl_easy_init();
-	if (curl) {
-		curl_easy_setopt(curl, CURLOPT_URL, url_.c_str());
-
-		if (type_ == Type::Post)
-			curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data_.c_str());
-
-		for (const std::string& h : headers_)
-			list = curl_slist_append(list, h.c_str());
-		curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
-
-		curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
-		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &RequestThread::WriteCallback);
-
-		/* Use system certs... useful on Windows. */
-		curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
-
-		/* does something with threading, don't remember what though */
-		curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
-
-		CURLcode res = curl_easy_perform(curl);
-		session.IncrementRequests();
-		curl_easy_cleanup(curl);
-
-		callback_data_mutex_.lock();
-		if (res != CURLE_OK && !(res == CURLE_WRITE_ERROR && cancelled_))
-			std::cerr << "curl_easy_perform(curl) failed!: " << curl_easy_strerror(res) << std::endl;
-		callback_data_mutex_.unlock();
-	}
-
-	emit ReceivedData(array_);
-	array_.clear();
-}
-
-void RequestThread::Stop() {
-	const std::lock_guard<std::mutex> lock(callback_data_mutex_);
-	cancelled_ = true;
-}
-
-} // namespace HTTP
+#include "core/http.h"
+#include "core/session.h"
+#include <QByteArray>
+#include <curl/curl.h>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace HTTP {
+
+static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userdata) {
+	reinterpret_cast<QByteArray*>(userdata)->append(reinterpret_cast<char*>(contents), size * nmemb);
+	return size * nmemb;
+}
+
+QByteArray Request(const std::string& url, const std::vector<std::string>& headers, const std::string& data, Type type) {
+	struct curl_slist* list = NULL;
+	QByteArray userdata;
+
+	CURL* curl = curl_easy_init();
+	if (curl) {
+		for (const std::string& h : headers)
+			list = curl_slist_append(list, h.c_str());
+
+		curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
+		if (type == Type::Post)
+			curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
+		curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
+		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &userdata);
+		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback);
+		/* Use system certs... useful on Windows. */
+		curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
+		curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); // threading
+		CURLcode res = curl_easy_perform(curl);
+		session.IncrementRequests();
+		curl_easy_cleanup(curl);
+		if (res != CURLE_OK)
+			std::cerr << "curl_easy_perform(curl) failed!: " << curl_easy_strerror(res) << std::endl;
+	}
+	return userdata;
+}
+
+/* this function is static */
+size_t RequestThread::WriteCallback(void* contents, size_t size, size_t nmemb, void* userdata) {
+	RequestThread* thread = reinterpret_cast<RequestThread*>(userdata);
+
+	const std::lock_guard<std::mutex> lock(thread->callback_data_mutex_);
+
+	/* stop writing, then! */
+	if (thread->cancelled_)
+		return CURL_WRITEFUNC_ERROR;
+
+	/* else, continue on as normal */
+	thread->array_.append(reinterpret_cast<char*>(contents), size * nmemb);
+	return size * nmemb;
+}
+
+RequestThread::RequestThread(Type type, QObject* parent) : QThread(parent) {
+	SetType(type);
+}
+
+RequestThread::RequestThread(const std::string& url, const std::vector<std::string>& headers,
+	                         const std::string& data, Type type, QObject* parent)
+	: QThread(parent) {
+	SetUrl(url);
+	SetData(data);
+	SetHeaders(headers);
+	SetType(type);
+}
+
+RequestThread::~RequestThread() {
+	/* block until the function can safely exit.
+	 *
+	 * this sucks. find out a better way to do this, which will probably
+	 * be to put all of the threads in a pool */
+	Stop();
+	wait();
+}
+
+void RequestThread::SetUrl(const std::string& url) {
+	url_ = url;
+}
+
+void RequestThread::SetHeaders(const std::vector<std::string>& headers) {
+	headers_ = headers;
+}
+
+void RequestThread::SetData(const std::string& data) {
+	data_ = data;
+}
+
+void RequestThread::SetType(Type type) {
+	type_ = type;
+}
+
+void RequestThread::run() {
+	struct curl_slist* list = NULL;
+
+	CURL* curl = curl_easy_init();
+	if (curl) {
+		curl_easy_setopt(curl, CURLOPT_URL, url_.c_str());
+
+		if (type_ == Type::Post)
+			curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data_.c_str());
+
+		for (const std::string& h : headers_)
+			list = curl_slist_append(list, h.c_str());
+		curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
+
+		curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
+		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &RequestThread::WriteCallback);
+
+		/* Use system certs... useful on Windows. */
+		curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
+
+		/* does something with threading, don't remember what though */
+		curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
+
+		CURLcode res = curl_easy_perform(curl);
+		session.IncrementRequests();
+		curl_easy_cleanup(curl);
+
+		callback_data_mutex_.lock();
+		if (res != CURLE_OK && !(res == CURLE_WRITE_ERROR && cancelled_))
+			std::cerr << "curl_easy_perform(curl) failed!: " << curl_easy_strerror(res) << std::endl;
+		callback_data_mutex_.unlock();
+	}
+
+	emit ReceivedData(array_);
+	array_.clear();
+}
+
+void RequestThread::Stop() {
+	const std::lock_guard<std::mutex> lock(callback_data_mutex_);
+	cancelled_ = true;
+}
+
+} // namespace HTTP
--- a/src/gui/dialog/settings/recognition.cc	Mon May 13 14:56:37 2024 -0400
+++ b/src/gui/dialog/settings/recognition.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,81 +1,81 @@
-#include "core/session.h"
-#include "core/strings.h"
-#include "gui/dialog/settings.h"
-
-#include <QCheckBox>
-#include <QGroupBox>
-#include <QLabel>
-#include <QListWidget>
-#include <QListWidgetItem>
-#include <QSizePolicy>
-#include <QVBoxLayout>
-
-#include <algorithm>
-
-QWidget* SettingsPageRecognition::CreatePlayersWidget() {
-	QWidget* result = new QWidget(this);
-	result->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
-
-	QVBoxLayout* full_layout = new QVBoxLayout(result);
-
-	{
-		/* Feed link */
-		QWidget* widget = new QWidget(result);
-		QVBoxLayout* widget_layout = new QVBoxLayout(widget);
-
-		QCheckBox* checkbox = new QCheckBox(result);
-		checkbox->setText(tr("Enable media player detection"));
-		checkbox->setCheckState(detect_media_players ? Qt::Checked : Qt::Unchecked);
-		widget_layout->addWidget(checkbox);
-
-		{
-			QLabel* label = new QLabel(tr("Allowed media players:"), widget);
-			widget_layout->addWidget(label);
-		}
-
-		{
-			QListWidget* listwidget = new QListWidget(widget);
-			for (size_t i = 0; i < players.size(); i++) {
-				const auto& [enabled, player] = players[i];
-				if (player.type == animone::PlayerType::Default) {
-					QListWidgetItem* item = new QListWidgetItem(listwidget);
-					item->setCheckState(enabled ? Qt::Checked : Qt::Unchecked);
-					item->setText(Strings::ToQString(player.name));
-					item->setData(Qt::UserRole, QVariant::fromValue(i));
-				}
-			}
-			connect(listwidget, &QListWidget::itemChanged, this, [this](QListWidgetItem* item) {
-				if (!item)
-					return;
-				size_t i = item->data(Qt::UserRole).toUInt();
-				players[i].enabled = item->checkState();
-			});
-			/* this is down here because the listwidget state depends on it */
-			connect(checkbox, &QCheckBox::stateChanged, this, [this, listwidget](int state) {
-				detect_media_players = (state == Qt::Checked);
-				listwidget->setEnabled(detect_media_players);
-			});
-			listwidget->setEnabled(checkbox->checkState() == Qt::Checked);
-
-			widget_layout->addWidget(listwidget);
-		}
-
-		full_layout->addWidget(widget);
-	}
-
-	full_layout->setSpacing(10);
-	full_layout->setContentsMargins(0, 0, 0, 0);
-
-	return result;
-}
-
-void SettingsPageRecognition::SaveInfo() {
-	session.config.recognition.detect_media_players = detect_media_players;
-	session.config.recognition.players = players;
-}
-
-SettingsPageRecognition::SettingsPageRecognition(QWidget* parent)
-    : SettingsPage(parent, tr("Recognition")), players(session.config.recognition.players) {
-	detect_media_players = session.config.recognition.detect_media_players;
-	AddTab(CreatePlayersWidget(), tr("Media players"));
-}
+#include "core/session.h"
+#include "core/strings.h"
+#include "gui/dialog/settings.h"
+
+#include <QCheckBox>
+#include <QGroupBox>
+#include <QLabel>
+#include <QListWidget>
+#include <QListWidgetItem>
+#include <QSizePolicy>
+#include <QVBoxLayout>
+
+#include <algorithm>
+
+QWidget* SettingsPageRecognition::CreatePlayersWidget() {
+	QWidget* result = new QWidget(this);
+	result->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
+
+	QVBoxLayout* full_layout = new QVBoxLayout(result);
+
+	{
+		/* Feed link */
+		QWidget* widget = new QWidget(result);
+		QVBoxLayout* widget_layout = new QVBoxLayout(widget);
+
+		QCheckBox* checkbox = new QCheckBox(result);
+		checkbox->setText(tr("Enable media player detection"));
+		checkbox->setCheckState(detect_media_players ? Qt::Checked : Qt::Unchecked);
+		widget_layout->addWidget(checkbox);
+
+		{
+			QLabel* label = new QLabel(tr("Allowed media players:"), widget);
+			widget_layout->addWidget(label);
+		}
+
+		{
+			QListWidget* listwidget = new QListWidget(widget);
+			for (size_t i = 0; i < players.size(); i++) {
+				const auto& [enabled, player] = players[i];
+				if (player.type == animone::PlayerType::Default) {
+					QListWidgetItem* item = new QListWidgetItem(listwidget);
+					item->setCheckState(enabled ? Qt::Checked : Qt::Unchecked);
+					item->setText(Strings::ToQString(player.name));
+					item->setData(Qt::UserRole, QVariant::fromValue(i));
+				}
+			}
+			connect(listwidget, &QListWidget::itemChanged, this, [this](QListWidgetItem* item) {
+				if (!item)
+					return;
+				size_t i = item->data(Qt::UserRole).toUInt();
+				players[i].enabled = item->checkState();
+			});
+			/* this is down here because the listwidget state depends on it */
+			connect(checkbox, &QCheckBox::stateChanged, this, [this, listwidget](int state) {
+				detect_media_players = (state == Qt::Checked);
+				listwidget->setEnabled(detect_media_players);
+			});
+			listwidget->setEnabled(checkbox->checkState() == Qt::Checked);
+
+			widget_layout->addWidget(listwidget);
+		}
+
+		full_layout->addWidget(widget);
+	}
+
+	full_layout->setSpacing(10);
+	full_layout->setContentsMargins(0, 0, 0, 0);
+
+	return result;
+}
+
+void SettingsPageRecognition::SaveInfo() {
+	session.config.recognition.detect_media_players = detect_media_players;
+	session.config.recognition.players = players;
+}
+
+SettingsPageRecognition::SettingsPageRecognition(QWidget* parent)
+    : SettingsPage(parent, tr("Recognition")), players(session.config.recognition.players) {
+	detect_media_players = session.config.recognition.detect_media_players;
+	AddTab(CreatePlayersWidget(), tr("Media players"));
+}
--- a/src/gui/dialog/settings/torrents.cc	Mon May 13 14:56:37 2024 -0400
+++ b/src/gui/dialog/settings/torrents.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,61 +1,61 @@
-#include "core/session.h"
-#include "core/strings.h"
-#include "gui/dialog/settings.h"
-#include <QGroupBox>
-#include <QLabel>
-#include <QLineEdit>
-#include <QSizePolicy>
-#include <QVBoxLayout>
-#include <algorithm>
-
-QWidget* SettingsPageTorrents::CreateGeneralWidget() {
-	QWidget* result = new QWidget(this);
-	result->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
-
-	QVBoxLayout* full_layout = new QVBoxLayout(result);
-
-	{
-		/* URLs */
-		QGroupBox* group = new QGroupBox(tr("URLs"), result);
-		group->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
-
-		QVBoxLayout* group_layout = new QVBoxLayout(group);
-
-		{
-			/* Feed link */
-			QWidget* widget = new QWidget(group);
-			QVBoxLayout* widget_layout = new QVBoxLayout(widget);
-
-			{
-				QLabel* sync_combo_box_label = new QLabel(tr("URL of the RSS feed to check:"), widget);
-				widget_layout->addWidget(sync_combo_box_label);
-			}
-
-			{
-				/* Username: this literally never gets used btw */
-				QLineEdit* lineedit = new QLineEdit(Strings::ToQString(feed_link), widget);
-				connect(lineedit, &QLineEdit::editingFinished, this,
-				        [this, lineedit] { feed_link = Strings::ToUtf8String(lineedit->text()); });
-				widget_layout->addWidget(lineedit);
-			}
-
-			group_layout->addWidget(widget);
-		}
-
-		full_layout->addWidget(group);
-	}
-
-	full_layout->setSpacing(10);
-	full_layout->addStretch();
-
-	return result;
-}
-
-void SettingsPageTorrents::SaveInfo() {
-	session.config.torrents.feed_link = feed_link;
-}
-
-SettingsPageTorrents::SettingsPageTorrents(QWidget* parent) : SettingsPage(parent, tr("Torrents")) {
-	feed_link = session.config.torrents.feed_link;
-	AddTab(CreateGeneralWidget(), tr("General"));
-}
+#include "core/session.h"
+#include "core/strings.h"
+#include "gui/dialog/settings.h"
+#include <QGroupBox>
+#include <QLabel>
+#include <QLineEdit>
+#include <QSizePolicy>
+#include <QVBoxLayout>
+#include <algorithm>
+
+QWidget* SettingsPageTorrents::CreateGeneralWidget() {
+	QWidget* result = new QWidget(this);
+	result->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
+
+	QVBoxLayout* full_layout = new QVBoxLayout(result);
+
+	{
+		/* URLs */
+		QGroupBox* group = new QGroupBox(tr("URLs"), result);
+		group->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
+
+		QVBoxLayout* group_layout = new QVBoxLayout(group);
+
+		{
+			/* Feed link */
+			QWidget* widget = new QWidget(group);
+			QVBoxLayout* widget_layout = new QVBoxLayout(widget);
+
+			{
+				QLabel* sync_combo_box_label = new QLabel(tr("URL of the RSS feed to check:"), widget);
+				widget_layout->addWidget(sync_combo_box_label);
+			}
+
+			{
+				/* Username: this literally never gets used btw */
+				QLineEdit* lineedit = new QLineEdit(Strings::ToQString(feed_link), widget);
+				connect(lineedit, &QLineEdit::editingFinished, this,
+				        [this, lineedit] { feed_link = Strings::ToUtf8String(lineedit->text()); });
+				widget_layout->addWidget(lineedit);
+			}
+
+			group_layout->addWidget(widget);
+		}
+
+		full_layout->addWidget(group);
+	}
+
+	full_layout->setSpacing(10);
+	full_layout->addStretch();
+
+	return result;
+}
+
+void SettingsPageTorrents::SaveInfo() {
+	session.config.torrents.feed_link = feed_link;
+}
+
+SettingsPageTorrents::SettingsPageTorrents(QWidget* parent) : SettingsPage(parent, tr("Torrents")) {
+	feed_link = session.config.torrents.feed_link;
+	AddTab(CreateGeneralWidget(), tr("General"));
+}
--- a/src/gui/locale.cc	Mon May 13 14:56:37 2024 -0400
+++ b/src/gui/locale.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,112 +1,112 @@
-#include "gui/locale.h"
-#include "core/strings.h"
-
-#include <QApplication>
-#include <QDir>
-#include <QLocale>
-#include <QString>
-#include <QTranslator>
-
-#include <QtGlobal>
-
-namespace Locale {
-
-std::string GetLocaleFullName(const QLocale& locale) {
-	QString res = QLocale::languageToString(locale.language());
-#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
-	/* silence deprecation warning for locale.country() */
-	if (locale.territory() != QLocale::AnyTerritory)
-		res += " (" + QLocale::territoryToString(locale.territory()) + ")";
-#else
-	if (locale.country() != QLocale::AnyCountry)
-		res += " (" + QLocale::countryToString(locale.country()) + ")";
-#endif
-	return Strings::ToUtf8String(res);
-}
-
-Locale::Locale() {
-	RefreshAvailableLocales();
-	SetActiveLocale(QLocale("en_US"));
-}
-
-Locale::Locale(const std::string& name) {
-	RefreshAvailableLocales();
-	SetActiveLocale(QLocale(Strings::ToQString(name)));
-}
-
-QLocale Locale::GetLocale() const {
-	return _locale;
-}
-
-std::vector<QLocale> Locale::GetAvailableLocales() const {
-	return _available_translations;
-}
-
-void Locale::RefreshAvailableLocales() {
-	_available_translations.clear();
-
-	/* we will always have en_US */
-	_available_translations.push_back(QLocale("en_US"));
-
-	QDir dir(":/locale");
-	if (!dir.exists())
-		return;
-
-	QStringList translations = dir.entryList({"*.qm"}, QDir::Files);
-
-	_available_translations.reserve(translations.size());
-	for (const QString& str : translations)
-		_available_translations.push_back(QLocale(str.mid(0, str.lastIndexOf("."))));
-}
-
-bool Locale::IsLocaleAvailable(const QLocale& locale) const {
-	for (const QLocale& l : _available_translations)
-		if (l == locale)
-			return true;
-	return false;
-}
-
-bool Locale::SetActiveLocale(const QLocale& locale) {
-	if (!IsLocaleAvailable(locale) || !qApp)
-		return false;
-
-	if (_locale == locale)
-		return true; /* we're... already on this locale :) */
-
-	_locale = locale;
-	QLocale::setDefault(_locale);
-
-	/* we can still do stuff even if one thing fails! */
-	bool return_value = true;
-
-	const QString name = _locale.name();
-	if (!SwitchTranslator(_translator, QString(":/locale/%1.qm").arg(name)))
-		return_value = false;
-
-	const QString path = qApp->applicationDirPath();
-	if (!SwitchTranslator(_translator_qt, path + QString("/translations/qt_%1.qm").arg(name))) {
-		/* Sometimes Qt will have proper translations for the language, but not the specific
-		 * country. In that case, we still want to use that language. */
-		const int underscore_index = name.lastIndexOf("_");
-		if (!underscore_index)
-			return false;
-
-		const QString short_name = name.mid(0, underscore_index);
-		if (!SwitchTranslator(_translator_qt, path + QString("/translations/qt_%1.qm").arg(short_name)))
-			return_value = false;
-	}
-
-	return return_value;
-}
-
-bool Locale::SwitchTranslator(QTranslator& translator, const QString& path) {
-	qApp->removeTranslator(&translator);
-
-	if (!translator.load(path))
-		return false;
-
-	qApp->installTranslator(&translator);
-	return true;
-}
-
-} // namespace Locale
+#include "gui/locale.h"
+#include "core/strings.h"
+
+#include <QApplication>
+#include <QDir>
+#include <QLocale>
+#include <QString>
+#include <QTranslator>
+
+#include <QtGlobal>
+
+namespace Locale {
+
+std::string GetLocaleFullName(const QLocale& locale) {
+	QString res = QLocale::languageToString(locale.language());
+#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
+	/* silence deprecation warning for locale.country() */
+	if (locale.territory() != QLocale::AnyTerritory)
+		res += " (" + QLocale::territoryToString(locale.territory()) + ")";
+#else
+	if (locale.country() != QLocale::AnyCountry)
+		res += " (" + QLocale::countryToString(locale.country()) + ")";
+#endif
+	return Strings::ToUtf8String(res);
+}
+
+Locale::Locale() {
+	RefreshAvailableLocales();
+	SetActiveLocale(QLocale("en_US"));
+}
+
+Locale::Locale(const std::string& name) {
+	RefreshAvailableLocales();
+	SetActiveLocale(QLocale(Strings::ToQString(name)));
+}
+
+QLocale Locale::GetLocale() const {
+	return _locale;
+}
+
+std::vector<QLocale> Locale::GetAvailableLocales() const {
+	return _available_translations;
+}
+
+void Locale::RefreshAvailableLocales() {
+	_available_translations.clear();
+
+	/* we will always have en_US */
+	_available_translations.push_back(QLocale("en_US"));
+
+	QDir dir(":/locale");
+	if (!dir.exists())
+		return;
+
+	QStringList translations = dir.entryList({"*.qm"}, QDir::Files);
+
+	_available_translations.reserve(translations.size());
+	for (const QString& str : translations)
+		_available_translations.push_back(QLocale(str.mid(0, str.lastIndexOf("."))));
+}
+
+bool Locale::IsLocaleAvailable(const QLocale& locale) const {
+	for (const QLocale& l : _available_translations)
+		if (l == locale)
+			return true;
+	return false;
+}
+
+bool Locale::SetActiveLocale(const QLocale& locale) {
+	if (!IsLocaleAvailable(locale) || !qApp)
+		return false;
+
+	if (_locale == locale)
+		return true; /* we're... already on this locale :) */
+
+	_locale = locale;
+	QLocale::setDefault(_locale);
+
+	/* we can still do stuff even if one thing fails! */
+	bool return_value = true;
+
+	const QString name = _locale.name();
+	if (!SwitchTranslator(_translator, QString(":/locale/%1.qm").arg(name)))
+		return_value = false;
+
+	const QString path = qApp->applicationDirPath();
+	if (!SwitchTranslator(_translator_qt, path + QString("/translations/qt_%1.qm").arg(name))) {
+		/* Sometimes Qt will have proper translations for the language, but not the specific
+		 * country. In that case, we still want to use that language. */
+		const int underscore_index = name.lastIndexOf("_");
+		if (!underscore_index)
+			return false;
+
+		const QString short_name = name.mid(0, underscore_index);
+		if (!SwitchTranslator(_translator_qt, path + QString("/translations/qt_%1.qm").arg(short_name)))
+			return_value = false;
+	}
+
+	return return_value;
+}
+
+bool Locale::SwitchTranslator(QTranslator& translator, const QString& path) {
+	qApp->removeTranslator(&translator);
+
+	if (!translator.load(path))
+		return false;
+
+	qApp->installTranslator(&translator);
+	return true;
+}
+
+} // namespace Locale
--- a/src/gui/translate/anilist.cc	Mon May 13 14:56:37 2024 -0400
+++ b/src/gui/translate/anilist.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,51 +1,51 @@
-#include "gui/translate/anilist.h"
-#include <unordered_map>
-
-namespace Translate {
-namespace AniList {
-
-Anime::SeriesStatus ToSeriesStatus(const std::string& status) {
-	static const std::unordered_map<std::string, Anime::SeriesStatus> map = {
-	    {"FINISHED",         Anime::SeriesStatus::Finished        },
-	    {"RELEASING",        Anime::SeriesStatus::Releasing       },
-	    {"NOT_YET_RELEASED", Anime::SeriesStatus::NotYetReleased},
-	    {"CANCELLED",        Anime::SeriesStatus::Cancelled       },
-	    {"HIATUS",           Anime::SeriesStatus::Hiatus          }
-    };
-
-	if (map.find(status) == map.end())
-		return Anime::SeriesStatus::Unknown;
-	return map.at(status);
-}
-
-Anime::SeriesSeason ToSeriesSeason(const std::string& season) {
-	static const std::unordered_map<std::string, Anime::SeriesSeason> map = {
-	    {"WINTER", Anime::SeriesSeason::Winter},
-	    {"SPRING", Anime::SeriesSeason::Spring},
-	    {"SUMMER", Anime::SeriesSeason::Summer},
-	    {"FALL",   Anime::SeriesSeason::Fall  }
-    };
-
-	if (map.find(season) == map.end())
-		return Anime::SeriesSeason::Unknown;
-	return map.at(season);
-}
-
-Anime::SeriesFormat ToSeriesFormat(const std::string& format) {
-	static const std::unordered_map<std::string, enum Anime::SeriesFormat> map = {
-	    {"TV",       Anime::SeriesFormat::Tv      },
-        {"TV_SHORT", Anime::SeriesFormat::TvShort},
-	    {"MOVIE",    Anime::SeriesFormat::Movie   },
-        {"SPECIAL",  Anime::SeriesFormat::Special },
-	    {"OVA",      Anime::SeriesFormat::Ova     },
-        {"ONA",      Anime::SeriesFormat::Ona     },
-	    {"MUSIC",    Anime::SeriesFormat::Music   }
-    };
-
-	if (map.find(format) == map.end())
-		return Anime::SeriesFormat::Unknown;
-	return map.at(format);
-}
-
-} // namespace AniList
-} // namespace Translate
+#include "gui/translate/anilist.h"
+#include <unordered_map>
+
+namespace Translate {
+namespace AniList {
+
+Anime::SeriesStatus ToSeriesStatus(const std::string& status) {
+	static const std::unordered_map<std::string, Anime::SeriesStatus> map = {
+	    {"FINISHED",         Anime::SeriesStatus::Finished        },
+	    {"RELEASING",        Anime::SeriesStatus::Releasing       },
+	    {"NOT_YET_RELEASED", Anime::SeriesStatus::NotYetReleased},
+	    {"CANCELLED",        Anime::SeriesStatus::Cancelled       },
+	    {"HIATUS",           Anime::SeriesStatus::Hiatus          }
+    };
+
+	if (map.find(status) == map.end())
+		return Anime::SeriesStatus::Unknown;
+	return map.at(status);
+}
+
+Anime::SeriesSeason ToSeriesSeason(const std::string& season) {
+	static const std::unordered_map<std::string, Anime::SeriesSeason> map = {
+	    {"WINTER", Anime::SeriesSeason::Winter},
+	    {"SPRING", Anime::SeriesSeason::Spring},
+	    {"SUMMER", Anime::SeriesSeason::Summer},
+	    {"FALL",   Anime::SeriesSeason::Fall  }
+    };
+
+	if (map.find(season) == map.end())
+		return Anime::SeriesSeason::Unknown;
+	return map.at(season);
+}
+
+Anime::SeriesFormat ToSeriesFormat(const std::string& format) {
+	static const std::unordered_map<std::string, enum Anime::SeriesFormat> map = {
+	    {"TV",       Anime::SeriesFormat::Tv      },
+        {"TV_SHORT", Anime::SeriesFormat::TvShort},
+	    {"MOVIE",    Anime::SeriesFormat::Movie   },
+        {"SPECIAL",  Anime::SeriesFormat::Special },
+	    {"OVA",      Anime::SeriesFormat::Ova     },
+        {"ONA",      Anime::SeriesFormat::Ona     },
+	    {"MUSIC",    Anime::SeriesFormat::Music   }
+    };
+
+	if (map.find(format) == map.end())
+		return Anime::SeriesFormat::Unknown;
+	return map.at(format);
+}
+
+} // namespace AniList
+} // namespace Translate
--- a/src/gui/translate/config.cc	Mon May 13 14:56:37 2024 -0400
+++ b/src/gui/translate/config.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,41 +1,41 @@
-#include "core/config.h"
-#include "core/strings.h"
-#include "gui/translate/config.h"
-
-#include <QCoreApplication>
-
-#include <unordered_map>
-
-namespace Translate {
-
-Theme::Theme ToTheme(const std::string& theme) {
-	const std::unordered_map<std::string, Theme::Theme> map = {
-	    {"Default", Theme::Theme::Default   },
-        {"Light",   Theme::Theme::Light},
-        {"Dark",    Theme::Theme::Dark }
-    };
-
-	if (map.find(theme) == map.end())
-		return Theme::Theme::Default;
-	return map.at(theme);
-}
-
-std::string ToString(const Theme::Theme& theme) {
-	switch (theme) {
-		default:
-		case Theme::Theme::Default: return "Default";
-		case Theme::Theme::Light: return "Light";
-		case Theme::Theme::Dark: return "Dark";
-	}
-}
-
-std::string ToLocalString(const Theme::Theme& theme) {
-	switch (theme) {
-		default:
-		case Theme::Theme::Default: return Strings::ToUtf8String(QCoreApplication::tr("Default"));
-		case Theme::Theme::Light: return Strings::ToUtf8String(QCoreApplication::tr("Light"));
-		case Theme::Theme::Dark: return Strings::ToUtf8String(QCoreApplication::tr("Dark"));
-	}
-}
-
-} // namespace Translate
+#include "core/config.h"
+#include "core/strings.h"
+#include "gui/translate/config.h"
+
+#include <QCoreApplication>
+
+#include <unordered_map>
+
+namespace Translate {
+
+Theme::Theme ToTheme(const std::string& theme) {
+	const std::unordered_map<std::string, Theme::Theme> map = {
+	    {"Default", Theme::Theme::Default   },
+        {"Light",   Theme::Theme::Light},
+        {"Dark",    Theme::Theme::Dark }
+    };
+
+	if (map.find(theme) == map.end())
+		return Theme::Theme::Default;
+	return map.at(theme);
+}
+
+std::string ToString(const Theme::Theme& theme) {
+	switch (theme) {
+		default:
+		case Theme::Theme::Default: return "Default";
+		case Theme::Theme::Light: return "Light";
+		case Theme::Theme::Dark: return "Dark";
+	}
+}
+
+std::string ToLocalString(const Theme::Theme& theme) {
+	switch (theme) {
+		default:
+		case Theme::Theme::Default: return Strings::ToUtf8String(QCoreApplication::tr("Default"));
+		case Theme::Theme::Light: return Strings::ToUtf8String(QCoreApplication::tr("Light"));
+		case Theme::Theme::Dark: return Strings::ToUtf8String(QCoreApplication::tr("Dark"));
+	}
+}
+
+} // namespace Translate
--- a/src/gui/widgets/anime_info.cc	Mon May 13 14:56:37 2024 -0400
+++ b/src/gui/widgets/anime_info.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,49 +1,49 @@
-#include "gui/widgets/anime_info.h"
-#include "core/anime.h"
-#include "core/strings.h"
-#include "gui/translate/anime.h"
-#include "gui/widgets/text.h"
-#include <QHBoxLayout>
-#include <QTextStream>
-
-AnimeInfoWidget::AnimeInfoWidget(QWidget* parent)
-	: QWidget(parent)
-	, _title(tr("Alternative titles"), "")
-	, _details(tr("Details"), tr("Type:\nEpisodes:\nStatus:\nSeason:\nGenres:\nScore:"), "")
-	, _synopsis(tr("Synopsis"), "") {
-	QVBoxLayout* layout = new QVBoxLayout(this);
-
-	layout->addWidget(&_title);
-	layout->addWidget(&_details);
-	layout->addWidget(&_synopsis);
-}
-
-AnimeInfoWidget::AnimeInfoWidget(const Anime::Anime& anime, QWidget* parent) : AnimeInfoWidget(parent) {
-	SetAnime(anime);
-}
-
-void AnimeInfoWidget::SetAnime(const Anime::Anime& anime) {
-	/* alt titles */
-	_title.GetLine()->SetText(Strings::ToQString(Strings::Implode(anime.GetTitleSynonyms(), ", ")));
-
-	/* details */
-	QString details_data;
-	QTextStream details_data_s(&details_data);
-
-	/* we have to convert ALL of these strings to
-	 * QString because QTextStream sucks and assumes
-	 * Latin1 (on Windows?) */
-	const auto genres = anime.GetGenres();
-	details_data_s << Strings::ToQString(Translate::ToLocalString(anime.GetFormat())) << "\n"
-	               << anime.GetEpisodes() << "\n"
-	               << Strings::ToQString(Translate::ToLocalString(anime.GetAiringStatus())) << "\n"
-	               << Strings::ToQString(Translate::ToLocalString(anime.GetSeason())) << " "
-	               << anime.GetAirDate().GetYear().value_or(2000) << "\n"
-	               << Strings::ToQString((genres.size() > 1) ? Strings::Implode(genres, ", ") : "-") << "\n"
-	               << anime.GetAudienceScore() << "%";
-	_details.GetData()->setText(details_data);
-
-	_synopsis.GetParagraph()->SetText(Strings::ToQString(anime.GetSynopsis()));
-
-	updateGeometry();
-}
+#include "gui/widgets/anime_info.h"
+#include "core/anime.h"
+#include "core/strings.h"
+#include "gui/translate/anime.h"
+#include "gui/widgets/text.h"
+#include <QHBoxLayout>
+#include <QTextStream>
+
+AnimeInfoWidget::AnimeInfoWidget(QWidget* parent)
+	: QWidget(parent)
+	, _title(tr("Alternative titles"), "")
+	, _details(tr("Details"), tr("Type:\nEpisodes:\nStatus:\nSeason:\nGenres:\nScore:"), "")
+	, _synopsis(tr("Synopsis"), "") {
+	QVBoxLayout* layout = new QVBoxLayout(this);
+
+	layout->addWidget(&_title);
+	layout->addWidget(&_details);
+	layout->addWidget(&_synopsis);
+}
+
+AnimeInfoWidget::AnimeInfoWidget(const Anime::Anime& anime, QWidget* parent) : AnimeInfoWidget(parent) {
+	SetAnime(anime);
+}
+
+void AnimeInfoWidget::SetAnime(const Anime::Anime& anime) {
+	/* alt titles */
+	_title.GetLine()->SetText(Strings::ToQString(Strings::Implode(anime.GetTitleSynonyms(), ", ")));
+
+	/* details */
+	QString details_data;
+	QTextStream details_data_s(&details_data);
+
+	/* we have to convert ALL of these strings to
+	 * QString because QTextStream sucks and assumes
+	 * Latin1 (on Windows?) */
+	const auto genres = anime.GetGenres();
+	details_data_s << Strings::ToQString(Translate::ToLocalString(anime.GetFormat())) << "\n"
+	               << anime.GetEpisodes() << "\n"
+	               << Strings::ToQString(Translate::ToLocalString(anime.GetAiringStatus())) << "\n"
+	               << Strings::ToQString(Translate::ToLocalString(anime.GetSeason())) << " "
+	               << anime.GetAirDate().GetYear().value_or(2000) << "\n"
+	               << Strings::ToQString((genres.size() > 1) ? Strings::Implode(genres, ", ") : "-") << "\n"
+	               << anime.GetAudienceScore() << "%";
+	_details.GetData()->setText(details_data);
+
+	_synopsis.GetParagraph()->SetText(Strings::ToQString(anime.GetSynopsis()));
+
+	updateGeometry();
+}
--- a/src/gui/widgets/clickable_label.cc	Mon May 13 14:56:37 2024 -0400
+++ b/src/gui/widgets/clickable_label.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,12 +1,12 @@
-#include "gui/widgets/clickable_label.h"
-
-ClickableLabel::ClickableLabel(QWidget* parent) : QLabel(parent) {
-	setCursor(Qt::PointingHandCursor);
-}
-
-ClickableLabel::~ClickableLabel() {
-}
-
-void ClickableLabel::mousePressEvent(QMouseEvent*) {
-	emit clicked();
-}
+#include "gui/widgets/clickable_label.h"
+
+ClickableLabel::ClickableLabel(QWidget* parent) : QLabel(parent) {
+	setCursor(Qt::PointingHandCursor);
+}
+
+ClickableLabel::~ClickableLabel() {
+}
+
+void ClickableLabel::mousePressEvent(QMouseEvent*) {
+	emit clicked();
+}
--- a/src/gui/widgets/poster.cc	Mon May 13 14:56:37 2024 -0400
+++ b/src/gui/widgets/poster.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,106 +1,106 @@
-#include "gui/widgets/poster.h"
-#include "core/anime_db.h"
-#include "core/http.h"
-#include "core/session.h"
-#include "core/strings.h"
-#include "gui/widgets/clickable_label.h"
-
-#include <QByteArray>
-#include <QDebug>
-#include <QDesktopServices>
-#include <QFrame>
-#include <QHBoxLayout>
-#include <QLabel>
-#include <QMessageBox>
-#include <QPixmap>
-#include <QThread>
-#include <QUrl>
-
-Poster::Poster(QWidget* parent) : QFrame(parent) {
-	QHBoxLayout* layout = new QHBoxLayout(this);
-	layout->setContentsMargins(1, 1, 1, 1);
-
-	setCursor(Qt::PointingHandCursor);
-	setFixedSize(150, 225);
-	setFrameShape(QFrame::Box);
-	setFrameShadow(QFrame::Plain);
-
-	label_.setAlignment(Qt::AlignCenter);
-	layout->addWidget(&label_);
-
-	get_thread_ = new HTTP::RequestThread(HTTP::Type::Get);
-	connect(get_thread_, &HTTP::RequestThread::ReceivedData, this, &Poster::ImageDownloadFinished);
-}
-
-Poster::Poster(const Anime::Anime& anime, QWidget* parent) : Poster(parent) {
-	SetAnime(anime);
-}
-
-Poster::~Poster() {
-	/* schedule deletion of the thread */
-	get_thread_->deleteLater();
-}
-
-void Poster::DownloadPoster() {
-	if (get_thread_->isRunning())
-		get_thread_->Stop();
-	get_thread_->wait();
-
-	get_thread_->SetUrl(poster_url_);
-	get_thread_->start();
-}
-
-void Poster::SetAnime(const Anime::Anime& anime) {
-	label_.clear();
-
-	poster_url_ = anime.GetPosterUrl();
-	if (isVisible())
-		DownloadPoster();
-	else
-		need_refresh_ = true;
-
-	std::optional<std::string> url = anime.GetServiceUrl(session.config.service);
-	if (url)
-		service_url_ = Strings::ToQString(url.value());
-
-	if (clickable_) {
-		label_.disconnect();
-		connect(&label_, &ClickableLabel::clicked, this, [this] { QDesktopServices::openUrl(service_url_); });
-	}
-}
-
-void Poster::showEvent(QShowEvent* event) {
-	if (need_refresh_) {
-		DownloadPoster();
-		need_refresh_ = false;
-	}
-}
-
-void Poster::SetClickable(bool enabled) {
-	clickable_ = enabled;
-
-	if (clickable_ && !service_url_.isEmpty()) {
-		setCursor(Qt::PointingHandCursor);
-		label_.disconnect();
-		connect(&label_, &ClickableLabel::clicked, this, [this] { QDesktopServices::openUrl(service_url_); });
-	} else {
-		setCursor(Qt::ArrowCursor);
-		label_.disconnect();
-	}
-}
-
-void Poster::ImageDownloadFinished(const QByteArray& arr) {
-	img_.loadFromData(arr);
-	RenderToLabel();
-}
-
-void Poster::RenderToLabel() {
-	const QPixmap pixmap = QPixmap::fromImage(img_);
-	if (pixmap.isNull())
-		return;
-	label_.setPixmap(pixmap.scaled(label_.size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
-}
-
-void Poster::resizeEvent(QResizeEvent*) {
-	RenderToLabel();
-}
+#include "gui/widgets/poster.h"
+#include "core/anime_db.h"
+#include "core/http.h"
+#include "core/session.h"
+#include "core/strings.h"
+#include "gui/widgets/clickable_label.h"
+
+#include <QByteArray>
+#include <QDebug>
+#include <QDesktopServices>
+#include <QFrame>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QMessageBox>
+#include <QPixmap>
+#include <QThread>
+#include <QUrl>
+
+Poster::Poster(QWidget* parent) : QFrame(parent) {
+	QHBoxLayout* layout = new QHBoxLayout(this);
+	layout->setContentsMargins(1, 1, 1, 1);
+
+	setCursor(Qt::PointingHandCursor);
+	setFixedSize(150, 225);
+	setFrameShape(QFrame::Box);
+	setFrameShadow(QFrame::Plain);
+
+	label_.setAlignment(Qt::AlignCenter);
+	layout->addWidget(&label_);
+
+	get_thread_ = new HTTP::RequestThread(HTTP::Type::Get);
+	connect(get_thread_, &HTTP::RequestThread::ReceivedData, this, &Poster::ImageDownloadFinished);
+}
+
+Poster::Poster(const Anime::Anime& anime, QWidget* parent) : Poster(parent) {
+	SetAnime(anime);
+}
+
+Poster::~Poster() {
+	/* schedule deletion of the thread */
+	get_thread_->deleteLater();
+}
+
+void Poster::DownloadPoster() {
+	if (get_thread_->isRunning())
+		get_thread_->Stop();
+	get_thread_->wait();
+
+	get_thread_->SetUrl(poster_url_);
+	get_thread_->start();
+}
+
+void Poster::SetAnime(const Anime::Anime& anime) {
+	label_.clear();
+
+	poster_url_ = anime.GetPosterUrl();
+	if (isVisible())
+		DownloadPoster();
+	else
+		need_refresh_ = true;
+
+	std::optional<std::string> url = anime.GetServiceUrl(session.config.service);
+	if (url)
+		service_url_ = Strings::ToQString(url.value());
+
+	if (clickable_) {
+		label_.disconnect();
+		connect(&label_, &ClickableLabel::clicked, this, [this] { QDesktopServices::openUrl(service_url_); });
+	}
+}
+
+void Poster::showEvent(QShowEvent* event) {
+	if (need_refresh_) {
+		DownloadPoster();
+		need_refresh_ = false;
+	}
+}
+
+void Poster::SetClickable(bool enabled) {
+	clickable_ = enabled;
+
+	if (clickable_ && !service_url_.isEmpty()) {
+		setCursor(Qt::PointingHandCursor);
+		label_.disconnect();
+		connect(&label_, &ClickableLabel::clicked, this, [this] { QDesktopServices::openUrl(service_url_); });
+	} else {
+		setCursor(Qt::ArrowCursor);
+		label_.disconnect();
+	}
+}
+
+void Poster::ImageDownloadFinished(const QByteArray& arr) {
+	img_.loadFromData(arr);
+	RenderToLabel();
+}
+
+void Poster::RenderToLabel() {
+	const QPixmap pixmap = QPixmap::fromImage(img_);
+	if (pixmap.isNull())
+		return;
+	label_.setPixmap(pixmap.scaled(label_.size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
+}
+
+void Poster::resizeEvent(QResizeEvent*) {
+	RenderToLabel();
+}
--- a/src/track/media.cc	Mon May 13 14:56:37 2024 -0400
+++ b/src/track/media.cc	Mon May 13 15:04:51 2024 -0400
@@ -1,65 +1,65 @@
-#include "track/media.h"
-#include "core/filesystem.h"
-#include "core/session.h"
-#include "core/strings.h"
-
-#include <QFile>
-#include <QTextStream>
-
-#include <filesystem>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#include <iostream>
-
-#include "animone.h"
-
-namespace Track {
-namespace Media {
-
-static bool GetCurrentlyPlayingResults(std::vector<animone::Result>& results) {
-	std::vector<animone::Player> players;
-
-	players.reserve(session.config.recognition.players.size());
-	for (const auto& [enabled, player] : session.config.recognition.players)
-		if (enabled && player.type == animone::PlayerType::Default)
-			players.push_back(player);
-
-	return animone::GetResults(players, results);
-}
-
-/* meh */
-bool GetCurrentlyPlaying(std::vector<std::string>& vec) {
-	std::vector<animone::Result> results;
-
-	if (!GetCurrentlyPlayingResults(results))
-		return false;
-
-	bool success = false;
-
-	for (const auto& result : results) {
-		for (const auto& media : result.media) {
-			for (const auto& info : media.information) {
-				std::cout << info.value << std::endl;
-
-				switch (info.type) {
-					case animone::MediaInfoType::File:
-						vec.push_back(std::filesystem::path(info.value).filename().u8string());
-						success |= true;
-						break;
-					case animone::MediaInfoType::Title:
-						vec.push_back(info.value);
-						success |= true;
-						break;
-					default: break;
-				}
-			}
-		}
-	}
-
-	return success;
-}
-
-} // namespace Media
-} // namespace Track
+#include "track/media.h"
+#include "core/filesystem.h"
+#include "core/session.h"
+#include "core/strings.h"
+
+#include <QFile>
+#include <QTextStream>
+
+#include <filesystem>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <iostream>
+
+#include "animone.h"
+
+namespace Track {
+namespace Media {
+
+static bool GetCurrentlyPlayingResults(std::vector<animone::Result>& results) {
+	std::vector<animone::Player> players;
+
+	players.reserve(session.config.recognition.players.size());
+	for (const auto& [enabled, player] : session.config.recognition.players)
+		if (enabled && player.type == animone::PlayerType::Default)
+			players.push_back(player);
+
+	return animone::GetResults(players, results);
+}
+
+/* meh */
+bool GetCurrentlyPlaying(std::vector<std::string>& vec) {
+	std::vector<animone::Result> results;
+
+	if (!GetCurrentlyPlayingResults(results))
+		return false;
+
+	bool success = false;
+
+	for (const auto& result : results) {
+		for (const auto& media : result.media) {
+			for (const auto& info : media.information) {
+				std::cout << info.value << std::endl;
+
+				switch (info.type) {
+					case animone::MediaInfoType::File:
+						vec.push_back(std::filesystem::path(info.value).filename().u8string());
+						success |= true;
+						break;
+					case animone::MediaInfoType::Title:
+						vec.push_back(info.value);
+						success |= true;
+						break;
+					default: break;
+				}
+			}
+		}
+	}
+
+	return success;
+}
+
+} // namespace Media
+} // namespace Track