From cd74075a2ed2f98919ce6c2d34b5cb6354f7a768 Mon Sep 17 00:00:00 2001 From: Mars Date: Sat, 26 Apr 2025 01:20:39 -0400 Subject: [PATCH] hopefully i dont regret doing allat --- meson.build | 21 ++- src/config/config.h | 15 +- src/core/system_data.cpp | 48 +++--- src/core/system_data.h | 25 ++- src/main.cpp | 65 ++++---- src/os/os.h | 61 +++---- src/os/windows.cpp | 335 ++++++++++++++++++--------------------- src/util/types.h | 106 +++++++++++-- subprojects/sqlite3.wrap | 2 - 9 files changed, 365 insertions(+), 313 deletions(-) delete mode 100644 subprojects/sqlite3.wrap diff --git a/meson.build b/meson.build index 1301d58..bfa9a03 100644 --- a/meson.build +++ b/meson.build @@ -48,9 +48,6 @@ common_cpp_flags = { '/external:W0', '/external:anglebrackets', '/std:c++latest', - '/w44668', - '/w44710', - '/w44820', ], 'unix_extra' : [ '-march=native', @@ -128,6 +125,13 @@ elif host_system == 'windows' cpp.find_library('windowsapp'), ] elif host_system == 'linux' + dbus_cxx_dep = dependency('dbus-cxx', include_type : 'system', required : false) + + if not dbus_cxx_dep.found() + cmake = import('cmake') + dbus_cxx_proj = cmake.subproject('dbus_cxx') + dbus_cxx_dep = dbus_cxx_proj.dependency('dbus_cxx', include_type : 'system') + endif platform_deps += [ dependency('SQLiteCpp'), dependency('xcb'), @@ -135,6 +139,7 @@ elif host_system == 'linux' dependency('xdmcp'), dependency('wayland-client'), dependency('sigc++-3.0', include_type : 'system'), + dbus_cxx_dep, ] endif @@ -158,14 +163,6 @@ if not ftxui_dep.found() ) endif -dbus_cxx_dep = dependency('dbus-cxx', include_type : 'system', required : false) - -if not dbus_cxx_dep.found() - cmake = import('cmake') - dbus_cxx_proj = cmake.subproject('dbus_cxx') - dbus_cxx_dep = dbus_cxx_proj.dependency('dbus_cxx', include_type : 'system') -endif - glaze_dep = dependency('glaze', include_type : 'system', required : false) if not glaze_dep.found() @@ -175,7 +172,7 @@ if not glaze_dep.found() endif # Combine all dependencies -deps = common_deps + platform_deps + ftxui_dep + dbus_cxx_dep + glaze_dep +deps = common_deps + platform_deps + ftxui_dep + glaze_dep # ------------------------- # # Link/ObjC Configuration # diff --git a/src/config/config.h b/src/config/config.h index cc7349e..97fed3c 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -86,11 +86,12 @@ struct NowPlaying { * @brief Holds configuration settings for the Weather feature. */ struct Weather { - bool enabled = false; ///< Flag to enable or disable the Weather feature. - bool show_town_name = false; ///< Flag to show the town name in the output. - Location location; ///< Location for weather data, can be a city name or coordinates. - String api_key; ///< API key for the weather service. - String units; ///< Units for temperature, either "metric" or "imperial". + Location location; ///< Location for weather data, can be a city name or coordinates. + String api_key; ///< API key for the weather service. + String units; ///< Units for temperature, either "metric" or "imperial". + + bool enabled = false; ///< Flag to enable or disable the Weather feature. + bool show_town_name = false; ///< Flag to show the town name in the output. /** * @brief Parses a TOML table to create a Weather instance. @@ -143,8 +144,8 @@ struct Weather { */ struct Config { General general; ///< General configuration settings. - NowPlaying now_playing; ///< Now Playing configuration settings. Weather weather; ///< Weather configuration settings.` + NowPlaying now_playing; ///< Now Playing configuration settings. /** * @brief Parses a TOML table to create a Config instance. @@ -158,8 +159,8 @@ struct Config { return { .general = genTbl.is_table() ? General::fromToml(*genTbl.as_table()) : General {}, - .now_playing = npTbl.is_table() ? NowPlaying::fromToml(*npTbl.as_table()) : NowPlaying {}, .weather = wthTbl.is_table() ? Weather::fromToml(*wthTbl.as_table()) : Weather {}, + .now_playing = npTbl.is_table() ? NowPlaying::fromToml(*npTbl.as_table()) : NowPlaying {}, }; } diff --git a/src/core/system_data.cpp b/src/core/system_data.cpp index ed7a920..f9a06f7 100644 --- a/src/core/system_data.cpp +++ b/src/core/system_data.cpp @@ -20,7 +20,7 @@ namespace { } } -fn SystemData::fetchSystemData(const Config& config) -> SystemData { +SystemData SystemData::fetchSystemData(const Config& config) { SystemData data { .date = GetDate(), .host = os::GetHost(), @@ -29,36 +29,42 @@ fn SystemData::fetchSystemData(const Config& config) -> SystemData { .mem_info = os::GetMemInfo(), .desktop_environment = os::GetDesktopEnvironment(), .window_manager = os::GetWindowManager(), - .now_playing = {}, - .weather_info = {}, - .disk_used = {}, - .disk_total = {}, - .shell = {}, + .disk_usage = {}, + .shell = None, + .now_playing = None, + .weather_info = None, }; - auto diskShell = std::async(std::launch::async, [] { - auto [used, total] = os::GetDiskUsage(); - return std::make_tuple(used, total, os::GetShell()); + auto diskShellFuture = std::async(std::launch::async, [] { + Result diskResult = os::GetDiskUsage(); + Option shellOption = os::GetShell(); + return std::make_tuple(std::move(diskResult), std::move(shellOption)); }); - std::future weather; - std::future> nowPlaying; + std::future weatherFuture; + std::future> nowPlayingFuture; if (config.weather.enabled) - weather = std::async(std::launch::async, [&config] { return config.weather.getWeatherInfo(); }); + weatherFuture = std::async(std::launch::async, [&config] { return config.weather.getWeatherInfo(); }); if (config.now_playing.enabled) - nowPlaying = std::async(std::launch::async, os::GetNowPlaying); + nowPlayingFuture = std::async(std::launch::async, os::GetNowPlaying); - auto [used, total, shell] = diskShell.get(); - data.disk_used = used; - data.disk_total = total; - data.shell = shell; + auto [diskResult, shellOption] = diskShellFuture.get(); - if (weather.valid()) - data.weather_info = weather.get(); - if (nowPlaying.valid()) - data.now_playing = nowPlaying.get(); + data.disk_usage = std::move(diskResult); + data.shell = std::move(shellOption); + + if (weatherFuture.valid()) + try { + data.weather_info = weatherFuture.get(); + } catch (const std::exception& e) { + ERROR_LOG("Failed to get weather info: {}", e.what()); + data.weather_info = None; + } + + if (nowPlayingFuture.valid()) + data.now_playing = nowPlayingFuture.get(); return data; } diff --git a/src/core/system_data.h b/src/core/system_data.h index 2e1a5da..a5415ca 100644 --- a/src/core/system_data.h +++ b/src/core/system_data.h @@ -56,21 +56,20 @@ struct std::formatter : std::formatter { * in order to display it at all at once during runtime. */ struct SystemData { - using NowPlayingResult = Option>; + using NowPlayingResult = Option>; // clang-format off - String date; ///< Current date (e.g., "April 24th"). - String host; ///< Host or product family name (e.g., "MacBook Pro"). - String kernel_version; ///< OS kernel version (e.g., "5.15.0-generic"). - Result os_version; ///< OS pretty name (e.g., "Ubuntu 22.04 LTS") or an error message. - Result mem_info; ///< Total physical RAM in bytes or an error message. - Option desktop_environment; ///< Detected desktop environment (e.g., "GNOME", "KDE", "Fluent (Windows 11)"). Might be None. - Option window_manager; ///< Detected window manager (e.g., "Mutter", "KWin", "DWM"). - NowPlayingResult now_playing; ///< Currently playing media ("Artist - Title") or an error/None if disabled/unavailable. - Option weather_info; ///< Weather information or None if disabled/unavailable. - u64 disk_used; ///< Used disk space in bytes for the root filesystem. - u64 disk_total; ///< Total disk space in bytes for the root filesystem. - String shell; ///< Name of the current user shell (e.g., "Bash", "Zsh", "PowerShell"). + String date; ///< Current date (e.g., "April 26th"). Always expected to succeed. + Result host; ///< Host/product family (e.g., "MacBookPro18,3") or OS error. + Result kernel_version; ///< OS kernel version (e.g., "23.4.0") or OS error. + Result os_version; ///< OS pretty name (e.g., "macOS Sonoma 14.4.1") or OS error. + Result mem_info; ///< Total physical RAM in bytes or OS error. + Option desktop_environment; ///< Detected desktop environment (e.g., "Aqua", "Plasma"). None if not detected/applicable. + Option window_manager; ///< Detected window manager (e.g., "Quartz Compositor", "KWin"). None if not detected/applicable. + Result disk_usage; ///< Used/Total disk space for root filesystem or OS error. + Option shell; ///< Name of the current user shell (e.g., "zsh"). None if not detected. + NowPlayingResult now_playing; ///< Optional: Result of fetching media info (MediaInfo on success, NowPlayingError on failure). None if disabled. + Option weather_info; ///< Optional: Weather information. None if disabled or error during fetch. // clang-format on /** diff --git a/src/main.cpp b/src/main.cpp index 34cde63..43b56cb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -94,9 +94,8 @@ namespace { } fn SystemInfoBox(const Config& config, const SystemData& data) -> Element { - const String& name = config.general.name; - const Weather weather = config.weather; - const bool nowPlayingEnabled = config.now_playing.enabled; + const String& name = config.general.name; + const Weather weather = config.weather; const auto& [userIcon, paletteIcon, calendarIcon, hostIcon, kernelIcon, osIcon, memoryIcon, weatherIcon, musicIcon, diskIcon, shellIcon, deIcon, wmIcon] = ui::SHOW_ICONS ? ui::NERD_ICONS : ui::EMPTY_ICONS; @@ -114,11 +113,11 @@ namespace { content.push_back(separator() | color(ui::DEFAULT_THEME.border)); // Helper function for aligned rows - fn createRow = [&](const auto& icon, const auto& label, const auto& value) { + fn createRow = [&](const StringView& icon, const StringView& label, const StringView& value) { // NEW return hbox( { text(String(icon)) | color(ui::DEFAULT_THEME.icon), - text(String(static_cast(label))) | color(ui::DEFAULT_THEME.label), + text(String(label)) | color(ui::DEFAULT_THEME.label), filler(), text(String(value)) | color(ui::DEFAULT_THEME.value), text(" "), @@ -171,28 +170,38 @@ namespace { content.push_back(separator() | color(ui::DEFAULT_THEME.border)); - if (!data.host.empty()) - content.push_back(createRow(hostIcon, "Host", data.host)); + if (data.host) + content.push_back(createRow(hostIcon, "Host", *data.host)); + else + ERROR_LOG("Failed to get host info: {}", data.host.error().message); - if (!data.kernel_version.empty()) - content.push_back(createRow(kernelIcon, "Kernel", data.kernel_version)); + if (data.kernel_version) + content.push_back(createRow(kernelIcon, "Kernel", *data.kernel_version)); + else + ERROR_LOG("Failed to get kernel version: {}", data.kernel_version.error().message); if (data.os_version) content.push_back(createRow(String(osIcon), "OS", *data.os_version)); else - ERROR_LOG("Failed to get OS version: {}", data.os_version.error()); + ERROR_LOG("Failed to get OS version: {}", data.os_version.error().message); if (data.mem_info) content.push_back(createRow(memoryIcon, "RAM", std::format("{}", BytesToGiB { *data.mem_info }))); else - ERROR_LOG("Failed to get memory info: {}", data.mem_info.error()); + ERROR_LOG("Failed to get memory info: {}", data.mem_info.error().message); // Add Disk usage row - content.push_back( - createRow(diskIcon, "Disk", std::format("{}/{}", BytesToGiB { data.disk_used }, BytesToGiB { data.disk_total })) - ); + if (data.disk_usage) + content.push_back(createRow( + diskIcon, + "Disk", + std::format("{}/{}", BytesToGiB { data.disk_usage->used_bytes }, BytesToGiB { data.disk_usage->total_bytes }) + )); + else + ERROR_LOG("Failed to get disk usage: {}", data.disk_usage.error().message); - content.push_back(createRow(shellIcon, "Shell", data.shell)); + if (data.shell) + content.push_back(createRow(shellIcon, "Shell", *data.shell)); content.push_back(separator() | color(ui::DEFAULT_THEME.border)); @@ -202,10 +211,13 @@ namespace { if (data.window_manager) content.push_back(createRow(wmIcon, "WM", *data.window_manager)); - // Now Playing row - if (nowPlayingEnabled && data.now_playing) { - if (const Result& nowPlayingResult = *data.now_playing; nowPlayingResult.has_value()) { - const String& npText = *nowPlayingResult; + if (config.now_playing.enabled && data.now_playing) { + if (const Result& nowPlayingResult = *data.now_playing) { + const MediaInfo& info = *nowPlayingResult; + + const String title = info.title.value_or("Unknown Title"); + const String artist = info.artist.value_or("Unknown Artist"); + const String npText = artist + " - " + title; content.push_back(separator() | color(ui::DEFAULT_THEME.border)); content.push_back(hbox( @@ -219,22 +231,13 @@ namespace { } )); } else { - const NowPlayingError& error = nowPlayingResult.error(); - - if (std::holds_alternative(error)) + if (const NowPlayingError& error = nowPlayingResult.error(); std::holds_alternative(error)) { switch (std::get(error)) { case NowPlayingCode::NoPlayers: DEBUG_LOG("No players found"); break; case NowPlayingCode::NoActivePlayer: DEBUG_LOG("No active player found"); break; - default: std::unreachable(); } - -#ifdef _WIN32 - if (std::holds_alternative(error)) - DEBUG_LOG("WinRT error: {}", to_string(std::get(error).message())); -#else - if (std::holds_alternative(error)) - DEBUG_LOG("NowPlaying error: {}", std::get(error)); -#endif + } else + ERROR_LOG("Failed to get now playing info: {}", std::get(error).message); } } diff --git a/src/os/os.h b/src/os/os.h index 242de24..e5717fc 100644 --- a/src/os/os.h +++ b/src/os/os.h @@ -16,27 +16,27 @@ namespace os { /** * @brief Get the total amount of physical RAM installed in the system. * @return A Result containing the total RAM in bytes (u64) on success, - * or an error message (String) on failure. + * or an OsError on failure. */ - fn GetMemInfo() -> Result; + fn GetMemInfo() -> Result; /** - * @brief Gets metadata about the currently playing media. - * @return A Result containing the media information (String) on success, - * or an error code (NowPlayingError) on failure. + * @brief Gets structured metadata about the currently playing media. + * @return A Result containing the media information (MediaInfo struct) on success, + * or a NowPlayingError (indicating player state or system error) on failure. */ - fn GetNowPlaying() -> Result; + fn GetNowPlaying() -> Result; /** * @brief Gets the "pretty" name of the operating system. * @details Examples: "Ubuntu 24.04.2 LTS", "Windows 11 Pro 24H2", "macOS 15 Sequoia". * @return A Result containing the OS version String on success, - * or an error message (String) on failure. + * or an OsError on failure. */ - fn GetOSVersion() -> Result; + fn GetOSVersion() -> Result; /** - * @brief Attempts to retrieve the desktop environment. + * @brief Attempts to retrieve the desktop environment name. * @details This is most relevant on Linux. May check environment variables (XDG_CURRENT_DESKTOP), * session files, or running processes. On Windows/macOS, it might return a * UI theme identifier (e.g., "Fluent", "Aqua") or None. @@ -45,53 +45,54 @@ namespace os { fn GetDesktopEnvironment() -> Option; /** - * @brief Attempts to retrieve the window manager. + * @brief Attempts to retrieve the window manager name. * @details On Linux, checks Wayland compositor or X11 WM properties. On Windows, returns "DWM" or similar. * On macOS, might return "Quartz Compositor" or a specific tiling WM name if active. - * @return A String containing the detected WM name, or None if detection fails or is not applicable. + * @return An Option containing the detected WM name String, or None if detection fails. */ fn GetWindowManager() -> Option; /** - * @brief Attempts to detect the current user shell. + * @brief Attempts to detect the current user shell name. * @details Checks the SHELL environment variable on Linux/macOS. On Windows, inspects the process tree * to identify known shells like PowerShell, Cmd, or MSYS2 shells (Bash, Zsh). - * @return A String containing the detected shell name (e.g., "Bash", "Zsh", "PowerShell", "Fish"). - * May return the full path or "Unknown" as a fallback. + * @return An Option containing the detected shell name (e.g., "Bash", "Zsh", "PowerShell", "Fish"), or None if + * detection fails. */ - fn GetShell() -> String; + fn GetShell() -> Option; /** * @brief Gets a system identifier, often the hardware model or product family. * @details Examples: "MacBookPro18,3", "Latitude 5420", "ThinkPad T490". * Implementation varies: reads DMI info on Linux, registry on Windows, sysctl on macOS. - * @return A String containing the host/product identifier. May be empty if retrieval fails. + * @return A Result containing the host/product identifier String on success, + * or an OsError on failure (e.g., permission reading DMI/registry, API error). */ - fn GetHost() -> String; + fn GetHost() -> Result; /** * @brief Gets the operating system's kernel version string. * @details Examples: "5.15.0-76-generic", "10.0.22621", "23.1.0". * Uses uname() on Linux/macOS, WinRT/registry on Windows. - * @return A String containing the kernel version. May be empty if retrieval fails. + * @return A Result containing the kernel version String on success, + * or an OsError on failure. */ - fn GetKernelVersion() -> String; + fn GetKernelVersion() -> Result; /** - * @brief Gets the number of installed packages (Linux-specific). - * @details Sums counts from various package managers (dpkg, rpm, pacman, flatpak, snap, etc.). - * Returns 0 on non-Linux platforms or if no package managers are found. - * @return A u64 representing the total count of detected packages. + * @brief Gets the number of installed packages (Platform-specific). + * @details On Linux, sums counts from various package managers. On other platforms, behavior may vary. + * @return A Result containing the package count (u64) on success, + * or an OsError on failure (e.g., permission errors, command not found) + * or if not supported (OsErrorCode::NotSupported). */ - fn GetPackageCount() -> u64; // Note: Implementation likely exists only in linux.cpp + fn GetPackageCount() -> Result; // Note: Returns OsError{OsErrorCode::NotSupported} on Win/Mac likely /** * @brief Gets the disk usage for the primary/root filesystem. * @details Uses statvfs on Linux/macOS, GetDiskFreeSpaceExW on Windows. - * @return A Pair where: - * - first: Used disk space in bytes. - * - second: Total disk space in bytes. - * Returns {0, 0} on failure. + * @return A Result containing the DiskSpace struct (used/total bytes) on success, + * or an OsError on failure (e.g., filesystem not found, permission error). */ - fn GetDiskUsage() -> Pair; -} + fn GetDiskUsage() -> Result; +} // namespace os diff --git a/src/os/windows.cpp b/src/os/windows.cpp index e20349a..f03fc89 100644 --- a/src/os/windows.cpp +++ b/src/os/windows.cpp @@ -5,12 +5,11 @@ #include #include #include -#include // clang-format on #include #include -#include +#include #include #include #include @@ -60,47 +59,6 @@ namespace { }}; // clang-format on - class ProcessSnapshot { - public: - ProcessSnapshot() : h_snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)) {} - - ProcessSnapshot(ProcessSnapshot&&) = delete; - ProcessSnapshot(const ProcessSnapshot&) = delete; - fn operator=(ProcessSnapshot&&)->ProcessSnapshot& = delete; - fn operator=(const ProcessSnapshot&)->ProcessSnapshot& = delete; - - ~ProcessSnapshot() { - if (h_snapshot != INVALID_HANDLE_VALUE) - CloseHandle(h_snapshot); - } - - [[nodiscard]] fn isValid() const -> bool { return h_snapshot != INVALID_HANDLE_VALUE; } - - [[nodiscard]] fn getProcesses() const -> Vec> { - Vec> processes; - - if (!isValid()) - return processes; - - PROCESSENTRY32 pe32; - pe32.dwSize = sizeof(PROCESSENTRY32); - - if (!Process32First(h_snapshot, &pe32)) - return processes; - - if (Process32First(h_snapshot, &pe32)) { - processes.emplace_back(pe32.th32ProcessID, String(reinterpret_cast(pe32.szExeFile))); - - while (Process32Next(h_snapshot, &pe32)) - processes.emplace_back(pe32.th32ProcessID, String(reinterpret_cast(pe32.szExeFile))); - } - - return processes; - } - - HANDLE h_snapshot; - }; - fn GetRegistryValue(const HKEY& hKey, const String& subKey, const String& valueName) -> String { HKEY key = nullptr; if (RegOpenKeyExA(hKey, subKey.c_str(), 0, KEY_READ, &key) != ERROR_SUCCESS) @@ -125,88 +83,74 @@ namespace { return value; } - fn GetProcessInfo() -> Vec> { - const ProcessSnapshot snapshot; - return snapshot.isValid() ? snapshot.getProcesses() : std::vector> {}; + fn GetProcessInfo() -> Result>, OsError> { + try { + using namespace winrt::Windows::System::Diagnostics; + using namespace winrt::Windows::Foundation::Collections; + + const IVectorView processInfos = ProcessDiagnosticInfo::GetForProcesses(); + + Vec> processes; + processes.reserve(processInfos.Size()); + + for (const auto& processInfo : processInfos) + processes.emplace_back(processInfo.ProcessId(), winrt::to_string(processInfo.ExecutableFileName())); + return processes; + } catch (const winrt::hresult_error& e) { return Err(OsError(e)); } catch (const std::exception& e) { + return Err(OsError(e)); + } } - fn IsProcessRunning(const Vec& processes, const String& name) -> bool { - return std::ranges::any_of(processes, [&name](const String& proc) -> bool { + fn IsProcessRunning(const Vec& processNames, const String& name) -> bool { + return std::ranges::any_of(processNames, [&name](const String& proc) -> bool { return _stricmp(proc.c_str(), name.c_str()) == 0; }); } - fn GetParentProcessId(const DWORD pid) -> DWORD { - const ProcessSnapshot snapshot; - if (!snapshot.isValid()) - return 0; - - PROCESSENTRY32 pe32 { .dwSize = sizeof(PROCESSENTRY32) }; - - if (!Process32First(snapshot.h_snapshot, &pe32)) - return 0; - - if (pe32.th32ProcessID == pid) - return pe32.th32ParentProcessID; - - while (Process32Next(snapshot.h_snapshot, &pe32)) - if (pe32.th32ProcessID == pid) - return pe32.th32ParentProcessID; - - return 0; - } - - fn GetProcessName(const DWORD pid) -> String { - const ProcessSnapshot snapshot; - if (!snapshot.isValid()) - return ""; - - PROCESSENTRY32 pe32; - pe32.dwSize = sizeof(PROCESSENTRY32); - - if (!Process32First(snapshot.h_snapshot, &pe32)) - return ""; - - if (pe32.th32ProcessID == pid) - return reinterpret_cast(pe32.szExeFile); - - while (Process32Next(snapshot.h_snapshot, &pe32)) - if (pe32.th32ProcessID == pid) - return reinterpret_cast(pe32.szExeFile); - - return ""; - } - template fn FindShellInProcessTree(const DWORD startPid, const Array, sz>& shellMap) - -> std::optional { - DWORD pid = startPid; - while (pid != 0) { - String processName = GetProcessName(pid); + -> Option { + if (startPid == 0) + return None; - if (processName.empty()) { - pid = GetParentProcessId(pid); - continue; + try { + using namespace winrt::Windows::System::Diagnostics; + + ProcessDiagnosticInfo currentProcessInfo = nullptr; + + try { + currentProcessInfo = ProcessDiagnosticInfo::TryGetForProcessId(startPid); + } catch (const winrt::hresult_error& e) { + RETURN_ERR("Failed to get process info for PID {}: {}", startPid, winrt::to_string(e.message())); } - std::ranges::transform(processName, processName.begin(), [](const u8 character) { - return static_cast(std::tolower(static_cast(character))); - }); + while (currentProcessInfo) { + String processName = winrt::to_string(currentProcessInfo.ExecutableFileName()); - if (processName.length() > 4 && processName.substr(processName.length() - 4) == ".exe") - processName.resize(processName.length() - 4); + if (!processName.empty()) { + std::ranges::transform(processName, processName.begin(), [](const u8 character) { + return static_cast(std::tolower(static_cast(character))); + }); - auto iter = std::ranges::find_if(shellMap, [&](const auto& pair) { - return std::string_view { processName } == pair.first; - }); + if (processName.length() > 4 && processName.ends_with(".exe")) + processName.resize(processName.length() - 4); - if (iter != std::ranges::end(shellMap)) - return String { iter->second }; + auto iter = + std::ranges::find_if(shellMap, [&](const auto& pair) { return StringView { processName } == pair.first; }); - pid = GetParentProcessId(pid); + if (iter != std::ranges::end(shellMap)) + return String { iter->second }; + } + + currentProcessInfo = currentProcessInfo.Parent(); + } + } catch (const winrt::hresult_error& e) { + ERROR_LOG("WinRT error during process tree walk (start PID {}): {}", startPid, winrt::to_string(e.message())); + } catch (const std::exception& e) { + ERROR_LOG("Standard exception during process tree walk (start PID {}): {}", startPid, e.what()); } - return std::nullopt; + return None; } fn GetBuildNumber() -> Option { @@ -227,36 +171,40 @@ namespace { } } -fn os::GetMemInfo() -> Result { +fn os::GetMemInfo() -> Result { try { return winrt::Windows::System::Diagnostics::SystemDiagnosticInfo::GetForCurrentSystem() .MemoryUsage() .GetReport() .TotalPhysicalSizeInBytes(); - } catch (const winrt::hresult_error& e) { - return Err(std::format("Failed to get memory info: {}", to_string(e.message()))); - } + } catch (const winrt::hresult_error& e) { return Err(OsError(e)); } } -fn os::GetNowPlaying() -> Result { +fn os::GetNowPlaying() -> Result { using namespace winrt::Windows::Media::Control; using namespace winrt::Windows::Foundation; - using Session = GlobalSystemMediaTransportControlsSession; - using SessionManager = GlobalSystemMediaTransportControlsSessionManager; + using Session = GlobalSystemMediaTransportControlsSession; + using SessionManager = GlobalSystemMediaTransportControlsSessionManager; + using MediaProperties = GlobalSystemMediaTransportControlsSessionMediaProperties; try { const IAsyncOperation sessionManagerOp = SessionManager::RequestAsync(); const SessionManager sessionManager = sessionManagerOp.get(); - if (const Session currentSession = sessionManager.GetCurrentSession()) - return winrt::to_string(currentSession.TryGetMediaPropertiesAsync().get().Title()); + if (const Session currentSession = sessionManager.GetCurrentSession()) { + const MediaProperties mediaProperties = currentSession.TryGetMediaPropertiesAsync().get(); + + return MediaInfo( + winrt::to_string(mediaProperties.Title()), winrt::to_string(mediaProperties.Artist()), None, None + ); + } return Err(NowPlayingCode::NoActivePlayer); - } catch (const winrt::hresult_error& e) { return Err(e); } + } catch (const winrt::hresult_error& e) { return Err(OsError(e)); } } -fn os::GetOSVersion() -> Result { +fn os::GetOSVersion() -> Result { try { const String regSubKey = R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)"; @@ -264,26 +212,33 @@ fn os::GetOSVersion() -> Result { const String displayVersion = GetRegistryValue(HKEY_LOCAL_MACHINE, regSubKey, "DisplayVersion"); if (productName.empty()) - return Err("Failed to read ProductName"); + return Err(OsError { OsErrorCode::NotFound, "ProductName not found in registry" }); - if (const Option buildNumber = GetBuildNumber()) { - if (*buildNumber >= 22000) - if (const usize pos = productName.find("Windows 10"); - pos != String::npos && (pos == 0 || !isalnum(static_cast(productName[pos - 1]))) && - (pos + 10 == productName.length() || !isalnum(static_cast(productName[pos + 10])))) - productName.replace(pos, 10, "Windows 11"); - } else - DEBUG_LOG("Warning: Could not get build number via WinRT; Win11 patch relies on registry ProductName only."); + if (const Option buildNumberOpt = GetBuildNumber()) { + if (const u64 buildNumber = *buildNumberOpt; buildNumber >= 22000) { + if (const size_t pos = productName.find("Windows 10"); pos != String::npos) { + const bool startBoundary = (pos == 0 || !isalnum(static_cast(productName[pos - 1]))); + const bool endBoundary = + (pos + 10 == productName.length() || !isalnum(static_cast(productName[pos + 10]))); + + if (startBoundary && endBoundary) { + productName.replace(pos, 10, "Windows 11"); + } + } + } + } else { + DEBUG_LOG("Warning: Could not get build number via WinRT; Win11 detection might be inaccurate."); + } return displayVersion.empty() ? productName : productName + " " + displayVersion; - } catch (const Exception& e) { return Err(std::format("Exception occurred getting OS version: {}", e.what())); } + } catch (const std::exception& e) { return Err(OsError(e)); } } -fn os::GetHost() -> String { +fn os::GetHost() -> Result { return GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SYSTEM\HardwareConfig\Current)", "SystemFamily"); } -fn os::GetKernelVersion() -> String { +fn os::GetKernelVersion() -> Result { try { using namespace winrt::Windows::System::Profile; @@ -292,36 +247,47 @@ fn os::GetKernelVersion() -> String { if (const winrt::hstring familyVersion = versionInfo.DeviceFamilyVersion(); !familyVersion.empty()) if (auto [major, minor, build, revision] = OSVersion::parseDeviceFamilyVersion(familyVersion); build > 0) return std::format("{}.{}.{}.{}", major, minor, build, revision); - } catch (const winrt::hresult_error& e) { - ERROR_LOG("WinRT error: {}", winrt::to_string(e.message())); - } catch (const Exception& e) { ERROR_LOG("Failed to get kernel version: {}", e.what()); } + } catch (const winrt::hresult_error& e) { return Err(OsError(e)); } catch (const Exception& e) { + return Err(OsError(e)); + } - return ""; + return Err(OsError { OsErrorCode::NotFound, "Could not determine kernel version" }); } -fn os::GetWindowManager() -> String { - const auto processInfo = GetProcessInfo(); - std::vector processNames; +fn os::GetWindowManager() -> Option { + if (const Result>, OsError> processInfoResult = GetProcessInfo()) { + const Vec>& processInfo = *processInfoResult; - processNames.reserve(processInfo.size()); - for (const auto& name : processInfo | std::views::values) processNames.push_back(name); + Vec processNames; + processNames.reserve(processInfo.size()); - const std::unordered_map wmProcesses = { - { "glazewm.exe", "GlazeWM" }, - { "fancywm.exe", "FancyWM" }, - { "komorebi.exe", "Komorebi" }, - { "komorebic.exe", "Komorebi" } - }; + for (const String& val : processInfo | std::views::values) { + if (!val.empty()) { + const usize lastSlash = val.find_last_of("/\\"); + processNames.push_back(lastSlash == String::npos ? val : val.substr(lastSlash + 1)); + } + } - for (const auto& [processName, wmName] : wmProcesses) - if (IsProcessRunning(processNames, processName)) - return wmName; + const std::unordered_map wmProcesses = { + { "glazewm.exe", "GlazeWM" }, + { "fancywm.exe", "FancyWM" }, + { "komorebi.exe", "Komorebi" }, + { "komorebic.exe", "Komorebi" }, + }; + + for (const auto& [processExe, wmName] : wmProcesses) + if (IsProcessRunning(processNames, processExe)) + return wmName; + } else { + ERROR_LOG("Failed to get process info for WM detection: {}", processInfoResult.error().message); + } BOOL compositionEnabled = FALSE; + if (SUCCEEDED(DwmIsCompositionEnabled(&compositionEnabled))) return compositionEnabled ? "DWM" : "Windows Manager (Basic)"; - return "Windows Manager"; + return None; } fn os::GetDesktopEnvironment() -> Option { @@ -371,51 +337,60 @@ fn os::GetDesktopEnvironment() -> Option { } } -fn os::GetShell() -> String { - const DWORD currentPid = GetCurrentProcessId(); +fn os::GetShell() -> Option { + try { + const DWORD currentPid = + winrt::Windows::System::Diagnostics::ProcessDiagnosticInfo::GetForCurrentProcess().ProcessId(); - if (const Result msystemResult = GetEnv("MSYSTEM")) { - String shellPath; - if (const Result shellResult = GetEnv("SHELL"); !shellResult->empty()) - shellPath = *shellResult; - else if (const Result loginShellResult = GetEnv("LOGINSHELL"); !loginShellResult->empty()) - shellPath = *loginShellResult; + if (const Result msystemResult = GetEnv("MSYSTEM"); msystemResult && !msystemResult->empty()) { + String shellPath; - if (!shellPath.empty()) { - const usize lastSlash = shellPath.find_last_of("\\/"); - String shellExe = (lastSlash != String::npos) ? shellPath.substr(lastSlash + 1) : shellPath; + if (const Result shellResult = GetEnv("SHELL"); shellResult && !shellResult->empty()) + shellPath = *shellResult; + else if (const Result loginShellResult = GetEnv("LOGINSHELL"); + loginShellResult && !loginShellResult->empty()) + shellPath = *loginShellResult; - std::ranges::transform(shellExe, shellExe.begin(), [](const u8 c) { return std::tolower(c); }); + if (!shellPath.empty()) { + const usize lastSlash = shellPath.find_last_of("\\/"); + String shellExe = (lastSlash != String::npos) ? shellPath.substr(lastSlash + 1) : shellPath; - if (shellExe.ends_with(".exe")) - shellExe.resize(shellExe.length() - 4); + std::ranges::transform(shellExe, shellExe.begin(), [](const u8 c) { + return static_cast(std::tolower(static_cast(c))); + }); - const auto iter = - std::ranges::find_if(msysShellMap, [&](const auto& pair) { return StringView { shellExe } == pair.first; }); + if (shellExe.ends_with(".exe")) + shellExe.resize(shellExe.length() - 4); - if (iter != std::ranges::end(msysShellMap)) - return String { iter->second }; + const auto iter = + std::ranges::find_if(msysShellMap, [&](const auto& pair) { return StringView { shellExe } == pair.first; }); + + if (iter != std::ranges::end(msysShellMap)) + return String { iter->second }; + } + + if (const Option msysShell = FindShellInProcessTree(currentPid, msysShellMap)) + return *msysShell; + + return "MSYS2 Environment"; } - if (const Option msysShell = FindShellInProcessTree(currentPid, msysShellMap)) - return *msysShell; + if (const Option windowsShell = FindShellInProcessTree(currentPid, windowsShellMap)) + return *windowsShell; + } catch (const winrt::hresult_error& e) { + ERROR_LOG("WinRT error during shell detection: {}", winrt::to_string(e.message())); + } catch (const std::exception& e) { ERROR_LOG("Standard exception during shell detection: {}", e.what()); } - return "MSYS2 Environment"; - } - - if (const Option windowsShell = FindShellInProcessTree(currentPid, windowsShellMap)) - return *windowsShell; - - return "Unknown Shell"; + return None; } -fn os::GetDiskUsage() -> Pair { +fn os::GetDiskUsage() -> Result { ULARGE_INTEGER freeBytes, totalBytes; if (GetDiskFreeSpaceExW(L"C:\\", nullptr, &totalBytes, &freeBytes)) - return { totalBytes.QuadPart - freeBytes.QuadPart, totalBytes.QuadPart }; + return DiskSpace { .used_bytes = totalBytes.QuadPart - freeBytes.QuadPart, .total_bytes = totalBytes.QuadPart }; - return { 0, 0 }; + return Err(OsError { OsErrorCode::NotFound, "Failed to get disk usage" }); } #endif diff --git a/src/util/types.h b/src/util/types.h index d9fbd36..277c408 100644 --- a/src/util/types.h +++ b/src/util/types.h @@ -1,7 +1,6 @@ #pragma once #include // std::array alias (Array) -#include // std::float_t, std::double_t (f32/f64) #include // std::getenv, std::free #include // std::expected alias (Result) #include // std::map alias (Map) @@ -36,8 +35,8 @@ using i64 = std::int64_t; ///< 64-bit signed integer. // Provides concise names for standard floating-point types. // //-----------------------------------------------------------// -using f32 = std::float_t; ///< 32-bit floating-point number. -using f64 = std::double_t; ///< 64-bit floating-point number. +using f32 = float; ///< 32-bit floating-point number. +using f64 = double; ///< 64-bit floating-point number. //-------------------------------------------------// // Size Type Aliases // @@ -155,25 +154,98 @@ enum class NowPlayingCode : u8 { NoActivePlayer, ///< Players were found, but none are currently active or playing. }; -#ifdef _WIN32 -using WindowsError = winrt::hresult_error; ///< Alias for WinRT HRESULT error type. -#endif +/** + * @enum OsErrorCode + * @brief Error codes for general OS-level operations. + */ +enum class OsErrorCode : u8 { + ApiUnavailable, ///< An underlying OS API failed, is unavailable, or returned an error. + BufferTooSmall, ///< A pre-allocated buffer was insufficient (less common with dynamic allocation). + InternalError, ///< An unspecified internal error within the abstraction layer. + IoError, ///< A general input/output error occurred. + NetworkError, ///< Network-related error (relevant if OS functions involve network). + NotFound, ///< A required resource (file, registry key, device) was not found. + NotSupported, ///< The requested operation is not supported on this platform or configuration. + ParseError, ///< Failed to parse data obtained from the OS (e.g., file content, API output). + PermissionDenied, ///< Insufficient permissions to perform the operation. + PlatformSpecific, ///< An error specific to the platform occurred (check message for details). + Success, ///< Operation completed successfully (often implicit). + Timeout, ///< An operation timed out (e.g., waiting for DBus reply). + Other, ///< A generic error code for unclassified errors. +}; /** - * @typedef NowPlayingError - * @brief Represents the possible errors returned by "Now Playing" functions. - * It's a variant that can hold either a generic NowPlayingCode, - * a platform-specific error (WindowsError on Windows, String on others), - * or potentially other error types if extended. + * @struct OsError + * @brief Holds structured information about an OS-level error. + * + * Used as the error type in Result for many os:: functions. */ -using NowPlayingError = std::variant< - NowPlayingCode, +struct OsError { + String message = "Unknown Error"; ///< A descriptive error message, potentially including platform details. + OsErrorCode code = OsErrorCode::Other; ///< The general category of the error. + + OsError(const OsErrorCode errc, String msg) : message(std::move(msg)), code(errc) {} + + explicit OsError(const Exception& e) : message(e.what()) {} + #ifdef _WIN32 - WindowsError -#else - String + explicit OsError(const winrt::hresult_error& e) + : message(winrt::to_string(e.message())), code(OsErrorCode::PlatformSpecific) {} #endif - >; + +#ifndef _WIN32 + OsError(OsErrorCode c, int errno_val) : code(c), message(std::system_category().message(errno_val)) {} +#endif +}; + +/** + * @struct DiskSpace + * @brief Represents disk usage information. + * + * Used as the success type for os::GetDiskUsage. + */ +struct DiskSpace { + u64 used_bytes; ///< Currently used disk space in bytes. + u64 total_bytes; ///< Total disk space in bytes. +}; + +/** + * @struct MediaInfo + * @brief Holds structured metadata about currently playing media. + * + * Used as the success type for os::GetNowPlaying. + * Using Option<> for fields that might not always be available. + */ +struct MediaInfo { + /** + * @enum PlaybackStatus + * @brief Represents the playback status of the media player. + */ + enum class PlaybackStatus : u8 { Playing, Paused, Stopped, Unknown }; + + Option title; ///< Track title. + Option artist; ///< Track artist(s). + Option album; ///< Album name. + Option app_name; ///< Name of the media player application (e.g., "Spotify", "Firefox"). + PlaybackStatus status = PlaybackStatus::Unknown; ///< Current playback status. + + MediaInfo(Option t, Option a, Option al, Option app) + : title(std::move(t)), artist(std::move(a)), album(std::move(al)), app_name(std::move(app)) {} +}; + +//--------------------------------------------------------// +// Potentially Update Existing Application-Specific Types // +//--------------------------------------------------------// + +/** + * @typedef NowPlayingError (Updated Recommendation) + * @brief Represents the possible errors returned by os::GetNowPlaying. + * + * It's a variant that can hold either a specific NowPlayingCode + * (indicating player state like 'no active player') or a general OsError + * (indicating an underlying system/API failure). + */ +using NowPlayingError = std::variant; /** * @enum EnvError diff --git a/subprojects/sqlite3.wrap b/subprojects/sqlite3.wrap deleted file mode 100644 index 8fe9601..0000000 --- a/subprojects/sqlite3.wrap +++ /dev/null @@ -1,2 +0,0 @@ -[wrap-redirect] -filename = SQLiteCpp-3.3.2/subprojects/sqlite3.wrap