i think i got everything
This commit is contained in:
parent
8293ef42b6
commit
cf51e3e569
19 changed files with 671 additions and 805 deletions
|
@ -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";
|
||||
|
|
34
flake.nix
34
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
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -11,26 +11,26 @@ namespace fs = std::filesystem;
|
|||
|
||||
namespace {
|
||||
fn GetConfigPath() -> fs::path {
|
||||
std::vector<fs::path> possiblePaths;
|
||||
Vec<fs::path> 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<String, EnvError> result = GetEnv("XDG_CONFIG_HOME"))
|
||||
possiblePaths.emplace_back(fs::path(*result) / "draconis++" / "config.toml");
|
||||
|
||||
if (auto result = GetEnv("HOME"); result) {
|
||||
if (Result<String, EnvError> 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<char, 256> username;
|
||||
Array<char, 256> username;
|
||||
|
||||
DWORD size = sizeof(username);
|
||||
defaultName = GetUserNameA(username.data(), &size) ? username.data() : "User";
|
||||
|
||||
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<String, EnvError> envUser = GetEnv("USER");
|
||||
const Result<String, EnvError> 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();
|
||||
|
|
|
@ -17,15 +17,15 @@ using Location = std::variant<String, Coords>;
|
|||
struct General {
|
||||
String name = []() -> String {
|
||||
#ifdef _WIN32
|
||||
std::array<char, 256> username;
|
||||
Array<char, 256> 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<String, EnvError> 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<String> name = tbl["name"].value<String>())
|
||||
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<bool>().value_or(false);
|
||||
return nowPlaying;
|
||||
}
|
||||
static fn fromToml(const toml::table& tbl) -> NowPlaying { return { .enabled = tbl["enabled"].value_or(false) }; }
|
||||
};
|
||||
|
||||
struct Weather {
|
||||
|
@ -60,38 +54,29 @@ struct Weather {
|
|||
|
||||
static fn fromToml(const toml::table& tbl) -> Weather {
|
||||
Weather weather;
|
||||
weather.enabled = tbl["enabled"].value_or<bool>(false);
|
||||
|
||||
if (auto apiKey = tbl["api_key"].value<String>()) {
|
||||
const String& keyVal = apiKey.value();
|
||||
Option<String> apiKey = tbl["api_key"].value<String>();
|
||||
|
||||
if (keyVal.empty())
|
||||
weather.enabled = false;
|
||||
|
||||
weather.api_key = keyVal;
|
||||
} else {
|
||||
weather.enabled = false;
|
||||
}
|
||||
weather.enabled = tbl["enabled"].value_or<bool>(false) && apiKey;
|
||||
|
||||
if (!weather.enabled)
|
||||
return weather;
|
||||
|
||||
weather.show_town_name = tbl["show_town_name"].value_or<bool>(false);
|
||||
weather.units = tbl["units"].value<String>().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<const toml::node> location = tbl["location"]) {
|
||||
if (location.is_string()) {
|
||||
weather.location = location.value<String>().value();
|
||||
} else if (location.is_table()) {
|
||||
const auto* coord = location.as_table();
|
||||
Coords coords;
|
||||
coords.lat = coord->get("lat")->value<double>().value();
|
||||
coords.lon = coord->get("lon")->value<double>().value();
|
||||
weather.location = coords;
|
||||
} else {
|
||||
if (location.is_string())
|
||||
weather.location = *location.value<String>();
|
||||
else if (location.is_table())
|
||||
weather.location = Coords {
|
||||
.lat = *location.as_table()->get("lat")->value<double>(),
|
||||
.lon = *location.as_table()->get("lon")->value<double>(),
|
||||
};
|
||||
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;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#include <chrono>
|
||||
#include <curl/curl.h>
|
||||
#include <expected>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
|
@ -15,27 +14,27 @@ using namespace std::string_literals;
|
|||
namespace {
|
||||
constexpr glz::opts glaze_opts = { .error_on_unknown_keys = false };
|
||||
|
||||
fn GetCachePath() -> std::expected<fs::path, String> {
|
||||
fn GetCachePath() -> Result<fs::path, String> {
|
||||
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<WeatherOutput, String> {
|
||||
std::expected<fs::path, String> cachePath = GetCachePath();
|
||||
fn ReadCacheFromFile() -> Result<WeatherOutput, String> {
|
||||
Result<fs::path, String> 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<glaze_opts>(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<void, String> {
|
||||
std::expected<fs::path, String> cachePath = GetCachePath();
|
||||
fn WriteCacheToFile(const WeatherOutput& data) -> Result<void, String> {
|
||||
Result<fs::path, String> 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<WeatherOutput, String> {
|
||||
fn MakeApiRequest(const String& url) -> Result<WeatherOutput, String> {
|
||||
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<glaze_opts>(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<WeatherOutput, String> data = ReadCacheFromFile()) {
|
||||
if (Result<WeatherOutput, String> data = ReadCacheFromFile()) {
|
||||
const WeatherOutput& dataVal = *data;
|
||||
|
||||
if (const duration<double> 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<WeatherOutput, String>& result) -> WeatherOutput {
|
||||
fn handleApiResult = [](const Result<WeatherOutput, String>& result) -> WeatherOutput {
|
||||
if (!result) {
|
||||
ERROR_LOG("API request failed: {}", result.error());
|
||||
return WeatherOutput {};
|
||||
}
|
||||
|
||||
if (std::expected<void, String> writeResult = WriteCacheToFile(*result); !writeResult)
|
||||
if (Result<void, String> 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<String>(location)) {
|
||||
const auto& city = std::get<String>(location);
|
||||
char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast<int>(city.length()));
|
||||
char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast<i32>(city.length()));
|
||||
DEBUG_LOG("Requesting city: {}", escaped);
|
||||
|
||||
const String apiUrl =
|
||||
|
|
|
@ -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);
|
||||
|
||||
static constexpr glz::detail::Object value = glz::object("description", &T::description);
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -19,7 +20,8 @@ struct Main {
|
|||
|
||||
struct glaze {
|
||||
using T = Main;
|
||||
static constexpr auto value = glz::object("temp", &T::temp);
|
||||
|
||||
static constexpr glz::detail::Object value = glz::object("temp", &T::temp);
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -31,12 +33,14 @@ struct Coords {
|
|||
struct WeatherOutput {
|
||||
Main main;
|
||||
String name;
|
||||
std::vector<Condition> weather;
|
||||
Vec<Condition> 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);
|
||||
|
||||
static constexpr glz::detail::Object value =
|
||||
glz::object("main", &T::main, "name", &T::name, "weather", &T::weather, "dt", &T::dt);
|
||||
};
|
||||
};
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
|
|
45
src/main.cpp
45
src/main.cpp
|
@ -1,5 +1,4 @@
|
|||
#include <chrono>
|
||||
#include <expected>
|
||||
#include <ftxui/dom/elements.hpp>
|
||||
#include <ftxui/screen/color.hpp>
|
||||
#include <ftxui/screen/screen.hpp>
|
||||
|
@ -22,7 +21,7 @@ constexpr u64 GIB = 1'073'741'824;
|
|||
|
||||
template <>
|
||||
struct std::formatter<BytesToGiB> : std::formatter<double> {
|
||||
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<f64>(BTG.value) / GIB);
|
||||
}
|
||||
};
|
||||
|
@ -30,7 +29,7 @@ struct std::formatter<BytesToGiB> : std::formatter<double> {
|
|||
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 <typename T, typename E, typename ValueFunc, typename ErrorFunc>
|
||||
fn expected_visit(const std::expected<T, E>& exp, ValueFunc value_func, ErrorFunc error_func) {
|
||||
fn visit_result(const Result<T, E>& exp, ValueFunc value_func, ErrorFunc error_func) {
|
||||
if (exp.has_value())
|
||||
return value_func(*exp);
|
||||
|
||||
|
@ -117,7 +116,7 @@ namespace {
|
|||
|
||||
u32 day = static_cast<u32>(ymd.day());
|
||||
|
||||
const char* suffix = static_cast<const char*>(
|
||||
CStr suffix = static_cast<CStr>(
|
||||
(day >= 11 && day <= 13) ? "th"
|
||||
: (day % 10 == 1) ? "st"
|
||||
: (day % 10 == 2) ? "nd"
|
||||
|
@ -132,12 +131,12 @@ namespace {
|
|||
String date;
|
||||
String host;
|
||||
String kernel_version;
|
||||
std::expected<String, String> os_version;
|
||||
std::expected<u64, String> mem_info;
|
||||
std::optional<String> desktop_environment;
|
||||
Result<String, String> os_version;
|
||||
Result<u64, String> mem_info;
|
||||
Option<String> desktop_environment;
|
||||
String window_manager;
|
||||
std::optional<std::expected<String, NowPlayingError>> now_playing;
|
||||
std::optional<WeatherOutput> weather_info;
|
||||
Option<Result<String, NowPlayingError>> now_playing;
|
||||
Option<WeatherOutput> weather_info;
|
||||
u64 disk_used;
|
||||
u64 disk_total;
|
||||
String shell;
|
||||
|
@ -164,7 +163,7 @@ namespace {
|
|||
|
||||
// Conditional tasks
|
||||
std::future<WeatherOutput> weather;
|
||||
std::future<std::expected<String, NowPlayingError>> nowPlaying;
|
||||
std::future<Result<String, NowPlayingError>> 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<Color::Palette256>(colorIndex)), text(" ") });
|
||||
}) |
|
||||
std::ranges::to<Elements>()
|
||||
|
@ -224,7 +223,7 @@ namespace {
|
|||
return hbox(
|
||||
{
|
||||
text(String(icon)) | color(ui::DEFAULT_THEME.icon),
|
||||
text(String(static_cast<const char*>(label))) | color(ui::DEFAULT_THEME.label),
|
||||
text(String(static_cast<CStr>(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<String, NowPlayingError>& nowPlayingResult = *data.now_playing;
|
||||
nowPlayingResult.has_value()) {
|
||||
if (const Result<String, NowPlayingError>& 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<LinuxError>(error))
|
||||
DEBUG_LOG("DBus error: {}", std::get<LinuxError>(error));
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
if (std::holds_alternative<WindowsError>(error))
|
||||
DEBUG_LOG("WinRT error: {}", to_string(std::get<WindowsError>(error).message()));
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (std::holds_alternative<MacError>(error))
|
||||
DEBUG_LOG("CoreAudio error: {}", std::get<MacError>(error));
|
||||
#else
|
||||
if (std::holds_alternative<String>(error))
|
||||
DEBUG_LOG("NowPlaying error: {}", std::get<String>(error));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
#ifdef __FreeBSD__
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#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<string> {
|
||||
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<sdbus::IProxy> dbusProxy = createProxy(connection, dbusInterface, dbusObjectPath);
|
||||
|
||||
std::vector<string> names;
|
||||
|
||||
dbusProxy->callMethod(dbusMethodListNames).onInterface(dbusInterface).storeResultsTo(names);
|
||||
|
||||
std::vector<string> mprisPlayers;
|
||||
|
||||
for (const std::basic_string<char>& 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<string>& 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<sdbus::IConnection> connection = sdbus::createSessionBusConnection();
|
||||
|
||||
std::vector<string> 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<std::map<String, sdbus::Variant>>()) {
|
||||
const auto& metadata = metadataVariant.get<std::map<String, sdbus::Variant>>();
|
||||
|
||||
auto iter = metadata.find("xesam:title");
|
||||
|
||||
if (iter != metadata.end() && iter->second.containsValueOfType<String>())
|
||||
return iter->second.get<String>();
|
||||
}
|
||||
} 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
|
611
src/os/linux.cpp
611
src/os/linux.cpp
|
@ -1,109 +1,68 @@
|
|||
#ifdef __linux__
|
||||
|
||||
// clang-format off
|
||||
#include <dbus-cxx.h> // needs to be at top for Success/None
|
||||
// clang-format on
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <dirent.h>
|
||||
#include <expected>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/statvfs.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <system_error>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#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 <dbus-cxx.h>
|
||||
|
||||
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;
|
||||
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<Atom>(33),
|
||||
XA_WINDOW,
|
||||
&actualType,
|
||||
&actualFormat,
|
||||
&nitems,
|
||||
&bytesAfter,
|
||||
&data
|
||||
) == 0 &&
|
||||
) == Success &&
|
||||
data) {
|
||||
wmWindow = *bit_cast<Window*>(data);
|
||||
XFree(data);
|
||||
data = nullptr;
|
||||
UniquePointer<u8, decltype(&XFree)> dataGuard(data, XFree);
|
||||
wmWindow = *std::bit_cast<Window*>(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<char*>(data));
|
||||
XFree(data);
|
||||
XCloseDisplay(display);
|
||||
return name;
|
||||
&nameData
|
||||
) == Success &&
|
||||
nameData) {
|
||||
UniquePointer<u8, decltype(&XFree)> nameGuard(nameData, XFree);
|
||||
return std::bit_cast<char*>(nameData);
|
||||
}
|
||||
}
|
||||
|
||||
XCloseDisplay(display);
|
||||
|
||||
return "Unknown (X11)";
|
||||
}
|
||||
|
||||
fn TrimHyprlandWrapper(const String& input) -> String {
|
||||
if (input.contains("hyprland"))
|
||||
return "Hyprland";
|
||||
return input;
|
||||
}
|
||||
|
||||
fn ReadProcessCmdline(int pid) -> String {
|
||||
String path = "/proc/" + to_string(pid) + "/cmdline";
|
||||
ifstream cmdlineFile(path);
|
||||
fn ReadProcessCmdline(i32 pid) -> String {
|
||||
std::ifstream cmdlineFile("/proc/" + std::to_string(pid) + "/cmdline");
|
||||
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<String, EnvError> 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<char, PATH_MAX> buf;
|
||||
String exePath = "/proc/" + std::to_string(cred.pid) + "/exe";
|
||||
Array<char, PATH_MAX> buf;
|
||||
ssize_t lenBuf = readlink(exePath.c_str(), buf.data(), buf.size() - 1);
|
||||
if (lenBuf != -1) {
|
||||
buf.at(static_cast<usize>(lenBuf)) = '\0';
|
||||
String exe(buf.data());
|
||||
if (exe.contains("hyprland")) {
|
||||
wl_display_disconnect(display);
|
||||
if (exe.contains("hyprland"))
|
||||
return "Hyprland";
|
||||
}
|
||||
|
||||
return compositorName.contains("hyprland") ? "Hyprland" : compositorName;
|
||||
}
|
||||
|
||||
wl_display_disconnect(display);
|
||||
fn DetectFromEnvVars() -> Option<String> {
|
||||
if (Result<String, EnvError> xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP")) {
|
||||
const size_t colon = xdgCurrentDesktop->find(':');
|
||||
|
||||
// Final cleanup of wrapper names
|
||||
return TrimHyprlandWrapper(compositorName);
|
||||
if (colon != String::npos)
|
||||
return xdgCurrentDesktop->substr(0, colon);
|
||||
|
||||
return *xdgCurrentDesktop;
|
||||
}
|
||||
|
||||
fn DetectFromEnvVars() -> optional<String> {
|
||||
// Use RAII to guard against concurrent env modifications
|
||||
static mutex EnvMutex;
|
||||
lock_guard<mutex> lock(EnvMutex);
|
||||
if (Result<String, EnvError> desktopSession = GetEnv("DESKTOP_SESSION"))
|
||||
return *desktopSession;
|
||||
|
||||
// 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
|
||||
return None;
|
||||
}
|
||||
|
||||
// DESKTOP_SESSION
|
||||
if (const char* desktopSession = getenv("DESKTOP_SESSION"))
|
||||
return String(string_view(desktopSession)); // Avoid intermediate view storage
|
||||
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
fn DetectFromSessionFiles() -> optional<String> {
|
||||
static constexpr array<pair<string_view, string_view>, 12> DE_PATTERNS = {
|
||||
fn DetectFromSessionFiles() -> Option<String> {
|
||||
// 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 },
|
||||
static constexpr Array<Pair<StringView, StringView>, 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<string_view, string_view>::first));
|
||||
static constexpr Array<StringView, 2> SESSION_PATHS = { "/usr/share/xsessions", "/usr/share/wayland-sessions" };
|
||||
|
||||
// Precomputed session paths
|
||||
static constexpr array<string_view, 2> 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<string_view, string_view>* const patternIter = lower_bound(
|
||||
DE_PATTERNS, lowercaseStem, less {}, &pair<string_view, string_view>::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<String> {
|
||||
const array processChecks = {
|
||||
fn DetectFromProcesses() -> Option<String> {
|
||||
// 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 },
|
||||
const Array<Pair<StringView, StringView>, 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<char>(cmdline)), istreambuf_iterator<char>());
|
||||
std::ifstream cmdline("/proc/self/environ");
|
||||
String envVars((std::istreambuf_iterator<char>(cmdline)), std::istreambuf_iterator<char>());
|
||||
|
||||
for (const auto& [process, deName] : processChecks)
|
||||
if (envVars.contains(process))
|
||||
return String(deName);
|
||||
|
||||
return nullopt;
|
||||
return None;
|
||||
}
|
||||
|
||||
fn GetMprisPlayers(const std::shared_ptr<DBus::Connection>& connection) -> expected<vector<String>, NowPlayingError> {
|
||||
fn GetMprisPlayers(const SharedPointer<DBus::Connection>& connection) -> Result<Vec<String>, NowPlayingError> {
|
||||
try {
|
||||
// Create the method call object
|
||||
std::shared_ptr<DBus::CallMessage> call =
|
||||
SharedPointer<DBus::CallMessage> 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<DBus::Message> reply = connection->send_with_reply_blocking(call, 5000);
|
||||
SharedPointer<DBus::Message> 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<String> allNamesStd;
|
||||
Vec<String> allNamesStd;
|
||||
DBus::MessageIterator reader(*reply);
|
||||
reader >> allNamesStd;
|
||||
|
||||
// Filter for MPRIS players
|
||||
vector<String> mprisPlayers; // Use std::string as String=std::string
|
||||
for (const auto& name : allNamesStd) {
|
||||
if (string_view(name).contains("org.mpris.MediaPlayer2")) {
|
||||
Vec<String> 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<String>& mprisPlayers) -> optional<String> {
|
||||
if (!mprisPlayers.empty())
|
||||
return mprisPlayers.front();
|
||||
return nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
fn GetOSVersion() -> expected<String, String> {
|
||||
constexpr const char* path = "/etc/os-release";
|
||||
fn GetOSVersion() -> Result<String, String> {
|
||||
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);
|
||||
|
||||
return prettyName;
|
||||
if (!valueView.empty() && valueView.front() == '"' && valueView.back() == '"') {
|
||||
valueView.remove_prefix(1);
|
||||
valueView.remove_suffix(1);
|
||||
}
|
||||
|
||||
return unexpected("PRETTY_NAME line not found in " + String(path));
|
||||
return String(valueView);
|
||||
}
|
||||
|
||||
fn GetMemInfo() -> expected<u64, String> {
|
||||
using std::from_chars, std::errc;
|
||||
|
||||
constexpr const char* path = "/proc/meminfo";
|
||||
|
||||
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 Err(std::format("PRETTY_NAME line not found in {}", path));
|
||||
}
|
||||
|
||||
return unexpected("MemTotal line not found in " + String(path));
|
||||
fn GetMemInfo() -> Result<u64, String> {
|
||||
struct sysinfo info;
|
||||
|
||||
if (sysinfo(&info) != 0)
|
||||
return Err(std::format("sysinfo failed: {}", std::error_code(errno, std::generic_category()).message()));
|
||||
|
||||
return static_cast<u64>(info.totalram * info.mem_unit);
|
||||
}
|
||||
|
||||
fn GetNowPlaying() -> expected<String, NowPlayingError> {
|
||||
fn GetNowPlaying() -> Result<String, NowPlayingError> {
|
||||
try {
|
||||
// 1. Get Dispatcher and Session Bus Connection
|
||||
std::shared_ptr<DBus::Dispatcher> dispatcher = DBus::StandaloneDispatcher::create();
|
||||
SharedPointer<DBus::Dispatcher> dispatcher = DBus::StandaloneDispatcher::create();
|
||||
if (!dispatcher)
|
||||
return unexpected(LinuxError("Failed to create DBus dispatcher"));
|
||||
return Err("Failed to create DBus dispatcher");
|
||||
|
||||
std::shared_ptr<DBus::Connection> connection = dispatcher->create_connection(DBus::BusType::SESSION);
|
||||
SharedPointer<DBus::Connection> 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<Vec<String>, NowPlayingError> mprisPlayersResult = GetMprisPlayers(connection);
|
||||
if (!mprisPlayersResult)
|
||||
return unexpected(mprisPlayersResult.error()); // Forward the error
|
||||
return Err(mprisPlayersResult.error());
|
||||
|
||||
const vector<String>& mprisPlayers = *mprisPlayersResult;
|
||||
const Vec<String>& mprisPlayers = *mprisPlayersResult;
|
||||
|
||||
if (mprisPlayers.empty())
|
||||
return unexpected(NowPlayingError { NowPlayingCode::NoPlayers });
|
||||
return Err(NowPlayingCode::NoPlayers);
|
||||
|
||||
// 3. Determine active player
|
||||
optional<String> 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<DBus::CallMessage> 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<DBus::Message> metadataReply = connection->send_with_reply_blocking(metadataCall, 5000);
|
||||
|
||||
(*call) << interfaceNameStd << propertyNameStd;
|
||||
String title;
|
||||
String artist;
|
||||
|
||||
// Send message and get reply
|
||||
std::shared_ptr<DBus::Message> replyMsg = connection->send_with_reply_blocking(call, 5000); // Use a timeout
|
||||
|
||||
if (!replyMsg) {
|
||||
ERROR_LOG("DBus timeout or null reply in Properties.Get");
|
||||
return unexpected(LinuxError("DBus timeout in Properties.Get"));
|
||||
}
|
||||
|
||||
// 5. Parse the reply
|
||||
if (metadataReply && metadataReply->is_valid()) {
|
||||
try {
|
||||
DBus::MessageIterator iter(*metadataReply);
|
||||
DBus::Variant metadataVariant;
|
||||
// Create reader/iterator from the message
|
||||
DBus::MessageIterator reader(*replyMsg); // Use constructor
|
||||
// *** Correction: Use get<T> on iterator instead of operator>> ***
|
||||
reader >> metadataVariant;
|
||||
iter >> metadataVariant;
|
||||
|
||||
// Check the variant's signature
|
||||
if (metadataVariant.to_signature() != "a{sv}") {
|
||||
return unexpected("Unexpected reply type for Metadata");
|
||||
if (metadataVariant.type() == DBus::DataType::ARRAY) {
|
||||
Map<String, DBus::Variant> metadata = metadataVariant.to_map<String, DBus::Variant>();
|
||||
|
||||
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<String> artists = artistIter->second.to_vector<String>(); !artists.empty())
|
||||
artist = artists[0];
|
||||
} else if (artistIter->second.type() == DBus::DataType::STRING)
|
||||
artist = artistIter->second.to_string();
|
||||
}
|
||||
String artistStd;
|
||||
String titleStd;
|
||||
|
||||
// Get the dictionary using the templated get<T>() method
|
||||
std::map<String, DBus::Variant> 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<std::string>(titleIter->second);
|
||||
} 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()); }
|
||||
}
|
||||
|
||||
// For line 525-534
|
||||
auto artistIter = metadataMap.find("xesam:artist");
|
||||
if (artistIter != metadataMap.end() && artistIter->second.to_signature() == "as") {
|
||||
// Cast to vector<String>
|
||||
std::vector<String> artistsStd = static_cast<std::vector<String>>(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<String, EnvError> waylandDisplay = GetEnv("WAYLAND_DISPLAY");
|
||||
const Result<String, EnvError> 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<String, EnvError> 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<String> {
|
||||
// Try environment variables first
|
||||
if (auto desktopEnvironment = DetectFromEnvVars(); desktopEnvironment.has_value())
|
||||
fn GetDesktopEnvironment() -> Option<String> {
|
||||
if (Option<String> desktopEnvironment = DetectFromEnvVars())
|
||||
return desktopEnvironment;
|
||||
|
||||
// Try session files next
|
||||
if (auto desktopEnvironment = DetectFromSessionFiles(); desktopEnvironment.has_value())
|
||||
if (Option<String> desktopEnvironment = DetectFromSessionFiles())
|
||||
return desktopEnvironment;
|
||||
|
||||
// Fallback to process detection
|
||||
return DetectFromProcesses();
|
||||
}
|
||||
|
||||
fn GetShell() -> String {
|
||||
const string_view shell = getenv("SHELL");
|
||||
const Vec<Pair<String, String>> 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<String, EnvError> 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<const char*>(uts.release);
|
||||
return static_cast<CStr>(uts.release);
|
||||
}
|
||||
|
||||
fn GetDiskUsage() -> pair<u64, u64> {
|
||||
fn GetDiskUsage() -> Pair<u64, u64> {
|
||||
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 };
|
||||
}
|
||||
|
||||
|
|
69
src/os/linux/display_guards.cpp
Normal file
69
src/os/linux/display_guards.cpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
#ifdef __linux__
|
||||
|
||||
#include <utility> // 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
|
69
src/os/linux/display_guards.h
Normal file
69
src/os/linux/display_guards.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#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
|
|
@ -1,9 +1 @@
|
|||
#include "src/os/linux/pkg_count.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
fn GetApkPackageCount() -> optional<usize> {
|
||||
fs::path apkDbPath("/lib/apk/db/installed");
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
|
|
@ -2,37 +2,35 @@
|
|||
|
||||
#include "src/util/macros.h"
|
||||
|
||||
using std::optional;
|
||||
|
||||
// Get package count from dpkg (Debian/Ubuntu)
|
||||
fn GetDpkgPackageCount() -> optional<usize>;
|
||||
fn GetDpkgPackageCount() -> Option<usize>;
|
||||
|
||||
// Get package count from RPM (Red Hat/Fedora/CentOS)
|
||||
fn GetRpmPackageCount() -> std::optional<usize>;
|
||||
fn GetRpmPackageCount() -> Option<usize>;
|
||||
|
||||
// Get package count from pacman (Arch Linux)
|
||||
fn GetPacmanPackageCount() -> std::optional<usize>;
|
||||
fn GetPacmanPackageCount() -> Option<usize>;
|
||||
|
||||
// Get package count from Portage (Gentoo)
|
||||
fn GetPortagePackageCount() -> std::optional<usize>;
|
||||
fn GetPortagePackageCount() -> Option<usize>;
|
||||
|
||||
// Get package count from zypper (openSUSE)
|
||||
fn GetZypperPackageCount() -> std::optional<usize>;
|
||||
fn GetZypperPackageCount() -> Option<usize>;
|
||||
|
||||
// Get package count from apk (Alpine)
|
||||
fn GetApkPackageCount() -> std::optional<usize>;
|
||||
fn GetApkPackageCount() -> Option<usize>;
|
||||
|
||||
// Get package count from nix
|
||||
fn GetNixPackageCount() -> std::optional<usize>;
|
||||
fn GetNixPackageCount() -> Option<usize>;
|
||||
|
||||
// Get package count from flatpak
|
||||
fn GetFlatpakPackageCount() -> std::optional<usize>;
|
||||
fn GetFlatpakPackageCount() -> Option<usize>;
|
||||
|
||||
// Get package count from snap
|
||||
fn GetSnapPackageCount() -> std::optional<usize>;
|
||||
fn GetSnapPackageCount() -> Option<usize>;
|
||||
|
||||
// Get package count from AppImage
|
||||
fn GetAppimagePackageCount() -> std::optional<usize>;
|
||||
fn GetAppimagePackageCount() -> Option<usize>;
|
||||
|
||||
// Get total package count from all available package managers
|
||||
fn GetTotalPackageCount() -> std::optional<usize>;
|
||||
fn GetTotalPackageCount() -> Option<usize>;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#ifdef __APPLE__
|
||||
|
||||
#include <expected>
|
||||
#include <flat_map>
|
||||
#include <span>
|
||||
#include <sys/statvfs.h>
|
||||
|
@ -10,21 +9,21 @@
|
|||
#include "os.h"
|
||||
#include "src/util/types.h"
|
||||
|
||||
fn GetMemInfo() -> expected<u64, String> {
|
||||
fn GetMemInfo() -> Result<u64, String> {
|
||||
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<String, NowPlayingError> { return GetCurrentPlayingInfo(); }
|
||||
fn GetNowPlaying() -> Result<String, NowPlayingError> { return GetCurrentPlayingInfo(); }
|
||||
|
||||
fn GetOSVersion() -> expected<String, String> { return GetMacOSVersion(); }
|
||||
fn GetOSVersion() -> Result<String, String> { return GetMacOSVersion(); }
|
||||
|
||||
fn GetDesktopEnvironment() -> optional<String> { return std::nullopt; }
|
||||
fn GetDesktopEnvironment() -> Option<String> { 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<u64, u64> {
|
||||
struct statvfs vfs;
|
||||
|
||||
|
|
14
src/os/os.h
14
src/os/os.h
|
@ -1,31 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <expected>
|
||||
|
||||
#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<u64, String>;
|
||||
fn GetMemInfo() -> Result<u64, String>;
|
||||
|
||||
/**
|
||||
* @brief Get the currently playing song metadata.
|
||||
*/
|
||||
fn GetNowPlaying() -> expected<String, NowPlayingError>;
|
||||
fn GetNowPlaying() -> Result<String, NowPlayingError>;
|
||||
|
||||
/**
|
||||
* @brief Get the OS version.
|
||||
*/
|
||||
fn GetOSVersion() -> expected<String, String>;
|
||||
fn GetOSVersion() -> Result<String, String>;
|
||||
|
||||
/**
|
||||
* @brief Get the current desktop environment.
|
||||
*/
|
||||
fn GetDesktopEnvironment() -> optional<String>;
|
||||
fn GetDesktopEnvironment() -> Option<String>;
|
||||
|
||||
/**
|
||||
* @brief Get the current window manager.
|
||||
|
@ -56,4 +52,4 @@ fn GetPackageCount() -> u64;
|
|||
* @brief Get the current disk usage.
|
||||
* @return std::pair<u64, u64> Used space/total space
|
||||
*/
|
||||
fn GetDiskUsage() -> std::pair<u64, u64>;
|
||||
fn GetDiskUsage() -> Pair<u64, u64>;
|
||||
|
|
|
@ -8,11 +8,8 @@
|
|||
#include <tlhelp32.h>
|
||||
// clang-format on
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <guiddef.h>
|
||||
#include <ranges>
|
||||
#include <vector>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Media.Control.h>
|
||||
#include <winrt/Windows.Storage.h>
|
||||
|
@ -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::pair<DWORD, string>> {
|
||||
std::vector<std::pair<DWORD, string>> processes;
|
||||
[[nodiscard]] fn getProcesses() const -> std::vector<std::pair<DWORD, String>> {
|
||||
std::vector<std::pair<DWORD, String>> 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<const char*>(pe32.szExeFile)));
|
||||
processes.emplace_back(pe32.th32ProcessID, String(reinterpret_cast<const char*>(pe32.szExeFile)));
|
||||
|
||||
// Add remaining processes
|
||||
while (Process32Next(h_snapshot, &pe32))
|
||||
processes.emplace_back(pe32.th32ProcessID, string(reinterpret_cast<const char*>(pe32.szExeFile)));
|
||||
processes.emplace_back(pe32.th32ProcessID, String(reinterpret_cast<const char*>(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<LPBYTE>(value.data()), &dataSize) !=
|
||||
ERROR_SUCCESS) {
|
||||
|
@ -96,13 +92,13 @@ namespace {
|
|||
return value;
|
||||
}
|
||||
|
||||
fn GetProcessInfo() -> std::vector<std::pair<DWORD, string>> {
|
||||
fn GetProcessInfo() -> std::vector<std::pair<DWORD, String>> {
|
||||
const ProcessSnapshot snapshot;
|
||||
return snapshot.isValid() ? snapshot.getProcesses() : std::vector<std::pair<DWORD, string>> {};
|
||||
return snapshot.isValid() ? snapshot.getProcesses() : std::vector<std::pair<DWORD, String>> {};
|
||||
}
|
||||
|
||||
fn IsProcessRunning(const std::vector<string>& processes, const string& name) -> bool {
|
||||
return std::ranges::any_of(processes, [&name](const string& proc) -> bool {
|
||||
fn IsProcessRunning(const std::vector<String>& 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<u64, string> {
|
||||
fn GetMemInfo() -> Result<u64, String> {
|
||||
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<string, NowPlayingError> {
|
||||
fn GetNowPlaying() -> Result<String, NowPlayingError> {
|
||||
using namespace winrt::Windows::Media::Control;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
|
||||
|
@ -181,36 +177,31 @@ fn GetNowPlaying() -> expected<string, NowPlayingError> {
|
|||
}
|
||||
|
||||
// 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<string, string> {
|
||||
// First try using the native Windows API
|
||||
fn GetOSVersion() -> Result<String, String> {
|
||||
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<RtlGetVersionPtr>(GetProcAddress(ntdllHandle, "RtlGetVersion")))
|
||||
status = rtlGetVersion(std::bit_cast<PRTL_OSVERSIONINFOW>(&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<string, string> {
|
|||
}
|
||||
}
|
||||
} 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<string, string> {
|
|||
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<RtlGetVersionPtr>(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<string> processNames;
|
||||
std::vector<String> 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<string, string> wmProcesses = {
|
||||
const std::unordered_map<String, String> 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<string> {
|
||||
// Get version information from registry
|
||||
const string buildStr =
|
||||
fn GetDesktopEnvironment() -> Option<String> {
|
||||
const String buildStr =
|
||||
GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "CurrentBuildNumber");
|
||||
|
||||
if (buildStr.empty()) {
|
||||
|
@ -339,10 +323,10 @@ fn GetDesktopEnvironment() -> optional<string> {
|
|||
// 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<string> {
|
|||
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<string> {
|
|||
}
|
||||
}
|
||||
|
||||
fn GetShell() -> string {
|
||||
// Define known shells map once for reuse
|
||||
const std::unordered_map<string, string> knownShells = {
|
||||
fn GetShell() -> String {
|
||||
// TODO: update this to use GetEnv
|
||||
|
||||
const std::unordered_map<String, String> 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<char, decltype(&free)> msystemEnvGuard(msystemEnv, free);
|
||||
|
||||
// Get shell from environment variables
|
||||
char* shell = nullptr;
|
||||
size_t shellLen = 0;
|
||||
_dupenv_s(&shell, &shellLen, "SHELL");
|
||||
const std::unique_ptr<char, decltype(&free)> 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<string_view, string> shellNames = {
|
||||
const std::unordered_map<StringView, String> 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<string, string> msysShells = {
|
||||
const std::unordered_map<String, String> 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())
|
||||
|
|
|
@ -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 <typename... Args>
|
||||
fn Print(const Style& style, std::format_string<Args...> fmt, Args&&... args) -> void {
|
||||
|
|
129
src/util/types.h
129
src/util/types.h
|
@ -1,10 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <expected>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#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 <typename Tp, typename Er>
|
||||
using Result = std::expected<Tp, Er>;
|
||||
|
||||
/**
|
||||
* @typedef Unexpected
|
||||
* @brief Represents an unexpected error.
|
||||
*/
|
||||
template <typename Er>
|
||||
using Err = std::unexpected<Er>;
|
||||
|
||||
/**
|
||||
* @typedef Optional
|
||||
* @brief Represents an optional value.
|
||||
*/
|
||||
template <typename Tp>
|
||||
using Option = std::optional<Tp>;
|
||||
|
||||
/**
|
||||
* @typedef Array
|
||||
* @brief Represents a fixed-size array.
|
||||
*/
|
||||
template <typename Tp, std::size_t nm>
|
||||
using Array = std::array<Tp, nm>;
|
||||
|
||||
/**
|
||||
* @typedef Vec
|
||||
* @brief Represents a dynamic array (vector).
|
||||
*/
|
||||
template <typename Tp>
|
||||
using Vec = std::vector<Tp>;
|
||||
|
||||
/**
|
||||
* @typedef Pair
|
||||
* @brief Represents a pair of values.
|
||||
*/
|
||||
template <typename T1, typename T2>
|
||||
using Pair = std::pair<T1, T2>;
|
||||
|
||||
/**
|
||||
* @typedef Map
|
||||
* @brief Represents a map (dictionary) of key-value pairs.
|
||||
*/
|
||||
template <typename Key, typename Val>
|
||||
using Map = std::map<Key, Val>;
|
||||
|
||||
/**
|
||||
* @typedef SharedPointer
|
||||
* @brief Represents a shared pointer.
|
||||
*
|
||||
* This type alias is used for shared ownership of dynamically allocated objects.
|
||||
*/
|
||||
template <typename Tp>
|
||||
using SharedPointer = std::shared_ptr<Tp>;
|
||||
|
||||
/**
|
||||
* @typedef UniquePointer
|
||||
* @brief Represents a unique pointer.
|
||||
*
|
||||
* This type alias is used for unique ownership of dynamically allocated objects.
|
||||
*/
|
||||
template <typename Tp, typename Dp>
|
||||
using UniquePointer = std::unique_ptr<Tp, Dp>;
|
||||
|
||||
/**
|
||||
* @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<String, EnvError> {
|
||||
inline auto GetEnv(const String& name) -> Result<String, EnvError> {
|
||||
#ifdef _WIN32
|
||||
char* rawPtr = nullptr;
|
||||
size_t bufferSize = 0;
|
||||
|
@ -195,9 +277,10 @@ inline auto GetEnv(const String& name) -> std::expected<String, EnvError> {
|
|||
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
|
||||
|
|
Loading…
Add table
Reference in a new issue