i have no idea if any of this is better
This commit is contained in:
parent
ea6bf62c90
commit
a02ffbed47
7 changed files with 326 additions and 248 deletions
368
src/os/linux.cpp
368
src/os/linux.cpp
|
@ -10,7 +10,10 @@
|
|||
#include <filesystem>
|
||||
#include <fmt/format.h>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <sdbus-c++/Error.h>
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
#include <sqlite3.h>
|
||||
#include <sys/socket.h>
|
||||
|
@ -21,36 +24,44 @@
|
|||
#include "os.h"
|
||||
#include "src/util/macros.h"
|
||||
|
||||
using std::errc, std::exception, std::expected, std::from_chars, std::getline, std::istreambuf_iterator, std::less,
|
||||
std::lock_guard, std::map, std::mutex, std::ofstream, std::pair, std::string_view, std::vector, std::nullopt,
|
||||
std::array, std::unique_ptr, std::optional, std::bit_cast, std::to_string, std::ifstream, std::getenv, std::string,
|
||||
std::unexpected, std::ranges::is_sorted, std::ranges::lower_bound, std::ranges::replace, std::ranges::subrange,
|
||||
std::ranges::transform;
|
||||
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
enum SessionType : u8 { Wayland, X11, TTY, Unknown };
|
||||
|
||||
namespace {
|
||||
fn GetMprisPlayers(sdbus::IConnection& connection) -> std::vector<string> {
|
||||
fn GetMprisPlayers(sdbus::IConnection& connection) -> 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);
|
||||
const unique_ptr<sdbus::IProxy> dbusProxy = createProxy(connection, dbusInterface, dbusObjectPath);
|
||||
|
||||
std::vector<string> names;
|
||||
vector<string> names;
|
||||
|
||||
dbusProxy->callMethod(dbusMethodListNames).onInterface(dbusInterface).storeResultsTo(names);
|
||||
|
||||
std::vector<string> mprisPlayers;
|
||||
vector<string> mprisPlayers;
|
||||
|
||||
for (const std::basic_string<char>& name : names)
|
||||
if (const char* mprisInterfaceName = "org.mpris.MediaPlayer2"; name.find(mprisInterfaceName) != std::string::npos)
|
||||
for (const 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<string>& mprisPlayers) -> string {
|
||||
fn GetActivePlayer(const vector<string>& mprisPlayers) -> optional<string> {
|
||||
if (!mprisPlayers.empty())
|
||||
return mprisPlayers.front();
|
||||
|
||||
return "";
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
fn GetX11WindowManager() -> string {
|
||||
|
@ -92,7 +103,7 @@ namespace {
|
|||
&data
|
||||
) == Success &&
|
||||
data) {
|
||||
wmWindow = *std::bit_cast<Window*>(data);
|
||||
memcpy(&wmWindow, data, sizeof(Window));
|
||||
XFree(data);
|
||||
data = nullptr;
|
||||
|
||||
|
@ -111,7 +122,7 @@ namespace {
|
|||
&data
|
||||
) == Success &&
|
||||
data) {
|
||||
string name(std::bit_cast<char*>(data));
|
||||
string name(bit_cast<char*>(data));
|
||||
XFree(data);
|
||||
XCloseDisplay(display);
|
||||
return name;
|
||||
|
@ -123,19 +134,19 @@ namespace {
|
|||
return "Unknown (X11)"; // Changed to empty string
|
||||
}
|
||||
|
||||
fn TrimHyprlandWrapper(const std::string& input) -> string {
|
||||
if (input.find("hyprland") != std::string::npos)
|
||||
fn TrimHyprlandWrapper(const string& input) -> string {
|
||||
if (input.find("hyprland") != string::npos)
|
||||
return "Hyprland";
|
||||
return input;
|
||||
}
|
||||
|
||||
fn ReadProcessCmdline(int pid) -> string {
|
||||
string path = "/proc/" + std::to_string(pid) + "/cmdline";
|
||||
std::ifstream cmdlineFile(path);
|
||||
string cmdline;
|
||||
if (std::getline(cmdlineFile, cmdline)) {
|
||||
string path = "/proc/" + to_string(pid) + "/cmdline";
|
||||
ifstream cmdlineFile(path);
|
||||
string cmdline;
|
||||
if (getline(cmdlineFile, cmdline)) {
|
||||
// Replace null bytes with spaces
|
||||
std::ranges::replace(cmdline, '\0', ' ');
|
||||
replace(cmdline, '\0', ' ');
|
||||
return cmdline;
|
||||
}
|
||||
return "";
|
||||
|
@ -143,16 +154,16 @@ namespace {
|
|||
|
||||
fn DetectHyprlandSpecific() -> string {
|
||||
// Check environment variables first
|
||||
const char* xdgCurrentDesktop = std::getenv("XDG_CURRENT_DESKTOP");
|
||||
const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP");
|
||||
if (xdgCurrentDesktop && strcasestr(xdgCurrentDesktop, "hyprland"))
|
||||
return "Hyprland";
|
||||
|
||||
// Check for Hyprland's specific environment variable
|
||||
if (std::getenv("HYPRLAND_INSTANCE_SIGNATURE"))
|
||||
if (getenv("HYPRLAND_INSTANCE_SIGNATURE"))
|
||||
return "Hyprland";
|
||||
|
||||
// Check for Hyprland socket
|
||||
if (fs::exists("/run/user/" + std::to_string(getuid()) + "/hypr"))
|
||||
if (fs::exists("/run/user/" + to_string(getuid()) + "/hypr"))
|
||||
return "Hyprland";
|
||||
|
||||
return "";
|
||||
|
@ -166,6 +177,7 @@ namespace {
|
|||
|
||||
// Then try the standard Wayland detection
|
||||
wl_display* display = wl_display_connect(nullptr);
|
||||
|
||||
if (!display)
|
||||
return "";
|
||||
|
||||
|
@ -173,6 +185,7 @@ namespace {
|
|||
|
||||
struct ucred cred;
|
||||
socklen_t len = sizeof(cred);
|
||||
|
||||
if (getsockopt(fileDescriptor, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) {
|
||||
wl_display_disconnect(display);
|
||||
return "";
|
||||
|
@ -182,28 +195,28 @@ namespace {
|
|||
string compositorName;
|
||||
|
||||
// 1. Check comm (might be wrapped)
|
||||
string commPath = "/proc/" + std::to_string(cred.pid) + "/comm";
|
||||
std::ifstream commFile(commPath);
|
||||
string commPath = "/proc/" + to_string(cred.pid) + "/comm";
|
||||
ifstream commFile(commPath);
|
||||
if (commFile >> compositorName) {
|
||||
std::ranges::subrange removedRange = std::ranges::remove(compositorName, '\n');
|
||||
subrange removedRange = std::ranges::remove(compositorName, '\n');
|
||||
compositorName.erase(removedRange.begin(), compositorName.end());
|
||||
}
|
||||
|
||||
// 2. Check cmdline for actual binary reference
|
||||
string cmdline = ReadProcessCmdline(cred.pid);
|
||||
if (cmdline.find("hyprland") != std::string::npos) {
|
||||
if (cmdline.find("hyprland") != string::npos) {
|
||||
wl_display_disconnect(display);
|
||||
return "Hyprland";
|
||||
}
|
||||
|
||||
// 3. Check exe symlink
|
||||
string exePath = "/proc/" + std::to_string(cred.pid) + "/exe";
|
||||
std::array<char, PATH_MAX> buf;
|
||||
ssize_t lenBuf = readlink(exePath.c_str(), buf.data(), buf.size() - 1);
|
||||
string exePath = "/proc/" + 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.find("hyprland") != std::string::npos) {
|
||||
if (exe.find("hyprland") != string::npos) {
|
||||
wl_display_disconnect(display);
|
||||
return "Hyprland";
|
||||
}
|
||||
|
@ -215,110 +228,115 @@ namespace {
|
|||
return TrimHyprlandWrapper(compositorName);
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
fn ToLowercase(string str) -> string {
|
||||
std::ranges::transform(str, str.begin(), ::tolower);
|
||||
return str;
|
||||
}
|
||||
fn DetectFromEnvVars() -> optional<string> {
|
||||
// Use RAII to guard against concurrent env modifications
|
||||
static mutex EnvMutex;
|
||||
lock_guard<mutex> lock(EnvMutex);
|
||||
|
||||
fn ContainsAny(std::string_view haystack, const std::vector<std::string>& needles) -> bool {
|
||||
return std::ranges::any_of(needles, [&](auto& n) { return haystack.find(n) != std::string_view::npos; });
|
||||
}
|
||||
|
||||
fn DetectFromEnvVars() -> string {
|
||||
// Check XDG_CURRENT_DESKTOP
|
||||
if (const char* xdgDe = std::getenv("XDG_CURRENT_DESKTOP")) {
|
||||
std::string_view sview(xdgDe);
|
||||
if (!sview.empty()) {
|
||||
string deStr(sview);
|
||||
if (size_t colon = deStr.find(':'); colon != std::string::npos)
|
||||
deStr.erase(colon);
|
||||
if (!deStr.empty())
|
||||
return deStr;
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
// Check DESKTOP_SESSION
|
||||
if (const char* desktopSession = std::getenv("DESKTOP_SESSION")) {
|
||||
std::string_view sview(desktopSession);
|
||||
if (!sview.empty())
|
||||
return std::string(sview);
|
||||
}
|
||||
// DESKTOP_SESSION
|
||||
if (const char* desktopSession = getenv("DESKTOP_SESSION"))
|
||||
return string(string_view(desktopSession)); // Avoid intermediate view storage
|
||||
|
||||
return "";
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
fn DetectFromSessionFiles() -> string {
|
||||
const std::vector<fs::path> sessionPaths = { "/usr/share/xsessions", "/usr/share/wayland-sessions" };
|
||||
|
||||
const std::vector<std::pair<std::string, std::vector<std::string>>> dePatterns = {
|
||||
{ "KDE", { "plasma", "plasmax11", "kde" } },
|
||||
{ "GNOME", { "gnome", "gnome-xorg", "gnome-wayland" } },
|
||||
{ "XFCE", { "xfce" } },
|
||||
{ "MATE", { "mate" } },
|
||||
{ "Cinnamon", { "cinnamon" } },
|
||||
{ "Budgie", { "budgie" } },
|
||||
{ "LXQt", { "lxqt" } },
|
||||
{ "Unity", { "unity" } }
|
||||
fn DetectFromSessionFiles() -> optional<string> {
|
||||
static constexpr array<pair<string_view, string_view>, 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
|
||||
};
|
||||
|
||||
for (const auto& sessionPath : sessionPaths) {
|
||||
if (!fs::exists(sessionPath))
|
||||
static_assert(is_sorted(DE_PATTERNS, {}, &pair<string_view, string_view>::first));
|
||||
|
||||
// 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) {
|
||||
if (!fs::exists(path))
|
||||
continue;
|
||||
|
||||
for (const auto& entry : fs::directory_iterator(sessionPath)) {
|
||||
for (const auto& entry : fs::directory_iterator(path)) {
|
||||
if (!entry.is_regular_file())
|
||||
continue;
|
||||
|
||||
const string filename = entry.path().stem();
|
||||
auto lowerFilename = ToLowercase(filename);
|
||||
// Reuse buffer
|
||||
lowercaseStem = entry.path().stem().string();
|
||||
transform(lowercaseStem, lowercaseStem.begin(), ::tolower);
|
||||
|
||||
for (const auto& [deName, patterns] : dePatterns) {
|
||||
if (ContainsAny(lowerFilename, patterns))
|
||||
return deName;
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
fn DetectFromProcesses() -> string {
|
||||
const std::vector<std::pair<std::string, std::string>> processChecks = {
|
||||
{ "plasmashell", "KDE" },
|
||||
{ "gnome-shell", "GNOME" },
|
||||
{ "xfce4-session", "XFCE" },
|
||||
{ "mate-session", "MATE" },
|
||||
{ "cinnamon-sessio", "Cinnamon" },
|
||||
{ "budgie-wm", "Budgie" },
|
||||
{ "lxqt-session", "LXQt" }
|
||||
fn DetectFromProcesses() -> optional<string> {
|
||||
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
|
||||
};
|
||||
|
||||
std::ifstream cmdline("/proc/self/environ");
|
||||
string envVars((std::istreambuf_iterator<char>(cmdline)), std::istreambuf_iterator<char>());
|
||||
ifstream cmdline("/proc/self/environ");
|
||||
string envVars((istreambuf_iterator<char>(cmdline)), istreambuf_iterator<char>());
|
||||
|
||||
for (const auto& [process, deName] : processChecks)
|
||||
if (envVars.find(process) != std::string::npos)
|
||||
return deName;
|
||||
if (envVars.find(process) != string::npos)
|
||||
return string(deName);
|
||||
|
||||
return "Unknown";
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
fn CountNix() noexcept -> std::optional<usize> {
|
||||
constexpr std::string_view dbPath = "/nix/var/nix/db/db.sqlite";
|
||||
constexpr std::string_view querySql = "SELECT COUNT(*) FROM ValidPaths WHERE sigs IS NOT NULL;";
|
||||
fn CountNix() noexcept -> optional<usize> {
|
||||
constexpr string_view dbPath = "/nix/var/nix/db/db.sqlite";
|
||||
constexpr string_view querySql = "SELECT COUNT(*) FROM ValidPaths WHERE sigs IS NOT NULL;";
|
||||
|
||||
sqlite3* sqlDB = nullptr;
|
||||
sqlite3_stmt* stmt = nullptr;
|
||||
usize count = 0;
|
||||
|
||||
// 1. Direct URI construction without string concatenation
|
||||
const string uri =
|
||||
fmt::format("file:{}{}immutable=1", dbPath, (dbPath.find('?') != std::string_view::npos) ? "&" : "?");
|
||||
const string uri = fmt::format("file:{}{}immutable=1", dbPath, (dbPath.find('?') != string_view::npos) ? "&" : "?");
|
||||
|
||||
// 2. Open database with optimized flags
|
||||
if (sqlite3_open_v2(uri.c_str(), &sqlDB, SQLITE_OPEN_READONLY | SQLITE_OPEN_URI | SQLITE_OPEN_NOMUTEX, nullptr) !=
|
||||
SQLITE_OK)
|
||||
return std::nullopt;
|
||||
return nullopt;
|
||||
|
||||
// 3. Configure database for maximum read performance
|
||||
sqlite3_exec(sqlDB, "PRAGMA journal_mode=OFF; PRAGMA mmap_size=268435456;", nullptr, nullptr, nullptr);
|
||||
|
@ -333,10 +351,10 @@ namespace {
|
|||
}
|
||||
|
||||
sqlite3_close(sqlDB);
|
||||
return count ? std::optional { count } : std::nullopt;
|
||||
return count ? optional { count } : nullopt;
|
||||
}
|
||||
|
||||
fn CountNixWithCache() noexcept -> std::optional<size_t> {
|
||||
fn CountNixWithCache() noexcept -> optional<size_t> {
|
||||
constexpr const char* dbPath = "/nix/var/nix/db/db.sqlite";
|
||||
constexpr const char* cachePath = "/tmp/nix_pkg_count.cache";
|
||||
|
||||
|
@ -347,21 +365,21 @@ namespace {
|
|||
const mtime cacheMtime = fs::last_write_time(cachePath);
|
||||
|
||||
if (fs::exists(cachePath) && dbMtime <= cacheMtime) {
|
||||
std::ifstream cache(cachePath, std::ios::binary);
|
||||
size_t count = 0;
|
||||
cache.read(std::bit_cast<char*>(&count), sizeof(count));
|
||||
return cache ? std::optional(count) : std::nullopt;
|
||||
ifstream cache(cachePath, std::ios::binary);
|
||||
size_t count = 0;
|
||||
cache.read(bit_cast<char*>(&count), sizeof(count));
|
||||
return cache ? optional(count) : nullopt;
|
||||
}
|
||||
} catch (const std::exception& e) { DEBUG_LOG("Cache access failed: {}, rebuilding...", e.what()); }
|
||||
} catch (const exception& e) { DEBUG_LOG("Cache access failed: {}, rebuilding...", e.what()); }
|
||||
|
||||
const std::optional<usize> count = CountNix();
|
||||
const optional<usize> count = CountNix();
|
||||
|
||||
if (count) {
|
||||
constexpr const char* tmpPath = "/tmp/nix_pkg_count.tmp";
|
||||
|
||||
{
|
||||
std::ofstream tmp(tmpPath, std::ios::binary | std::ios::trunc);
|
||||
tmp.write(std::bit_cast<const char*>(&*count), sizeof(*count));
|
||||
ofstream tmp(tmpPath, std::ios::binary | std::ios::trunc);
|
||||
tmp.write(bit_cast<const char*>(&*count), sizeof(*count));
|
||||
}
|
||||
|
||||
fs::rename(tmpPath, cachePath);
|
||||
|
@ -371,20 +389,18 @@ namespace {
|
|||
}
|
||||
}
|
||||
|
||||
fn GetOSVersion() -> string {
|
||||
fn GetOSVersion() -> expected<string, string> {
|
||||
constexpr const char* path = "/etc/os-release";
|
||||
|
||||
std::ifstream file(path);
|
||||
ifstream file(path);
|
||||
|
||||
if (!file.is_open()) {
|
||||
ERROR_LOG("Failed to open {}", path);
|
||||
return "";
|
||||
}
|
||||
if (!file.is_open())
|
||||
return unexpected("Failed to open " + string(path));
|
||||
|
||||
string line;
|
||||
const string prefix = "PRETTY_NAME=";
|
||||
|
||||
while (std::getline(file, line))
|
||||
while (getline(file, line))
|
||||
if (line.starts_with(prefix)) {
|
||||
string prettyName = line.substr(prefix.size());
|
||||
|
||||
|
@ -394,36 +410,39 @@ fn GetOSVersion() -> string {
|
|||
return prettyName;
|
||||
}
|
||||
|
||||
ERROR_LOG("PRETTY_NAME line not found in {}", path);
|
||||
return "";
|
||||
return unexpected("PRETTY_NAME line not found in " + string(path));
|
||||
}
|
||||
|
||||
fn GetMemInfo() -> std::expected<u64, string> {
|
||||
fn GetMemInfo() -> expected<u64, string> {
|
||||
constexpr const char* path = "/proc/meminfo";
|
||||
|
||||
std::ifstream input(path);
|
||||
if (!input.is_open())
|
||||
return std::unexpected("Failed to open " + std::string(path));
|
||||
ifstream input(path);
|
||||
|
||||
std::string line;
|
||||
while (std::getline(input, line)) {
|
||||
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 == std::string::npos)
|
||||
return std::unexpected("Invalid MemTotal line: no colon found");
|
||||
|
||||
std::string_view view(line);
|
||||
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 == std::string_view::npos)
|
||||
return std::unexpected("No number found after colon in MemTotal line");
|
||||
|
||||
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 != std::string_view::npos)
|
||||
if (end != string_view::npos)
|
||||
view = view.substr(0, end);
|
||||
|
||||
// Get pointers via iterators
|
||||
|
@ -431,89 +450,82 @@ fn GetMemInfo() -> std::expected<u64, string> {
|
|||
const char* endPtr = &*view.end();
|
||||
|
||||
u64 value = 0;
|
||||
const auto result = std::from_chars(startPtr, endPtr, value);
|
||||
if (result.ec != std::errc() || result.ptr != endPtr)
|
||||
return std::unexpected("Failed to parse number in MemTotal line");
|
||||
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 std::unexpected("MemTotal line not found in " + std::string(path));
|
||||
return unexpected("MemTotal line not found in " + string(path));
|
||||
}
|
||||
|
||||
fn GetNowPlaying() -> string {
|
||||
fn GetNowPlaying() -> expected<string, NowPlayingError> {
|
||||
try {
|
||||
const char *playerObjectPath = "/org/mpris/MediaPlayer2", *playerInterfaceName = "org.mpris.MediaPlayer2.Player";
|
||||
|
||||
std::unique_ptr<sdbus::IConnection> connection = sdbus::createSessionBusConnection();
|
||||
unique_ptr<sdbus::IConnection> connection = sdbus::createSessionBusConnection();
|
||||
|
||||
std::vector<string> mprisPlayers = GetMprisPlayers(*connection);
|
||||
vector<string> mprisPlayers = GetMprisPlayers(*connection);
|
||||
|
||||
if (mprisPlayers.empty()) {
|
||||
DEBUG_LOG("No MPRIS players found");
|
||||
return "";
|
||||
}
|
||||
if (mprisPlayers.empty())
|
||||
return unexpected(NowPlayingError { NowPlayingCode::NoPlayers });
|
||||
|
||||
string activePlayer = GetActivePlayer(mprisPlayers);
|
||||
optional<string> activePlayer = GetActivePlayer(mprisPlayers);
|
||||
|
||||
if (activePlayer.empty()) {
|
||||
DEBUG_LOG("No active player found");
|
||||
return "";
|
||||
}
|
||||
if (!activePlayer.has_value())
|
||||
return unexpected(NowPlayingError { NowPlayingCode::NoActivePlayer });
|
||||
|
||||
std::unique_ptr<sdbus::IProxy> playerProxy =
|
||||
sdbus::createProxy(*connection, sdbus::ServiceName(activePlayer), sdbus::ObjectPath(playerObjectPath));
|
||||
unique_ptr<sdbus::IProxy> playerProxy =
|
||||
sdbus::createProxy(*connection, sdbus::ServiceName(*activePlayer), sdbus::ObjectPath(playerObjectPath));
|
||||
|
||||
sdbus::Variant metadataVariant = playerProxy->getProperty("Metadata").onInterface(playerInterfaceName);
|
||||
|
||||
if (metadataVariant.containsValueOfType<std::map<std::string, sdbus::Variant>>()) {
|
||||
const std::map<std::basic_string<char>, sdbus::Variant>& metadata =
|
||||
metadataVariant.get<std::map<std::string, sdbus::Variant>>();
|
||||
if (metadataVariant.containsValueOfType<map<string, sdbus::Variant>>()) {
|
||||
const map<string, sdbus::Variant>& metadata = metadataVariant.get<map<string, sdbus::Variant>>();
|
||||
|
||||
string title;
|
||||
auto titleIter = metadata.find("xesam:title");
|
||||
if (titleIter != metadata.end() && titleIter->second.containsValueOfType<std::string>()) {
|
||||
title = titleIter->second.get<std::string>();
|
||||
}
|
||||
if (titleIter != metadata.end() && titleIter->second.containsValueOfType<string>())
|
||||
title = titleIter->second.get<string>();
|
||||
|
||||
string artist;
|
||||
auto artistIter = metadata.find("xesam:artist");
|
||||
if (artistIter != metadata.end() && artistIter->second.containsValueOfType<std::vector<std::string>>()) {
|
||||
auto artists = artistIter->second.get<std::vector<std::string>>();
|
||||
if (!artists.empty()) {
|
||||
if (artistIter != metadata.end() && artistIter->second.containsValueOfType<vector<string>>()) {
|
||||
auto artists = artistIter->second.get<vector<string>>();
|
||||
if (!artists.empty())
|
||||
artist = artists[0];
|
||||
}
|
||||
}
|
||||
|
||||
string result;
|
||||
if (!artist.empty() && !title.empty()) {
|
||||
|
||||
if (!artist.empty() && !title.empty())
|
||||
result = artist + " - " + title;
|
||||
} else if (!title.empty()) {
|
||||
else if (!title.empty())
|
||||
result = title;
|
||||
} else if (!artist.empty()) {
|
||||
else if (!artist.empty())
|
||||
result = artist;
|
||||
} else {
|
||||
else
|
||||
result = "";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} catch (const sdbus::Error& e) {
|
||||
if (e.getName() != "com.github.altdesktop.playerctld.NoActivePlayer") {
|
||||
ERROR_LOG("Error: {}", e.what());
|
||||
return "";
|
||||
}
|
||||
if (e.getName() != "com.github.altdesktop.playerctld.NoActivePlayer")
|
||||
return unexpected(NowPlayingError { LinuxError(e) });
|
||||
|
||||
return "No active player";
|
||||
return unexpected(NowPlayingError { NowPlayingCode::NoActivePlayer });
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
fn GetWindowManager() -> string {
|
||||
// Check environment variables first
|
||||
const char* xdgSessionType = std::getenv("XDG_SESSION_TYPE");
|
||||
const char* waylandDisplay = std::getenv("WAYLAND_DISPLAY");
|
||||
const char* xdgSessionType = getenv("XDG_SESSION_TYPE");
|
||||
const char* waylandDisplay = getenv("WAYLAND_DISPLAY");
|
||||
|
||||
// Prefer Wayland detection if Wayland session
|
||||
if ((waylandDisplay != nullptr) || (xdgSessionType && strstr(xdgSessionType, "wayland"))) {
|
||||
|
@ -522,11 +534,11 @@ fn GetWindowManager() -> string {
|
|||
return compositor;
|
||||
|
||||
// Fallback environment check
|
||||
const char* xdgCurrentDesktop = std::getenv("XDG_CURRENT_DESKTOP");
|
||||
const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP");
|
||||
if (xdgCurrentDesktop) {
|
||||
string desktop(xdgCurrentDesktop);
|
||||
std::ranges::transform(compositor, compositor.begin(), ::tolower);
|
||||
if (desktop.find("hyprland") != std::string::npos)
|
||||
transform(compositor, compositor.begin(), ::tolower);
|
||||
if (desktop.find("hyprland") != string::npos)
|
||||
return "hyprland";
|
||||
}
|
||||
}
|
||||
|
@ -539,13 +551,13 @@ fn GetWindowManager() -> string {
|
|||
return "Unknown";
|
||||
}
|
||||
|
||||
fn GetDesktopEnvironment() -> string {
|
||||
fn GetDesktopEnvironment() -> optional<string> {
|
||||
// Try environment variables first
|
||||
if (auto desktopEnvironment = DetectFromEnvVars(); !desktopEnvironment.empty())
|
||||
if (auto desktopEnvironment = DetectFromEnvVars(); desktopEnvironment.has_value())
|
||||
return desktopEnvironment;
|
||||
|
||||
// Try session files next
|
||||
if (auto desktopEnvironment = DetectFromSessionFiles(); !desktopEnvironment.empty())
|
||||
if (auto desktopEnvironment = DetectFromSessionFiles(); desktopEnvironment.has_value())
|
||||
return desktopEnvironment;
|
||||
|
||||
// Fallback to process detection
|
||||
|
@ -553,7 +565,7 @@ fn GetDesktopEnvironment() -> string {
|
|||
}
|
||||
|
||||
fn GetShell() -> string {
|
||||
const char* shell = std::getenv("SHELL");
|
||||
const char* shell = getenv("SHELL");
|
||||
|
||||
return shell ? shell : "";
|
||||
}
|
||||
|
@ -561,14 +573,14 @@ fn GetShell() -> string {
|
|||
fn GetHost() -> string {
|
||||
constexpr const char* path = "/sys/class/dmi/id/product_family";
|
||||
|
||||
std::ifstream file(path);
|
||||
ifstream file(path);
|
||||
if (!file.is_open()) {
|
||||
ERROR_LOG("Failed to open {}", path);
|
||||
return "";
|
||||
}
|
||||
|
||||
string productFamily;
|
||||
if (!std::getline(file, productFamily)) {
|
||||
if (!getline(file, productFamily)) {
|
||||
ERROR_LOG("Failed to read from {}", path);
|
||||
return "";
|
||||
}
|
||||
|
@ -580,7 +592,7 @@ fn GetKernelVersion() -> string {
|
|||
struct utsname uts;
|
||||
|
||||
if (uname(&uts) == -1) {
|
||||
ERROR_LOG("uname() failed: {}", std::strerror(errno));
|
||||
ERROR_LOG("uname() failed: {}", strerror(errno));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue