From caca94bc6aa63cd0aa80e14bd4db51b282317c72 Mon Sep 17 00:00:00 2001 From: Arif Hasanic Date: Sun, 1 Feb 2026 00:57:16 +0100 Subject: [PATCH] vibed a cached downloader for images --- CMakeLists.txt | 4 +- include/app.hpp | 5 +- include/services/dbus/mpris.hpp | 136 +++++++++++++++++++ include/services/{ => dbus}/notification.hpp | 3 +- include/services/notificationController.hpp | 23 ++++ include/services/textureCache.hpp | 19 +++ src/app.cpp | 4 +- src/services/{ => dbus}/notification.cpp | 23 +--- src/services/notificationController.cpp | 116 ++++++++++++++++ src/services/textureCache.cpp | 67 +++++++++ 10 files changed, 376 insertions(+), 24 deletions(-) create mode 100644 include/services/dbus/mpris.hpp rename include/services/{ => dbus}/notification.hpp (99%) create mode 100644 include/services/notificationController.hpp create mode 100644 include/services/textureCache.hpp rename src/services/{ => dbus}/notification.cpp (84%) create mode 100644 src/services/notificationController.cpp create mode 100644 src/services/textureCache.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b169e9..8f46658 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,8 +33,10 @@ target_sources(bar_lib src/widgets/webWidget.cpp src/services/hyprland.cpp - src/services/notification.cpp + src/services/notificationController.cpp + src/services/textureCache.cpp src/services/tray.cpp + src/services/dbus/notification.cpp src/widgets/tray.cpp src/widgets/controlCenter.cpp diff --git a/include/app.hpp b/include/app.hpp index 5f261bd..2da1894 100644 --- a/include/app.hpp +++ b/include/app.hpp @@ -4,11 +4,13 @@ #include "bar/bar.hpp" #include "services/hyprland.hpp" -#include "services/notification.hpp" +#include "services/dbus/notification.hpp" #include "glibmm/refptr.h" #include "gtkmm/application.h" +class MprisController; + class App { public: App(); @@ -19,6 +21,7 @@ class App { Glib::RefPtr app; std::vector> bars; std::shared_ptr notificationService = nullptr; + std::shared_ptr mprisController = nullptr; HyprlandService *hyprlandService = nullptr; TrayService *trayService = TrayService::getInstance(); diff --git a/include/services/dbus/mpris.hpp b/include/services/dbus/mpris.hpp new file mode 100644 index 0000000..d3eec24 --- /dev/null +++ b/include/services/dbus/mpris.hpp @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include "services/notificationController.hpp" +#include "giomm/dbusconnection.h" +#include "giomm/dbusproxy.h" + +class MprisController { +public: + MprisController() { + // 1. Connect to the Session Bus + Gio::DBus::Connection::get( + Gio::DBus::BusType::SESSION, + sigc::mem_fun(*this, &MprisController::on_bus_connected) + ); + } + +private: + Glib::RefPtr m_connection; + Glib::RefPtr m_proxy; + + void on_bus_connected(const Glib::RefPtr& result) { + if (!result) { + std::cerr << "DBus Connection Error: null async result" << std::endl; + return; + } + try { + m_connection = Gio::DBus::Connection::get_finish(result); + + // 2. Create a Proxy to the media player + // NOTE: In a real app, you must find the name dynamically (see Step 2 below). + // For now, ensure a player like Spotify or VLC is running. + // Try "org.mpris.MediaPlayer2.spotify" or "org.mpris.MediaPlayer2.vlc" + std::string player_bus_name = "org.mpris.MediaPlayer2.spotify"; + + m_proxy = Gio::DBus::Proxy::create_sync( + m_connection, + player_bus_name, // The Bus Name + "/org/mpris/MediaPlayer2", // The Object Path + "org.mpris.MediaPlayer2.Player" // The Interface + ); + + if (m_proxy) { + std::cout << "Connected to: " << player_bus_name << std::endl; + + // Get initial state + print_metadata(); + + // 3. Listen for changes (Song change, Pause/Play) + m_proxy->signal_properties_changed().connect( + sigc::mem_fun(*this, &MprisController::on_properties_changed) + ); + } + + } catch (const Glib::Error& ex) { + std::cerr << "DBus Connection Error: " << ex.what() << std::endl; + } + } + + void print_metadata() { + // Retrieve the cached property "Metadata" + Glib::VariantBase metadata_var; + m_proxy->get_cached_property(metadata_var, "Metadata"); + + if (!metadata_var) { + std::cout << "No metadata available." << std::endl; + return; + } + + // 4. Unpack Metadata (Type: a{sv}) + // This is a dictionary of string -> variant + using MetadataMap = std::map; + MetadataMap metadata_map; + + // Cast the variant to a container and iterate + Glib::Variant variant_dict = + Glib::VariantBase::cast_dynamic>(metadata_var); + + metadata_map = variant_dict.get(); + + std::string title, artist, artwork_url; + + if (metadata_map.count("xesam:title")) { + auto title_var = Glib::VariantBase::cast_dynamic>(metadata_map["xesam:title"]); + title = title_var.get(); + } + + if (metadata_map.count("xesam:artist")) { + auto artist_var = metadata_map["xesam:artist"]; + + if (artist_var.is_of_type(Glib::VariantType("as"))) { + auto artists = Glib::VariantBase::cast_dynamic>>(artist_var).get(); + if (!artists.empty()) { + artist = artists[0]; // Take the first artist + } + } + } + + if (metadata_map.count("mpris:artUrl")) { + auto art_var = Glib::VariantBase::cast_dynamic>(metadata_map["mpris:artUrl"]); + artwork_url = art_var.get(); + } + + auto notifactionController = NotificationController::getInstance(); + notifactionController->showSpotifyNotification(title, artist, artwork_url); + + } + + // Called when the song changes + void on_properties_changed(const Gio::DBus::Proxy::MapChangedProperties& changed_properties, + const std::vector& invalidated_properties) { + (void)invalidated_properties; + + // Only refresh when Metadata is updated + if (changed_properties.find("Metadata") != changed_properties.end()) { + // You could parse 'changed_properties' directly, but it's easier to just pull the new cache + print_metadata(); + } + } + +public: + // Call this to toggle play/pause + void toggle_play() { + if(m_proxy) { + m_proxy->call("PlayPause"); + } + } + + // Call this to skip + void next_song() { + if(m_proxy) { + m_proxy->call("Next"); + } + } +}; \ No newline at end of file diff --git a/include/services/notification.hpp b/include/services/dbus/notification.hpp similarity index 99% rename from include/services/notification.hpp rename to include/services/dbus/notification.hpp index 5118667..eddc439 100644 --- a/include/services/notification.hpp +++ b/include/services/dbus/notification.hpp @@ -7,7 +7,6 @@ #include #include - #include "gdkmm/monitor.h" #include "giomm/dbusconnection.h" #include "giomm/dbusownname.h" @@ -47,7 +46,6 @@ class NotificationService { public: NotificationService() : notificationIdCounter(1) { - Gio::DBus::own_name( Gio::DBus::BusType::SESSION, "org.freedesktop.Notifications", @@ -56,6 +54,7 @@ class NotificationService { {}, // Name lost slot (optional) Gio::DBus::BusNameOwnerFlags::REPLACE); } + void onBusAcquired(const Glib::RefPtr &connection, const Glib::ustring &name); private: diff --git a/include/services/notificationController.hpp b/include/services/notificationController.hpp new file mode 100644 index 0000000..8ed978b --- /dev/null +++ b/include/services/notificationController.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +#include "gdkmm/monitor.h" +class NotificationController { + static std::shared_ptr instance; + + public: + static std::shared_ptr getInstance() { + if (!NotificationController::instance) { + NotificationController::instance = std::shared_ptr(new NotificationController()); + } + return NotificationController::instance; + } + + void showSpotifyNotification(const std::string &title, const std::string &message, const std::string &artwork_url); + void showNotificationOnAllMonitors(const std::string &title, const std::string &message); + private: + NotificationController(); + std::vector> activeMonitors; +}; \ No newline at end of file diff --git a/include/services/textureCache.hpp b/include/services/textureCache.hpp new file mode 100644 index 0000000..80aedaa --- /dev/null +++ b/include/services/textureCache.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +#include "gdkmm/texture.h" +#include "glibmm/refptr.h" + +class TextureCacheService { + public: + static TextureCacheService *getInstance(); + + Glib::RefPtr getTexture(const std::string &url); + + private: + TextureCacheService() = default; + + std::unordered_map> cache; +}; diff --git a/src/app.cpp b/src/app.cpp index 2eb2805..160391a 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -2,13 +2,15 @@ #include #include -#include "services/notification.hpp" +#include "services/dbus/notification.hpp" +#include "services/dbus/mpris.hpp" App::App() { this->app = Gtk::Application::create("org.example.mybar"); this->setupServices(); this->hyprlandService = HyprlandService::getInstance(); this->notificationService = std::make_shared(); + this->mprisController = std::make_shared(); app->signal_activate().connect([&]() { auto display = Gdk::Display::get_default(); diff --git a/src/services/notification.cpp b/src/services/dbus/notification.cpp similarity index 84% rename from src/services/notification.cpp rename to src/services/dbus/notification.cpp index 37c6d1b..ef7c636 100644 --- a/src/services/notification.cpp +++ b/src/services/dbus/notification.cpp @@ -1,8 +1,8 @@ -#include "services/notification.hpp" +#include "services/dbus/notification.hpp" #include +#include "services/notificationController.hpp" #include "widgets/notification.hpp" -#include "gtkmm/object.h" void NotificationService::onBusAcquired(const Glib::RefPtr &connection, const Glib::ustring &name) { std::cout << "Acquired bus name: " << name << std::endl; @@ -68,22 +68,7 @@ void NotificationService::handle_notify(const Glib::VariantContainerBase ¶me } void NotificationService::createNotificationPopup(const Glib::ustring &title, const Glib::ustring &message) { - auto display = Gdk::Display::get_default(); - if (!display) { - std::cerr << "Error: No default display found" << std::endl; - return; - } - auto monitors = display->get_monitors(); - if (!monitors) { - std::cerr << "Error: No monitors found" << std::endl; - return; - } - - for (guint i = 0; i < monitors->get_n_items(); ++i) { - auto monitor = std::dynamic_pointer_cast(monitors->get_object(i)); - if (!monitor) continue; - - auto widget = std::make_shared(monitor, title, message); - } + auto controller = NotificationController::getInstance(); + controller->showNotificationOnAllMonitors(title, message); } diff --git a/src/services/notificationController.cpp b/src/services/notificationController.cpp new file mode 100644 index 0000000..8ee99ca --- /dev/null +++ b/src/services/notificationController.cpp @@ -0,0 +1,116 @@ +#include "services/notificationController.hpp" + +#include + +#include "gdkmm/display.h" +#include "giomm/listmodel.h" +#include "glibmm/main.h" +#include "gtk4-layer-shell.h" +#include "gtkmm/box.h" +#include "gtkmm/image.h" +#include "gtkmm/label.h" +#include "gtkmm/window.h" +#include "services/textureCache.hpp" + +std::shared_ptr NotificationController::instance = nullptr; + +NotificationController::NotificationController() { + auto display = Gdk::Display::get_default(); + if (!display) { + return; + } + + auto monitors = display->get_monitors(); + if (!monitors) { + return; + } + + for (guint i = 0; i < monitors->get_n_items(); ++i) { + auto monitor = std::dynamic_pointer_cast( + monitors->get_object(i)); + + this->activeMonitors.push_back(monitor); + } +} + +void NotificationController::showSpotifyNotification(const std::string &title, const std::string &message, const std::string &artwork_url) { + for (const auto &monitor : this->activeMonitors) { + + auto win = new Gtk::Window(); + win->set_title(title); + win->set_default_size(300, 100); + + gtk_layer_init_for_window(win->gobj()); + gtk_layer_set_monitor(win->gobj(), monitor->gobj()); + gtk_layer_set_layer(win->gobj(), GTK_LAYER_SHELL_LAYER_OVERLAY); + gtk_layer_set_anchor(win->gobj(), GTK_LAYER_SHELL_EDGE_TOP, TRUE); + gtk_layer_set_anchor(win->gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, TRUE); + gtk_layer_set_margin(win->gobj(), GTK_LAYER_SHELL_EDGE_TOP, 2); + + win->add_css_class("notification-popup"); + + auto container = Gtk::make_managed(Gtk::Orientation::HORIZONTAL, 10); + + if (auto texture = TextureCacheService::getInstance()->getTexture(artwork_url)) { + auto img = Gtk::make_managed(texture); + // make it larger + img->set_pixel_size(64); + container->append(*img); + } + + auto text_box = Gtk::make_managed(Gtk::Orientation::VERTICAL, 5); + text_box->set_halign(Gtk::Align::CENTER); + text_box->set_valign(Gtk::Align::CENTER); + + auto title_label = Gtk::make_managed("" + title + ""); + title_label->set_use_markup(true); + title_label->set_halign(Gtk::Align::START); + text_box->append(*title_label); + + + auto message_label = Gtk::make_managed(message); + message_label->set_halign(Gtk::Align::START); + text_box->append(*message_label); + + container->append(*text_box); + + win->set_child(*container); + win->show(); + + // Auto close after 3 seconds for demo purposes + Glib::signal_timeout().connect([win]() { + win->close(); + delete win; + return false; // Don't repeat + }, + 3000); + } +} + +void NotificationController::showNotificationOnAllMonitors(const std::string &title, const std::string &message) { + for (const auto &monitor : this->activeMonitors) { + auto win = new Gtk::Window(); + win->set_title(title); + win->set_default_size(300, 100); + + gtk_layer_init_for_window(win->gobj()); + gtk_layer_set_monitor(win->gobj(), monitor->gobj()); + gtk_layer_set_layer(win->gobj(), GTK_LAYER_SHELL_LAYER_OVERLAY); + gtk_layer_set_anchor(win->gobj(), GTK_LAYER_SHELL_EDGE_TOP, TRUE); + gtk_layer_set_anchor(win->gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, TRUE); + gtk_layer_set_margin(win->gobj(), GTK_LAYER_SHELL_EDGE_TOP, 2); + + win->add_css_class("notification-popup"); + auto label = Gtk::make_managed(message); + label->set_use_markup(true); + win->set_child(*label); + win->show(); + + Glib::signal_timeout().connect([win]() { + win->close(); + delete win; + return false; // Don't repeat + }, + 3000); + } +} \ No newline at end of file diff --git a/src/services/textureCache.cpp b/src/services/textureCache.cpp new file mode 100644 index 0000000..9aca9dc --- /dev/null +++ b/src/services/textureCache.cpp @@ -0,0 +1,67 @@ +#include "services/textureCache.hpp" + +#include +#include + +#include "glibmm/bytes.h" + +namespace { +size_t write_to_buffer(void *contents, size_t size, size_t nmemb, void *userp) { + auto *buffer = static_cast *>(userp); + auto total = size * nmemb; + auto *bytes = static_cast(contents); + buffer->insert(buffer->end(), bytes, bytes + total); + return total; +} + +Glib::RefPtr download_texture_from_url(const std::string &url) { + if (url.empty()) { + return {}; + } + + CURL *curl = curl_easy_init(); + if (!curl) { + return {}; + } + + std::vector buffer; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_buffer); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "bar/1.0"); + + auto res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + + if (res != CURLE_OK || buffer.empty()) { + return {}; + } + + auto bytes = Glib::Bytes::create(buffer.data(), buffer.size()); + return Gdk::Texture::create_from_bytes(bytes); +} +} // namespace + +TextureCacheService *TextureCacheService::getInstance() { + static TextureCacheService instance; + return &instance; +} + +Glib::RefPtr TextureCacheService::getTexture(const std::string &url) { + if (url.empty()) { + return {}; + } + + auto it = cache.find(url); + if (it != cache.end()) { + return it->second; + } + + auto texture = download_texture_from_url(url); + if (texture) { + cache.emplace(url, texture); + } + + return texture; +}