Mercurial > minori
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
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