Mercurial > minori
changeset 138:28842a8d0c6b
dep/animia: huge refactor (again...)
but this time, it actually compiles! and it WORKS! (on win32... not sure about
other platforms...)
configuring players is still not supported: at some point I'll prune something
up...
author | Paper <mrpapersonic@gmail.com> |
---|---|
date | Sun, 12 Nov 2023 04:53:19 -0500 |
parents | 69db40272acd |
children | 478f3b366199 |
files | CMakeLists.txt dep/animia/CMakeLists.txt dep/animia/README.md dep/animia/include/animia.h dep/animia/include/animia/fd/bsd.h dep/animia/include/animia/fd/linux.h dep/animia/include/animia/fd/win32.h dep/animia/include/animia/matroska.h dep/animia/include/animia/media.h dep/animia/include/animia/platform/win32.h dep/animia/include/animia/platform/win32/fd.h dep/animia/include/animia/platform/win32/ui_auto.h dep/animia/include/animia/platform/win32/util.h dep/animia/include/animia/platform/win32/win.h dep/animia/include/animia/player.h dep/animia/include/animia/strategies.h dep/animia/include/animia/util.h dep/animia/src/animia.cc dep/animia/src/fd/bsd.cc dep/animia/src/fd/linux.cc dep/animia/src/fd/win32.cc dep/animia/src/matroska.cc dep/animia/src/platform/bsd/fd.cc dep/animia/src/platform/linux/fd.cc dep/animia/src/platform/win32.cc dep/animia/src/platform/win32/fd.cc dep/animia/src/platform/win32/ui_auto.cc dep/animia/src/platform/win32/util.cc dep/animia/src/platform/win32/win.cc dep/animia/src/player.cc dep/animia/src/strategist.cc dep/animia/src/util.cc dep/animia/util/Anisthesia.sublime-syntax include/track/media.h rc/player_data.qrc src/core/filesystem.cc src/gui/window.cc src/track/media.cc |
diffstat | 33 files changed, 1160 insertions(+), 922 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Fri Nov 10 13:52:47 2023 -0500 +++ b/CMakeLists.txt Sun Nov 12 04:53:19 2023 -0500 @@ -110,6 +110,7 @@ # Qt resources rc/icons.qrc rc/dark.qrc + rc/player_data.qrc ) set(INCLUDE @@ -204,3 +205,7 @@ target_compile_definitions(minori PUBLIC WIN32) endif() target_link_libraries(minori ${LIBRARIES}) + +if(WIN32) + install(FILES $<TARGET_RUNTIME_DLLS:minori> TYPE BIN) +endif()
--- a/dep/animia/CMakeLists.txt Fri Nov 10 13:52:47 2023 -0500 +++ b/dep/animia/CMakeLists.txt Sun Nov 12 04:53:19 2023 -0500 @@ -3,28 +3,24 @@ set(SRC_FILES # any non-platform-specific files go here src/animia.cc - src/matroska.cc src/player.cc src/util.cc + src/strategist.cc ) if(LINUX) list(APPEND SRC_FILES # linux - src/linux/fd.cc + src/fd/linux.cc ) elseif(UNIX) # this won't run on Linux list(APPEND SRC_FILES # bsd - src/bsd/fd.cc + src/fd/bsd.cc ) elseif(WIN32) list(APPEND SRC_FILES # win32 - src/platform/win32.cc - src/platform/win32/fd.cc - src/platform/win32/ui_auto.cc - src/platform/win32/util.cc - src/platform/win32/win.cc + src/fd/win32.cc ) endif() add_library(animia SHARED ${SRC_FILES})
--- a/dep/animia/README.md Fri Nov 10 13:52:47 2023 -0500 +++ b/dep/animia/README.md Sun Nov 12 04:53:19 2023 -0500 @@ -1,5 +1,4 @@ # Animia -Animia is a work-in-progress cross-platform fork of Anisthesia and part of Minori. +Animia is a work-in-progress cross-platform clone of Anisthesia and part of Minori. -## License -Because this project is a hard-fork of Anisthesia, it is under the MIT license. +Most (if not all) Anisthesia configs should also work in this library as well.
--- a/dep/animia/include/animia.h Fri Nov 10 13:52:47 2023 -0500 +++ b/dep/animia/include/animia.h Sun Nov 12 04:53:19 2023 -0500 @@ -3,10 +3,31 @@ #include "animia/media.h" #include "animia/player.h" +#include "animia/util.h" namespace animia { +/* pid_t should be DWORD on windows, and defined by the system + anywhere else */ +struct Process { + pid_t pid = 0; + 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; + std::vector<Media> media; +}; + +bool GetResults(const std::vector<Player>& players, std::vector<Result>& results); } // namespace Animia
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dep/animia/include/animia/fd/bsd.h Sun Nov 12 04:53:19 2023 -0500 @@ -0,0 +1,19 @@ +#ifndef __animia__animia__fd__linux_h +#define __animia__animia__fd__linux_h + +#include <vector> +#include <tuple> +#include <string> +#include <set> + +#include <sys/types.h> // pid_t + +namespace animia::internal::unix { + +bool GetAllPids(std::set<pid_t>& pids); +bool GetProcessName(pid_t pid, std::string& result); +bool EnumerateOpenFiles(const std::set<pid_t>& pids, std::vector<std::tuple<pid_t, std::string>>& files); + +} + +#endif // __animia__animia__fd__linux_h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dep/animia/include/animia/fd/linux.h Sun Nov 12 04:53:19 2023 -0500 @@ -0,0 +1,19 @@ +#ifndef __animia__animia__fd__linux_h +#define __animia__animia__fd__linux_h + +#include <vector> +#include <tuple> +#include <string> +#include <set> + +#include <sys/types.h> // pid_t + +namespace animia::internal::linux { + +bool GetAllPids(std::set<pid_t>& pids); +bool GetProcessName(pid_t pid, std::string& result); +bool EnumerateOpenFiles(const std::set<pid_t>& pids, std::vector<std::tuple<pid_t, std::string>>& files); + +} + +#endif // __animia__animia__fd__linux_h \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dep/animia/include/animia/fd/win32.h Sun Nov 12 04:53:19 2023 -0500 @@ -0,0 +1,29 @@ +#ifndef __animia__animia__fd__win32_h +#define __animia__animia__fd__win32_h + +#include <vector> +#include <tuple> +#include <string> +#include <set> +#include <memory> + +#include <windows.h> + +#include "animia/util.h" + +namespace animia::internal::win32 { + +struct HandleDeconstructor { + using pointer = HANDLE; + void operator()(pointer t) const { ::CloseHandle(t); }; +}; + +using Handle = std::unique_ptr<HANDLE, HandleDeconstructor>; + +bool GetAllPids(std::set<pid_t>& pids); +bool GetProcessName(pid_t pid, std::string& result); +bool EnumerateOpenFiles(const std::set<pid_t>& pids, std::vector<std::tuple<pid_t, std::string>>& files); + +} + +#endif // __animia__animia__fd__win32_h
--- a/dep/animia/include/animia/matroska.h Fri Nov 10 13:52:47 2023 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,70 +0,0 @@ -#ifndef __animia__animia__matroska_h -#define __animia__animia__matroska_h - -#include <chrono> -#include <cstdint> -#include <string> -#include <vector> - -namespace animia::matroska { - -namespace detail { - -using timecode_scale_t = std::chrono::duration<float, std::nano>; -constexpr uint32_t kDefaultTimecodeScale = 1000000; // 1 milliseconds - -enum ElementId { - // EBML Header - EL_EBML = 0x1A45DFA3, - // Segment - EL_SEGMENT = 0x18538067, - // Segment Information - EL_INFO = 0x1549A966, - EL_TIMECODESCALE = 0x2AD7B1, - EL_DURATION = 0x4489, - EL_TITLE = 0x7BA9, - // Track - EL_TRACKS = 0x1654AE6B, - EL_TRACKENTRY = 0xAE, - EL_TRACKTYPE = 0x83, - EL_TRACKNAME = 0x536E -}; - -enum TrackType { - kVideo = 1 -}; - -class Buffer { - public: - Buffer(size_t size); - - uint8_t* data(); - size_t pos() const; - size_t size() const; - void skip(size_t size); - - bool read_encoded_value(uint32_t& value, bool clear_leading_bits); - uint32_t read_uint32(const size_t size); - float read_float(const size_t size); - std::string read_string(const size_t size); - - private: - std::vector<uint8_t> data_; - size_t pos_ = 0; -}; - -} // namespace detail - -using duration_t = std::chrono::duration<float, std::milli>; - -struct Info { - duration_t duration = duration_t::zero(); - std::string title; - std::string video_track_name; -}; - -bool ReadInfoFromFile(const std::string& path, Info& info); - -} - -#endif // __animia__animia__matroska_h
--- a/dep/animia/include/animia/media.h Fri Nov 10 13:52:47 2023 -0500 +++ b/dep/animia/include/animia/media.h Sun Nov 12 04:53:19 2023 -0500 @@ -15,14 +15,7 @@ File, Tab, Title, - Url, -}; - -enum class MediaState { - Unknown, - Playing, - Paused, - Stopped, + Url }; struct MediaInfo { @@ -31,12 +24,9 @@ }; struct Media { - MediaState state = MediaState::Unknown; // currently unused - media_time_t duration; // currently unused - media_time_t position; // currently unused std::vector<MediaInfo> information; }; -using media_proc_t = std::function<bool(const MediaInfo&)>; +} // namespace animia -#endif // __animia__animia__media_h \ No newline at end of file +#endif // __animia__animia__media_h
--- a/dep/animia/include/animia/platform/win32.h Fri Nov 10 13:52:47 2023 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -#ifndef __animia__animia__platform__win32_h -#define __animia__animia__platform__win32_h - -#include <string> -#include <vector> - -#include <windows.h> - -#include "animia/media.h" -#include "animia/player.h" - -namespace animia::win { - -struct Process { - DWORD id = 0; - std::wstring name; -}; - -struct Window { - HWND handle = nullptr; - std::wstring class_name; - std::wstring text; -}; - -struct Result { - Player player; - Process process; - Window window; - std::vector<Media> media; -}; - -bool GetResults(const std::vector<Player>& players, media_proc_t media_proc, - std::vector<Result>& results); - -namespace detail { - -bool ApplyStrategies(media_proc_t media_proc, std::vector<Result>& results); - -} // namespace detail - -} // namespace animia::win - -#endif // __animia__animia__platform__win32_h
--- a/dep/animia/include/animia/platform/win32/fd.h Fri Nov 10 13:52:47 2023 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -#ifndef __animia__animia__platform__win32__fd_h -#define __animia__animia__platform__win32__fd_h - -#include <functional> -#include <set> -#include <string> - -#include <windows.h> - -namespace animia::win::detail { - -struct OpenFile { - DWORD proc_id; - std::wstring path; -} - -using open_file_proc_t = std::function<bool(const OpenFile&)>; - -bool EnumerateOpenFiles(const std::set<DWORD>& process_ids, - open_file_proc_t open_file_proc); - -} - -#endif // __animia__animia__platform__win32__fd_h \ No newline at end of file
--- a/dep/animia/include/animia/platform/win32/ui_auto.h Fri Nov 10 13:52:47 2023 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -#ifndef __animia__animia__win32__ui_auto_h -#define __animia__animia__win32__ui_auto_h - -/* "UI automation" == web browser stuff.. */ - -#include <functional> -#include <string> - -#include <windows.h> - -namespace animia::win::detail { - -enum class WebBrowserInformationType { - Address, - Tab, - Title, -}; - -struct WebBrowserInformation { - WebBrowserInformationType type = WebBrowserInformationType::Title; - std::string value; -}; - -using web_browser_proc_t = std::function<void(const WebBrowserInformation&)>; - -bool GetWebBrowserInformation(HWND hwnd, web_browser_proc_t web_browser_proc); - -} // namespace animia::win::detail - -#endif // __animia__animia__win32__ui_auto_h
--- a/dep/animia/include/animia/platform/win32/util.h Fri Nov 10 13:52:47 2023 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -#ifndef __animia__animia__win32__util_h -#define __animia__animia__win32__util_h - -#include <memory> -#include <string> -#include <type_traits> - -#include <windows.h> - -namespace animia::win::detail { - -struct HandleDeleter { - void operator()(HANDLE p) const { ::CloseHandle(p); } -}; - -using Handle = std::unique_ptr<HANDLE, HandleDeleter>; - -/* ----------- Alternative to Microsoft::WRL::ComPtr ------------- */ -template <typename T> -struct ComInterfaceDeleter { - static_assert(std::is_base_of<IUnknown, T>::value, "Invalid COM interface"); - void operator()(T* p) const { if (p) p->Release(); } -}; - -template <typename T> -using ComInterface = std::unique_ptr<T, ComInterfaceDeleter<T>>; -/* --------------------------------------------------------------- */ - -std::wstring GetFileNameFromPath(const std::wstring& path); -std::wstring GetFileNameWithoutExtension(const std::wstring& filename); -bool IsSystemDirectory(const std::wstring& path); - -std::string ToUtf8String(const std::wstring& str); - -} - -#endif // __animia__animia__win32__util_h
--- a/dep/animia/include/animia/platform/win32/win.h Fri Nov 10 13:52:47 2023 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -#ifndef __animia__animia__win32__win_h -#define __animia__animia__win32__win_h - -#include <functional> - -namespace animia::win { - -struct Process; -struct Window; - -namespace detail { - -using window_proc_t = std::function<bool(const Process&, const Window&)>; - -bool EnumerateWindows(window_proc_t window_proc); - -} // namespace detail - -} // namespace animia::win - -#endif // __animia__animia__win32__win_h
--- a/dep/animia/include/animia/player.h Fri Nov 10 13:52:47 2023 -0500 +++ b/dep/animia/include/animia/player.h Sun Nov 12 04:53:19 2023 -0500 @@ -7,15 +7,15 @@ namespace animia { enum class Strategy { - WindowTitle, + WindowTitle, // unused OpenFiles, - UiAutomation // ??? -} + UiAutomation // unused +}; enum class PlayerType { Default, - WebBrowser -} + WebBrowser // unused +}; struct Player { PlayerType type = PlayerType::Default; @@ -24,11 +24,11 @@ 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 animia -#endif // __animia__animia__player_h +#endif // __animia__animia__player_h \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dep/animia/include/animia/strategies.h Sun Nov 12 04:53:19 2023 -0500 @@ -0,0 +1,12 @@ +#ifndef __animia__animia__strategies_h +#define __animia__animia__strategies_h + +#include "animia.h" + +namespace animia::internal { + +bool ApplyStrategies(std::vector<Result>& results); + +} + +#endif // __animia__animia__strategies_h \ No newline at end of file
--- a/dep/animia/include/animia/util.h Fri Nov 10 13:52:47 2023 -0500 +++ b/dep/animia/include/animia/util.h Sun Nov 12 04:53:19 2023 -0500 @@ -1,16 +1,31 @@ #ifndef __animia__animia__util_h #define __animia__animia__util_h -#include <string> +/* these should not be #defines, make them some constexpr + magic at some point */ +#ifdef __linux__ +# define ANIMIA_ON_LINUX +#elif (defined(unix) || defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) +# if (defined(__APPLE__) && defined(__MACH__)) +# define ANIMIA_ON_LINUX +# endif +# define ANIMIA_ON_LINUX +#elif defined(_WIN32) +# define ANIMIA_ON_WIN32 +/* move this to a types.h or something */ +#include <windows.h> +namespace animia { + typedef DWORD pid_t; +} +#endif -namespace animia::detail::util { +namespace animia::internal::util { bool ReadFile(const std::string& path, std::string& data); - bool EqualStrings(const std::string& str1, const std::string& str2); bool TrimLeft(std::string& str, const char* chars); bool TrimRight(std::string& str, const char* chars); -} // namespace animia::detail::util +} -#endif // __animia__animia__util_h +#endif // __animia__animia__util_h \ No newline at end of file
--- a/dep/animia/src/animia.cc Fri Nov 10 13:52:47 2023 -0500 +++ b/dep/animia/src/animia.cc Sun Nov 12 04:53:19 2023 -0500 @@ -0,0 +1,62 @@ +#include <string> +#include <vector> + +#include <windows.h> + +#include "animia.h" +#include "animia/util.h" +#include "animia/strategies.h" +#ifdef ANIMIA_ON_WIN32 +#include "animia/fd/win32.h" +#endif +#include <iostream> + +namespace animia { + +static bool ProcessInPlayers(const std::vector<Player>& players, const std::string& name, Player& player_) { + for (const auto& player : players) { + for (const auto& exe : player.executables) { + if (exe == name.substr(0, name.rfind(".exe"))) { + player_ = player; + return true; + } + } + } + return false; +} + +bool GetResults(const std::vector<Player>& players, std::vector<Result>& results) { + std::set<pid_t> pids; + +#ifdef ANIMIA_ON_WIN32 + /* todo: make these functions also return process names in a tuple, + cause the win32 api gives it to us for free! */ + if (!internal::win32::GetAllPids(pids)) +#elif ANIMIA_ON_LINUX + if (!internal::linux::GetAllPids(pids)) +#elif ANIMIA_ON_UNIX + if (!internal::unix::GetAllPids(pids)) +#endif + return false; + + for (const auto& pid : pids) { + std::string name; +#ifdef ANIMIA_ON_WIN32 + animia::internal::win32::GetProcessName(pid, name); +#endif + + Player player; + if (!ProcessInPlayers(players, name, player)) + continue; + + Result result; + result.process.pid = pid; + result.process.name = name; + result.player = player; + results.push_back(result); + } + + return internal::ApplyStrategies(results); +} + +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dep/animia/src/fd/bsd.cc Sun Nov 12 04:53:19 2023 -0500 @@ -0,0 +1,116 @@ +/** + * bsd.cpp + * - provides support for most* versions of BSD + * - this also works for OS X :) + * more technical details: this is essentially a wrapper + * around the very C-like BSD system functions that are... + * kind of unnatural to use in modern C++. + **/ +#include <fcntl.h> +#include <iostream> +#include <string> +#include <sys/sysctl.h> +#include <sys/types.h> +#include <sys/user.h> +#include <unordered_map> +#include <vector> +#ifdef __FreeBSD__ +# include <libutil.h> +#elif defined(__APPLE__) +# include <libproc.h> +#endif + +namespace animia::internal::unix { + +/* this is a cleaned up version of a function from... Apple? + ...anyway, what it essentially does is gets the size and stuff from + sysctl() and reserves the space in a vector to store the PIDs */ +bool GetAllPids(std::set<pid_t>& pids) { + struct kinfo_proc* result = NULL; + size_t length = 0; + static const int name[] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0}; + + /* get appropriate length from sysctl() + note: the reason this isn't checked is actually because this will + *always* return an error on OS X (or... maybe I'm doing it wrong :) ) */ + sysctl((int*)name, (sizeof(name) / sizeof(*name)) - 1, NULL, &length, NULL, 0); + + result = (struct kinfo_proc*)malloc(length); + if (result == NULL) + return std::vector<int>(); + + /* TODO: this might actually return ENOMEM if the amount of file handles changes between the + original sysctl() call and this one, which is technically possible */ + if (sysctl((int*)name, (sizeof(name) / sizeof(*name)) - 1, result, &length, NULL, 0) == ENOMEM) { + assert(result != NULL); + free(result); + throw std::bad_alloc(); + } + + /* add pids to our vector */ + pids.reserve(length / sizeof(*result)); + for (int i = 0; i < length / sizeof(*result); i++) + pids.push_back(result[i].kp_proc.p_pid); +} + +bool GetProcessName(pid_t pid, std::string& result) { +#ifdef __FreeBSD__ + struct kinfo_proc* proc = kinfo_getproc(pid); + if (!proc) + return false; + result = proc->ki_comm; + + /* FreeBSD manpage for kinfo_getproc(): + "The pointer was obtained by an internal call to malloc(3) and + must be freed by the caller with a call to free(3)." */ + free(proc); + + return true; +#elif defined(__APPLE__) + struct proc_bsdinfo proc; + + int st = proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &proc, PROC_PIDTBSDINFO_SIZE); + if (st != PROC_PIDTBSDINFO_SIZE) + return false; + + /* fixme: is this right? pbi_comm is an alternative, but it reduces the string size to + 16 chars. does pbi_name do the same, or is it different? */ + result = proc.pbi_name; + return true; +#endif +} + +/* this only works on OS X :( */ +bool EnumerateOpenFiles(const std::set<pid_t>& pids, std::vector<std::tuple<pid_t, std::string>>& files) { + for (const auto& pid : pids) { + int bufsz = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0); + if (bufsz == -1) + return false; + + struct proc_fdinfo* info = (struct proc_fdinfo*)malloc(bufsz); + if (!info) + return false; + + proc_pidinfo(pid, PROC_PIDLISTFDS, 0, info, bufsz); + + for (int i = 0; i < bufsz / sizeof(info[0]); 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; + + /* this doesn't work! + if (vnodeInfo.pfi.fi_openflags & O_WRONLY || vnodeInfo.pfi.fi_openflags & O_RDWR) + continue; + */ + + files.push_back({pid, vnodeInfo.pvip.vip_path}); + } + } + } + return true; +} + +} // namespace animia::internal::unix
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dep/animia/src/fd/linux.cc Sun Nov 12 04:53:19 2023 -0500 @@ -0,0 +1,132 @@ +#include <algorithm> +#include <fcntl.h> +#include <filesystem> +#include <fstream> +#include <iostream> +#include <sstream> +#include <string> +#include <sys/stat.h> +#include <unistd.h> +#include <unordered_map> +#include <vector> +#include <cstring> +#include <dirent.h> + +#include "animia/util.h" + +#define PROC_LOCATION "/proc" + +namespace animia::internal::linux { + +/* this uses dirent instead of std::filesystem; it would make a bit + more sense to use the latter, but this is platform dependent already :) */ +std::vector<std::string> GetAllFilesInDir(const std::string& _dir) { + std::vector<std::string> ret; + + DIR* dir = opendir(_dir.c_str()); + if (!dir) + return ret; + + struct dirent* dp; + while ((dp = readdir(dir)) != NULL) { + if (!(!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))) + ret.push_back(_dir + "/" + dp->d_name); + } + + closedir(dir); + return ret; +} + +std::string Basename(const std::string& path) { + return path.substr(path.find_last_of("/") + 1, path.length()); +} + +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::string path = PROC_LOCATION "/" + std::to_string(pid) + "/fdinfo/" + std::to_string(fd); + std::stringstream buffer(util::ReadFile(path)); + + int flags = 0; + for (std::string line; std::getline(buffer, line);) { + /* FIXME: exception handling here!! */ + if (line.rfind("flags:", 0) == 0) { + flags = std::stoi(line.substr(line.find_last_not_of("0123456789") + 1)); + } + } + if (flags & O_WRONLY || flags & O_RDWR) + return false; + return true; +} + +static std::string GetFilenameFromFd(std::string link) { + /* gets around stupid linux limitation where /proc doesn't + give actual filesize readings */ + size_t exe_size = 1024; + ssize_t exe_used; + std::string ret; + + while (1) { + ret = std::string(exe_size, '\0'); + exe_used = readlink(link.c_str(), &ret.front(), ret.length()); + if (exe_used == (ssize_t)-1) + return NULL; + + if (exe_used < (ssize_t)1) { + errno = ENOENT; + return NULL; + } + + if (exe_used < (ssize_t)(exe_size - 1)) + break; + + exe_size += 1024; + } + + return ret.c_str(); +} + +bool GetAllPids(std::set<pid_t>& pids) { + for (const auto& dir : get_all_files_in_dir(PROC_LOCATION)) { + pid_t pid; + try { + pid = std::stoul(basename(dir)); + } catch (std::invalid_argument) { + continue; + } + pids.push_back(pid); + } +} + +bool GetProcessName(pid_t pid, std::string& result) { + const std::string path = PROC_LOCATION "/" + std::to_string(pid) + "/comm"; + + std::string result = util::ReadFile(path); + result.erase(std::remove(result.begin(), result.end(), '\n'), result.end()); +} + +bool EnumerateOpenFiles(const std::set<pid_t>& pids, std::vector<std::tuple<pid_t, std::string>>& files) { + for (const auto& pid : pids) { + const std::string path = PROC_LOCATION "/" + std::to_string(pid) + "/fd"; + + for (const auto& dir : GetAllFilesInDir(path)) { + if (!AreFlagsOk(pid, std::stoi(basename(dir)))) + continue; + + std::string name = GetFilenameFromFd(dir); + + if (!IsRegularFile(name)) + continue; + + files.push_back({pid, name}); + } + } + return true; +} + +} // namespace animia::internal::linux
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dep/animia/src/fd/win32.cc Sun Nov 12 04:53:19 2023 -0500 @@ -0,0 +1,288 @@ +/** + * win32.cpp + * - provides support for Windows clients + * + **/ +#include "animia/fd/win32.h" +#include <fileapi.h> +#include <handleapi.h> +#include <iostream> +#include <libloaderapi.h> +#include <ntdef.h> +#include <psapi.h> +#include <shlobj.h> +#include <stdexcept> +#include <string> +#include <stringapiset.h> +#include <tlhelp32.h> +#include <unordered_map> +#include <vector> +#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. */ + +#define SystemExtendedHandleInformation ((SYSTEM_INFORMATION_CLASS)0x40) +constexpr NTSTATUS STATUS_INFO_LENGTH_MISMATCH = 0xC0000004UL; +constexpr NTSTATUS STATUS_SUCCESS = 0x00000000UL; + +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 animia::internal::win32 { + +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 PVOID GetNTDLLAddress(LPCSTR proc_name) { + return reinterpret_cast<PVOID>(::GetProcAddress(::GetModuleHandleA("ntdll.dll"), proc_name)); +} + +static NTSTATUS QuerySystemInformation(SYSTEM_INFORMATION_CLASS cls, PVOID sysinfo, ULONG len, PULONG retlen) { + static const auto func = + reinterpret_cast<decltype(::NtQuerySystemInformation)*>(GetNTDLLAddress("NtQuerySystemInformation")); + return func(cls, sysinfo, len, retlen); +} + +static NTSTATUS QueryObject(HANDLE handle, OBJECT_INFORMATION_CLASS cls, PVOID objinf, ULONG objinflen, PULONG retlen) { + static const auto func = reinterpret_cast<decltype(::NtQueryObject)*>(GetNTDLLAddress("NtQueryObject")); + return func(handle, cls, objinf, objinflen, retlen); +} + +static std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> GetSystemHandleInformation() { + std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> res; + /* we should really put a cap on this */ + ULONG cb = 1 << 19; + NTSTATUS status = STATUS_SUCCESS; + SYSTEM_HANDLE_INFORMATION_EX* info; + + do { + status = STATUS_NO_MEMORY; + + if (!(info = (SYSTEM_HANDLE_INFORMATION_EX*)malloc(cb *= 2))) + continue; + + res.reserve(cb); + + if (0 <= (status = QuerySystemInformation(SystemExtendedHandleInformation, info, cb, &cb))) { + if (ULONG_PTR handles = info->NumberOfHandles) { + SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX* entry = info->Handles; + do { + if (entry) + res.push_back(*entry); + } while (entry++, --handles); + } + } + free(info); + } while (status == STATUS_INFO_LENGTH_MISMATCH); + + return res; +} + +static OBJECT_TYPE_INFORMATION QueryObjectTypeInfo(HANDLE handle) { + OBJECT_TYPE_INFORMATION info; + QueryObject(handle, ObjectTypeInformation, &info, sizeof(info), NULL); + return info; +} + +/* we're using UTF-8. originally, I had used just the ANSI versions of functions, but that + sucks massive dick. this way we get unicode in the way every single other OS does it */ +static std::string UnicodeStringToUtf8(std::wstring string) { + unsigned long size = ::WideCharToMultiByte(CP_UTF8, 0, string.c_str(), string.length(), NULL, 0, NULL, NULL); + std::string ret = std::string(size, '\0'); + ::WideCharToMultiByte(CP_UTF8, 0, string.c_str(), string.length(), &ret.front(), ret.length(), NULL, NULL); + return ret; +} + +static std::string UnicodeStringToUtf8(UNICODE_STRING string) { + unsigned long size = ::WideCharToMultiByte(CP_UTF8, 0, string.Buffer, string.Length, NULL, 0, NULL, NULL); + std::string ret = std::string(size, '\0'); + ::WideCharToMultiByte(CP_UTF8, 0, string.Buffer, string.Length, &ret.front(), ret.length(), NULL, NULL); + return ret; +} + +static std::wstring Utf8StringToUnicode(std::string string) { + unsigned long size = ::MultiByteToWideChar(CP_UTF8, 0, string.c_str(), string.length(), NULL, 0); + std::wstring ret = std::wstring(size, L'\0'); + ::MultiByteToWideChar(CP_UTF8, 0, string.c_str(), string.length(), &ret.front(), ret.length()); + return ret; +} + +static std::string GetHandleType(HANDLE handle) { + OBJECT_TYPE_INFORMATION info = QueryObjectTypeInfo(handle); + return UnicodeStringToUtf8(info.TypeName); +} + +static std::string GetFinalPathNameByHandle(HANDLE handle) { + std::wstring buffer; + + int result = ::GetFinalPathNameByHandleW(handle, NULL, 0, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); + buffer.resize(result); + ::GetFinalPathNameByHandleW(handle, &buffer.front(), buffer.size(), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); + buffer.resize(buffer.find('\0')); + + return UnicodeStringToUtf8(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) == "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 std::string GetSystemDirectory() { + PWSTR path_wch; + SHGetKnownFolderPath(FOLDERID_Windows, 0, NULL, &path_wch); + std::wstring path_wstr(path_wch); + CoTaskMemFree(path_wch); + return UnicodeStringToUtf8(path_wstr); +} + +static bool IsSystemFile(const std::string& path) { + std::wstring path_w = Utf8StringToUnicode(path); + CharUpperBuffW(&path_w.front(), path_w.length()); + std::wstring windir_w = Utf8StringToUnicode(GetSystemDirectory()); + CharUpperBuffW(&windir_w.front(), windir_w.length()); + return path_w.find(windir_w) == 4; +} + +static bool IsFilePathOk(const std::string& path) { + if (path.empty()) + return false; + + if (IsSystemFile(path)) + return false; + + const auto file_attributes = GetFileAttributesA(path.c_str()); + if ((file_attributes == INVALID_FILE_ATTRIBUTES) || (file_attributes & FILE_ATTRIBUTE_DIRECTORY)) + return false; + + return true; +} + +bool GetAllPids(std::set<pid_t>& pids) { + 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; + + pids.insert(pe32.th32ProcessID); + + while (::Process32Next(hProcessSnap, &pe32)) + pids.insert(pe32.th32ProcessID); + + ::CloseHandle(hProcessSnap); + + return true; +} + +bool GetProcessName(pid_t pid, std::string& result) { + unsigned long ret_size = 0; // size given by GetModuleBaseNameW + Handle handle(::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); + if (!handle.get()) + return false; + + /* agh... */ + std::wstring ret(256, L'\0'); + for (; ret.length() < 32768; ret.resize(ret.length() * 2)) { + if (!(ret_size = ::GetModuleBaseNameW(handle.get(), 0, &ret.front(), ret.length()))) { + return false; + } else if (ret.length() > ret_size) { + ret.resize(ret.find(L'\0')); + result = UnicodeStringToUtf8(ret); + break; + } + } + + 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, std::vector<std::tuple<pid_t, std::string>>& files) { + 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) + 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()) + continue; + + if (GetFileType(handle.get()) != FILE_TYPE_DISK) + continue; + + const std::string path = GetFinalPathNameByHandle(handle.get()); + if (!IsFilePathOk(path)) + continue; + + /* create an empty vector if it doesn't exist, probably unnecessary */ + files.push_back({pid, path}); + } + + return true; +} + +} // namespace animia::internal::win32
--- a/dep/animia/src/platform/bsd/fd.cc Fri Nov 10 13:52:47 2023 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,124 +0,0 @@ -/** - * bsd.cpp - * - provides support for most* versions of BSD - * - this also works for OS X :) - * more technical details: this is essentially a wrapper - * around the very C-like BSD system functions that are... - * kind of unnatural to use in modern C++. - **/ -#include <fcntl.h> -#include <iostream> -#include <string> -#include <sys/sysctl.h> -#include <sys/types.h> -#include <sys/user.h> -#include <unordered_map> -#include <vector> -#ifdef __FreeBSD__ -# include <libutil.h> -#elif defined(__APPLE__) -# include <libproc.h> -#endif - -namespace Animia { namespace Unix { - -/* this is a cleaned up version of a function from... Apple? - ...anyway, what it essentially does is gets the size and stuff from - sysctl() and reserves the space in a vector to store the PIDs */ -std::vector<int> get_all_pids() { - std::vector<int> ret; - struct kinfo_proc* result = NULL; - size_t length = 0; - static const int name[] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0}; - - /* get appropriate length from sysctl() - note: the reason this isn't checked is actually because this will - *always* return an error on OS X (or... maybe I'm doing it wrong :) ) */ - sysctl((int*)name, (sizeof(name) / sizeof(*name)) - 1, NULL, &length, NULL, 0); - - result = (struct kinfo_proc*)malloc(length); - if (result == NULL) - return std::vector<int>(); - - /* actually get our results */ - if (sysctl((int*)name, (sizeof(name) / sizeof(*name)) - 1, result, &length, NULL, 0) == ENOMEM) { - assert(result != NULL); - free(result); - throw std::bad_alloc(); - } - - /* add pids to our vector */ - ret.reserve(length / sizeof(*result)); - for (int i = 0; i < length / sizeof(*result); i++) - ret.push_back(result[i].kp_proc.p_pid); - - return ret; -} - -std::string get_process_name(int pid) { - std::string ret; -#ifdef __FreeBSD__ - struct kinfo_proc* proc = kinfo_getproc(pid); - if (!proc) - return ""; - ret = proc->ki_comm; - free(proc); -#elif defined(__APPLE__) - struct proc_bsdinfo proc; - - int st = proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &proc, PROC_PIDTBSDINFO_SIZE); - if (st != PROC_PIDTBSDINFO_SIZE) - return ""; - ret = proc.pbi_comm; -#endif - return ret; -} - -std::vector<std::string> get_open_files(int pid) { - /* note: this is OS X only right now. eventually, I'll find a way - to do this in FreeBSD, OpenBSD and the like */ - std::vector<std::string> ret; - - if (pid == 0) - return ret; - - int bufsz = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0); - if (bufsz == -1) - return ret; - - struct proc_fdinfo* info = (struct proc_fdinfo*)malloc(bufsz); - if (!info) - return ret; - - proc_pidinfo(pid, PROC_PIDLISTFDS, 0, info, bufsz); - - // iterate over stuff - ret.reserve(bufsz / sizeof(info[0])); - for (int i = 0; i < bufsz / sizeof(info[0]); 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) - continue; - - if (vnodeInfo.pfi.fi_openflags & O_WRONLY || vnodeInfo.pfi.fi_openflags & O_RDWR) - continue; - - ret.push_back(vnodeInfo.pvip.vip_path); - } - } - return ret; -} - -std::unordered_map<int, std::vector<std::string>> get_all_open_files() { - std::unordered_map<int, std::vector<std::string>> map; - std::vector<int> pids = get_all_pids(); - for (int i : pids) { - map[i] = get_open_files(i); - } - return map; -} - -} // namespace Unix -} // namespace Animia
--- a/dep/animia/src/platform/linux/fd.cc Fri Nov 10 13:52:47 2023 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,147 +0,0 @@ -#include <algorithm> -#include <fcntl.h> -#include <filesystem> -#include <fstream> -#include <iostream> -#include <sstream> -#include <string> -#include <sys/stat.h> -#include <unistd.h> -#include <unordered_map> -#include <vector> -#include <cstring> -#include <dirent.h> - -#define PROC_LOCATION "/proc" - -namespace Animia { namespace Linux { - -std::vector<std::string> get_all_files_in_dir(const std::string& _dir) { - std::vector<std::string> ret; - - DIR* dir = opendir(_dir.c_str()); - if (!dir) - return ret; - - struct dirent* dp; - while ((dp = readdir(dir)) != NULL) { - if (!(!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))) - ret.push_back(_dir + "/" + dp->d_name); - } - - closedir(dir); - return ret; -} - -std::string basename(const std::string& path) { - return path.substr(path.find_last_of("/") + 1, path.length()); -} - -std::string stem(const std::string& path) { - std::string bn = basename(path); - return bn.substr(0, path.find_last_of(".")); -} - -std::vector<int> get_all_pids() { - std::vector<int> ret; - - for (const auto& dir : get_all_files_in_dir(PROC_LOCATION)) { - int pid; - try { - pid = std::stoi(basename(dir)); - } catch (std::invalid_argument) { - continue; - } - ret.push_back(pid); - } - - return ret; -} - -std::string get_process_name(int pid) { - std::string path = PROC_LOCATION "/" + std::to_string(pid) + "/comm"; - std::ifstream t(path); - std::stringstream buf; - buf << t.rdbuf(); - - std::string str = buf.str(); - str.erase(std::remove(str.begin(), str.end(), '\n'), str.end()); - return str; -} - -static bool is_regular_file(std::string link) { - struct stat sb; - if (stat(link.c_str(), &sb) == -1) - return false; - return S_ISREG(sb.st_mode); -} - -static bool are_flags_ok(int pid, int fd) { - std::string path = PROC_LOCATION "/" + std::to_string(pid) + "/fdinfo/" + std::to_string(fd); - std::ifstream t(path); - std::stringstream buffer; - buffer << t.rdbuf(); - std::string raw; - int flags = 0; - while (std::getline(buffer, raw)) { - if (raw.rfind("flags:", 0) == 0) { - flags = std::stoi(raw.substr(raw.find_last_not_of("0123456789") + 1)); - } - } - if (flags & O_WRONLY || flags & O_RDWR) - return false; - return true; -} - -static std::string get_name_from_fd(std::string link) { - size_t exe_size = 1024; - ssize_t exe_used; - std::string ret; - while (1) { - ret = std::string(exe_size, '\0'); - exe_used = readlink(link.c_str(), &ret.front(), ret.length()); - if (exe_used == (ssize_t)-1) - return NULL; - - if (exe_used < (ssize_t)1) { - errno = ENOENT; - return NULL; - } - - if (exe_used < (ssize_t)(exe_size - 1)) - break; - - exe_size += 1024; - } - - return ret.c_str(); -} - -std::vector<std::string> get_open_files(int pid) { - std::vector<std::string> ret; - std::string path = PROC_LOCATION "/" + std::to_string(pid) + "/fd"; - - for (const auto& dir : get_all_files_in_dir(path)) { - if (!are_flags_ok(pid, std::stoi(basename(dir)))) - continue; - - std::string buf = get_name_from_fd(dir); - - if (!is_regular_file(buf)) - continue; - - ret.push_back(buf); - } - return ret; -} - -std::unordered_map<int, std::vector<std::string>> get_all_open_files() { - std::unordered_map<int, std::vector<std::string>> map; - std::vector<int> pids = get_all_pids(); - for (int i : pids) - map[i] = get_open_files(i); - return map; -} - -} // namespace Linux -} // namespace Animia \ No newline at end of file
--- a/dep/animia/src/platform/win32/fd.cc Fri Nov 10 13:52:47 2023 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,314 +0,0 @@ -/** - * win32.cpp - * - provides support for Windows clients - * - **/ -#include "win32.h" -#include <fileapi.h> -#include <handleapi.h> -#include <iostream> -#include <libloaderapi.h> -#include <ntdef.h> -#include <psapi.h> -#include <shlobj.h> -#include <stdexcept> -#include <string> -#include <stringapiset.h> -#include <tlhelp32.h> -#include <unordered_map> -#include <vector> -#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. */ - -#define SystemExtendedHandleInformation ((SYSTEM_INFORMATION_CLASS)0x40) -constexpr NTSTATUS STATUS_INFO_LENGTH_MISMATCH = 0xC0000004UL; -constexpr NTSTATUS STATUS_SUCCESS = 0x00000000UL; - -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 Animia { namespace Windows { - -/* All of this BS is required on Windows. Why? */ - -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; -} - -PVOID GetNTDLLAddress(LPCSTR proc_name) { - return reinterpret_cast<PVOID>(::GetProcAddress(::GetModuleHandleA("ntdll.dll"), proc_name)); -} - -NTSTATUS QuerySystemInformation(SYSTEM_INFORMATION_CLASS cls, PVOID sysinfo, ULONG len, PULONG retlen) { - static const auto func = - reinterpret_cast<decltype(::NtQuerySystemInformation)*>(GetNTDLLAddress("NtQuerySystemInformation")); - return func(cls, sysinfo, len, retlen); -} - -NTSTATUS QueryObject(HANDLE handle, OBJECT_INFORMATION_CLASS cls, PVOID objinf, ULONG objinflen, PULONG retlen) { - static const auto func = reinterpret_cast<decltype(::NtQueryObject)*>(GetNTDLLAddress("NtQueryObject")); - return func(handle, cls, objinf, objinflen, retlen); -} - -std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> GetSystemHandleInformation() { - std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> res; - ULONG cb = 1 << 19; - NTSTATUS status = STATUS_SUCCESS; - SYSTEM_HANDLE_INFORMATION_EX* info; - - do { - status = STATUS_NO_MEMORY; - - if (!(info = (SYSTEM_HANDLE_INFORMATION_EX*)malloc(cb *= 2))) - continue; - - res.reserve(cb); - - if (0 <= (status = QuerySystemInformation(SystemExtendedHandleInformation, info, cb, &cb))) { - if (ULONG_PTR handles = info->NumberOfHandles) { - SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX* entry = info->Handles; - do { - if (entry) - res.push_back(*entry); - } while (entry++, --handles); - } - } - free(info); - } while (status == STATUS_INFO_LENGTH_MISMATCH); - - return res; -} - -OBJECT_TYPE_INFORMATION QueryObjectTypeInfo(HANDLE handle) { - OBJECT_TYPE_INFORMATION info; - QueryObject(handle, ObjectTypeInformation, &info, sizeof(info), NULL); - return info; -} - -/* we're using UTF-8. originally, I had used just the ANSI versions of functions, but that - sucks massive dick. this way we get unicode in the way every single other OS does it */ -std::string UnicodeStringToUtf8(std::wstring string) { - unsigned long size = ::WideCharToMultiByte(CP_UTF8, 0, string.c_str(), string.length(), NULL, 0, NULL, NULL); - std::string ret = std::string(size, '\0'); - ::WideCharToMultiByte(CP_UTF8, 0, string.c_str(), string.length(), &ret.front(), ret.length(), NULL, NULL); - return ret; -} - -std::string UnicodeStringToUtf8(UNICODE_STRING string) { - unsigned long size = ::WideCharToMultiByte(CP_UTF8, 0, string.Buffer, string.Length, NULL, 0, NULL, NULL); - std::string ret = std::string(size, '\0'); - ::WideCharToMultiByte(CP_UTF8, 0, string.Buffer, string.Length, &ret.front(), ret.length(), NULL, NULL); - return ret; -} - -std::wstring Utf8StringToUnicode(std::string string) { - unsigned long size = ::MultiByteToWideChar(CP_UTF8, 0, string.c_str(), string.length(), NULL, 0); - std::wstring ret = std::wstring(size, L'\0'); - ::MultiByteToWideChar(CP_UTF8, 0, string.c_str(), string.length(), &ret.front(), ret.length()); - return ret; -} - -std::string GetHandleType(HANDLE handle) { - OBJECT_TYPE_INFORMATION info = QueryObjectTypeInfo(handle); - return UnicodeStringToUtf8(info.TypeName); -} - -std::string GetFinalPathNameByHandle(HANDLE handle) { - std::wstring buffer; - - int result = ::GetFinalPathNameByHandleW(handle, NULL, 0, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); - buffer.resize(result); - ::GetFinalPathNameByHandleW(handle, &buffer.front(), buffer.size(), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); - buffer.resize(buffer.find('\0')); - - return UnicodeStringToUtf8(buffer); -} - -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) == "File") { - file_type_index = object_type_index; - return true; - } - return false; -} - -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; -} - -bool IsFilePathOk(const std::string& path) { - if (path.empty()) - return false; - - const auto file_attributes = GetFileAttributesA(path.c_str()); - if ((file_attributes == INVALID_FILE_ATTRIBUTES) || (file_attributes & FILE_ATTRIBUTE_DIRECTORY)) - return false; - - return true; -} - -std::string GetSystemDirectory() { - PWSTR path_wch; - SHGetKnownFolderPath(FOLDERID_Windows, 0, NULL, &path_wch); - std::wstring path_wstr(path_wch); - CoTaskMemFree(path_wch); - return UnicodeStringToUtf8(path_wstr); -} - -bool IsSystemFile(const std::string& path) { - std::wstring path_w = Utf8StringToUnicode(path); - CharUpperBuffW(&path_w.front(), path_w.length()); - std::wstring windir_w = Utf8StringToUnicode(GetSystemDirectory()); - CharUpperBuffW(&windir_w.front(), windir_w.length()); - return path_w.find(windir_w) == 4; -} - -std::vector<int> get_all_pids() { - std::vector<int> ret; - HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - PROCESSENTRY32 pe32; - pe32.dwSize = sizeof(PROCESSENTRY32); - - if (hProcessSnap == INVALID_HANDLE_VALUE) - return std::vector<int>(); - - if (!Process32First(hProcessSnap, &pe32)) - return std::vector<int>(); - - ret.push_back(pe32.th32ProcessID); - while (Process32Next(hProcessSnap, &pe32)) { - ret.push_back(pe32.th32ProcessID); - } - // clean the snapshot object - CloseHandle(hProcessSnap); - - return ret; -} - -std::string get_process_name(int pid) { - unsigned long size = 256, ret_size = 0; - HANDLE handle = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); - if (!handle) - return ""; - - std::wstring ret(size, L'\0'); - while (size < 32768) { - ret.resize(size, L'\0'); - - if (!(ret_size = ::GetModuleBaseNameW(handle, 0, &ret.front(), ret.length()))) - ret = L""; - else if (size > ret_size) - ret.resize(ret.find('\0')); - - size *= 2; - } - - CloseHandle(handle); - - return UnicodeStringToUtf8(ret); -} - -std::vector<std::string> get_open_files(int pid) { - std::vector<std::string> ret; - std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> info = GetSystemHandleInformation(); - for (auto& h : info) { - if (h.UniqueProcessId != pid) - continue; - - if (!IsFileHandle(nullptr, h.ObjectTypeIndex)) - continue; - if (!IsFileMaskOk(h.GrantedAccess)) - continue; - - const HANDLE proc = ::OpenProcess(PROCESS_DUP_HANDLE, false, pid); - HANDLE handle = DuplicateHandle(proc, h.HandleValue); - if (!handle) - continue; - - if (GetFileType(handle) != FILE_TYPE_DISK) - continue; - - std::string path = GetFinalPathNameByHandle(handle); - if (!IsFilePathOk(path)) - continue; - - ret.push_back(path); - } - return ret; -} - -std::vector<std::string> filter_system_files(const std::vector<std::string>& source) { - std::vector<std::string> ret; - for (const std::string& s : source) { - if (!IsSystemFile(s)) - ret.push_back(s); - } - return ret; -} - -std::unordered_map<int, std::vector<std::string>> get_all_open_files() { - std::unordered_map<int, std::vector<std::string>> map; - std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> info = GetSystemHandleInformation(); - for (auto& h : info) { - int pid = h.UniqueProcessId; - - if (!IsFileHandle(nullptr, h.ObjectTypeIndex)) - continue; - if (!IsFileMaskOk(h.GrantedAccess)) - continue; - - const HANDLE proc = ::OpenProcess(PROCESS_DUP_HANDLE, false, pid); - HANDLE handle = DuplicateHandle(proc, h.HandleValue); - if (!handle) - continue; - - if (GetFileType(handle) != FILE_TYPE_DISK) - continue; - - std::string path = GetFinalPathNameByHandle(handle); - if (!IsFilePathOk(path)) - continue; - - if (map.find(pid) == map.end()) - map[pid] = {}; - map[pid].push_back(path); - } - return map; -} - -} // namespace Windows -} // namespace Animia
--- a/dep/animia/src/player.cc Fri Nov 10 13:52:47 2023 -0500 +++ b/dep/animia/src/player.cc Sun Nov 12 04:53:19 2023 -0500 @@ -0,0 +1,198 @@ +#include <map> +#include <sstream> +#include <string> +#include <vector> + +#include "animia/player.h" +#include "animia/util.h" + +namespace animia { + +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 anisthesia \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dep/animia/src/strategist.cc Sun Nov 12 04:53:19 2023 -0500 @@ -0,0 +1,91 @@ +#include "animia/strategies.h" +#include "animia/util.h" +#include "animia.h" +#include <iostream> + +#ifdef ANIMIA_ON_WIN32 +# include "animia/fd/win32.h" +#elif defined(ANIMIA_ON_LINUX) +# include "animia/fd/linux.h" +#elif defined(ANIMIA_ON_UNIX) +# include "animia/fd/bsd.h" +#endif + +namespace animia::internal { + +class Strategist { + public: + Strategist(Result& result) : result_(result) {} + + bool ApplyStrategies(); + + private: + bool AddMedia(const MediaInfo media_information); + + bool ApplyOpenFilesStrategy(); + + Result& result_; +}; + +bool Strategist::ApplyStrategies() { + bool success = false; + + for (const auto strategy : result_.player.strategies) { + switch (strategy) { + case Strategy::OpenFiles: + success |= ApplyOpenFilesStrategy(); + break; + } + } + + return success; +} + +bool ApplyStrategies(std::vector<Result>& results) { + bool success = false; + + for (auto& result : results) { + Strategist strategist(result); + success |= strategist.ApplyStrategies(); + } + + return success; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool Strategist::ApplyOpenFilesStrategy() { + bool success = false; + + const std::set<pid_t> pids{result_.process.pid}; + std::vector<std::tuple<pid_t, std::string>> files; + +#ifdef ANIMIA_ON_WIN32 + win32::EnumerateOpenFiles(pids, files); +#elif defined(ANIMIA_ON_LINUX) + linux::EnumerateOpenFiles(pids, files); +#elif defined(ANIMIA_ON_UNIX) + unix::EnumerateOpenFiles(pids, files); +#endif + + for (const auto& [pid, file] : files) { + success |= AddMedia({MediaInfoType::File, file}); + } + + return success; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool Strategist::AddMedia(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; +} + +}
--- a/dep/animia/src/util.cc Fri Nov 10 13:52:47 2023 -0500 +++ b/dep/animia/src/util.cc Sun Nov 12 04:53:19 2023 -0500 @@ -0,0 +1,69 @@ +#include <algorithm> +#include <fstream> +#include <sstream> +#include <string> +#include <cctype> + +#include "animia/util.h" + +namespace animia::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; + + file.seekg(0, std::ios::end); + data.resize(static_cast<size_t>(file.tellg())); + file.seekg(0, std::ios::beg); + + file.read(&data.front(), data.size()); + file.close(); + + return true; +} + +bool EqualStrings(const std::string& str1, const std::string& str2) { + auto equal_chars = [](const char c1, const char c2) -> bool { + return std::tolower(static_cast<unsigned char>(c1)) == std::tolower(static_cast<unsigned char>(c2)); + }; + + return str1.size() == str2.size() && std::equal(str1.begin(), str1.end(), str2.begin(), equal_chars); +} + +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 anisthesia::detail::util
--- a/dep/animia/util/Anisthesia.sublime-syntax Fri Nov 10 13:52:47 2023 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -%YAML 1.2 ---- -name: Anisthesia -file_extensions: [anisthesia] -scope: text.anisthesia - -contexts: - main: - - include: comments - - include: keywords - - include: player - - include: strategies - - include: type - - include: string - - comments: - - match: ^\t*#.*\n - scope: comment.anisthesia - - keywords: - - match: ^\t+(executables|strategies|type|windows):?\n - scope: keyword.anisthesia - - player: - - match: ^[^\s].+\n - scope: markup.heading.player.anisthesia - - strategies: - - match: ^\t+(open_files|ui_automation|window_title):?\n - scope: constant.strategy.anisthesia - - type: - - match: ^\t+(default|web_browser)\n - scope: constant.type.anisthesia - - string: - - match: ^\t+\^?(.+)\n - scope: string.anisthesia
--- a/include/track/media.h Fri Nov 10 13:52:47 2023 -0500 +++ b/include/track/media.h Sun Nov 12 04:53:19 2023 -0500 @@ -2,13 +2,15 @@ #define __track__media_h #include "core/filesystem.h" #include <unordered_map> +#include <string> +#include <vector> namespace Track { namespace Media { -std::filesystem::path GetCurrentPlaying(); +bool GetCurrentlyPlaying(std::vector<std::string>& vec); std::unordered_map<std::string, std::string> GetFileElements(const std::string& basename); -std::unordered_map<std::string, std::string> GetFileElements(const std::filesystem::path& path); +//std::unordered_map<std::string, std::string> GetFileElements(const std::filesystem::path& path); } // namespace Media } // namespace Track
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rc/player_data.qrc Sun Nov 12 04:53:19 2023 -0500 @@ -0,0 +1,5 @@ +<!DOCTYPE rcc><RCC version="1.0"> + <qresource> + <file alias="players.anisthesia">../dep/animia/data/players.anisthesia</file> + </qresource> +</RCC>
--- a/src/core/filesystem.cc Fri Nov 10 13:52:47 2023 -0500 +++ b/src/core/filesystem.cc Sun Nov 12 04:53:19 2023 -0500 @@ -43,15 +43,14 @@ #elif defined(MACOSX) return std::filesystem::path(osx::GetApplicationSupportDirectory()) / CONFIG_DIR; #else // just assume POSIX + std::filesystem::path path; const char* home = getenv("HOME"); - if (home != NULL) - path = home; # ifdef __linux__ - else - path = getpwuid(getuid())->pw_dir; + if (!home) + home = getpwuid(getuid())->pw_dir; # endif // __linux__ - if (!path.empty()) - return path / ".config"; + if (!home) + return std::filesystem::path(home) / ".config"; else return std::filesystem::path(); #endif // !WIN32 && !MACOSX
--- a/src/gui/window.cc Fri Nov 10 13:52:47 2023 -0500 +++ b/src/gui/window.cc Sun Nov 12 04:53:19 2023 -0500 @@ -69,15 +69,22 @@ connect(timer, &QTimer::timeout, this, [this] { NowPlayingPage* page = reinterpret_cast<NowPlayingPage*>(stack->widget(static_cast<int>(Pages::NOW_PLAYING))); - std::filesystem::path path = Track::Media::GetCurrentPlaying(); - std::unordered_map<std::string, std::string> elements = Track::Media::GetFileElements(path); - int id = Anime::db.GetAnimeFromTitle(elements["title"]); - if (id <= 0) { - page->SetDefault(); + std::vector<std::string> files; + if (!Track::Media::GetCurrentlyPlaying(files)) return; - } - page->SetPlaying(Anime::db.items[id], elements); + /* this should really be more intertwined with anitomy */ + for (const auto& file : files) { + std::filesystem::path path(file); // in the future it will not be guaranteed this is a path! + std::unordered_map<std::string, std::string> elements = Track::Media::GetFileElements(path.filename().string()); + int id = Anime::db.GetAnimeFromTitle(elements["title"]); + if (id <= 0) + continue; + + qDebug() << id; + + page->SetPlaying(Anime::db.items[id], elements); + } }); timer->start(5000); }
--- a/src/track/media.cc Fri Nov 10 13:52:47 2023 -0500 +++ b/src/track/media.cc Sun Nov 12 04:53:19 2023 -0500 @@ -9,39 +9,51 @@ #include <unordered_map> #include <vector> #include <iostream> +#include <QFile> namespace Track { namespace Media { -std::vector<std::filesystem::path> GetCurrentlyPlayingFiles() { - /* getting all open files */ - std::vector<std::filesystem::path> ret; +static bool GetCurrentlyPlayingResults(std::vector<animia::Result>& results) { + std::vector<animia::Player> players; + + { + QFile f(":/players.anisthesia"); + if (!f.exists()) + return false; + + f.open(QFile::ReadOnly | QFile::Text); + QTextStream ts(&f); + + if (!animia::ParsePlayersData(Strings::ToUtf8String(ts.readAll()), players)) + return false; + } + + if (!animia::GetResults(players, results)) + return false; - std::vector<int> pids = Animia::get_all_pids(); - for (int pid : pids) { - for (const Types::MediaPlayer& player : session.recognition.players) { - if (!player.GetEnabled() || Animia::get_process_name(pid) != player.GetExecutable()) - continue; + return true; +} + +/* meh */ +bool GetCurrentlyPlaying(std::vector<std::string>& vec) { + std::vector<animia::Result> results; - for (const std::string& file : Animia::filter_system_files(Animia::get_open_files(pid))) { - const std::filesystem::path path(file); + if (!GetCurrentlyPlayingResults(results)) + return false; + + bool success = false; - for (const Types::MediaExtension& ext : session.recognition.extensions) - if (path.extension() == ext.GetExtension()) - ret.push_back(path); + for (const auto& result : results) { + for (const auto& media : result.media) { + for (const auto& info : media.information) { + vec.push_back(info.value); + success |= true; } } } - return ret; -} - -std::filesystem::path GetCurrentPlaying() { - /* getting all open files */ - std::vector<std::filesystem::path> paths = GetCurrentlyPlayingFiles(); - if (paths.size()) - return paths.at(0); - return std::filesystem::path(); + return success; } std::unordered_map<std::string, std::string> GetMapFromElements(const anitomy::Elements& elements) {