From 733d422aeaf8e9cc01a2e42187d599fffcd31fdd Mon Sep 17 00:00:00 2001 From: pupbrained Date: Fri, 21 Feb 2025 00:27:58 -0500 Subject: [PATCH] ONE POINT SIX MILLISECONDS?????? --- .gitmodules | 7 + flake.nix | 6 +- meson.build | 8 +- src/main.cpp | 98 ++++++---- src/os/linux.cpp | 382 +++++++++++++++++++++++-------------- src/util/types.h | 6 +- subprojects/sdbus-c++ | 1 + subprojects/sqlite3.wrap | 2 + subprojects/sqlitecpp.wrap | 10 + 9 files changed, 319 insertions(+), 201 deletions(-) create mode 160000 subprojects/sdbus-c++ create mode 100644 subprojects/sqlite3.wrap create mode 100644 subprojects/sqlitecpp.wrap diff --git a/.gitmodules b/.gitmodules index 1daf085..2e7b532 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,10 @@ [submodule "subprojects/reflectcpp"] path = subprojects/reflectcpp 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 diff --git a/flake.nix b/flake.nix index 9c85329..21c85aa 100644 --- a/flake.nix +++ b/flake.nix @@ -84,19 +84,17 @@ # tomlplusplus # yyjson # reflect-cpp - sqlitecpp + # sqlitecpp # ftxui ] ++ linuxPkgs; linuxPkgs = nixpkgs.lib.optionals stdenv.isLinux (with pkgs; [ - systemdLibs valgrind ] ++ (with pkgsStatic; [ - glib - sdbus-cpp + dbus xorg.libX11 wayland ])); diff --git a/meson.build b/meson.build index 51da6ee..945d73e 100644 --- a/meson.build +++ b/meson.build @@ -5,7 +5,7 @@ project( default_options: [ 'default_library=static', 'warning_level=everything', - 'buildtype=debugoptimized', + 'buildtype=release', 'cpp_args=-fvisibility=hidden', ], ) @@ -93,14 +93,16 @@ if host_machine.system() == 'darwin' ) elif host_machine.system() == 'linux' or host_machine.system() == 'freebsd' deps += dependency('SQLiteCpp') - deps += dependency('sdbus-c++') deps += dependency('x11') deps += dependency('xcb') deps += dependency('xau') deps += dependency('xdmcp') deps += dependency('wayland-client') + deps += dependency('dbus-1') endif +cmake = import('cmake') + ftxui_dep = dependency( 'ftxui', modules: ['ftxui::screen', 'ftxui::dom', 'ftxui::component'], @@ -118,8 +120,6 @@ endif deps += ftxui_dep -cmake = import('cmake') - reflectcpp_proj = cmake.subproject('reflectcpp') reflectcpp_dep = reflectcpp_proj.dependency('reflectcpp') deps += reflectcpp_dep diff --git a/src/main.cpp b/src/main.cpp index 29b5457..204f700 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -160,21 +160,25 @@ namespace { content.push_back(text("  Hello " + name + "! ") | bold | color(Color::Cyan)); content.push_back(separator() | color(borderColor)); - content.push_back(hbox({ - text("  ") | color(iconColor), // Palette icon - CreateColorCircles(), - })); + content.push_back(hbox( + { + text("  ") | color(iconColor), // Palette icon + CreateColorCircles(), + } + )); content.push_back(separator() | color(borderColor)); // Helper function for aligned rows fn createRow = [&](const std::string& icon, const std::string& label, const std::string& value) { - return hbox({ - text(icon) | color(iconColor), - text(label) | color(labelColor), - filler(), - text(value) | color(valueColor), - text(" "), - }); + return hbox( + { + text(icon) | color(iconColor), + text(label) | color(labelColor), + filler(), + text(value) | color(valueColor), + text(" "), + } + ); }; // System info rows @@ -185,31 +189,39 @@ namespace { const WeatherOutput& weatherInfo = data.weather_info.value(); if (weather.show_town_name) - content.push_back(hbox({ - text(weatherIcon) | color(iconColor), - text("Weather") | color(labelColor), - filler(), + content.push_back(hbox( + { + text(weatherIcon) | color(iconColor), + text("Weather") | color(labelColor), + filler(), - hbox({ - text(fmt::format("{}°F ", std::lround(weatherInfo.main.temp))), - text("in "), - text(weatherInfo.name), - text(" "), - }) | - color(valueColor), - })); + hbox( + { + text(fmt::format("{}°F ", std::lround(weatherInfo.main.temp))), + text("in "), + text(weatherInfo.name), + text(" "), + } + ) | + color(valueColor), + } + )); else - content.push_back(hbox({ - text(weatherIcon) | color(iconColor), - text("Weather") | color(labelColor), - filler(), + content.push_back(hbox( + { + text(weatherIcon) | color(iconColor), + text("Weather") | color(labelColor), + filler(), - hbox({ - text(fmt::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description)), - text(" "), - }) | - color(valueColor), - })); + hbox( + { + text(fmt::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description)), + text(" "), + } + ) | + color(valueColor), + } + )); } content.push_back(separator() | color(borderColor)); @@ -246,14 +258,16 @@ namespace { const std::string& npText = *nowPlayingResult; content.push_back(separator() | color(borderColor)); - content.push_back(hbox({ - text(musicIcon) | color(iconColor), - text("Playing") | color(labelColor), - text(" "), - filler(), - paragraph(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, 30), - text(" "), - })); + content.push_back(hbox( + { + text(musicIcon) | color(iconColor), + text("Playing") | color(labelColor), + text(" "), + filler(), + paragraph(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, 30), + text(" "), + } + )); } else { const NowPlayingError& error = nowPlayingResult.error(); @@ -269,7 +283,7 @@ namespace { #ifdef __linux__ if (std::holds_alternative(error)) - DEBUG_LOG("DBus error: {}", std::get(error).getMessage()); + DEBUG_LOG("DBus error: {}", std::get(error)); #endif #ifdef __WIN32__ diff --git a/src/os/linux.cpp b/src/os/linux.cpp index 709fe17..4ef8f9c 100644 --- a/src/os/linux.cpp +++ b/src/os/linux.cpp @@ -1,34 +1,32 @@ #ifdef __linux__ -#include #include #include #include #include +#include #include #include #include #include #include +#include #include #include #include -#include -#include -#include #include #include +#include #include #include #include "os.h" #include "src/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 std::errc, std::expected, std::from_chars, std::getline, std::istreambuf_iterator, std::less, std::lock_guard, + std::mutex, std::ofstream, std::pair, std::string_view, std::vector, std::nullopt, std::array, 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; @@ -37,33 +35,6 @@ namespace fs = std::filesystem; enum SessionType : u8 { Wayland, X11, TTY, Unknown }; namespace { - fn GetMprisPlayers(sdbus::IConnection& connection) -> vector { - 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 dbusProxy = createProxy(connection, dbusInterface, dbusObjectPath); - - vector names; - - dbusProxy->callMethod(dbusMethodListNames).onInterface(dbusInterface).storeResultsTo(names); - - vector 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& mprisPlayers) -> optional { - if (!mprisPlayers.empty()) - return mprisPlayers.front(); - - return nullopt; - } - fn GetX11WindowManager() -> string { Display* display = XOpenDisplay(nullptr); @@ -103,7 +74,7 @@ namespace { &data ) == Success && data) { - wmWindow = *std::bit_cast(data); + wmWindow = *bit_cast(data); XFree(data); data = nullptr; @@ -322,70 +293,79 @@ namespace { return nullopt; } - fn CountNix() noexcept -> optional { - constexpr string_view dbPath = "/nix/var/nix/db/db.sqlite"; - constexpr string_view querySql = "SELECT COUNT(*) FROM ValidPaths WHERE sigs IS NOT NULL;"; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" + fn GetMprisPlayers(DBusConnection* connection) -> vector { + vector mprisPlayers; + DBusError err; + dbus_error_init(&err); - sqlite3* sqlDB = nullptr; - sqlite3_stmt* stmt = nullptr; - usize count = 0; + // Create a method call to org.freedesktop.DBus.ListNames + DBusMessage* msg = dbus_message_new_method_call( + "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 - const string uri = fmt::format("file:{}{}immutable=1", dbPath, (dbPath.find('?') == string::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 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(sqlite3_column_int64(stmt, 0)); - - sqlite3_finalize(stmt); + if (!msg) { + DEBUG_LOG("Failed to create message for ListNames."); + return mprisPlayers; } - sqlite3_close(sqlDB); - return count ? optional { count } : nullopt; + // 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(&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 { - constexpr const char* dbPath = "/nix/var/nix/db/db.sqlite"; - constexpr const char* cachePath = "/tmp/nix_pkg_count.cache"; - - 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(&count), sizeof(count)); - return cache ? optional(count) : nullopt; - } - } catch (const exception& e) { DEBUG_LOG("Cache access failed: {}, rebuilding...", e.what()); } - - const optional 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(&*count), sizeof(*count)); - } - - fs::rename(tmpPath, cachePath); - } - - return count; + fn GetActivePlayer(const vector& mprisPlayers) -> optional { + if (!mprisPlayers.empty()) + return mprisPlayers.front(); + return nullopt; } } @@ -462,65 +442,177 @@ fn GetMemInfo() -> expected { return unexpected("MemTotal line not found in " + string(path)); } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" fn GetNowPlaying() -> expected { - try { - const char *playerObjectPath = "/org/mpris/MediaPlayer2", *playerInterfaceName = "org.mpris.MediaPlayer2.Player"; + DBusError err; + dbus_error_init(&err); - unique_ptr connection = sdbus::createSessionBusConnection(); + // Connect to the session bus + DBusConnection* connection = dbus_bus_get(DBUS_BUS_SESSION, &err); - vector mprisPlayers = GetMprisPlayers(*connection); + if (!connection) + if (dbus_error_is_set(&err)) { + ERROR_LOG("DBus connection error: {}", err.message); - if (mprisPlayers.empty()) - return unexpected(NowPlayingError { NowPlayingCode::NoPlayers }); + NowPlayingError error = LinuxError(err.message); + dbus_error_free(&err); - optional activePlayer = GetActivePlayer(mprisPlayers); - - if (!activePlayer.has_value()) - return unexpected(NowPlayingError { NowPlayingCode::NoActivePlayer }); - - unique_ptr playerProxy = - sdbus::createProxy(*connection, sdbus::ServiceName(*activePlayer), sdbus::ObjectPath(playerObjectPath)); - - sdbus::Variant metadataVariant = playerProxy->getProperty("Metadata").onInterface(playerInterfaceName); - - if (metadataVariant.containsValueOfType>()) { - const map& metadata = metadataVariant.get>(); - - string title; - auto titleIter = metadata.find("xesam:title"); - if (titleIter != metadata.end() && titleIter->second.containsValueOfType()) - title = titleIter->second.get(); - - string artist; - auto artistIter = metadata.find("xesam:artist"); - if (artistIter != metadata.end() && artistIter->second.containsValueOfType>()) { - auto artists = artistIter->second.get>(); - 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; + return unexpected(error); } - } catch (const sdbus::Error& e) { - if (e.getName() != "com.github.altdesktop.playerctld.NoActivePlayer") - return unexpected(NowPlayingError { LinuxError(e) }); + vector mprisPlayers = GetMprisPlayers(connection); + + if (mprisPlayers.empty()) { + dbus_connection_unref(connection); + return unexpected(NowPlayingError { NowPlayingCode::NoPlayers }); + } + + optional activePlayer = GetActivePlayer(mprisPlayers); + + if (!activePlayer.has_value()) { + dbus_connection_unref(connection); 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(&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(&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(&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 { // Check environment variables first @@ -528,7 +620,7 @@ fn GetWindowManager() -> string { const char* waylandDisplay = getenv("WAYLAND_DISPLAY"); // 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(); if (!compositor.empty()) return compositor; @@ -596,8 +688,6 @@ fn GetKernelVersion() -> string { return ""; } - DEBUG_LOG("{}", CountNixWithCache().value_or(0)); - return static_cast(uts.release); } diff --git a/src/util/types.h b/src/util/types.h index f8da8fd..48a10ae 100644 --- a/src/util/types.h +++ b/src/util/types.h @@ -12,10 +12,6 @@ #include #endif -#ifdef __linux__ -#include -#endif - /** * @typedef u8 * @brief Represents an 8-bit unsigned integer. @@ -152,7 +148,7 @@ enum class NowPlayingCode : u8 { * @typedef LinuxError * @brief Represents a Linux-specific error. */ -using LinuxError = sdbus::Error; +using LinuxError = std::string; #elif defined(__APPLE__) /** * @typedef MacError diff --git a/subprojects/sdbus-c++ b/subprojects/sdbus-c++ new file mode 160000 index 0000000..48ea775 --- /dev/null +++ b/subprojects/sdbus-c++ @@ -0,0 +1 @@ +Subproject commit 48ea775531523ac290abec91107c628f00156543 diff --git a/subprojects/sqlite3.wrap b/subprojects/sqlite3.wrap new file mode 100644 index 0000000..8fe9601 --- /dev/null +++ b/subprojects/sqlite3.wrap @@ -0,0 +1,2 @@ +[wrap-redirect] +filename = SQLiteCpp-3.3.2/subprojects/sqlite3.wrap diff --git a/subprojects/sqlitecpp.wrap b/subprojects/sqlitecpp.wrap new file mode 100644 index 0000000..129ee94 --- /dev/null +++ b/subprojects/sqlitecpp.wrap @@ -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