i have no idea if this works
This commit is contained in:
parent
203d56e06b
commit
8293ef42b6
6 changed files with 262 additions and 271 deletions
23
_sources/generated.json
Normal file
23
_sources/generated.json
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"dbus-cxx": {
|
||||||
|
"cargoLocks": null,
|
||||||
|
"date": null,
|
||||||
|
"extract": null,
|
||||||
|
"name": "dbus-cxx",
|
||||||
|
"passthru": null,
|
||||||
|
"pinned": false,
|
||||||
|
"src": {
|
||||||
|
"deepClone": false,
|
||||||
|
"fetchSubmodules": false,
|
||||||
|
"leaveDotGit": false,
|
||||||
|
"name": null,
|
||||||
|
"owner": "dbus-cxx",
|
||||||
|
"repo": "dbus-cxx",
|
||||||
|
"rev": "2.5.2",
|
||||||
|
"sha256": "sha256-if/9XIsf3an5Sij91UIIyNB3vlFAcKrm6YT5Mk7NhB0=",
|
||||||
|
"sparseCheckout": [],
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"version": "2.5.2"
|
||||||
|
}
|
||||||
|
}
|
15
_sources/generated.nix
Normal file
15
_sources/generated.nix
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# This file was generated by nvfetcher, please do not modify it manually.
|
||||||
|
{ fetchgit, fetchurl, fetchFromGitHub, dockerTools }:
|
||||||
|
{
|
||||||
|
dbus-cxx = {
|
||||||
|
pname = "dbus-cxx";
|
||||||
|
version = "2.5.2";
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "dbus-cxx";
|
||||||
|
repo = "dbus-cxx";
|
||||||
|
rev = "2.5.2";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
sha256 = "sha256-if/9XIsf3an5Sij91UIIyNB3vlFAcKrm6YT5Mk7NhB0=";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
44
flake.nix
44
flake.nix
|
@ -28,6 +28,44 @@
|
||||||
)
|
)
|
||||||
llvmPackages.stdenv;
|
llvmPackages.stdenv;
|
||||||
|
|
||||||
|
sources = import ./_sources/generated.nix {
|
||||||
|
inherit (pkgs) fetchFromGitHub fetchgit fetchurl dockerTools;
|
||||||
|
};
|
||||||
|
|
||||||
|
dbus-cxx = stdenv.mkDerivation {
|
||||||
|
inherit (sources.dbus-cxx) pname version src;
|
||||||
|
nativeBuildInputs = [
|
||||||
|
pkgs.cmake
|
||||||
|
pkgs.pkg-config
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
pkgs.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
|
||||||
|
'';
|
||||||
|
|
||||||
|
cmakeFlags = [
|
||||||
|
"-DENABLE_QT_SUPPORT=OFF"
|
||||||
|
"-DENABLE_UV_SUPPORT=OFF"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
deps = with pkgs;
|
deps = with pkgs;
|
||||||
[
|
[
|
||||||
(glaze.override {enableAvx2 = hostPlatform.isx86;})
|
(glaze.override {enableAvx2 = hostPlatform.isx86;})
|
||||||
|
@ -35,7 +73,9 @@
|
||||||
++ (with pkgsStatic; [
|
++ (with pkgsStatic; [
|
||||||
curl
|
curl
|
||||||
ftxui
|
ftxui
|
||||||
tomlplusplus
|
(tomlplusplus.overrideAttrs {
|
||||||
|
doCheck = false;
|
||||||
|
})
|
||||||
])
|
])
|
||||||
++ darwinPkgs
|
++ darwinPkgs
|
||||||
++ linuxPkgs;
|
++ linuxPkgs;
|
||||||
|
@ -44,10 +84,12 @@
|
||||||
|
|
||||||
linuxPkgs = nixpkgs.lib.optionals stdenv.isLinux (with pkgs;
|
linuxPkgs = nixpkgs.lib.optionals stdenv.isLinux (with pkgs;
|
||||||
[
|
[
|
||||||
|
libsigcxx30
|
||||||
valgrind
|
valgrind
|
||||||
]
|
]
|
||||||
++ (with pkgsStatic; [
|
++ (with pkgsStatic; [
|
||||||
dbus
|
dbus
|
||||||
|
dbus-cxx
|
||||||
sqlitecpp
|
sqlitecpp
|
||||||
xorg.libX11
|
xorg.libX11
|
||||||
wayland
|
wayland
|
||||||
|
|
|
@ -129,7 +129,8 @@ elif host_system == 'linux' or host_system == 'freebsd'
|
||||||
dependency('xau'),
|
dependency('xau'),
|
||||||
dependency('xdmcp'),
|
dependency('xdmcp'),
|
||||||
dependency('wayland-client'),
|
dependency('wayland-client'),
|
||||||
dependency('dbus-1', include_type: 'system'),
|
dependency('sigc++-3.0', include_type: 'system'),
|
||||||
|
dependency('dbus-cxx', include_type: 'system'),
|
||||||
]
|
]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
3
nvfetcher.toml
Normal file
3
nvfetcher.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[dbus-cxx]
|
||||||
|
src.github = "dbus-cxx/dbus-cxx"
|
||||||
|
fetch.github = "dbus-cxx/dbus-cxx"
|
445
src/os/linux.cpp
445
src/os/linux.cpp
|
@ -4,12 +4,11 @@
|
||||||
#include <X11/Xlib.h>
|
#include <X11/Xlib.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <dbus/dbus.h>
|
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <expected>
|
#include <expected>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
|
@ -23,6 +22,15 @@
|
||||||
#include "os.h"
|
#include "os.h"
|
||||||
#include "src/util/macros.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::expected;
|
||||||
using std::optional;
|
using std::optional;
|
||||||
|
|
||||||
|
@ -51,7 +59,7 @@ namespace {
|
||||||
using std::ranges::subrange;
|
using std::ranges::subrange;
|
||||||
using std::ranges::transform;
|
using std::ranges::transform;
|
||||||
|
|
||||||
fn GetX11WindowManager() -> string {
|
fn GetX11WindowManager() -> String {
|
||||||
Display* display = XOpenDisplay(nullptr);
|
Display* display = XOpenDisplay(nullptr);
|
||||||
|
|
||||||
// If XOpenDisplay fails, likely in a TTY
|
// If XOpenDisplay fails, likely in a TTY
|
||||||
|
@ -88,7 +96,7 @@ namespace {
|
||||||
&nitems,
|
&nitems,
|
||||||
&bytesAfter,
|
&bytesAfter,
|
||||||
&data
|
&data
|
||||||
) == Success &&
|
) == 0 &&
|
||||||
data) {
|
data) {
|
||||||
wmWindow = *bit_cast<Window*>(data);
|
wmWindow = *bit_cast<Window*>(data);
|
||||||
XFree(data);
|
XFree(data);
|
||||||
|
@ -107,9 +115,9 @@ namespace {
|
||||||
&nitems,
|
&nitems,
|
||||||
&bytesAfter,
|
&bytesAfter,
|
||||||
&data
|
&data
|
||||||
) == Success &&
|
) == 0 &&
|
||||||
data) {
|
data) {
|
||||||
string name(bit_cast<char*>(data));
|
String name(bit_cast<char*>(data));
|
||||||
XFree(data);
|
XFree(data);
|
||||||
XCloseDisplay(display);
|
XCloseDisplay(display);
|
||||||
return name;
|
return name;
|
||||||
|
@ -121,16 +129,16 @@ namespace {
|
||||||
return "Unknown (X11)";
|
return "Unknown (X11)";
|
||||||
}
|
}
|
||||||
|
|
||||||
fn TrimHyprlandWrapper(const string& input) -> string {
|
fn TrimHyprlandWrapper(const String& input) -> String {
|
||||||
if (input.contains("hyprland"))
|
if (input.contains("hyprland"))
|
||||||
return "Hyprland";
|
return "Hyprland";
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ReadProcessCmdline(int pid) -> string {
|
fn ReadProcessCmdline(int pid) -> String {
|
||||||
string path = "/proc/" + to_string(pid) + "/cmdline";
|
String path = "/proc/" + to_string(pid) + "/cmdline";
|
||||||
ifstream cmdlineFile(path);
|
ifstream cmdlineFile(path);
|
||||||
string cmdline;
|
String cmdline;
|
||||||
if (getline(cmdlineFile, cmdline)) {
|
if (getline(cmdlineFile, cmdline)) {
|
||||||
// Replace null bytes with spaces
|
// Replace null bytes with spaces
|
||||||
replace(cmdline, '\0', ' ');
|
replace(cmdline, '\0', ' ');
|
||||||
|
@ -139,7 +147,7 @@ namespace {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
fn DetectHyprlandSpecific() -> string {
|
fn DetectHyprlandSpecific() -> String {
|
||||||
// Check environment variables first
|
// Check environment variables first
|
||||||
const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP");
|
const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP");
|
||||||
if (xdgCurrentDesktop && strcasestr(xdgCurrentDesktop, "hyprland"))
|
if (xdgCurrentDesktop && strcasestr(xdgCurrentDesktop, "hyprland"))
|
||||||
|
@ -156,9 +164,9 @@ namespace {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
fn GetWaylandCompositor() -> string {
|
fn GetWaylandCompositor() -> String {
|
||||||
// First try Hyprland-specific detection
|
// First try Hyprland-specific detection
|
||||||
string hypr = DetectHyprlandSpecific();
|
String hypr = DetectHyprlandSpecific();
|
||||||
if (!hypr.empty())
|
if (!hypr.empty())
|
||||||
return hypr;
|
return hypr;
|
||||||
|
|
||||||
|
@ -179,10 +187,10 @@ namespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read both comm and cmdline
|
// Read both comm and cmdline
|
||||||
string compositorName;
|
String compositorName;
|
||||||
|
|
||||||
// 1. Check comm (might be wrapped)
|
// 1. Check comm (might be wrapped)
|
||||||
string commPath = "/proc/" + to_string(cred.pid) + "/comm";
|
String commPath = "/proc/" + to_string(cred.pid) + "/comm";
|
||||||
ifstream commFile(commPath);
|
ifstream commFile(commPath);
|
||||||
if (commFile >> compositorName) {
|
if (commFile >> compositorName) {
|
||||||
subrange removedRange = std::ranges::remove(compositorName, '\n');
|
subrange removedRange = std::ranges::remove(compositorName, '\n');
|
||||||
|
@ -190,19 +198,19 @@ namespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Check cmdline for actual binary reference
|
// 2. Check cmdline for actual binary reference
|
||||||
string cmdline = ReadProcessCmdline(cred.pid);
|
String cmdline = ReadProcessCmdline(cred.pid);
|
||||||
if (cmdline.contains("hyprland")) {
|
if (cmdline.contains("hyprland")) {
|
||||||
wl_display_disconnect(display);
|
wl_display_disconnect(display);
|
||||||
return "Hyprland";
|
return "Hyprland";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Check exe symlink
|
// 3. Check exe symlink
|
||||||
string exePath = "/proc/" + to_string(cred.pid) + "/exe";
|
String exePath = "/proc/" + to_string(cred.pid) + "/exe";
|
||||||
array<char, PATH_MAX> buf;
|
array<char, PATH_MAX> buf;
|
||||||
ssize_t lenBuf = readlink(exePath.c_str(), buf.data(), buf.size() - 1);
|
ssize_t lenBuf = readlink(exePath.c_str(), buf.data(), buf.size() - 1);
|
||||||
if (lenBuf != -1) {
|
if (lenBuf != -1) {
|
||||||
buf.at(static_cast<usize>(lenBuf)) = '\0';
|
buf.at(static_cast<usize>(lenBuf)) = '\0';
|
||||||
string exe(buf.data());
|
String exe(buf.data());
|
||||||
if (exe.contains("hyprland")) {
|
if (exe.contains("hyprland")) {
|
||||||
wl_display_disconnect(display);
|
wl_display_disconnect(display);
|
||||||
return "Hyprland";
|
return "Hyprland";
|
||||||
|
@ -215,7 +223,7 @@ namespace {
|
||||||
return TrimHyprlandWrapper(compositorName);
|
return TrimHyprlandWrapper(compositorName);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn DetectFromEnvVars() -> optional<string> {
|
fn DetectFromEnvVars() -> optional<String> {
|
||||||
// Use RAII to guard against concurrent env modifications
|
// Use RAII to guard against concurrent env modifications
|
||||||
static mutex EnvMutex;
|
static mutex EnvMutex;
|
||||||
lock_guard<mutex> lock(EnvMutex);
|
lock_guard<mutex> lock(EnvMutex);
|
||||||
|
@ -224,17 +232,17 @@ namespace {
|
||||||
if (const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP")) {
|
if (const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP")) {
|
||||||
const string_view sview(xdgCurrentDesktop);
|
const string_view sview(xdgCurrentDesktop);
|
||||||
const size_t colon = sview.find(':');
|
const size_t colon = sview.find(':');
|
||||||
return string(sview.substr(0, colon)); // Direct construct from view
|
return String(sview.substr(0, colon)); // Direct construct from view
|
||||||
}
|
}
|
||||||
|
|
||||||
// DESKTOP_SESSION
|
// DESKTOP_SESSION
|
||||||
if (const char* desktopSession = getenv("DESKTOP_SESSION"))
|
if (const char* desktopSession = getenv("DESKTOP_SESSION"))
|
||||||
return string(string_view(desktopSession)); // Avoid intermediate view storage
|
return String(string_view(desktopSession)); // Avoid intermediate view storage
|
||||||
|
|
||||||
return nullopt;
|
return nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn DetectFromSessionFiles() -> optional<string> {
|
fn DetectFromSessionFiles() -> optional<String> {
|
||||||
static constexpr array<pair<string_view, string_view>, 12> DE_PATTERNS = {
|
static constexpr array<pair<string_view, string_view>, 12> DE_PATTERNS = {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
pair { "Budgie"sv, "budgie"sv },
|
pair { "Budgie"sv, "budgie"sv },
|
||||||
|
@ -258,7 +266,7 @@ namespace {
|
||||||
static constexpr array<string_view, 2> SESSION_PATHS = { "/usr/share/xsessions", "/usr/share/wayland-sessions" };
|
static constexpr array<string_view, 2> SESSION_PATHS = { "/usr/share/xsessions", "/usr/share/wayland-sessions" };
|
||||||
|
|
||||||
// Single memory reserve for lowercase conversions
|
// Single memory reserve for lowercase conversions
|
||||||
string lowercaseStem;
|
String lowercaseStem;
|
||||||
lowercaseStem.reserve(32);
|
lowercaseStem.reserve(32);
|
||||||
|
|
||||||
for (const auto& path : SESSION_PATHS) {
|
for (const auto& path : SESSION_PATHS) {
|
||||||
|
@ -279,14 +287,14 @@ namespace {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (patternIter != DE_PATTERNS.end() && patternIter->first == lowercaseStem)
|
if (patternIter != DE_PATTERNS.end() && patternIter->first == lowercaseStem)
|
||||||
return string(patternIter->second);
|
return String(patternIter->second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullopt;
|
return nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn DetectFromProcesses() -> optional<string> {
|
fn DetectFromProcesses() -> optional<String> {
|
||||||
const array processChecks = {
|
const array processChecks = {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
pair { "plasmashell"sv, "KDE"sv },
|
pair { "plasmashell"sv, "KDE"sv },
|
||||||
|
@ -300,105 +308,74 @@ namespace {
|
||||||
};
|
};
|
||||||
|
|
||||||
ifstream cmdline("/proc/self/environ");
|
ifstream cmdline("/proc/self/environ");
|
||||||
string envVars((istreambuf_iterator<char>(cmdline)), istreambuf_iterator<char>());
|
String envVars((istreambuf_iterator<char>(cmdline)), istreambuf_iterator<char>());
|
||||||
|
|
||||||
for (const auto& [process, deName] : processChecks)
|
for (const auto& [process, deName] : processChecks)
|
||||||
if (envVars.contains(process))
|
if (envVars.contains(process))
|
||||||
return string(deName);
|
return String(deName);
|
||||||
|
|
||||||
return nullopt;
|
return nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma clang diagnostic push
|
fn GetMprisPlayers(const std::shared_ptr<DBus::Connection>& connection) -> expected<vector<String>, NowPlayingError> {
|
||||||
#pragma clang diagnostic ignored "-Wold-style-cast"
|
try {
|
||||||
fn GetMprisPlayers(DBusConnection* connection) -> vector<string> {
|
// Create the method call object
|
||||||
vector<string> mprisPlayers;
|
std::shared_ptr<DBus::CallMessage> call =
|
||||||
DBusError err;
|
DBus::CallMessage::create("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames");
|
||||||
dbus_error_init(&err);
|
|
||||||
|
|
||||||
// Create a method call to org.freedesktop.DBus.ListNames
|
// Send the message synchronously and get the reply
|
||||||
DBusMessage* msg = dbus_message_new_method_call(
|
// Timeout parameter might be needed (e.g., 5000 ms)
|
||||||
"org.freedesktop.DBus", // target service
|
std::shared_ptr<DBus::Message> reply = connection->send_with_reply_blocking(call, 5000);
|
||||||
"/org/freedesktop/DBus", // object path
|
|
||||||
"org.freedesktop.DBus", // interface name
|
|
||||||
"ListNames" // method name
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!msg) {
|
// Check if the reply itself is an error type
|
||||||
DEBUG_LOG("Failed to create message for ListNames.");
|
if (reply) {
|
||||||
return mprisPlayers;
|
ERROR_LOG("DBus timeout or null reply in ListNames");
|
||||||
}
|
return unexpected(LinuxError("DBus timeout in ListNames"));
|
||||||
|
|
||||||
// Send the message and block until we get a reply.
|
|
||||||
DBusMessage* reply = dbus_connection_send_with_reply_and_block(connection, msg, -1, &err);
|
|
||||||
dbus_message_unref(msg);
|
|
||||||
|
|
||||||
if (dbus_error_is_set(&err)) {
|
|
||||||
DEBUG_LOG("DBus error in ListNames: {}", err.message);
|
|
||||||
dbus_error_free(&err);
|
|
||||||
return mprisPlayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!reply) {
|
|
||||||
DEBUG_LOG("No reply received for ListNames.");
|
|
||||||
return mprisPlayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The expected reply signature is "as" (an array of strings)
|
|
||||||
DBusMessageIter iter;
|
|
||||||
|
|
||||||
if (!dbus_message_iter_init(reply, &iter)) {
|
|
||||||
DEBUG_LOG("Reply has no arguments.");
|
|
||||||
dbus_message_unref(reply);
|
|
||||||
return mprisPlayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
|
|
||||||
DEBUG_LOG("Reply argument is not an array.");
|
|
||||||
dbus_message_unref(reply);
|
|
||||||
return mprisPlayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over the array of strings
|
|
||||||
DBusMessageIter subIter;
|
|
||||||
dbus_message_iter_recurse(&iter, &subIter);
|
|
||||||
|
|
||||||
while (dbus_message_iter_get_arg_type(&subIter) != DBUS_TYPE_INVALID) {
|
|
||||||
if (dbus_message_iter_get_arg_type(&subIter) == DBUS_TYPE_STRING) {
|
|
||||||
const char* name = nullptr;
|
|
||||||
dbus_message_iter_get_basic(&subIter, static_cast<void*>(&name));
|
|
||||||
if (name && std::string_view(name).contains("org.mpris.MediaPlayer2"))
|
|
||||||
mprisPlayers.emplace_back(name);
|
|
||||||
}
|
}
|
||||||
dbus_message_iter_next(&subIter);
|
|
||||||
|
vector<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")) {
|
||||||
|
mprisPlayers.emplace_back(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mprisPlayers;
|
||||||
|
} catch (const DBus::Error& e) { // Catch specific dbus-cxx exceptions
|
||||||
|
ERROR_LOG("DBus::Error exception in ListNames: {}", e.what());
|
||||||
|
return unexpected(LinuxError(e.what()));
|
||||||
|
} catch (const std::exception& e) { // Catch other potential standard exceptions
|
||||||
|
ERROR_LOG("Standard exception getting MPRIS players: {}", e.what());
|
||||||
|
return unexpected(String(e.what()));
|
||||||
}
|
}
|
||||||
|
|
||||||
dbus_message_unref(reply);
|
|
||||||
return mprisPlayers;
|
|
||||||
}
|
}
|
||||||
#pragma clang diagnostic pop
|
|
||||||
|
|
||||||
fn GetActivePlayer(const vector<string>& mprisPlayers) -> optional<string> {
|
// --- Logic remains the same ---
|
||||||
|
fn GetActivePlayer(const vector<String>& mprisPlayers) -> optional<String> {
|
||||||
if (!mprisPlayers.empty())
|
if (!mprisPlayers.empty())
|
||||||
return mprisPlayers.front();
|
return mprisPlayers.front();
|
||||||
return nullopt;
|
return nullopt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn GetOSVersion() -> expected<string, string> {
|
fn GetOSVersion() -> expected<String, String> {
|
||||||
constexpr const char* path = "/etc/os-release";
|
constexpr const char* path = "/etc/os-release";
|
||||||
|
|
||||||
ifstream file(path);
|
ifstream file(path);
|
||||||
|
|
||||||
if (!file.is_open())
|
if (!file.is_open())
|
||||||
return unexpected("Failed to open " + string(path));
|
return unexpected("Failed to open " + String(path));
|
||||||
|
|
||||||
string line;
|
String line;
|
||||||
const string prefix = "PRETTY_NAME=";
|
const String prefix = "PRETTY_NAME=";
|
||||||
|
|
||||||
while (getline(file, line))
|
while (getline(file, line))
|
||||||
if (line.starts_with(prefix)) {
|
if (line.starts_with(prefix)) {
|
||||||
string prettyName = line.substr(prefix.size());
|
String prettyName = line.substr(prefix.size());
|
||||||
|
|
||||||
if (!prettyName.empty() && prettyName.front() == '"' && prettyName.back() == '"')
|
if (!prettyName.empty() && prettyName.front() == '"' && prettyName.back() == '"')
|
||||||
return prettyName.substr(1, prettyName.size() - 2);
|
return prettyName.substr(1, prettyName.size() - 2);
|
||||||
|
@ -406,10 +383,10 @@ fn GetOSVersion() -> expected<string, string> {
|
||||||
return prettyName;
|
return prettyName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return unexpected("PRETTY_NAME line not found in " + string(path));
|
return unexpected("PRETTY_NAME line not found in " + String(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn GetMemInfo() -> expected<u64, string> {
|
fn GetMemInfo() -> expected<u64, String> {
|
||||||
using std::from_chars, std::errc;
|
using std::from_chars, std::errc;
|
||||||
|
|
||||||
constexpr const char* path = "/proc/meminfo";
|
constexpr const char* path = "/proc/meminfo";
|
||||||
|
@ -417,14 +394,14 @@ fn GetMemInfo() -> expected<u64, string> {
|
||||||
ifstream input(path);
|
ifstream input(path);
|
||||||
|
|
||||||
if (!input.is_open())
|
if (!input.is_open())
|
||||||
return unexpected("Failed to open " + string(path));
|
return unexpected("Failed to open " + String(path));
|
||||||
|
|
||||||
string line;
|
String line;
|
||||||
while (getline(input, line)) {
|
while (getline(input, line)) {
|
||||||
if (line.starts_with("MemTotal")) {
|
if (line.starts_with("MemTotal")) {
|
||||||
const size_t colonPos = line.find(':');
|
const size_t colonPos = line.find(':');
|
||||||
|
|
||||||
if (colonPos == string::npos)
|
if (colonPos == String::npos)
|
||||||
return unexpected("Invalid MemTotal line: no colon found");
|
return unexpected("Invalid MemTotal line: no colon found");
|
||||||
|
|
||||||
string_view view(line);
|
string_view view(line);
|
||||||
|
@ -457,196 +434,126 @@ fn GetMemInfo() -> expected<u64, string> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return unexpected("MemTotal line not found in " + string(path));
|
return unexpected("MemTotal line not found in " + String(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma clang diagnostic push
|
fn GetNowPlaying() -> expected<String, NowPlayingError> {
|
||||||
#pragma clang diagnostic ignored "-Wold-style-cast"
|
try {
|
||||||
fn GetNowPlaying() -> expected<string, NowPlayingError> {
|
// 1. Get Dispatcher and Session Bus Connection
|
||||||
DBusError err;
|
std::shared_ptr<DBus::Dispatcher> dispatcher = DBus::StandaloneDispatcher::create();
|
||||||
dbus_error_init(&err);
|
if (!dispatcher)
|
||||||
|
return unexpected(LinuxError("Failed to create DBus dispatcher"));
|
||||||
|
|
||||||
// Connect to the session bus
|
std::shared_ptr<DBus::Connection> connection = dispatcher->create_connection(DBus::BusType::SESSION);
|
||||||
DBusConnection* connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
|
if (!connection)
|
||||||
|
return unexpected(LinuxError("Failed to connect to session bus"));
|
||||||
|
|
||||||
if (!connection)
|
// 2. Get list of MPRIS players
|
||||||
if (dbus_error_is_set(&err)) {
|
auto mprisPlayersResult = GetMprisPlayers(connection);
|
||||||
ERROR_LOG("DBus connection error: {}", err.message);
|
if (!mprisPlayersResult)
|
||||||
|
return unexpected(mprisPlayersResult.error()); // Forward the error
|
||||||
|
|
||||||
NowPlayingError error = LinuxError(err.message);
|
const vector<String>& mprisPlayers = *mprisPlayersResult;
|
||||||
dbus_error_free(&err);
|
|
||||||
|
|
||||||
return unexpected(error);
|
if (mprisPlayers.empty())
|
||||||
|
return unexpected(NowPlayingError { NowPlayingCode::NoPlayers });
|
||||||
|
|
||||||
|
// 3. Determine active player
|
||||||
|
optional<String> activePlayerOpt = GetActivePlayer(mprisPlayers);
|
||||||
|
if (!activePlayerOpt)
|
||||||
|
return unexpected(NowPlayingError { NowPlayingCode::NoActivePlayer });
|
||||||
|
|
||||||
|
// Use std::string for D-Bus service name
|
||||||
|
const String& activePlayerService = *activePlayerOpt;
|
||||||
|
|
||||||
|
// 4. Call Properties.Get for Metadata
|
||||||
|
const String interfaceNameStd = "org.mpris.MediaPlayer2.Player";
|
||||||
|
const String propertyNameStd = "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
|
||||||
|
|
||||||
|
(*call) << interfaceNameStd << propertyNameStd;
|
||||||
|
|
||||||
|
// 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"));
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<string> mprisPlayers = GetMprisPlayers(connection);
|
// 5. Parse the reply
|
||||||
|
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;
|
||||||
|
|
||||||
if (mprisPlayers.empty()) {
|
// Check the variant's signature
|
||||||
dbus_connection_unref(connection);
|
if (metadataVariant.to_signature() != "a{sv}") {
|
||||||
return unexpected(NowPlayingError { NowPlayingCode::NoPlayers });
|
return unexpected("Unexpected reply type for Metadata");
|
||||||
}
|
}
|
||||||
|
String artistStd;
|
||||||
|
String titleStd;
|
||||||
|
|
||||||
optional<string> activePlayer = GetActivePlayer(mprisPlayers);
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
if (!activePlayer.has_value()) {
|
// For line 525-534
|
||||||
dbus_connection_unref(connection);
|
auto artistIter = metadataMap.find("xesam:artist");
|
||||||
return unexpected(NowPlayingError { NowPlayingCode::NoActivePlayer });
|
if (artistIter != metadataMap.end() && artistIter->second.to_signature() == "as") {
|
||||||
}
|
// Cast to vector<String>
|
||||||
|
std::vector<String> artistsStd = static_cast<std::vector<String>>(artistIter->second);
|
||||||
// Prepare a call to the Properties.Get method to fetch "Metadata"
|
if (!artistsStd.empty()) {
|
||||||
DBusMessage* msg = dbus_message_new_method_call(
|
artistStd = artistsStd.front();
|
||||||
activePlayer->c_str(), // target service (active player)
|
|
||||||
"/org/mpris/MediaPlayer2", // object path
|
|
||||||
"org.freedesktop.DBus.Properties", // interface
|
|
||||||
"Get" // method name
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!msg) {
|
|
||||||
dbus_connection_unref(connection);
|
|
||||||
return unexpected(NowPlayingError { /* error creating message */ });
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* interfaceName = "org.mpris.MediaPlayer2.Player";
|
|
||||||
const char* propertyName = "Metadata";
|
|
||||||
|
|
||||||
if (!dbus_message_append_args(
|
|
||||||
msg, DBUS_TYPE_STRING, &interfaceName, DBUS_TYPE_STRING, &propertyName, DBUS_TYPE_INVALID
|
|
||||||
)) {
|
|
||||||
dbus_message_unref(msg);
|
|
||||||
dbus_connection_unref(connection);
|
|
||||||
return unexpected(NowPlayingError { /* error appending arguments */ });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the method and block until reply is received.
|
|
||||||
DBusMessage* reply = dbus_connection_send_with_reply_and_block(connection, msg, -1, &err);
|
|
||||||
dbus_message_unref(msg);
|
|
||||||
|
|
||||||
if (dbus_error_is_set(&err)) {
|
|
||||||
ERROR_LOG("DBus error in Properties.Get: {}", err.message);
|
|
||||||
|
|
||||||
NowPlayingError error = LinuxError(err.message);
|
|
||||||
dbus_error_free(&err);
|
|
||||||
dbus_connection_unref(connection);
|
|
||||||
|
|
||||||
return unexpected(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!reply) {
|
|
||||||
dbus_connection_unref(connection);
|
|
||||||
return unexpected(NowPlayingError { /* no reply error */ });
|
|
||||||
}
|
|
||||||
|
|
||||||
// The reply should contain a variant holding a dictionary ("a{sv}")
|
|
||||||
DBusMessageIter iter;
|
|
||||||
|
|
||||||
if (!dbus_message_iter_init(reply, &iter)) {
|
|
||||||
dbus_message_unref(reply);
|
|
||||||
dbus_connection_unref(connection);
|
|
||||||
return unexpected(NowPlayingError { /* no arguments in reply */ });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
|
|
||||||
dbus_message_unref(reply);
|
|
||||||
dbus_connection_unref(connection);
|
|
||||||
return unexpected(NowPlayingError { /* unexpected argument type */ });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recurse into the variant to get the dictionary
|
|
||||||
DBusMessageIter variantIter;
|
|
||||||
dbus_message_iter_recurse(&iter, &variantIter);
|
|
||||||
|
|
||||||
if (dbus_message_iter_get_arg_type(&variantIter) != DBUS_TYPE_ARRAY) {
|
|
||||||
dbus_message_unref(reply);
|
|
||||||
dbus_connection_unref(connection);
|
|
||||||
return unexpected(NowPlayingError { /* expected array type */ });
|
|
||||||
}
|
|
||||||
|
|
||||||
string title;
|
|
||||||
string artist;
|
|
||||||
DBusMessageIter arrayIter;
|
|
||||||
dbus_message_iter_recurse(&variantIter, &arrayIter);
|
|
||||||
|
|
||||||
// Iterate over each dictionary entry (each entry is of type dict entry)
|
|
||||||
while (dbus_message_iter_get_arg_type(&arrayIter) != DBUS_TYPE_INVALID) {
|
|
||||||
if (dbus_message_iter_get_arg_type(&arrayIter) == DBUS_TYPE_DICT_ENTRY) {
|
|
||||||
DBusMessageIter dictEntry;
|
|
||||||
dbus_message_iter_recurse(&arrayIter, &dictEntry);
|
|
||||||
|
|
||||||
// Get the key (a string)
|
|
||||||
const char* key = nullptr;
|
|
||||||
|
|
||||||
if (dbus_message_iter_get_arg_type(&dictEntry) == DBUS_TYPE_STRING)
|
|
||||||
dbus_message_iter_get_basic(&dictEntry, static_cast<void*>(&key));
|
|
||||||
|
|
||||||
// Move to the value (a variant)
|
|
||||||
dbus_message_iter_next(&dictEntry);
|
|
||||||
|
|
||||||
if (dbus_message_iter_get_arg_type(&dictEntry) == DBUS_TYPE_VARIANT) {
|
|
||||||
DBusMessageIter valueIter;
|
|
||||||
dbus_message_iter_recurse(&dictEntry, &valueIter);
|
|
||||||
|
|
||||||
if (key && std::string_view(key) == "xesam:title") {
|
|
||||||
if (dbus_message_iter_get_arg_type(&valueIter) == DBUS_TYPE_STRING) {
|
|
||||||
const char* val = nullptr;
|
|
||||||
dbus_message_iter_get_basic(&valueIter, static_cast<void*>(&val));
|
|
||||||
|
|
||||||
if (val)
|
|
||||||
title = val;
|
|
||||||
}
|
|
||||||
} else if (key && std::string_view(key) == "xesam:artist") {
|
|
||||||
// Expect an array of strings
|
|
||||||
if (dbus_message_iter_get_arg_type(&valueIter) == DBUS_TYPE_ARRAY) {
|
|
||||||
DBusMessageIter subIter;
|
|
||||||
dbus_message_iter_recurse(&valueIter, &subIter);
|
|
||||||
|
|
||||||
if (dbus_message_iter_get_arg_type(&subIter) == DBUS_TYPE_STRING) {
|
|
||||||
const char* val = nullptr;
|
|
||||||
dbus_message_iter_get_basic(&subIter, static_cast<void*>(&val));
|
|
||||||
|
|
||||||
if (val)
|
|
||||||
artist = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dbus_message_iter_next(&arrayIter);
|
// 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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
dbus_message_unref(reply);
|
|
||||||
dbus_connection_unref(connection);
|
|
||||||
|
|
||||||
string result;
|
|
||||||
|
|
||||||
if (!artist.empty() && !title.empty())
|
|
||||||
result = artist + " - " + title;
|
|
||||||
else if (!title.empty())
|
|
||||||
result = title;
|
|
||||||
else if (!artist.empty())
|
|
||||||
result = artist;
|
|
||||||
else
|
|
||||||
result = "";
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
#pragma clang diagnostic pop
|
|
||||||
|
|
||||||
fn GetWindowManager() -> string {
|
fn GetWindowManager() -> String {
|
||||||
// Check environment variables first
|
// Check environment variables first
|
||||||
const char* xdgSessionType = getenv("XDG_SESSION_TYPE");
|
const char* xdgSessionType = getenv("XDG_SESSION_TYPE");
|
||||||
const char* waylandDisplay = getenv("WAYLAND_DISPLAY");
|
const char* waylandDisplay = getenv("WAYLAND_DISPLAY");
|
||||||
|
|
||||||
// Prefer Wayland detection if Wayland session
|
// Prefer Wayland detection if Wayland session
|
||||||
if ((waylandDisplay != nullptr) || (xdgSessionType && string_view(xdgSessionType).contains("wayland"))) {
|
if ((waylandDisplay != nullptr) || (xdgSessionType && string_view(xdgSessionType).contains("wayland"))) {
|
||||||
string compositor = GetWaylandCompositor();
|
String compositor = GetWaylandCompositor();
|
||||||
if (!compositor.empty())
|
if (!compositor.empty())
|
||||||
return compositor;
|
return compositor;
|
||||||
|
|
||||||
// Fallback environment check
|
// Fallback environment check
|
||||||
const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP");
|
const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP");
|
||||||
if (xdgCurrentDesktop) {
|
if (xdgCurrentDesktop) {
|
||||||
string desktop(xdgCurrentDesktop);
|
String desktop(xdgCurrentDesktop);
|
||||||
transform(compositor, compositor.begin(), ::tolower);
|
transform(compositor, compositor.begin(), ::tolower);
|
||||||
if (desktop.contains("hyprland"))
|
if (desktop.contains("hyprland"))
|
||||||
return "hyprland";
|
return "hyprland";
|
||||||
|
@ -654,14 +561,14 @@ fn GetWindowManager() -> string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// X11 detection
|
// X11 detection
|
||||||
string x11wm = GetX11WindowManager();
|
String x11wm = GetX11WindowManager();
|
||||||
if (!x11wm.empty())
|
if (!x11wm.empty())
|
||||||
return x11wm;
|
return x11wm;
|
||||||
|
|
||||||
return "Unknown";
|
return "Unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
fn GetDesktopEnvironment() -> optional<string> {
|
fn GetDesktopEnvironment() -> optional<String> {
|
||||||
// Try environment variables first
|
// Try environment variables first
|
||||||
if (auto desktopEnvironment = DetectFromEnvVars(); desktopEnvironment.has_value())
|
if (auto desktopEnvironment = DetectFromEnvVars(); desktopEnvironment.has_value())
|
||||||
return desktopEnvironment;
|
return desktopEnvironment;
|
||||||
|
@ -674,7 +581,7 @@ fn GetDesktopEnvironment() -> optional<string> {
|
||||||
return DetectFromProcesses();
|
return DetectFromProcesses();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn GetShell() -> string {
|
fn GetShell() -> String {
|
||||||
const string_view shell = getenv("SHELL");
|
const string_view shell = getenv("SHELL");
|
||||||
|
|
||||||
if (shell.ends_with("bash"))
|
if (shell.ends_with("bash"))
|
||||||
|
@ -688,10 +595,10 @@ fn GetShell() -> string {
|
||||||
if (shell.ends_with("sh"))
|
if (shell.ends_with("sh"))
|
||||||
return "SH";
|
return "SH";
|
||||||
|
|
||||||
return !shell.empty() ? string(shell) : "";
|
return !shell.empty() ? String(shell) : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
fn GetHost() -> string {
|
fn GetHost() -> String {
|
||||||
constexpr const char* path = "/sys/class/dmi/id/product_family";
|
constexpr const char* path = "/sys/class/dmi/id/product_family";
|
||||||
|
|
||||||
ifstream file(path);
|
ifstream file(path);
|
||||||
|
@ -700,7 +607,7 @@ fn GetHost() -> string {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
string productFamily;
|
String productFamily;
|
||||||
if (!getline(file, productFamily)) {
|
if (!getline(file, productFamily)) {
|
||||||
ERROR_LOG("Failed to read from {}", path);
|
ERROR_LOG("Failed to read from {}", path);
|
||||||
return "";
|
return "";
|
||||||
|
@ -709,7 +616,7 @@ fn GetHost() -> string {
|
||||||
return productFamily;
|
return productFamily;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn GetKernelVersion() -> string {
|
fn GetKernelVersion() -> String {
|
||||||
struct utsname uts;
|
struct utsname uts;
|
||||||
|
|
||||||
if (uname(&uts) == -1) {
|
if (uname(&uts) == -1) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue