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