From cf51e3e569b1a4e715bfb1f295d1d88434ee4d2f Mon Sep 17 00:00:00 2001 From: pupbrained Date: Wed, 23 Apr 2025 02:17:26 -0400 Subject: [PATCH] i think i got everything --- _sources/generated.nix | 3 +- flake.nix | 34 +- meson.build | 5 +- src/config/config.cpp | 36 +- src/config/config.h | 78 ++-- src/config/weather.cpp | 49 ++- src/config/weather.h | 26 +- src/main.cpp | 61 ++-- src/os/freebsd.cpp | 111 ------ src/os/linux.cpp | 623 ++++++++++++-------------------- src/os/linux/display_guards.cpp | 69 ++++ src/os/linux/display_guards.h | 69 ++++ src/os/linux/pkg_count.cpp | 8 - src/os/linux/pkg_count.h | 24 +- src/os/macos.cpp | 12 +- src/os/os.h | 14 +- src/os/windows.cpp | 118 +++--- src/util/macros.h | 7 +- src/util/types.h | 129 +++++-- 19 files changed, 671 insertions(+), 805 deletions(-) delete mode 100644 src/os/freebsd.cpp create mode 100644 src/os/linux/display_guards.cpp create mode 100644 src/os/linux/display_guards.h diff --git a/_sources/generated.nix b/_sources/generated.nix index 6cb27da..38eba1c 100644 --- a/_sources/generated.nix +++ b/_sources/generated.nix @@ -1,6 +1,7 @@ # This file was generated by nvfetcher, please do not modify it manually. -{ fetchgit, fetchurl, fetchFromGitHub, dockerTools }: { + fetchFromGitHub, +}: { dbus-cxx = { pname = "dbus-cxx"; version = "2.5.2"; diff --git a/flake.nix b/flake.nix index 1e91eaf..2aea20a 100644 --- a/flake.nix +++ b/flake.nix @@ -34,36 +34,13 @@ dbus-cxx = stdenv.mkDerivation { inherit (sources.dbus-cxx) pname version src; - nativeBuildInputs = [ - pkgs.cmake - pkgs.pkg-config - ]; + nativeBuildInputs = with pkgs; [cmake pkg-config]; - buildInputs = [ - pkgs.libsigcxx30 - ]; + buildInputs = with pkgs.pkgsStatic; [libsigcxx30]; - preConfigure = '' - set -x # Print commands being run - echo "--- Checking pkg-config paths ---" - echo "PKG_CONFIG_PATH: $PKG_CONFIG_PATH" - echo "--- Searching for sigc++ pc files ---" - find $PKG_CONFIG_PATH -name '*.pc' | grep sigc || echo "No sigc pc file found in PKG_CONFIG_PATH" - echo "--- Running pkg-config check ---" - pkg-config --exists --print-errors 'sigc++-3.0' - if [ $? -ne 0 ]; then - echo "ERROR: pkg-config check for sigc++-3.0 failed!" - # Optionally list all available packages known to pkg-config: - # pkg-config --list-all - fi - echo "--- End Debug ---" - set +x + prePatch = '' + substituteInPlace CMakeLists.txt --replace "add_library( dbus-cxx SHARED" "add_library( dbus-cxx STATIC" ''; - - cmakeFlags = [ - "-DENABLE_QT_SUPPORT=OFF" - "-DENABLE_UV_SUPPORT=OFF" - ]; }; deps = with pkgs; @@ -84,12 +61,11 @@ linuxPkgs = nixpkgs.lib.optionals stdenv.isLinux (with pkgs; [ - libsigcxx30 valgrind ] ++ (with pkgsStatic; [ - dbus dbus-cxx + libsigcxx30 sqlitecpp xorg.libX11 wayland diff --git a/meson.build b/meson.build index 1472a1f..89eefed 100644 --- a/meson.build +++ b/meson.build @@ -87,8 +87,7 @@ add_project_arguments(common_cpp_args, language: 'cpp') base_sources = files('src/config/config.cpp', 'src/config/weather.cpp', 'src/main.cpp') platform_sources = { - 'linux': ['src/os/linux.cpp', 'src/os/linux/issetugid_stub.cpp'], - 'freebsd': ['src/os/freebsd.cpp'], + 'linux': ['src/os/linux.cpp', 'src/os/linux/issetugid_stub.cpp', 'src/os/linux/display_guards.cpp'], 'darwin': ['src/os/macos.cpp', 'src/os/macos/bridge.mm'], 'windows': ['src/os/windows.cpp'], } @@ -121,7 +120,7 @@ elif host_system == 'windows' cpp.find_library('dwmapi'), cpp.find_library('windowsapp'), ] -elif host_system == 'linux' or host_system == 'freebsd' +elif host_system == 'linux' platform_deps += [ dependency('SQLiteCpp'), dependency('x11'), diff --git a/src/config/config.cpp b/src/config/config.cpp index bc0b304..9af3ac7 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -11,26 +11,26 @@ namespace fs = std::filesystem; namespace { fn GetConfigPath() -> fs::path { - std::vector possiblePaths; + Vec possiblePaths; #ifdef _WIN32 - if (auto result = GetEnv("LOCALAPPDATA"); result) + if (auto result = GetEnv("LOCALAPPDATA")) possiblePaths.push_back(fs::path(*result) / "draconis++" / "config.toml"); - if (auto result = GetEnv("USERPROFILE"); result) { + if (auto result = GetEnv("USERPROFILE")) { possiblePaths.push_back(fs::path(*result) / ".config" / "draconis++" / "config.toml"); possiblePaths.push_back(fs::path(*result) / "AppData" / "Local" / "draconis++" / "config.toml"); } - if (auto result = GetEnv("APPDATA"); result) + if (auto result = GetEnv("APPDATA")) possiblePaths.push_back(fs::path(*result) / "draconis++" / "config.toml"); possiblePaths.push_back(fs::path(".") / "config.toml"); #else - if (auto result = GetEnv("XDG_CONFIG_HOME"); result) + if (Result result = GetEnv("XDG_CONFIG_HOME")) possiblePaths.emplace_back(fs::path(*result) / "draconis++" / "config.toml"); - if (auto result = GetEnv("HOME"); result) { + if (Result result = GetEnv("HOME")) { possiblePaths.emplace_back(fs::path(*result) / ".config" / "draconis++" / "config.toml"); possiblePaths.emplace_back(fs::path(*result) / ".draconis++" / "config.toml"); } @@ -38,7 +38,7 @@ namespace { possiblePaths.emplace_back("/etc/draconis++/config.toml"); #endif - for (const auto& path : possiblePaths) + for (const fs::path& path : possiblePaths) if (std::error_code errc; exists(path, errc) && !errc) return path; @@ -68,18 +68,20 @@ namespace { toml::table root; - String defaultName; #ifdef _WIN32 - std::array username; - DWORD size = sizeof(username); - defaultName = GetUserNameA(username.data(), &size) ? username.data() : "User"; + Array username; + + DWORD size = sizeof(username); + + String defaultName = GetUserNameA(username.data(), &size) ? username.data() : "User"; #else - if (struct passwd* pwd = getpwuid(getuid()); pwd) - defaultName = pwd->pw_name; - else if (const char* envUser = getenv("USER")) - defaultName = envUser; - else - defaultName = "User"; + const struct passwd* pwd = getpwuid(getuid()); + CStr pwdName = pwd ? pwd->pw_name : nullptr; + + const Result envUser = GetEnv("USER"); + const Result envLogname = GetEnv("LOGNAME"); + + String defaultName = (pwdName) ? pwdName : (envUser) ? *envUser : (envLogname) ? *envLogname : "User"; #endif toml::table* general = root.insert("general", toml::table {}).first->second.as_table(); diff --git a/src/config/config.h b/src/config/config.h index 552abe9..af9f253 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -17,15 +17,15 @@ using Location = std::variant; struct General { String name = []() -> String { #ifdef _WIN32 - std::array username; - DWORD size = sizeof(username); + Array username; + DWORD size = sizeof(username); return GetUserNameA(username.data(), &size) ? username.data() : "User"; #else - if (struct passwd* pwd = getpwuid(getuid()); pwd) + if (struct passwd* pwd = getpwuid(getuid())) return pwd->pw_name; - if (const char* envUser = getenv("USER")) - return envUser; + if (Result envUser = GetEnv("USER")) + return *envUser; return "User"; #endif @@ -33,22 +33,16 @@ struct General { static fn fromToml(const toml::table& tbl) -> General { General gen; - - if (const std::optional name = tbl["name"].value()) - gen.name = *name; - - return gen; + return { + .name = tbl["name"].value_or(gen.name), + }; } }; struct NowPlaying { bool enabled = false; - static fn fromToml(const toml::table& tbl) -> NowPlaying { - NowPlaying nowPlaying; - nowPlaying.enabled = tbl["enabled"].value().value_or(false); - return nowPlaying; - } + static fn fromToml(const toml::table& tbl) -> NowPlaying { return { .enabled = tbl["enabled"].value_or(false) }; } }; struct Weather { @@ -60,37 +54,28 @@ struct Weather { static fn fromToml(const toml::table& tbl) -> Weather { Weather weather; - weather.enabled = tbl["enabled"].value_or(false); - if (auto apiKey = tbl["api_key"].value()) { - const String& keyVal = apiKey.value(); + Option apiKey = tbl["api_key"].value(); - if (keyVal.empty()) - weather.enabled = false; - - weather.api_key = keyVal; - } else { - weather.enabled = false; - } + weather.enabled = tbl["enabled"].value_or(false) && apiKey; if (!weather.enabled) return weather; - weather.show_town_name = tbl["show_town_name"].value_or(false); - weather.units = tbl["units"].value().value_or("metric"); + weather.api_key = *apiKey; + weather.show_town_name = tbl["show_town_name"].value_or(false); + weather.units = tbl["units"].value_or("metric"); if (const toml::node_view location = tbl["location"]) { - if (location.is_string()) { - weather.location = location.value().value(); - } else if (location.is_table()) { - const auto* coord = location.as_table(); - Coords coords; - coords.lat = coord->get("lat")->value().value(); - coords.lon = coord->get("lon")->value().value(); - weather.location = coords; - } else { + if (location.is_string()) + weather.location = *location.value(); + else if (location.is_table()) + weather.location = Coords { + .lat = *location.as_table()->get("lat")->value(), + .lon = *location.as_table()->get("lon")->value(), + }; + else throw std::runtime_error("Invalid location type"); - } } return weather; @@ -105,18 +90,15 @@ struct Config { Weather weather; static fn fromToml(const toml::table& tbl) -> Config { - Config cfg; + const toml::node_view genTbl = tbl["general"]; + const toml::node_view npTbl = tbl["now_playing"]; + const toml::node_view wthTbl = tbl["weather"]; - if (const auto* general = tbl["general"].as_table()) - cfg.general = General::fromToml(*general); - - if (const auto* nowPlaying = tbl["now_playing"].as_table()) - cfg.now_playing = NowPlaying::fromToml(*nowPlaying); - - if (const auto* weather = tbl["weather"].as_table()) - cfg.weather = Weather::fromToml(*weather); - - return cfg; + 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 {}, + }; } static fn getInstance() -> Config; diff --git a/src/config/weather.cpp b/src/config/weather.cpp index d9d396d..a56d3b8 100644 --- a/src/config/weather.cpp +++ b/src/config/weather.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include @@ -15,27 +14,27 @@ using namespace std::string_literals; namespace { constexpr glz::opts glaze_opts = { .error_on_unknown_keys = false }; - fn GetCachePath() -> std::expected { + fn GetCachePath() -> Result { std::error_code errc; fs::path cachePath = fs::temp_directory_path(errc); if (errc) - return std::unexpected("Failed to get temp directory: " + errc.message()); + return Err("Failed to get temp directory: " + errc.message()); cachePath /= "weather_cache.json"; return cachePath; } - fn ReadCacheFromFile() -> std::expected { - std::expected cachePath = GetCachePath(); + fn ReadCacheFromFile() -> Result { + Result cachePath = GetCachePath(); if (!cachePath) - return std::unexpected(cachePath.error()); + return Err(cachePath.error()); std::ifstream ifs(*cachePath, std::ios::binary); if (!ifs.is_open()) - return std::unexpected("Cache file not found: " + cachePath->string()); + return Err("Cache file not found: " + cachePath->string()); DEBUG_LOG("Reading from cache file..."); @@ -44,18 +43,18 @@ namespace { WeatherOutput result; if (const glz::error_ctx errc = glz::read(result, content); errc.ec != glz::error_code::none) - return std::unexpected("JSON parse error: " + glz::format_error(errc, content)); + return Err("JSON parse error: " + glz::format_error(errc, content)); DEBUG_LOG("Successfully read from cache file."); return result; - } catch (const std::exception& e) { return std::unexpected("Error reading cache: "s + e.what()); } + } catch (const std::exception& e) { return Err("Error reading cache: "s + e.what()); } } - fn WriteCacheToFile(const WeatherOutput& data) -> std::expected { - std::expected cachePath = GetCachePath(); + fn WriteCacheToFile(const WeatherOutput& data) -> Result { + Result cachePath = GetCachePath(); if (!cachePath) - return std::unexpected(cachePath.error()); + return Err(cachePath.error()); DEBUG_LOG("Writing to cache file..."); fs::path tempPath = *cachePath; @@ -65,28 +64,28 @@ namespace { { std::ofstream ofs(tempPath, std::ios::binary | std::ios::trunc); if (!ofs.is_open()) - return std::unexpected("Failed to open temp file: " + tempPath.string()); + return Err("Failed to open temp file: " + tempPath.string()); String jsonStr; if (const glz::error_ctx errc = glz::write_json(data, jsonStr); errc.ec != glz::error_code::none) - return std::unexpected("JSON serialization error: " + glz::format_error(errc, jsonStr)); + return Err("JSON serialization error: " + glz::format_error(errc, jsonStr)); ofs << jsonStr; if (!ofs) - return std::unexpected("Failed to write to temp file"); + return Err("Failed to write to temp file"); } std::error_code errc; fs::rename(tempPath, *cachePath, errc); if (errc) { fs::remove(tempPath, errc); - return std::unexpected("Failed to replace cache file: " + errc.message()); + return Err("Failed to replace cache file: " + errc.message()); } DEBUG_LOG("Successfully wrote to cache file."); return {}; - } catch (const std::exception& e) { return std::unexpected("File operation error: "s + e.what()); } + } catch (const std::exception& e) { return Err("File operation error: "s + e.what()); } } fn WriteCallback(void* contents, const size_t size, const size_t nmemb, String* str) -> size_t { @@ -95,13 +94,13 @@ namespace { return totalSize; } - fn MakeApiRequest(const String& url) -> std::expected { + fn MakeApiRequest(const String& url) -> Result { DEBUG_LOG("Making API request to URL: {}", url); CURL* curl = curl_easy_init(); String responseBuffer; if (!curl) - return std::unexpected("Failed to initialize cURL"); + return Err("Failed to initialize cURL"); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); @@ -113,12 +112,12 @@ namespace { curl_easy_cleanup(curl); if (res != CURLE_OK) - return std::unexpected(std::format("cURL error: {}", curl_easy_strerror(res))); + return Err(std::format("cURL error: {}", curl_easy_strerror(res))); WeatherOutput output; if (const glz::error_ctx errc = glz::read(output, responseBuffer); errc.ec != glz::error_code::none) - return std::unexpected("API response parse error: " + glz::format_error(errc, responseBuffer)); + return Err("API response parse error: " + glz::format_error(errc, responseBuffer)); return std::move(output); } @@ -127,7 +126,7 @@ namespace { fn Weather::getWeatherInfo() const -> WeatherOutput { using namespace std::chrono; - if (std::expected data = ReadCacheFromFile()) { + if (Result data = ReadCacheFromFile()) { const WeatherOutput& dataVal = *data; if (const duration cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.dt)); @@ -141,13 +140,13 @@ fn Weather::getWeatherInfo() const -> WeatherOutput { DEBUG_LOG("Cache error: {}", data.error()); } - fn handleApiResult = [](const std::expected& result) -> WeatherOutput { + fn handleApiResult = [](const Result& result) -> WeatherOutput { if (!result) { ERROR_LOG("API request failed: {}", result.error()); return WeatherOutput {}; } - if (std::expected writeResult = WriteCacheToFile(*result); !writeResult) + if (Result writeResult = WriteCacheToFile(*result); !writeResult) ERROR_LOG("Failed to write cache: {}", writeResult.error()); return *result; @@ -155,7 +154,7 @@ fn Weather::getWeatherInfo() const -> WeatherOutput { if (std::holds_alternative(location)) { const auto& city = std::get(location); - char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast(city.length())); + char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast(city.length())); DEBUG_LOG("Requesting city: {}", escaped); const String apiUrl = diff --git a/src/config/weather.h b/src/config/weather.h index d887bd7..25063d9 100644 --- a/src/config/weather.h +++ b/src/config/weather.h @@ -4,13 +4,14 @@ #include "../util/types.h" -// NOLINTBEGIN(readability-identifier-naming) +// NOLINTBEGIN(readability-identifier-naming) - Needs to specifically use `glaze` struct Condition { String description; struct glaze { - using T = Condition; - static constexpr auto value = glz::object("description", &T::description); + using T = Condition; + + static constexpr glz::detail::Object value = glz::object("description", &T::description); }; }; @@ -18,8 +19,9 @@ struct Main { f64 temp; struct glaze { - using T = Main; - static constexpr auto value = glz::object("temp", &T::temp); + using T = Main; + + static constexpr glz::detail::Object value = glz::object("temp", &T::temp); }; }; @@ -29,14 +31,16 @@ struct Coords { }; struct WeatherOutput { - Main main; - String name; - std::vector weather; - usize dt; + Main main; + String name; + Vec weather; + usize dt; struct glaze { - using T = WeatherOutput; - static constexpr auto value = glz::object("main", &T::main, "name", &T::name, "weather", &T::weather, "dt", &T::dt); + using T = WeatherOutput; + + static constexpr glz::detail::Object value = + glz::object("main", &T::main, "name", &T::name, "weather", &T::weather, "dt", &T::dt); }; }; // NOLINTEND(readability-identifier-naming) diff --git a/src/main.cpp b/src/main.cpp index 44a1a92..8404d5f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -22,7 +21,7 @@ constexpr u64 GIB = 1'073'741'824; template <> struct std::formatter : std::formatter { - auto format(const BytesToGiB& BTG, auto& ctx) const { + fn format(const BytesToGiB& BTG, auto& ctx) const { return std::format_to(ctx.out(), "{:.2f}GiB", static_cast(BTG.value) / GIB); } }; @@ -30,7 +29,7 @@ struct std::formatter : std::formatter { namespace ui { using ftxui::Color; - constexpr int MAX_PARAGRAPH_LENGTH = 30; + constexpr i32 MAX_PARAGRAPH_LENGTH = 30; // Color themes struct Theme { @@ -101,7 +100,7 @@ namespace ui { namespace { template - fn expected_visit(const std::expected& exp, ValueFunc value_func, ErrorFunc error_func) { + fn visit_result(const Result& exp, ValueFunc value_func, ErrorFunc error_func) { if (exp.has_value()) return value_func(*exp); @@ -117,7 +116,7 @@ namespace { u32 day = static_cast(ymd.day()); - const char* suffix = static_cast( + CStr suffix = static_cast( (day >= 11 && day <= 13) ? "th" : (day % 10 == 1) ? "st" : (day % 10 == 2) ? "nd" @@ -129,18 +128,18 @@ namespace { } struct SystemData { - String date; - String host; - String kernel_version; - std::expected os_version; - std::expected mem_info; - std::optional desktop_environment; - String window_manager; - std::optional> now_playing; - std::optional weather_info; - u64 disk_used; - u64 disk_total; - String shell; + String date; + String host; + String kernel_version; + Result os_version; + Result mem_info; + Option desktop_environment; + String window_manager; + Option> now_playing; + Option weather_info; + u64 disk_used; + u64 disk_total; + String shell; static fn fetchSystemData(const Config& config) -> SystemData { SystemData data; @@ -163,8 +162,8 @@ namespace { }); // Conditional tasks - std::future weather; - std::future> nowPlaying; + std::future weather; + std::future> nowPlaying; if (config.weather.enabled) weather = std::async(std::launch::async, [&config] { return config.weather.getWeatherInfo(); }); @@ -191,7 +190,7 @@ namespace { fn CreateColorCircles() -> Element { return hbox( - std::views::iota(0, 16) | std::views::transform([](int colorIndex) { + std::views::iota(0, 16) | std::views::transform([](i32 colorIndex) { return hbox({ text("◯") | bold | color(static_cast(colorIndex)), text(" ") }); }) | std::ranges::to() @@ -224,7 +223,7 @@ namespace { return hbox( { text(String(icon)) | color(ui::DEFAULT_THEME.icon), - text(String(static_cast(label))) | color(ui::DEFAULT_THEME.label), + text(String(static_cast(label))) | color(ui::DEFAULT_THEME.label), filler(), text(String(value)) | color(ui::DEFAULT_THEME.value), text(" "), @@ -283,13 +282,13 @@ namespace { if (!data.kernel_version.empty()) content.push_back(createRow(kernelIcon, "Kernel", data.kernel_version)); - expected_visit( + visit_result( data.os_version, [&](const String& version) { content.push_back(createRow(String(osIcon), "OS", version)); }, [](const String& error) { ERROR_LOG("Failed to get OS version: {}", error); } ); - expected_visit( + visit_result( data.mem_info, [&](const u64& mem) { content.push_back(createRow(memoryIcon, "RAM", std::format("{}", BytesToGiB { mem }))); }, [](const String& error) { ERROR_LOG("Failed to get memory info: {}", error); } @@ -312,8 +311,7 @@ namespace { // Now Playing row if (nowPlayingEnabled && data.now_playing.has_value()) { - if (const std::expected& nowPlayingResult = *data.now_playing; - nowPlayingResult.has_value()) { + if (const Result& nowPlayingResult = *data.now_playing; nowPlayingResult.has_value()) { const String& npText = *nowPlayingResult; content.push_back(separator() | color(ui::DEFAULT_THEME.border)); @@ -345,19 +343,12 @@ namespace { #pragma clang diagnostic pop } -#ifdef __linux__ - if (std::holds_alternative(error)) - DEBUG_LOG("DBus error: {}", std::get(error)); -#endif - #ifdef _WIN32 if (std::holds_alternative(error)) DEBUG_LOG("WinRT error: {}", to_string(std::get(error).message())); -#endif - -#ifdef __APPLE__ - if (std::holds_alternative(error)) - DEBUG_LOG("CoreAudio error: {}", std::get(error)); +#else + if (std::holds_alternative(error)) + DEBUG_LOG("NowPlaying error: {}", std::get(error)); #endif } } diff --git a/src/os/freebsd.cpp b/src/os/freebsd.cpp deleted file mode 100644 index 5facc38..0000000 --- a/src/os/freebsd.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#ifdef __FreeBSD__ - -#include -#include -#include -#include - -#include "os.h" - -fn GetMemInfo() -> u64 { - u64 mem = 0; - usize size = sizeof(mem); - - sysctlbyname("hw.physmem", &mem, &size, nullptr, 0); - - return mem; -} - -fn GetOSVersion() -> string { - std::ifstream file("/etc/os-release"); - - if (!file.is_open()) { - std::cerr << "Failed to open /etc/os-release" << std::endl; - return ""; // Return empty string indicating failure - } - - string line; - const string prefix = "PRETTY_NAME="; - - while (std::getline(file, line)) { - if (line.find(prefix) == 0) { - string prettyName = line.substr(prefix.size()); - - // Remove surrounding quotes if present - if (!prettyName.empty() && prettyName.front() == '"' && prettyName.back() == '"') - prettyName = prettyName.substr(1, prettyName.size() - 2); - - return prettyName; - } - } - - return ""; // Return empty string if PRETTY_NAME= line not found -} - -fn GetMprisPlayers(sdbus::IConnection& connection) -> std::vector { - const sdbus::ServiceName dbusInterface = sdbus::ServiceName("org.freedesktop.DBus"); - const sdbus::ObjectPath dbusObjectPath = sdbus::ObjectPath("/org/freedesktop/DBus"); - const char* dbusMethodListNames = "ListNames"; - - const std::unique_ptr dbusProxy = createProxy(connection, dbusInterface, dbusObjectPath); - - std::vector names; - - dbusProxy->callMethod(dbusMethodListNames).onInterface(dbusInterface).storeResultsTo(names); - - std::vector mprisPlayers; - - for (const std::basic_string& name : names) - if (const char* mprisInterfaceName = "org.mpris.MediaPlayer2"; name.find(mprisInterfaceName) != String::npos) - mprisPlayers.push_back(name); - - return mprisPlayers; -} - -fn GetActivePlayer(const std::vector& mprisPlayers) -> string { - if (!mprisPlayers.empty()) - return mprisPlayers.front(); - - return ""; -} - -fn GetNowPlaying() -> string { - try { - const char *playerObjectPath = "/org/mpris/MediaPlayer2", *playerInterfaceName = "org.mpris.MediaPlayer2.Player"; - - std::unique_ptr connection = sdbus::createSessionBusConnection(); - - std::vector mprisPlayers = GetMprisPlayers(*connection); - - if (mprisPlayers.empty()) - return ""; - - string activePlayer = GetActivePlayer(mprisPlayers); - - if (activePlayer.empty()) - return ""; - - auto playerProxy = - sdbus::createProxy(*connection, sdbus::ServiceName(activePlayer), sdbus::ObjectPath(playerObjectPath)); - - sdbus::Variant metadataVariant = playerProxy->getProperty("Metadata").onInterface(playerInterfaceName); - - if (metadataVariant.containsValueOfType>()) { - const auto& metadata = metadataVariant.get>(); - - auto iter = metadata.find("xesam:title"); - - if (iter != metadata.end() && iter->second.containsValueOfType()) - return iter->second.get(); - } - } catch (const sdbus::Error& e) { - if (e.getName() != "com.github.altdesktop.playerctld.NoActivePlayer") - return std::format("Error: {}", e.what()); - - return "No active player"; - } - - return ""; -} - -#endif diff --git a/src/os/linux.cpp b/src/os/linux.cpp index a5bee5e..6e23854 100644 --- a/src/os/linux.cpp +++ b/src/os/linux.cpp @@ -1,109 +1,68 @@ #ifdef __linux__ +// clang-format off +#include // needs to be at top for Success/None +// clang-format on #include #include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include #include #include +#include #include -#include -#include +#include #include #include "os.h" +#include "src/os/linux/display_guards.h" #include "src/util/macros.h" -#ifdef Success -#undef Success -#endif -#ifdef None -#undef None -#endif - -#include - -using std::expected; -using std::optional; - namespace fs = std::filesystem; -using namespace std::literals::string_view_literals; namespace { - using std::array; - using std::bit_cast; - using std::getenv; - using std::ifstream; - using std::istreambuf_iterator; - using std::less; - using std::lock_guard; - using std::mutex; - using std::nullopt; - using std::ofstream; - using std::pair; - using std::string_view; - using std::to_string; - using std::unexpected; - using std::vector; - using std::ranges::is_sorted; - using std::ranges::lower_bound; - using std::ranges::replace; - using std::ranges::subrange; - using std::ranges::transform; + using os::linux::DisplayGuard; + using os::linux::WaylandDisplayGuard; fn GetX11WindowManager() -> String { - Display* display = XOpenDisplay(nullptr); + DisplayGuard display; - // If XOpenDisplay fails, likely in a TTY if (!display) return ""; - Atom supportingWmCheck = XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", False); - Atom wmName = XInternAtom(display, "_NET_WM_NAME", False); - Atom utf8String = XInternAtom(display, "UTF8_STRING", False); + Atom supportingWmCheck = XInternAtom(display.get(), "_NET_SUPPORTING_WM_CHECK", False); + Atom wmName = XInternAtom(display.get(), "_NET_WM_NAME", False); + Atom utf8String = XInternAtom(display.get(), "UTF8_STRING", False); -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wold-style-cast" -#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" - Window root = DefaultRootWindow(display); // NOLINT -#pragma clang diagnostic pop + Window root = display.defaultRootWindow(); - Window wmWindow = 0; - Atom actualType = 0; - int actualFormat = 0; - unsigned long nitems = 0, bytesAfter = 0; - unsigned char* data = nullptr; + Window wmWindow = 0; + Atom actualType = 0; + i32 actualFormat = 0; + u64 nitems = 0, bytesAfter = 0; + u8* data = nullptr; if (XGetWindowProperty( - display, + display.get(), root, supportingWmCheck, 0, 1, False, - // XA_WINDOW - static_cast(33), + XA_WINDOW, &actualType, &actualFormat, &nitems, &bytesAfter, &data - ) == 0 && + ) == Success && data) { - wmWindow = *bit_cast(data); - XFree(data); - data = nullptr; + UniquePointer dataGuard(data, XFree); + wmWindow = *std::bit_cast(data); + + u8* nameData = nullptr; if (XGetWindowProperty( - display, + display.get(), wmWindow, wmName, 0, @@ -114,525 +73,399 @@ namespace { &actualFormat, &nitems, &bytesAfter, - &data - ) == 0 && - data) { - String name(bit_cast(data)); - XFree(data); - XCloseDisplay(display); - return name; + &nameData + ) == Success && + nameData) { + UniquePointer nameGuard(nameData, XFree); + return std::bit_cast(nameData); } } - XCloseDisplay(display); - return "Unknown (X11)"; } - fn TrimHyprlandWrapper(const String& input) -> String { - if (input.contains("hyprland")) - return "Hyprland"; - return input; - } + fn ReadProcessCmdline(i32 pid) -> String { + std::ifstream cmdlineFile("/proc/" + std::to_string(pid) + "/cmdline"); + String cmdline; - fn ReadProcessCmdline(int pid) -> String { - String path = "/proc/" + to_string(pid) + "/cmdline"; - ifstream cmdlineFile(path); - String cmdline; if (getline(cmdlineFile, cmdline)) { - // Replace null bytes with spaces - replace(cmdline, '\0', ' '); + std::ranges::replace(cmdline, '\0', ' '); return cmdline; } + return ""; } fn DetectHyprlandSpecific() -> String { - // Check environment variables first - const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP"); - if (xdgCurrentDesktop && strcasestr(xdgCurrentDesktop, "hyprland")) + Result xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP"); + + if (xdgCurrentDesktop) { + std::ranges::transform(*xdgCurrentDesktop, xdgCurrentDesktop->begin(), ::tolower); + + if (xdgCurrentDesktop->contains("hyprland")) + return "Hyprland"; + } + + if (GetEnv("HYPRLAND_INSTANCE_SIGNATURE")) return "Hyprland"; - // Check for Hyprland's specific environment variable - if (getenv("HYPRLAND_INSTANCE_SIGNATURE")) - return "Hyprland"; - - // Check for Hyprland socket - if (fs::exists("/run/user/" + to_string(getuid()) + "/hypr")) + if (fs::exists("/run/user/" + std::to_string(getuid()) + "/hypr")) return "Hyprland"; return ""; } fn GetWaylandCompositor() -> String { - // First try Hyprland-specific detection String hypr = DetectHyprlandSpecific(); + if (!hypr.empty()) return hypr; - // Then try the standard Wayland detection - wl_display* display = wl_display_connect(nullptr); + WaylandDisplayGuard display; if (!display) return ""; - int fileDescriptor = wl_display_get_fd(display); + i32 fileDescriptor = display.fd(); struct ucred cred; socklen_t len = sizeof(cred); - if (getsockopt(fileDescriptor, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) { - wl_display_disconnect(display); + if (getsockopt(fileDescriptor, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) return ""; - } - // Read both comm and cmdline String compositorName; - // 1. Check comm (might be wrapped) - String commPath = "/proc/" + to_string(cred.pid) + "/comm"; - ifstream commFile(commPath); + String commPath = "/proc/" + std::to_string(cred.pid) + "/comm"; + std::ifstream commFile(commPath); if (commFile >> compositorName) { - subrange removedRange = std::ranges::remove(compositorName, '\n'); - compositorName.erase(removedRange.begin(), compositorName.end()); + std::ranges::subrange removedRange = std::ranges::remove(compositorName, '\n'); + compositorName.erase(removedRange.begin(), removedRange.end()); } - // 2. Check cmdline for actual binary reference String cmdline = ReadProcessCmdline(cred.pid); - if (cmdline.contains("hyprland")) { - wl_display_disconnect(display); + if (cmdline.contains("hyprland")) return "Hyprland"; - } - // 3. Check exe symlink - String exePath = "/proc/" + to_string(cred.pid) + "/exe"; - array buf; + String exePath = "/proc/" + std::to_string(cred.pid) + "/exe"; + Array buf; ssize_t lenBuf = readlink(exePath.c_str(), buf.data(), buf.size() - 1); if (lenBuf != -1) { buf.at(static_cast(lenBuf)) = '\0'; String exe(buf.data()); - if (exe.contains("hyprland")) { - wl_display_disconnect(display); + if (exe.contains("hyprland")) return "Hyprland"; - } } - wl_display_disconnect(display); - - // Final cleanup of wrapper names - return TrimHyprlandWrapper(compositorName); + return compositorName.contains("hyprland") ? "Hyprland" : compositorName; } - fn DetectFromEnvVars() -> optional { - // Use RAII to guard against concurrent env modifications - static mutex EnvMutex; - lock_guard lock(EnvMutex); + fn DetectFromEnvVars() -> Option { + if (Result xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP")) { + const size_t colon = xdgCurrentDesktop->find(':'); - // XDG_CURRENT_DESKTOP - if (const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP")) { - const string_view sview(xdgCurrentDesktop); - const size_t colon = sview.find(':'); - return String(sview.substr(0, colon)); // Direct construct from view + if (colon != String::npos) + return xdgCurrentDesktop->substr(0, colon); + + return *xdgCurrentDesktop; } - // DESKTOP_SESSION - if (const char* desktopSession = getenv("DESKTOP_SESSION")) - return String(string_view(desktopSession)); // Avoid intermediate view storage + if (Result desktopSession = GetEnv("DESKTOP_SESSION")) + return *desktopSession; - return nullopt; + return None; } - fn DetectFromSessionFiles() -> optional { - static constexpr array, 12> DE_PATTERNS = { - // clang-format off - pair { "Budgie"sv, "budgie"sv }, - pair { "Cinnamon"sv, "cinnamon"sv }, - pair { "LXQt"sv, "lxqt"sv }, - pair { "MATE"sv, "mate"sv }, - pair { "Unity"sv, "unity"sv }, - pair { "gnome"sv, "GNOME"sv }, - pair { "gnome-wayland"sv, "GNOME"sv }, - pair { "gnome-xorg"sv, "GNOME"sv }, - pair { "kde"sv, "KDE"sv }, - pair { "plasma"sv, "KDE"sv }, - pair { "plasmax11"sv, "KDE"sv }, - pair { "xfce"sv, "XFCE"sv }, - // clang-format on - }; + fn DetectFromSessionFiles() -> Option { + // clang-format off + static constexpr Array, 12> DE_PATTERNS = {{ + { "budgie", "Budgie" }, + { "cinnamon", "Cinnamon" }, + { "lxqt", "LXQt" }, + { "mate", "MATE" }, + { "unity", "Unity" }, + { "gnome", "GNOME" }, + { "gnome-wayland", "GNOME" }, + { "gnome-xorg", "GNOME" }, + { "kde", "KDE" }, + { "plasma", "KDE" }, + { "plasmax11", "KDE" }, + { "xfce", "XFCE" }, + }}; + // clang-format on - static_assert(is_sorted(DE_PATTERNS, {}, &pair::first)); + static constexpr Array SESSION_PATHS = { "/usr/share/xsessions", "/usr/share/wayland-sessions" }; - // Precomputed session paths - static constexpr array SESSION_PATHS = { "/usr/share/xsessions", "/usr/share/wayland-sessions" }; - - // Single memory reserve for lowercase conversions - String lowercaseStem; - lowercaseStem.reserve(32); - - for (const auto& path : SESSION_PATHS) { + for (const StringView& path : SESSION_PATHS) { if (!fs::exists(path)) continue; - for (const auto& entry : fs::directory_iterator(path)) { + for (const fs::directory_entry& entry : fs::directory_iterator(path)) { if (!entry.is_regular_file()) continue; - // Reuse buffer - lowercaseStem = entry.path().stem().string(); - transform(lowercaseStem, lowercaseStem.begin(), ::tolower); + String lowercaseStem = entry.path().stem().string(); + std::ranges::transform(lowercaseStem, lowercaseStem.begin(), ::tolower); - // Modern ranges version - const pair* const patternIter = lower_bound( - DE_PATTERNS, lowercaseStem, less {}, &pair::first // Projection - ); - - if (patternIter != DE_PATTERNS.end() && patternIter->first == lowercaseStem) - return String(patternIter->second); + for (const Pair pattern : DE_PATTERNS) + if (pattern.first == lowercaseStem) + return String(pattern.second); } } - return nullopt; + return None; } - fn DetectFromProcesses() -> optional { - const array processChecks = { - // clang-format off - pair { "plasmashell"sv, "KDE"sv }, - pair { "gnome-shell"sv, "GNOME"sv }, - pair { "xfce4-session"sv, "XFCE"sv }, - pair { "mate-session"sv, "MATE"sv }, - pair { "cinnamon-sessio"sv, "Cinnamon"sv }, - pair { "budgie-wm"sv, "Budgie"sv }, - pair { "lxqt-session"sv, "LXQt"sv }, - // clang-format on - }; + fn DetectFromProcesses() -> Option { + // clang-format off + const Array, 7> processChecks = {{ + { "plasmashell", "KDE" }, + { "gnome-shell", "GNOME" }, + { "xfce4-session", "XFCE" }, + { "mate-session", "MATE" }, + { "cinnamon-session", "Cinnamon" }, + { "budgie-wm", "Budgie" }, + { "lxqt-session", "LXQt" }, + }}; + // clang-format on - ifstream cmdline("/proc/self/environ"); - String envVars((istreambuf_iterator(cmdline)), istreambuf_iterator()); + std::ifstream cmdline("/proc/self/environ"); + String envVars((std::istreambuf_iterator(cmdline)), std::istreambuf_iterator()); for (const auto& [process, deName] : processChecks) if (envVars.contains(process)) return String(deName); - return nullopt; + return None; } - fn GetMprisPlayers(const std::shared_ptr& connection) -> expected, NowPlayingError> { + fn GetMprisPlayers(const SharedPointer& connection) -> Result, NowPlayingError> { try { - // Create the method call object - std::shared_ptr call = + SharedPointer call = DBus::CallMessage::create("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames"); - // Send the message synchronously and get the reply - // Timeout parameter might be needed (e.g., 5000 ms) - std::shared_ptr reply = connection->send_with_reply_blocking(call, 5000); + SharedPointer reply = connection->send_with_reply_blocking(call, 500); - // Check if the reply itself is an error type - if (reply) { + if (!reply) { ERROR_LOG("DBus timeout or null reply in ListNames"); - return unexpected(LinuxError("DBus timeout in ListNames")); + return Err("DBus timeout in ListNames"); } - vector allNamesStd; + Vec allNamesStd; DBus::MessageIterator reader(*reply); reader >> allNamesStd; - // Filter for MPRIS players - vector mprisPlayers; // Use std::string as String=std::string - for (const auto& name : allNamesStd) { - if (string_view(name).contains("org.mpris.MediaPlayer2")) { + Vec mprisPlayers; + for (const String& name : allNamesStd) + if (StringView(name).contains("org.mpris.MediaPlayer2")) mprisPlayers.emplace_back(name); - } - } + return mprisPlayers; - } catch (const DBus::Error& e) { // Catch specific dbus-cxx exceptions + } catch (const DBus::Error& e) { ERROR_LOG("DBus::Error exception in ListNames: {}", e.what()); - return unexpected(LinuxError(e.what())); - } catch (const std::exception& e) { // Catch other potential standard exceptions + return Err(e.what()); + } catch (const Exception& e) { ERROR_LOG("Standard exception getting MPRIS players: {}", e.what()); - return unexpected(String(e.what())); + return Err(e.what()); } } - // --- Logic remains the same --- - fn GetActivePlayer(const vector& mprisPlayers) -> optional { - if (!mprisPlayers.empty()) - return mprisPlayers.front(); - return nullopt; - } } -fn GetOSVersion() -> expected { - constexpr const char* path = "/etc/os-release"; +fn GetOSVersion() -> Result { + constexpr CStr path = "/etc/os-release"; - ifstream file(path); + std::ifstream file(path); - if (!file.is_open()) - return unexpected("Failed to open " + String(path)); + if (!file) + return Err(std::format("Failed to open {}", path)); String line; const String prefix = "PRETTY_NAME="; while (getline(file, line)) if (line.starts_with(prefix)) { - String prettyName = line.substr(prefix.size()); + StringView valueView = StringView(line).substr(prefix.size()); - if (!prettyName.empty() && prettyName.front() == '"' && prettyName.back() == '"') - return prettyName.substr(1, prettyName.size() - 2); + if (!valueView.empty() && valueView.front() == '"' && valueView.back() == '"') { + valueView.remove_prefix(1); + valueView.remove_suffix(1); + } - return prettyName; + return String(valueView); } - return unexpected("PRETTY_NAME line not found in " + String(path)); + return Err(std::format("PRETTY_NAME line not found in {}", path)); } -fn GetMemInfo() -> expected { - using std::from_chars, std::errc; +fn GetMemInfo() -> Result { + struct sysinfo info; - constexpr const char* path = "/proc/meminfo"; + if (sysinfo(&info) != 0) + return Err(std::format("sysinfo failed: {}", std::error_code(errno, std::generic_category()).message())); - ifstream input(path); - - if (!input.is_open()) - return unexpected("Failed to open " + String(path)); - - String line; - while (getline(input, line)) { - if (line.starts_with("MemTotal")) { - const size_t colonPos = line.find(':'); - - if (colonPos == String::npos) - return unexpected("Invalid MemTotal line: no colon found"); - - string_view view(line); - view.remove_prefix(colonPos + 1); - - // Trim leading whitespace - const size_t firstNonSpace = view.find_first_not_of(' '); - - if (firstNonSpace == string_view::npos) - return unexpected("No number found after colon in MemTotal line"); - - view.remove_prefix(firstNonSpace); - - // Find the end of the numeric part - const size_t end = view.find_first_not_of("0123456789"); - if (end != string_view::npos) - view = view.substr(0, end); - - // Get pointers via iterators - const char* startPtr = &*view.begin(); - const char* endPtr = &*view.end(); - - u64 value = 0; - const auto result = from_chars(startPtr, endPtr, value); - - if (result.ec != errc() || result.ptr != endPtr) - return unexpected("Failed to parse number in MemTotal line"); - - return value * 1024; - } - } - - return unexpected("MemTotal line not found in " + String(path)); + return static_cast(info.totalram * info.mem_unit); } -fn GetNowPlaying() -> expected { +fn GetNowPlaying() -> Result { try { - // 1. Get Dispatcher and Session Bus Connection - std::shared_ptr dispatcher = DBus::StandaloneDispatcher::create(); + SharedPointer dispatcher = DBus::StandaloneDispatcher::create(); if (!dispatcher) - return unexpected(LinuxError("Failed to create DBus dispatcher")); + return Err("Failed to create DBus dispatcher"); - std::shared_ptr connection = dispatcher->create_connection(DBus::BusType::SESSION); + SharedPointer connection = dispatcher->create_connection(DBus::BusType::SESSION); if (!connection) - return unexpected(LinuxError("Failed to connect to session bus")); + return Err("Failed to connect to session bus"); - // 2. Get list of MPRIS players - auto mprisPlayersResult = GetMprisPlayers(connection); + Result, NowPlayingError> mprisPlayersResult = GetMprisPlayers(connection); if (!mprisPlayersResult) - return unexpected(mprisPlayersResult.error()); // Forward the error + return Err(mprisPlayersResult.error()); - const vector& mprisPlayers = *mprisPlayersResult; + const Vec& mprisPlayers = *mprisPlayersResult; if (mprisPlayers.empty()) - return unexpected(NowPlayingError { NowPlayingCode::NoPlayers }); + return Err(NowPlayingCode::NoPlayers); - // 3. Determine active player - optional activePlayerOpt = GetActivePlayer(mprisPlayers); - if (!activePlayerOpt) - return unexpected(NowPlayingError { NowPlayingCode::NoActivePlayer }); + String activePlayer = mprisPlayers.front(); - // Use std::string for D-Bus service name - const String& activePlayerService = *activePlayerOpt; + SharedPointer metadataCall = + DBus::CallMessage::create(activePlayer, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get"); - // 4. Call Properties.Get for Metadata - const String interfaceNameStd = "org.mpris.MediaPlayer2.Player"; - const String propertyNameStd = "Metadata"; + (*metadataCall) << "org.mpris.MediaPlayer2.Player" << "Metadata"; - // Create call message - auto call = DBus::CallMessage::create( - activePlayerService, // Target service - "/org/mpris/MediaPlayer2", // Object path - "org.freedesktop.DBus.Properties", // Interface - "Get" - ); // Method name + SharedPointer metadataReply = connection->send_with_reply_blocking(metadataCall, 5000); - (*call) << interfaceNameStd << propertyNameStd; + String title; + String artist; - // Send message and get reply - std::shared_ptr replyMsg = connection->send_with_reply_blocking(call, 5000); // Use a timeout + if (metadataReply && metadataReply->is_valid()) { + try { + DBus::MessageIterator iter(*metadataReply); + DBus::Variant metadataVariant; + iter >> metadataVariant; - if (!replyMsg) { - ERROR_LOG("DBus timeout or null reply in Properties.Get"); - return unexpected(LinuxError("DBus timeout in Properties.Get")); + if (metadataVariant.type() == DBus::DataType::ARRAY) { + Map metadata = metadataVariant.to_map(); + + auto titleIter = metadata.find("xesam:title"); + if (titleIter != metadata.end() && titleIter->second.type() == DBus::DataType::STRING) + title = titleIter->second.to_string(); + + auto artistIter = metadata.find("xesam:artist"); + if (artistIter != metadata.end()) { + if (artistIter->second.type() == DBus::DataType::ARRAY) { + if (Vec artists = artistIter->second.to_vector(); !artists.empty()) + artist = artists[0]; + } else if (artistIter->second.type() == DBus::DataType::STRING) + artist = artistIter->second.to_string(); + } + } else { + ERROR_LOG( + "Metadata variant is not the expected type, expected a{{sv}} but got {}", metadataVariant.signature().str() + ); + } + } catch (const DBus::Error& e) { + ERROR_LOG("DBus error processing metadata reply: {}", e.what()); + } catch (const Exception& e) { ERROR_LOG("Error processing metadata reply: {}", e.what()); } } - // 5. Parse the reply - DBus::Variant metadataVariant; - // Create reader/iterator from the message - DBus::MessageIterator reader(*replyMsg); // Use constructor - // *** Correction: Use get on iterator instead of operator>> *** - reader >> metadataVariant; - - // Check the variant's signature - if (metadataVariant.to_signature() != "a{sv}") { - return unexpected("Unexpected reply type for Metadata"); - } - String artistStd; - String titleStd; - - // Get the dictionary using the templated get() method - std::map metadataMap; - auto titleIter = metadataMap.find("xesam:title"); - if (titleIter != metadataMap.end() && titleIter->second.to_signature() == "s") { - // Use the cast operator on variant to string - titleStd = static_cast(titleIter->second); - } - - // For line 525-534 - auto artistIter = metadataMap.find("xesam:artist"); - if (artistIter != metadataMap.end() && artistIter->second.to_signature() == "as") { - // Cast to vector - std::vector artistsStd = static_cast>(artistIter->second); - if (!artistsStd.empty()) { - artistStd = artistsStd.front(); - } - } - - // 6. Construct result string - String result; - if (!artistStd.empty() && !titleStd.empty()) - result = artistStd + " - " + titleStd; - else if (!titleStd.empty()) - result = titleStd; - else if (!artistStd.empty()) - result = artistStd; - - return result; - } catch (const DBus::Error& e) { // Catch specific dbus-cxx exceptions - ERROR_LOG("DBus::Error exception in GetNowPlaying: {}", e.what()); - return unexpected(LinuxError(e.what())); - } catch (const std::exception& e) { // Catch other potential standard exceptions - ERROR_LOG("Standard exception in GetNowPlaying: {}", e.what()); - return unexpected(String(e.what())); + return std::format("{}{}{}", artist, (!artist.empty() && !title.empty()) ? " - " : "", title); + } catch (const DBus::Error& e) { return Err(std::format("DBus error: {}", e.what())); } catch (const Exception& e) { + return Err(std::format("General error: {}", e.what())); } } fn GetWindowManager() -> String { - // Check environment variables first - const char* xdgSessionType = getenv("XDG_SESSION_TYPE"); - const char* waylandDisplay = getenv("WAYLAND_DISPLAY"); + const Result waylandDisplay = GetEnv("WAYLAND_DISPLAY"); + const Result xdgSessionType = GetEnv("XDG_SESSION_TYPE"); - // Prefer Wayland detection if Wayland session - if ((waylandDisplay != nullptr) || (xdgSessionType && string_view(xdgSessionType).contains("wayland"))) { + if (waylandDisplay || (xdgSessionType && xdgSessionType->contains("wayland"))) { String compositor = GetWaylandCompositor(); if (!compositor.empty()) return compositor; - // Fallback environment check - const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP"); - if (xdgCurrentDesktop) { - String desktop(xdgCurrentDesktop); - transform(compositor, compositor.begin(), ::tolower); - if (desktop.contains("hyprland")) - return "hyprland"; + if (const Result xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP")) { + std::ranges::transform(compositor, compositor.begin(), ::tolower); + if (xdgCurrentDesktop->contains("hyprland")) + return "Hyprland"; } } - // X11 detection - String x11wm = GetX11WindowManager(); - if (!x11wm.empty()) + if (String x11wm = GetX11WindowManager(); !x11wm.empty()) return x11wm; return "Unknown"; } -fn GetDesktopEnvironment() -> optional { - // Try environment variables first - if (auto desktopEnvironment = DetectFromEnvVars(); desktopEnvironment.has_value()) +fn GetDesktopEnvironment() -> Option { + if (Option desktopEnvironment = DetectFromEnvVars()) return desktopEnvironment; - // Try session files next - if (auto desktopEnvironment = DetectFromSessionFiles(); desktopEnvironment.has_value()) + if (Option desktopEnvironment = DetectFromSessionFiles()) return desktopEnvironment; - // Fallback to process detection return DetectFromProcesses(); } fn GetShell() -> String { - const string_view shell = getenv("SHELL"); + const Vec> shellMap { + { "bash", "Bash" }, + { "zsh", "Zsh" }, + { "fish", "Fish" }, + { "nu", "Nushell" }, + { "sh", "SH" }, // sh last because other shells contain "sh" + }; - if (shell.ends_with("bash")) - return "Bash"; - if (shell.ends_with("zsh")) - return "Zsh"; - if (shell.ends_with("fish")) - return "Fish"; - if (shell.ends_with("nu")) - return "Nushell"; - if (shell.ends_with("sh")) - return "SH"; + if (const Result shellPath = GetEnv("SHELL")) { + for (const auto& shellPair : shellMap) + if (shellPath->contains(shellPair.first)) + return shellPair.second; - return !shell.empty() ? String(shell) : ""; + return *shellPath; // fallback to the raw shell path + } + + return ""; } fn GetHost() -> String { - constexpr const char* path = "/sys/class/dmi/id/product_family"; + constexpr CStr path = "/sys/class/dmi/id/product_family"; - ifstream file(path); - if (!file.is_open()) { + std::ifstream file(path); + + if (!file) { ERROR_LOG("Failed to open {}", path); return ""; } String productFamily; + if (!getline(file, productFamily)) { - ERROR_LOG("Failed to read from {}", path); + ERROR_LOG("Failed to read from {} (is it empty?)", path); return ""; } - return productFamily; + return productFamily.erase(productFamily.find_last_not_of(" \t\n\r") + 1); } fn GetKernelVersion() -> String { struct utsname uts; if (uname(&uts) == -1) { - ERROR_LOG("uname() failed: {}", strerror(errno)); + ERROR_LOG("uname() failed: {}", std::error_code(errno, std::generic_category()).message()); return ""; } - return static_cast(uts.release); + return static_cast(uts.release); } -fn GetDiskUsage() -> pair { +fn GetDiskUsage() -> Pair { struct statvfs stat; + if (statvfs("/", &stat) == -1) { - ERROR_LOG("statvfs() failed: {}", strerror(errno)); + ERROR_LOG("statvfs() failed: {}", std::error_code(errno, std::generic_category()).message()); return { 0, 0 }; } + return { (stat.f_blocks * stat.f_frsize) - (stat.f_bfree * stat.f_frsize), stat.f_blocks * stat.f_frsize }; } diff --git a/src/os/linux/display_guards.cpp b/src/os/linux/display_guards.cpp new file mode 100644 index 0000000..3b523a1 --- /dev/null +++ b/src/os/linux/display_guards.cpp @@ -0,0 +1,69 @@ +#ifdef __linux__ + +#include // for std::exchange + +#include "display_guards.h" + +#include "src/util/macros.h" + +namespace os::linux { + DisplayGuard::DisplayGuard(CStr name) : m_Display(XOpenDisplay(name)) {} + + DisplayGuard::~DisplayGuard() { + if (m_Display) + XCloseDisplay(m_Display); + } + + DisplayGuard::DisplayGuard(DisplayGuard&& other) noexcept : m_Display(std::exchange(other.m_Display, nullptr)) {} + + fn DisplayGuard::operator=(DisplayGuard&& other) noexcept -> DisplayGuard& { + if (this != &other) { + if (m_Display) + XCloseDisplay(m_Display); + + m_Display = std::exchange(other.m_Display, nullptr); + } + + return *this; + } + + DisplayGuard::operator bool() const { return m_Display != nullptr; } + + fn DisplayGuard::get() const -> Display* { return m_Display; } + + fn DisplayGuard::defaultRootWindow() const -> Window { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + return DefaultRootWindow(m_Display); +#pragma clang diagnostic pop + } + + WaylandDisplayGuard::WaylandDisplayGuard() : m_Display(wl_display_connect(nullptr)) {} + + WaylandDisplayGuard::~WaylandDisplayGuard() { + if (m_Display) + wl_display_disconnect(m_Display); + } + + WaylandDisplayGuard::WaylandDisplayGuard(WaylandDisplayGuard&& other) noexcept + : m_Display(std::exchange(other.m_Display, nullptr)) {} + + fn WaylandDisplayGuard::operator=(WaylandDisplayGuard&& other) noexcept -> WaylandDisplayGuard& { + if (this != &other) { + if (m_Display) + wl_display_disconnect(m_Display); + + m_Display = std::exchange(other.m_Display, nullptr); + } + + return *this; + } + + WaylandDisplayGuard::operator bool() const { return m_Display != nullptr; } + + fn WaylandDisplayGuard::get() const -> wl_display* { return m_Display; } + + fn WaylandDisplayGuard::fd() const -> i32 { return m_Display ? wl_display_get_fd(m_Display) : -1; } +} + +#endif diff --git a/src/os/linux/display_guards.h b/src/os/linux/display_guards.h new file mode 100644 index 0000000..f47198f --- /dev/null +++ b/src/os/linux/display_guards.h @@ -0,0 +1,69 @@ +#pragma once + +#ifdef __linux__ + +#include +#include + +#include "src/util/macros.h" + +namespace os::linux { + /** + * RAII wrapper for X11 Display connections + * Automatically handles resource acquisition and cleanup + */ + class DisplayGuard { + private: + Display* m_Display; + + public: + /** + * Opens an X11 display connection + * @param name Display name (nullptr for default) + */ + explicit DisplayGuard(CStr name = nullptr); + ~DisplayGuard(); + + // Non-copyable + DisplayGuard(const DisplayGuard&) = delete; + fn operator=(const DisplayGuard&)->DisplayGuard& = delete; + + // Movable + DisplayGuard(DisplayGuard&& other) noexcept; + fn operator=(DisplayGuard&& other) noexcept -> DisplayGuard&; + + [[nodiscard]] operator bool() const; + [[nodiscard]] fn get() const -> Display*; + [[nodiscard]] fn defaultRootWindow() const -> Window; + }; + + /** + * RAII wrapper for Wayland display connections + * Automatically handles resource acquisition and cleanup + */ + class WaylandDisplayGuard { + private: + wl_display* m_Display; + + public: + /** + * Opens a Wayland display connection + */ + WaylandDisplayGuard(); + ~WaylandDisplayGuard(); + + // Non-copyable + WaylandDisplayGuard(const WaylandDisplayGuard&) = delete; + fn operator=(const WaylandDisplayGuard&)->WaylandDisplayGuard& = delete; + + // Movable + WaylandDisplayGuard(WaylandDisplayGuard&& other) noexcept; + fn operator=(WaylandDisplayGuard&& other) noexcept -> WaylandDisplayGuard&; + + [[nodiscard]] operator bool() const; + [[nodiscard]] fn get() const -> wl_display*; + [[nodiscard]] fn fd() const -> i32; + }; +} + +#endif diff --git a/src/os/linux/pkg_count.cpp b/src/os/linux/pkg_count.cpp index 0d7c6a6..7413648 100644 --- a/src/os/linux/pkg_count.cpp +++ b/src/os/linux/pkg_count.cpp @@ -1,9 +1 @@ #include "src/os/linux/pkg_count.h" - -namespace fs = std::filesystem; - -fn GetApkPackageCount() -> optional { - fs::path apkDbPath("/lib/apk/db/installed"); - - return std::nullopt; -} diff --git a/src/os/linux/pkg_count.h b/src/os/linux/pkg_count.h index a560736..b2eaa96 100644 --- a/src/os/linux/pkg_count.h +++ b/src/os/linux/pkg_count.h @@ -2,37 +2,35 @@ #include "src/util/macros.h" -using std::optional; - // Get package count from dpkg (Debian/Ubuntu) -fn GetDpkgPackageCount() -> optional; +fn GetDpkgPackageCount() -> Option; // Get package count from RPM (Red Hat/Fedora/CentOS) -fn GetRpmPackageCount() -> std::optional; +fn GetRpmPackageCount() -> Option; // Get package count from pacman (Arch Linux) -fn GetPacmanPackageCount() -> std::optional; +fn GetPacmanPackageCount() -> Option; // Get package count from Portage (Gentoo) -fn GetPortagePackageCount() -> std::optional; +fn GetPortagePackageCount() -> Option; // Get package count from zypper (openSUSE) -fn GetZypperPackageCount() -> std::optional; +fn GetZypperPackageCount() -> Option; // Get package count from apk (Alpine) -fn GetApkPackageCount() -> std::optional; +fn GetApkPackageCount() -> Option; // Get package count from nix -fn GetNixPackageCount() -> std::optional; +fn GetNixPackageCount() -> Option; // Get package count from flatpak -fn GetFlatpakPackageCount() -> std::optional; +fn GetFlatpakPackageCount() -> Option; // Get package count from snap -fn GetSnapPackageCount() -> std::optional; +fn GetSnapPackageCount() -> Option; // Get package count from AppImage -fn GetAppimagePackageCount() -> std::optional; +fn GetAppimagePackageCount() -> Option; // Get total package count from all available package managers -fn GetTotalPackageCount() -> std::optional; +fn GetTotalPackageCount() -> Option; diff --git a/src/os/macos.cpp b/src/os/macos.cpp index 7be71e8..2de837b 100644 --- a/src/os/macos.cpp +++ b/src/os/macos.cpp @@ -1,6 +1,5 @@ #ifdef __APPLE__ -#include #include #include #include @@ -10,21 +9,21 @@ #include "os.h" #include "src/util/types.h" -fn GetMemInfo() -> expected { +fn GetMemInfo() -> Result { u64 mem = 0; usize size = sizeof(mem); if (sysctlbyname("hw.memsize", &mem, &size, nullptr, 0) == -1) - return std::unexpected(std::format("sysctlbyname failed: {}", strerror(errno))); + return Err(std::format("sysctlbyname failed: {}", strerror(errno))); return mem; } -fn GetNowPlaying() -> expected { return GetCurrentPlayingInfo(); } +fn GetNowPlaying() -> Result { return GetCurrentPlayingInfo(); } -fn GetOSVersion() -> expected { return GetMacOSVersion(); } +fn GetOSVersion() -> Result { return GetMacOSVersion(); } -fn GetDesktopEnvironment() -> optional { return std::nullopt; } +fn GetDesktopEnvironment() -> Option { return None; } fn GetWindowManager() -> String { return "Yabai"; } @@ -196,7 +195,6 @@ fn GetHost() -> String { return String(modelNameByHwModel[hwModel.data()]); } -// returns free/total fn GetDiskUsage() -> std::pair { struct statvfs vfs; diff --git a/src/os/os.h b/src/os/os.h index a5734d6..9fd2340 100644 --- a/src/os/os.h +++ b/src/os/os.h @@ -1,31 +1,27 @@ #pragma once -#include - #include "../util/macros.h" #include "../util/types.h" -using std::optional, std::expected; - /** * @brief Get the amount of installed RAM in bytes. */ -fn GetMemInfo() -> expected; +fn GetMemInfo() -> Result; /** * @brief Get the currently playing song metadata. */ -fn GetNowPlaying() -> expected; +fn GetNowPlaying() -> Result; /** * @brief Get the OS version. */ -fn GetOSVersion() -> expected; +fn GetOSVersion() -> Result; /** * @brief Get the current desktop environment. */ -fn GetDesktopEnvironment() -> optional; +fn GetDesktopEnvironment() -> Option; /** * @brief Get the current window manager. @@ -56,4 +52,4 @@ fn GetPackageCount() -> u64; * @brief Get the current disk usage. * @return std::pair Used space/total space */ -fn GetDiskUsage() -> std::pair; +fn GetDiskUsage() -> Pair; diff --git a/src/os/windows.cpp b/src/os/windows.cpp index a8ae4f1..51e7a6c 100644 --- a/src/os/windows.cpp +++ b/src/os/windows.cpp @@ -8,11 +8,8 @@ #include // clang-format on -#include #include #include -#include -#include #include #include #include @@ -22,7 +19,6 @@ #include "os.h" -using std::string_view; using RtlGetVersionPtr = NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW); // NOLINTBEGIN(*-pro-type-cstyle-cast,*-no-int-to-ptr,*-pro-type-reinterpret-cast) @@ -43,8 +39,8 @@ namespace { [[nodiscard]] fn isValid() const -> bool { return h_snapshot != INVALID_HANDLE_VALUE; } - [[nodiscard]] fn getProcesses() const -> std::vector> { - std::vector> processes; + [[nodiscard]] fn getProcesses() const -> std::vector> { + std::vector> processes; if (!isValid()) return processes; @@ -58,11 +54,11 @@ namespace { // Get first process if (Process32First(h_snapshot, &pe32)) { // Add first process to vector - processes.emplace_back(pe32.th32ProcessID, string(reinterpret_cast(pe32.szExeFile))); + processes.emplace_back(pe32.th32ProcessID, String(reinterpret_cast(pe32.szExeFile))); // Add remaining processes while (Process32Next(h_snapshot, &pe32)) - processes.emplace_back(pe32.th32ProcessID, string(reinterpret_cast(pe32.szExeFile))); + processes.emplace_back(pe32.th32ProcessID, String(reinterpret_cast(pe32.szExeFile))); } return processes; @@ -71,7 +67,7 @@ namespace { HANDLE h_snapshot; }; - fn GetRegistryValue(const HKEY& hKey, const string& subKey, const string& valueName) -> string { + 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) return ""; @@ -84,7 +80,7 @@ namespace { } // For string values, allocate one less byte to avoid the null terminator - string value((type == REG_SZ || type == REG_EXPAND_SZ) ? dataSize - 1 : dataSize, '\0'); + String value((type == REG_SZ || type == REG_EXPAND_SZ) ? dataSize - 1 : dataSize, '\0'); if (RegQueryValueExA(key, valueName.c_str(), nullptr, nullptr, std::bit_cast(value.data()), &dataSize) != ERROR_SUCCESS) { @@ -96,13 +92,13 @@ namespace { return value; } - fn GetProcessInfo() -> std::vector> { + fn GetProcessInfo() -> std::vector> { const ProcessSnapshot snapshot; - return snapshot.isValid() ? snapshot.getProcesses() : std::vector> {}; + return snapshot.isValid() ? snapshot.getProcesses() : std::vector> {}; } - fn IsProcessRunning(const std::vector& processes, const string& name) -> bool { - return std::ranges::any_of(processes, [&name](const string& proc) -> bool { + fn IsProcessRunning(const std::vector& processes, const String& name) -> bool { + return std::ranges::any_of(processes, [&name](const String& proc) -> bool { return _stricmp(proc.c_str(), name.c_str()) == 0; }); } @@ -127,7 +123,7 @@ namespace { return 0; } - fn GetProcessName(const DWORD pid) -> string { + fn GetProcessName(const DWORD pid) -> String { const ProcessSnapshot snapshot; if (!snapshot.isValid()) return ""; @@ -149,17 +145,17 @@ namespace { } } -fn GetMemInfo() -> expected { +fn GetMemInfo() -> Result { try { using namespace winrt::Windows::System::Diagnostics; const SystemDiagnosticInfo diag = SystemDiagnosticInfo::GetForCurrentSystem(); return diag.MemoryUsage().GetReport().TotalPhysicalSizeInBytes(); } catch (const winrt::hresult_error& e) { - return std::unexpected("Failed to get memory info: " + to_string(e.message())); + return Err(std::format("Failed to get memory info: {}", to_string(e.message()))); } } -fn GetNowPlaying() -> expected { +fn GetNowPlaying() -> Result { using namespace winrt::Windows::Media::Control; using namespace winrt::Windows::Foundation; @@ -181,36 +177,31 @@ fn GetNowPlaying() -> expected { } // If we reach this point, there is no current session - return std::unexpected(NowPlayingError { NowPlayingCode::NoActivePlayer }); - } catch (const winrt::hresult_error& e) { return std::unexpected(NowPlayingError { e }); } + return Err(NowPlayingCode::NoActivePlayer); + } catch (const winrt::hresult_error& e) { return Err(e); } } -fn GetOSVersion() -> expected { - // First try using the native Windows API +fn GetOSVersion() -> Result { constexpr OSVERSIONINFOEXW osvi = { sizeof(OSVERSIONINFOEXW), 0, 0, 0, 0, { 0 }, 0, 0, 0, 0, 0 }; NTSTATUS status = 0; - // Get RtlGetVersion function from ntdll.dll (not affected by application manifest) if (const HMODULE ntdllHandle = GetModuleHandleW(L"ntdll.dll")) if (const auto rtlGetVersion = std::bit_cast(GetProcAddress(ntdllHandle, "RtlGetVersion"))) status = rtlGetVersion(std::bit_cast(&osvi)); - string productName; - string edition; + String productName; + String edition; - if (status == 0) { // STATUS_SUCCESS - // We need to get the edition information which isn't available from version API - // Use GetProductInfo which is available since Vista + if (status == 0) { DWORD productType = 0; if (GetProductInfo( osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.wServicePackMajor, osvi.wServicePackMinor, &productType )) { if (osvi.dwMajorVersion == 10) { - if (osvi.dwBuildNumber >= 22000) { + if (osvi.dwBuildNumber >= 22000) productName = "Windows 11"; - } else { + else productName = "Windows 10"; - } switch (productType) { case PRODUCT_PROFESSIONAL: @@ -235,22 +226,20 @@ fn GetOSVersion() -> expected { } } } else { - // Fallback to registry method if the API approach fails productName = GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "ProductName"); - // Check for Windows 11 if (const i32 buildNumber = stoi( GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "CurrentBuildNumber") ); - buildNumber >= 22000 && productName.find("Windows 10") != string::npos) + buildNumber >= 22000 && productName.find("Windows 10") != String::npos) productName.replace(productName.find("Windows 10"), 10, "Windows 11"); } if (!productName.empty()) { - string result = productName + edition; + String result = productName + edition; - const string displayVersion = + const String displayVersion = GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "DisplayVersion"); if (!displayVersion.empty()) @@ -262,13 +251,13 @@ fn GetOSVersion() -> expected { return "Windows"; } -fn GetHost() -> string { - string hostName = GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SYSTEM\HardwareConfig\Current)", "SystemFamily"); +fn GetHost() -> String { + String hostName = GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SYSTEM\HardwareConfig\Current)", "SystemFamily"); return hostName; } -fn GetKernelVersion() -> string { +fn GetKernelVersion() -> String { // ReSharper disable once CppLocalVariableMayBeConst if (HMODULE ntdllHandle = GetModuleHandleW(L"ntdll.dll")) { if (const auto rtlGetVersion = std::bit_cast(GetProcAddress(ntdllHandle, "RtlGetVersion"))) { @@ -286,28 +275,24 @@ fn GetKernelVersion() -> string { return ""; } -fn GetWindowManager() -> string { - // Get process information once and reuse it +fn GetWindowManager() -> String { const auto processInfo = GetProcessInfo(); - std::vector processNames; + std::vector processNames; processNames.reserve(processInfo.size()); for (const auto& name : processInfo | std::views::values) processNames.push_back(name); - // Check for third-party WMs using a map for cleaner code - const std::unordered_map wmProcesses = { + const std::unordered_map wmProcesses = { { "glazewm.exe", "GlazeWM" }, { "fancywm.exe", "FancyWM" }, { "komorebi.exe", "Komorebi" }, { "komorebic.exe", "Komorebi" } }; - for (const auto& [processName, wmName] : wmProcesses) { + for (const auto& [processName, wmName] : wmProcesses) if (IsProcessRunning(processNames, processName)) return wmName; - } - // Fallback to DWM detection BOOL compositionEnabled = FALSE; if (SUCCEEDED(DwmIsCompositionEnabled(&compositionEnabled))) return compositionEnabled ? "DWM" : "Windows Manager (Basic)"; @@ -315,9 +300,8 @@ fn GetWindowManager() -> string { return "Windows Manager"; } -fn GetDesktopEnvironment() -> optional { - // Get version information from registry - const string buildStr = +fn GetDesktopEnvironment() -> Option { + const String buildStr = GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "CurrentBuildNumber"); if (buildStr.empty()) { @@ -339,10 +323,10 @@ fn GetDesktopEnvironment() -> optional { // Windows 8.1/10 Metro Era if (build >= 9200) { // Windows 8+ // Distinguish between Windows 8 and 10 - const string productName = + const String productName = GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "ProductName"); - if (productName.find("Windows 10") != string::npos) + if (productName.find("Windows 10") != String::npos) return "Metro (Windows 10)"; if (build >= 9600) @@ -355,7 +339,7 @@ fn GetDesktopEnvironment() -> optional { if (build >= 7600) return "Aero (Windows 7)"; - // Older versions + // Pre-Win7 return "Classic"; } catch (...) { DEBUG_LOG("Failed to parse CurrentBuildNumber"); @@ -363,9 +347,10 @@ fn GetDesktopEnvironment() -> optional { } } -fn GetShell() -> string { - // Define known shells map once for reuse - const std::unordered_map knownShells = { +fn GetShell() -> String { + // TODO: update this to use GetEnv + + const std::unordered_map knownShells = { { "cmd.exe", "Command Prompt" }, { "powershell.exe", "PowerShell" }, { "pwsh.exe", "PowerShell Core" }, @@ -374,18 +359,15 @@ fn GetShell() -> string { { "bash.exe", "Windows Subsystem for Linux" } }; - // Detect MSYS2/MinGW shells char* msystemEnv = nullptr; if (_dupenv_s(&msystemEnv, nullptr, "MSYSTEM") == 0 && msystemEnv != nullptr) { const std::unique_ptr msystemEnvGuard(msystemEnv, free); - // Get shell from environment variables char* shell = nullptr; size_t shellLen = 0; _dupenv_s(&shell, &shellLen, "SHELL"); const std::unique_ptr shellGuard(shell, free); - // If SHELL is empty, try LOGINSHELL if (!shell || strlen(shell) == 0) { char* loginShell = nullptr; size_t loginShellLen = 0; @@ -395,36 +377,35 @@ fn GetShell() -> string { } if (shell) { - string shellExe; - const string shellPath = shell; + String shellExe; + const String shellPath = shell; const size_t lastSlash = shellPath.find_last_of("\\/"); - shellExe = (lastSlash != string::npos) ? shellPath.substr(lastSlash + 1) : shellPath; + shellExe = (lastSlash != String::npos) ? shellPath.substr(lastSlash + 1) : shellPath; std::ranges::transform(shellExe, shellExe.begin(), ::tolower); // Use a map for shell name lookup instead of multiple if statements - const std::unordered_map shellNames = { + const std::unordered_map shellNames = { { "bash", "Bash" }, { "zsh", "Zsh" }, { "fish", "Fish" } }; for (const auto& [pattern, name] : shellNames) { - if (shellExe.find(pattern) != string::npos) + if (shellExe.find(pattern) != String::npos) return name; } return shellExe.empty() ? "MSYS2" : "MSYS2/" + shellExe; } - // Fallback to process ancestry with cached process info const auto processInfo = GetProcessInfo(); DWORD pid = GetCurrentProcessId(); while (pid != 0) { - string processName = GetProcessName(pid); + String processName = GetProcessName(pid); std::ranges::transform(processName, processName.begin(), ::tolower); - const std::unordered_map msysShells = { + const std::unordered_map msysShells = { { "bash.exe", "Bash" }, { "zsh.exe", "Zsh" }, { "fish.exe", "Fish" }, @@ -442,10 +423,9 @@ fn GetShell() -> string { return "MSYS2"; } - // Detect Windows shells DWORD pid = GetCurrentProcessId(); while (pid != 0) { - string processName = GetProcessName(pid); + String processName = GetProcessName(pid); std::ranges::transform(processName, processName.begin(), ::tolower); if (auto shellIterator = knownShells.find(processName); shellIterator != knownShells.end()) diff --git a/src/util/macros.h b/src/util/macros.h index fb5d51b..6b673ad 100644 --- a/src/util/macros.h +++ b/src/util/macros.h @@ -15,6 +15,11 @@ #define fn auto +#ifdef None +#undef None +#define None std::nullopt +#endif + namespace term { enum class Emphasis : u8 { none = 0, bold = 1, italic = 2 }; @@ -75,7 +80,7 @@ namespace term { constexpr fn operator|(Emphasis emph, FgColor fgColor)->Style { return { .emph = emph, .fg_col = fgColor }; } constexpr fn operator|(FgColor fgColor, Emphasis emph)->Style { return { .emph = emph, .fg_col = fgColor }; } - constexpr const char* reset = "\033[0m"; + constexpr CStr reset = "\033[0m"; template fn Print(const Style& style, std::format_string fmt, Args&&... args) -> void { diff --git a/src/util/types.h b/src/util/types.h index e6daf7b..08b8200 100644 --- a/src/util/types.h +++ b/src/util/types.h @@ -1,10 +1,17 @@ #pragma once +#include #include #include #include #include +#include +#include +#include #include +#include +#include +#include #ifdef _WIN32 // ReSharper disable once CppUnusedIncludeDirective @@ -131,11 +138,101 @@ using usize = std::size_t; using isize = std::ptrdiff_t; /** - * @typedef string + * @typedef String * @brief Represents a string. */ using String = std::string; +/** + * @typedef StringView + * @brief Represents a string view. + * + * This type alias is used for non-owning views of strings, allowing for efficient string manipulation + * without copying the underlying data. + */ +using StringView = std::string_view; + +/** + * @typedef Exception + * @brief Represents a generic exception type. + */ +using Exception = std::exception; + +/** + * @typedef Expected + * @brief Represents an expected value or an error. + */ +template +using Result = std::expected; + +/** + * @typedef Unexpected + * @brief Represents an unexpected error. + */ +template +using Err = std::unexpected; + +/** + * @typedef Optional + * @brief Represents an optional value. + */ +template +using Option = std::optional; + +/** + * @typedef Array + * @brief Represents a fixed-size array. + */ +template +using Array = std::array; + +/** + * @typedef Vec + * @brief Represents a dynamic array (vector). + */ +template +using Vec = std::vector; + +/** + * @typedef Pair + * @brief Represents a pair of values. + */ +template +using Pair = std::pair; + +/** + * @typedef Map + * @brief Represents a map (dictionary) of key-value pairs. + */ +template +using Map = std::map; + +/** + * @typedef SharedPointer + * @brief Represents a shared pointer. + * + * This type alias is used for shared ownership of dynamically allocated objects. + */ +template +using SharedPointer = std::shared_ptr; + +/** + * @typedef UniquePointer + * @brief Represents a unique pointer. + * + * This type alias is used for unique ownership of dynamically allocated objects. + */ +template +using UniquePointer = std::unique_ptr; + +/** + * @typedef CStr + * @brief Represents a C string (const char*). + * + * This type alias is used for C-style strings, which are null-terminated arrays of characters. + */ +using CStr = const char*; + /** * @enum NowPlayingCode * @brief Represents error codes for Now Playing functionality. @@ -145,20 +242,7 @@ enum class NowPlayingCode : u8 { NoActivePlayer, }; -// Platform-specific error details -#ifdef __linux__ -/** - * @typedef LinuxError - * @brief Represents a Linux-specific error. - */ -using LinuxError = String; -#elif defined(__APPLE__) -/** - * @typedef MacError - * @brief Represents a macOS-specific error. - */ -using MacError = String; -#elif defined(_WIN32) +#ifdef _WIN32 /** * @typedef WindowsError * @brief Represents a Windows-specific error. @@ -169,18 +253,16 @@ using WindowsError = winrt::hresult_error; // Unified error type using NowPlayingError = std::variant< NowPlayingCode, -#ifdef __linux__ - LinuxError -#elif defined(__APPLE__) - MacError -#elif defined(_WIN32) +#ifdef _WIN32 WindowsError +#else + String #endif >; enum class EnvError : u8 { NotFound, AccessError }; -inline auto GetEnv(const String& name) -> std::expected { +inline auto GetEnv(const String& name) -> Result { #ifdef _WIN32 char* rawPtr = nullptr; size_t bufferSize = 0; @@ -195,9 +277,10 @@ inline auto GetEnv(const String& name) -> std::expected { free(rawPtr); return result; #else - const char* value = std::getenv(name.c_str()); + CStr value = std::getenv(name.c_str()); + if (!value) - return std::unexpected(EnvError::NotFound); + return Err(EnvError::NotFound); return String(value); #endif