ONE POINT SIX MILLISECONDS??????
This commit is contained in:
parent
63e4106cca
commit
733d422aea
7
.gitmodules
vendored
7
.gitmodules
vendored
|
@ -1,3 +1,10 @@
|
||||||
[submodule "subprojects/reflectcpp"]
|
[submodule "subprojects/reflectcpp"]
|
||||||
path = subprojects/reflectcpp
|
path = subprojects/reflectcpp
|
||||||
url = https://github.com/getml/reflect-cpp
|
url = https://github.com/getml/reflect-cpp
|
||||||
|
[submodule "subprojects/sdbus-c++"]
|
||||||
|
path = subprojects/sdbus-c++
|
||||||
|
url = https://github.com/Kistler-Group/sdbus-cpp
|
||||||
|
[submodule "subprojects/systemd"]
|
||||||
|
path = subprojects/systemd
|
||||||
|
url = https://github.com/systemd/systemd
|
||||||
|
branch = v257-stable
|
||||||
|
|
|
@ -84,19 +84,17 @@
|
||||||
# tomlplusplus
|
# tomlplusplus
|
||||||
# yyjson
|
# yyjson
|
||||||
# reflect-cpp
|
# reflect-cpp
|
||||||
sqlitecpp
|
# sqlitecpp
|
||||||
# ftxui
|
# ftxui
|
||||||
]
|
]
|
||||||
++ linuxPkgs;
|
++ linuxPkgs;
|
||||||
|
|
||||||
linuxPkgs = nixpkgs.lib.optionals stdenv.isLinux (with pkgs;
|
linuxPkgs = nixpkgs.lib.optionals stdenv.isLinux (with pkgs;
|
||||||
[
|
[
|
||||||
systemdLibs
|
|
||||||
valgrind
|
valgrind
|
||||||
]
|
]
|
||||||
++ (with pkgsStatic; [
|
++ (with pkgsStatic; [
|
||||||
glib
|
dbus
|
||||||
sdbus-cpp
|
|
||||||
xorg.libX11
|
xorg.libX11
|
||||||
wayland
|
wayland
|
||||||
]));
|
]));
|
||||||
|
|
|
@ -5,7 +5,7 @@ project(
|
||||||
default_options: [
|
default_options: [
|
||||||
'default_library=static',
|
'default_library=static',
|
||||||
'warning_level=everything',
|
'warning_level=everything',
|
||||||
'buildtype=debugoptimized',
|
'buildtype=release',
|
||||||
'cpp_args=-fvisibility=hidden',
|
'cpp_args=-fvisibility=hidden',
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -93,14 +93,16 @@ if host_machine.system() == 'darwin'
|
||||||
)
|
)
|
||||||
elif host_machine.system() == 'linux' or host_machine.system() == 'freebsd'
|
elif host_machine.system() == 'linux' or host_machine.system() == 'freebsd'
|
||||||
deps += dependency('SQLiteCpp')
|
deps += dependency('SQLiteCpp')
|
||||||
deps += dependency('sdbus-c++')
|
|
||||||
deps += dependency('x11')
|
deps += dependency('x11')
|
||||||
deps += dependency('xcb')
|
deps += dependency('xcb')
|
||||||
deps += dependency('xau')
|
deps += dependency('xau')
|
||||||
deps += dependency('xdmcp')
|
deps += dependency('xdmcp')
|
||||||
deps += dependency('wayland-client')
|
deps += dependency('wayland-client')
|
||||||
|
deps += dependency('dbus-1')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
cmake = import('cmake')
|
||||||
|
|
||||||
ftxui_dep = dependency(
|
ftxui_dep = dependency(
|
||||||
'ftxui',
|
'ftxui',
|
||||||
modules: ['ftxui::screen', 'ftxui::dom', 'ftxui::component'],
|
modules: ['ftxui::screen', 'ftxui::dom', 'ftxui::component'],
|
||||||
|
@ -118,8 +120,6 @@ endif
|
||||||
|
|
||||||
deps += ftxui_dep
|
deps += ftxui_dep
|
||||||
|
|
||||||
cmake = import('cmake')
|
|
||||||
|
|
||||||
reflectcpp_proj = cmake.subproject('reflectcpp')
|
reflectcpp_proj = cmake.subproject('reflectcpp')
|
||||||
reflectcpp_dep = reflectcpp_proj.dependency('reflectcpp')
|
reflectcpp_dep = reflectcpp_proj.dependency('reflectcpp')
|
||||||
deps += reflectcpp_dep
|
deps += reflectcpp_dep
|
||||||
|
|
98
src/main.cpp
98
src/main.cpp
|
@ -160,21 +160,25 @@ namespace {
|
||||||
|
|
||||||
content.push_back(text(" Hello " + name + "! ") | bold | color(Color::Cyan));
|
content.push_back(text(" Hello " + name + "! ") | bold | color(Color::Cyan));
|
||||||
content.push_back(separator() | color(borderColor));
|
content.push_back(separator() | color(borderColor));
|
||||||
content.push_back(hbox({
|
content.push_back(hbox(
|
||||||
text(" ") | color(iconColor), // Palette icon
|
{
|
||||||
CreateColorCircles(),
|
text(" ") | color(iconColor), // Palette icon
|
||||||
}));
|
CreateColorCircles(),
|
||||||
|
}
|
||||||
|
));
|
||||||
content.push_back(separator() | color(borderColor));
|
content.push_back(separator() | color(borderColor));
|
||||||
|
|
||||||
// Helper function for aligned rows
|
// Helper function for aligned rows
|
||||||
fn createRow = [&](const std::string& icon, const std::string& label, const std::string& value) {
|
fn createRow = [&](const std::string& icon, const std::string& label, const std::string& value) {
|
||||||
return hbox({
|
return hbox(
|
||||||
text(icon) | color(iconColor),
|
{
|
||||||
text(label) | color(labelColor),
|
text(icon) | color(iconColor),
|
||||||
filler(),
|
text(label) | color(labelColor),
|
||||||
text(value) | color(valueColor),
|
filler(),
|
||||||
text(" "),
|
text(value) | color(valueColor),
|
||||||
});
|
text(" "),
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// System info rows
|
// System info rows
|
||||||
|
@ -185,31 +189,39 @@ namespace {
|
||||||
const WeatherOutput& weatherInfo = data.weather_info.value();
|
const WeatherOutput& weatherInfo = data.weather_info.value();
|
||||||
|
|
||||||
if (weather.show_town_name)
|
if (weather.show_town_name)
|
||||||
content.push_back(hbox({
|
content.push_back(hbox(
|
||||||
text(weatherIcon) | color(iconColor),
|
{
|
||||||
text("Weather") | color(labelColor),
|
text(weatherIcon) | color(iconColor),
|
||||||
filler(),
|
text("Weather") | color(labelColor),
|
||||||
|
filler(),
|
||||||
|
|
||||||
hbox({
|
hbox(
|
||||||
text(fmt::format("{}°F ", std::lround(weatherInfo.main.temp))),
|
{
|
||||||
text("in "),
|
text(fmt::format("{}°F ", std::lround(weatherInfo.main.temp))),
|
||||||
text(weatherInfo.name),
|
text("in "),
|
||||||
text(" "),
|
text(weatherInfo.name),
|
||||||
}) |
|
text(" "),
|
||||||
color(valueColor),
|
}
|
||||||
}));
|
) |
|
||||||
|
color(valueColor),
|
||||||
|
}
|
||||||
|
));
|
||||||
else
|
else
|
||||||
content.push_back(hbox({
|
content.push_back(hbox(
|
||||||
text(weatherIcon) | color(iconColor),
|
{
|
||||||
text("Weather") | color(labelColor),
|
text(weatherIcon) | color(iconColor),
|
||||||
filler(),
|
text("Weather") | color(labelColor),
|
||||||
|
filler(),
|
||||||
|
|
||||||
hbox({
|
hbox(
|
||||||
text(fmt::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description)),
|
{
|
||||||
text(" "),
|
text(fmt::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description)),
|
||||||
}) |
|
text(" "),
|
||||||
color(valueColor),
|
}
|
||||||
}));
|
) |
|
||||||
|
color(valueColor),
|
||||||
|
}
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
content.push_back(separator() | color(borderColor));
|
content.push_back(separator() | color(borderColor));
|
||||||
|
@ -246,14 +258,16 @@ namespace {
|
||||||
const std::string& npText = *nowPlayingResult;
|
const std::string& npText = *nowPlayingResult;
|
||||||
|
|
||||||
content.push_back(separator() | color(borderColor));
|
content.push_back(separator() | color(borderColor));
|
||||||
content.push_back(hbox({
|
content.push_back(hbox(
|
||||||
text(musicIcon) | color(iconColor),
|
{
|
||||||
text("Playing") | color(labelColor),
|
text(musicIcon) | color(iconColor),
|
||||||
text(" "),
|
text("Playing") | color(labelColor),
|
||||||
filler(),
|
text(" "),
|
||||||
paragraph(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, 30),
|
filler(),
|
||||||
text(" "),
|
paragraph(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, 30),
|
||||||
}));
|
text(" "),
|
||||||
|
}
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
const NowPlayingError& error = nowPlayingResult.error();
|
const NowPlayingError& error = nowPlayingResult.error();
|
||||||
|
|
||||||
|
@ -269,7 +283,7 @@ namespace {
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
if (std::holds_alternative<LinuxError>(error))
|
if (std::holds_alternative<LinuxError>(error))
|
||||||
DEBUG_LOG("DBus error: {}", std::get<LinuxError>(error).getMessage());
|
DEBUG_LOG("DBus error: {}", std::get<LinuxError>(error));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __WIN32__
|
#ifdef __WIN32__
|
||||||
|
|
382
src/os/linux.cpp
382
src/os/linux.cpp
|
@ -1,34 +1,32 @@
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
|
||||||
#include <SQLiteCpp/SQLiteCpp.h>
|
|
||||||
#include <X11/Xatom.h>
|
#include <X11/Xatom.h>
|
||||||
#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 <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
#include <sdbus-c++/Error.h>
|
|
||||||
#include <sdbus-c++/sdbus-c++.h>
|
|
||||||
#include <sqlite3.h>
|
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/utsname.h>
|
#include <sys/utsname.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <wayland-client.h>
|
#include <wayland-client.h>
|
||||||
|
|
||||||
#include "os.h"
|
#include "os.h"
|
||||||
#include "src/util/macros.h"
|
#include "src/util/macros.h"
|
||||||
|
|
||||||
using std::errc, std::exception, std::expected, std::from_chars, std::getline, std::istreambuf_iterator, std::less,
|
using std::errc, std::expected, std::from_chars, std::getline, std::istreambuf_iterator, std::less, std::lock_guard,
|
||||||
std::lock_guard, std::map, std::mutex, std::ofstream, std::pair, std::string_view, std::vector, std::nullopt,
|
std::mutex, std::ofstream, std::pair, std::string_view, std::vector, std::nullopt, std::array, std::optional,
|
||||||
std::array, std::unique_ptr, std::optional, std::bit_cast, std::to_string, std::ifstream, std::getenv, std::string,
|
std::bit_cast, std::to_string, std::ifstream, std::getenv, std::string, std::unexpected, std::ranges::is_sorted,
|
||||||
std::unexpected, std::ranges::is_sorted, std::ranges::lower_bound, std::ranges::replace, std::ranges::subrange,
|
std::ranges::lower_bound, std::ranges::replace, std::ranges::subrange, std::ranges::transform;
|
||||||
std::ranges::transform;
|
|
||||||
|
|
||||||
using namespace std::literals::string_view_literals;
|
using namespace std::literals::string_view_literals;
|
||||||
|
|
||||||
|
@ -37,33 +35,6 @@ namespace fs = std::filesystem;
|
||||||
enum SessionType : u8 { Wayland, X11, TTY, Unknown };
|
enum SessionType : u8 { Wayland, X11, TTY, Unknown };
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
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 unique_ptr<sdbus::IProxy> dbusProxy = createProxy(connection, dbusInterface, dbusObjectPath);
|
|
||||||
|
|
||||||
vector<string> names;
|
|
||||||
|
|
||||||
dbusProxy->callMethod(dbusMethodListNames).onInterface(dbusInterface).storeResultsTo(names);
|
|
||||||
|
|
||||||
vector<string> mprisPlayers;
|
|
||||||
|
|
||||||
for (const string& name : names)
|
|
||||||
if (const char* mprisInterfaceName = "org.mpris.MediaPlayer2"; name.contains(mprisInterfaceName))
|
|
||||||
mprisPlayers.push_back(name);
|
|
||||||
|
|
||||||
return mprisPlayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn GetActivePlayer(const vector<string>& mprisPlayers) -> optional<string> {
|
|
||||||
if (!mprisPlayers.empty())
|
|
||||||
return mprisPlayers.front();
|
|
||||||
|
|
||||||
return nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn GetX11WindowManager() -> string {
|
fn GetX11WindowManager() -> string {
|
||||||
Display* display = XOpenDisplay(nullptr);
|
Display* display = XOpenDisplay(nullptr);
|
||||||
|
|
||||||
|
@ -103,7 +74,7 @@ namespace {
|
||||||
&data
|
&data
|
||||||
) == Success &&
|
) == Success &&
|
||||||
data) {
|
data) {
|
||||||
wmWindow = *std::bit_cast<Window*>(data);
|
wmWindow = *bit_cast<Window*>(data);
|
||||||
XFree(data);
|
XFree(data);
|
||||||
data = nullptr;
|
data = nullptr;
|
||||||
|
|
||||||
|
@ -322,70 +293,79 @@ namespace {
|
||||||
return nullopt;
|
return nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn CountNix() noexcept -> optional<usize> {
|
#pragma clang diagnostic push
|
||||||
constexpr string_view dbPath = "/nix/var/nix/db/db.sqlite";
|
#pragma clang diagnostic ignored "-Wold-style-cast"
|
||||||
constexpr string_view querySql = "SELECT COUNT(*) FROM ValidPaths WHERE sigs IS NOT NULL;";
|
fn GetMprisPlayers(DBusConnection* connection) -> vector<string> {
|
||||||
|
vector<string> mprisPlayers;
|
||||||
|
DBusError err;
|
||||||
|
dbus_error_init(&err);
|
||||||
|
|
||||||
sqlite3* sqlDB = nullptr;
|
// Create a method call to org.freedesktop.DBus.ListNames
|
||||||
sqlite3_stmt* stmt = nullptr;
|
DBusMessage* msg = dbus_message_new_method_call(
|
||||||
usize count = 0;
|
"org.freedesktop.DBus", // target service
|
||||||
|
"/org/freedesktop/DBus", // object path
|
||||||
|
"org.freedesktop.DBus", // interface name
|
||||||
|
"ListNames" // method name
|
||||||
|
);
|
||||||
|
|
||||||
// 1. Direct URI construction without string concatenation
|
if (!msg) {
|
||||||
const string uri = fmt::format("file:{}{}immutable=1", dbPath, (dbPath.find('?') == string::npos) ? "&" : "?");
|
DEBUG_LOG("Failed to create message for ListNames.");
|
||||||
|
return mprisPlayers;
|
||||||
// 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 nullopt;
|
|
||||||
|
|
||||||
// 3. Configure database for maximum read performance
|
|
||||||
sqlite3_exec(sqlDB, "PRAGMA journal_mode=OFF; PRAGMA mmap_size=268435456;", nullptr, nullptr, nullptr);
|
|
||||||
|
|
||||||
// 4. Single-step prepared statement execution
|
|
||||||
if (sqlite3_prepare_v3(sqlDB, querySql.data(), querySql.size(), SQLITE_PREPARE_PERSISTENT, &stmt, nullptr) ==
|
|
||||||
SQLITE_OK) {
|
|
||||||
if (sqlite3_step(stmt) == SQLITE_ROW)
|
|
||||||
count = static_cast<usize>(sqlite3_column_int64(stmt, 0));
|
|
||||||
|
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_close(sqlDB);
|
// Send the message and block until we get a reply.
|
||||||
return count ? optional { count } : nullopt;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
dbus_message_unref(reply);
|
||||||
|
return mprisPlayers;
|
||||||
}
|
}
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
|
||||||
fn CountNixWithCache() noexcept -> optional<size_t> {
|
fn GetActivePlayer(const vector<string>& mprisPlayers) -> optional<string> {
|
||||||
constexpr const char* dbPath = "/nix/var/nix/db/db.sqlite";
|
if (!mprisPlayers.empty())
|
||||||
constexpr const char* cachePath = "/tmp/nix_pkg_count.cache";
|
return mprisPlayers.front();
|
||||||
|
return nullopt;
|
||||||
try {
|
|
||||||
using mtime = fs::file_time_type;
|
|
||||||
|
|
||||||
const mtime dbMtime = fs::last_write_time(dbPath);
|
|
||||||
const mtime cacheMtime = fs::last_write_time(cachePath);
|
|
||||||
|
|
||||||
if (fs::exists(cachePath) && dbMtime <= cacheMtime) {
|
|
||||||
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 exception& e) { DEBUG_LOG("Cache access failed: {}, rebuilding...", e.what()); }
|
|
||||||
|
|
||||||
const optional<usize> count = CountNix();
|
|
||||||
|
|
||||||
if (count) {
|
|
||||||
constexpr const char* tmpPath = "/tmp/nix_pkg_count.tmp";
|
|
||||||
|
|
||||||
{
|
|
||||||
ofstream tmp(tmpPath, std::ios::binary | std::ios::trunc);
|
|
||||||
tmp.write(bit_cast<const char*>(&*count), sizeof(*count));
|
|
||||||
}
|
|
||||||
|
|
||||||
fs::rename(tmpPath, cachePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,65 +442,177 @@ 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
|
||||||
|
#pragma clang diagnostic ignored "-Wold-style-cast"
|
||||||
fn GetNowPlaying() -> expected<string, NowPlayingError> {
|
fn GetNowPlaying() -> expected<string, NowPlayingError> {
|
||||||
try {
|
DBusError err;
|
||||||
const char *playerObjectPath = "/org/mpris/MediaPlayer2", *playerInterfaceName = "org.mpris.MediaPlayer2.Player";
|
dbus_error_init(&err);
|
||||||
|
|
||||||
unique_ptr<sdbus::IConnection> connection = sdbus::createSessionBusConnection();
|
// Connect to the session bus
|
||||||
|
DBusConnection* connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
|
||||||
|
|
||||||
vector<string> mprisPlayers = GetMprisPlayers(*connection);
|
if (!connection)
|
||||||
|
if (dbus_error_is_set(&err)) {
|
||||||
|
ERROR_LOG("DBus connection error: {}", err.message);
|
||||||
|
|
||||||
if (mprisPlayers.empty())
|
NowPlayingError error = LinuxError(err.message);
|
||||||
return unexpected(NowPlayingError { NowPlayingCode::NoPlayers });
|
dbus_error_free(&err);
|
||||||
|
|
||||||
optional<string> activePlayer = GetActivePlayer(mprisPlayers);
|
return unexpected(error);
|
||||||
|
|
||||||
if (!activePlayer.has_value())
|
|
||||||
return unexpected(NowPlayingError { NowPlayingCode::NoActivePlayer });
|
|
||||||
|
|
||||||
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<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<string>())
|
|
||||||
title = titleIter->second.get<string>();
|
|
||||||
|
|
||||||
string artist;
|
|
||||||
auto artistIter = metadata.find("xesam:artist");
|
|
||||||
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())
|
|
||||||
result = artist + " - " + title;
|
|
||||||
else if (!title.empty())
|
|
||||||
result = title;
|
|
||||||
else if (!artist.empty())
|
|
||||||
result = artist;
|
|
||||||
else
|
|
||||||
result = "";
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
} catch (const sdbus::Error& e) {
|
|
||||||
if (e.getName() != "com.github.altdesktop.playerctld.NoActivePlayer")
|
|
||||||
return unexpected(NowPlayingError { LinuxError(e) });
|
|
||||||
|
|
||||||
|
vector<string> mprisPlayers = GetMprisPlayers(connection);
|
||||||
|
|
||||||
|
if (mprisPlayers.empty()) {
|
||||||
|
dbus_connection_unref(connection);
|
||||||
|
return unexpected(NowPlayingError { NowPlayingCode::NoPlayers });
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<string> activePlayer = GetActivePlayer(mprisPlayers);
|
||||||
|
|
||||||
|
if (!activePlayer.has_value()) {
|
||||||
|
dbus_connection_unref(connection);
|
||||||
return unexpected(NowPlayingError { NowPlayingCode::NoActivePlayer });
|
return unexpected(NowPlayingError { NowPlayingCode::NoActivePlayer });
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
// Prepare a call to the Properties.Get method to fetch "Metadata"
|
||||||
|
DBusMessage* msg = dbus_message_new_method_call(
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
@ -528,7 +620,7 @@ fn GetWindowManager() -> string {
|
||||||
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 && std::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;
|
||||||
|
@ -596,8 +688,6 @@ fn GetKernelVersion() -> string {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG_LOG("{}", CountNixWithCache().value_or(0));
|
|
||||||
|
|
||||||
return static_cast<const char*>(uts.release);
|
return static_cast<const char*>(uts.release);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,10 +12,6 @@
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
#include <sdbus-c++/sdbus-c++.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef u8
|
* @typedef u8
|
||||||
* @brief Represents an 8-bit unsigned integer.
|
* @brief Represents an 8-bit unsigned integer.
|
||||||
|
@ -152,7 +148,7 @@ enum class NowPlayingCode : u8 {
|
||||||
* @typedef LinuxError
|
* @typedef LinuxError
|
||||||
* @brief Represents a Linux-specific error.
|
* @brief Represents a Linux-specific error.
|
||||||
*/
|
*/
|
||||||
using LinuxError = sdbus::Error;
|
using LinuxError = std::string;
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
/**
|
/**
|
||||||
* @typedef MacError
|
* @typedef MacError
|
||||||
|
|
1
subprojects/sdbus-c++
Submodule
1
subprojects/sdbus-c++
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 48ea775531523ac290abec91107c628f00156543
|
2
subprojects/sqlite3.wrap
Normal file
2
subprojects/sqlite3.wrap
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[wrap-redirect]
|
||||||
|
filename = SQLiteCpp-3.3.2/subprojects/sqlite3.wrap
|
10
subprojects/sqlitecpp.wrap
Normal file
10
subprojects/sqlitecpp.wrap
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[wrap-file]
|
||||||
|
directory = SQLiteCpp-3.3.2
|
||||||
|
source_url = https://github.com/SRombauts/SQLiteCpp/archive/refs/tags/3.3.2.zip
|
||||||
|
source_filename = sqlitecpp-3.3.2.zip
|
||||||
|
source_hash = 1f41ef7322da573fdfca95655bd1329282638b4d9d3dc16a48f4bad16008eda8
|
||||||
|
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/sqlitecpp_3.3.2-1/sqlitecpp-3.3.2.zip
|
||||||
|
wrapdb_version = 3.3.2-1
|
||||||
|
|
||||||
|
[provide]
|
||||||
|
sqlitecpp = sqlitecpp_dep
|
Loading…
Reference in a new issue