From cddcc96aa98b534db94ad832af52720ec4004603 Mon Sep 17 00:00:00 2001 From: Arif Hasanic Date: Tue, 3 Feb 2026 22:12:45 +0100 Subject: [PATCH] media widget fix --- CMakeLists.txt | 1 + include/services/dbus/mpris.hpp | 3 + include/services/notificationController.hpp | 1 + .../widgets/controlCenter/mediaControl.hpp | 2 + .../widgets/notification/baseNotification.hpp | 10 +- .../widgets/notification/copyNotification.hpp | 34 ++++ src/services/dbus/mpris.cpp | 44 +++++ src/services/dbus/notification.cpp | 70 +++++--- src/services/notificationController.cpp | 35 +++- src/widgets/controlCenter/mediaControl.cpp | 40 ++++- src/widgets/notification/baseNotification.cpp | 15 +- src/widgets/notification/copyNotification.cpp | 162 ++++++++++++++++++ 12 files changed, 369 insertions(+), 48 deletions(-) create mode 100644 include/widgets/notification/copyNotification.hpp create mode 100644 src/widgets/notification/copyNotification.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f35e932..1eb0252 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ target_sources(bar_lib src/widgets/clock.cpp src/widgets/date.cpp src/widgets/notification/baseNotification.cpp + src/widgets/notification/copyNotification.cpp src/widgets/notification/notificationWindow.cpp src/widgets/notification/spotifyNotification.cpp src/widgets/volumeWidget.cpp diff --git a/include/services/dbus/mpris.hpp b/include/services/dbus/mpris.hpp index b5d2376..82239f6 100644 --- a/include/services/dbus/mpris.hpp +++ b/include/services/dbus/mpris.hpp @@ -23,6 +23,7 @@ class MprisController { static std::shared_ptr getInstance(); void toggle_play(); + void pause(); void next_song(); void previous_song(); void emit_seeked(int64_t position_us); @@ -57,6 +58,8 @@ class MprisController { void on_bus_connected(const Glib::RefPtr &result); void signalNotification(); + void emit_cached_playback_status(); + void emit_cached_position(); void on_dbus_signal(const Glib::ustring &sender_name, const Glib::ustring &signal_name, const Glib::VariantContainerBase ¶meters); diff --git a/include/services/notificationController.hpp b/include/services/notificationController.hpp index 32e5fa2..7aec2af 100644 --- a/include/services/notificationController.hpp +++ b/include/services/notificationController.hpp @@ -24,6 +24,7 @@ class NotificationController { void showSpotifyNotification(MprisPlayer2Message mpris); void showNotificationOnAllMonitors(NotifyMessage notify); + void showCopyNotification(NotifyMessage notify); private: uint64_t globalNotificationId = 1; diff --git a/include/widgets/controlCenter/mediaControl.hpp b/include/widgets/controlCenter/mediaControl.hpp index 51cddcd..6d05696 100644 --- a/include/widgets/controlCenter/mediaControl.hpp +++ b/include/widgets/controlCenter/mediaControl.hpp @@ -21,11 +21,13 @@ class MediaControlWidget : public Gtk::Box { sigc::connection seekTimerConnection; bool suppressSeekSignal = false; std::string currentTrackId; + MprisController::PlaybackStatus playbackStatus = MprisController::PlaybackStatus::Stopped; void setCurrentPosition(int64_t position_us); void setTotalLength(int64_t length_us); void resetSeekTimer(int64_t start_position_us); bool onSeekTick(); + void schedulePauseAfterSeek(); Gtk::Box spotifyContainer; diff --git a/include/widgets/notification/baseNotification.hpp b/include/widgets/notification/baseNotification.hpp index 63ae365..0d2f7f7 100644 --- a/include/widgets/notification/baseNotification.hpp +++ b/include/widgets/notification/baseNotification.hpp @@ -1,20 +1,19 @@ #pragma once +#include #include #include -#include - #include #include "gdkmm/monitor.h" +#include "gtkmm/scrolledwindow.h" #include "gtkmm/window.h" #define DEFAULT_NOTIFICATION_TIMEOUT 6700 - class BaseNotification : public Gtk::Window { public: - BaseNotification( uint64_t notificationId, std::shared_ptr monitor); + BaseNotification(uint64_t notificationId, std::shared_ptr monitor); void pauseAutoClose(); void resumeAutoClose(); @@ -28,6 +27,7 @@ class BaseNotification : public Gtk::Window { uint64_t getNotificationId() const { return this->notificationId; } + private: void ensure_notification_css_loaded(); @@ -38,7 +38,7 @@ class BaseNotification : public Gtk::Window { protected: uint64_t notificationId; - bool autoClosePaused = false; + bool autoClosePaused = false; int autoCloseRemainingMs = 0; std::chrono::steady_clock::time_point autoCloseDeadline; sigc::connection autoCloseConnection; diff --git a/include/widgets/notification/copyNotification.hpp b/include/widgets/notification/copyNotification.hpp new file mode 100644 index 0000000..959907d --- /dev/null +++ b/include/widgets/notification/copyNotification.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include "services/dbus/messages.hpp" +#include "widgets/notification/baseNotification.hpp" +#include "gtkmm/box.h" + +class CopyNotification : public BaseNotification { + enum class CopyType { + TEXT, + IMAGE + }; + + public: + CopyNotification(uint64_t id, + std::shared_ptr monitor, + NotifyMessage notify); + virtual ~CopyNotification() = default; + + protected: + private: + CopyType type; + std::string copiedText; + Glib::RefPtr copiedImage; + Gtk::Box mainBox; + + std::string title; + + void createImageNotification(NotifyMessage notify); + void createTextNotification(NotifyMessage notify); + + void setupTitle(std::string title); +}; \ No newline at end of file diff --git a/src/services/dbus/mpris.cpp b/src/services/dbus/mpris.cpp index ff05c54..1099305 100644 --- a/src/services/dbus/mpris.cpp +++ b/src/services/dbus/mpris.cpp @@ -122,6 +122,8 @@ void MprisController::handle_player_registered(const std::string &bus_name) { m_proxy->signal_properties_changed().connect( sigc::mem_fun(*this, &MprisController::on_properties_changed)); signalNotification(); + emit_cached_playback_status(); + emit_cached_position(); } } catch (const Glib::Error &ex) { spdlog::error("DBus Connection Error: {}", ex.what()); @@ -237,6 +239,42 @@ void MprisController::signalNotification() { mprisUpdatedSignal.emit(mpris); } +void MprisController::emit_cached_playback_status() { + if (!m_proxy) { + return; + } + + Glib::VariantBase status_var; + m_proxy->get_cached_property(status_var, "PlaybackStatus"); + if (!status_var || !status_var.is_of_type(Glib::VariantType("s"))) { + return; + } + + auto status = Glib::VariantBase::cast_dynamic>(status_var).get(); + auto parsedStatusIt = playbackStatusMap.find(static_cast(status)); + if (parsedStatusIt != playbackStatusMap.end()) { + currentPlaybackStatus = parsedStatusIt->second; + playbackStatusChangedSignal.emit(currentPlaybackStatus); + } else { + spdlog::error("Unknown playback status: {}", status.raw()); + } +} + +void MprisController::emit_cached_position() { + if (!m_proxy) { + return; + } + + Glib::VariantBase position_var; + m_proxy->get_cached_property(position_var, "Position"); + if (!position_var || !position_var.is_of_type(Glib::VariantType("x"))) { + return; + } + + auto position = Glib::VariantBase::cast_dynamic>(position_var).get(); + playbackPositionChangedSignal.emit(static_cast(position)); +} + void MprisController::on_properties_changed(const Gio::DBus::Proxy::MapChangedProperties &changed_properties, const std::vector &) { @@ -282,6 +320,12 @@ void MprisController::toggle_play() { } } +void MprisController::pause() { + if (m_proxy) { + m_proxy->call("Pause"); + } +} + void MprisController::next_song() { if (m_proxy) { m_proxy->call("Next"); diff --git a/src/services/dbus/notification.cpp b/src/services/dbus/notification.cpp index 3fd1cb4..69d6424 100644 --- a/src/services/dbus/notification.cpp +++ b/src/services/dbus/notification.cpp @@ -5,6 +5,7 @@ #include "helpers/string.hpp" #include "services/notificationController.hpp" +#include "widgets/notification/copyNotification.hpp" #include "glib.h" #include "glibconfig.h" @@ -39,8 +40,7 @@ void NotificationService::on_method_call(const Glib::RefPtr{ - "body", "actions", "actions-icons", "persistence", "icon-static" - }; + "body", "actions", "actions-icons", "persistence", "icon-static"}; invocation->return_value(Glib::VariantContainerBase::create_tuple( Glib::Variant>::create(caps))); } else if (method_name == "GetServerInformation") { @@ -67,45 +67,44 @@ void NotificationService::handle_notify(const Glib::VariantContainerBase ¶me parameters.get_child(hints_var, 6); parameters.get_child(timeout_var, 7); - Glib::ustring app_name = Glib::VariantBase::cast_dynamic>(app_name_var).get(); - guint32 replaces_id = Glib::VariantBase::cast_dynamic>(replaces_id_var).get(); - Glib::ustring app_icon = Glib::VariantBase::cast_dynamic>(app_icon_var).get(); - Glib::ustring summary = Glib::VariantBase::cast_dynamic>(summary_var).get(); - Glib::ustring body = Glib::VariantBase::cast_dynamic>(body_var).get(); - std::vector actions = Glib::VariantBase::cast_dynamic>>(actions_var).get(); + Glib::ustring app_name = Glib::VariantBase::cast_dynamic>(app_name_var).get(); + guint32 replaces_id = Glib::VariantBase::cast_dynamic>(replaces_id_var).get(); + Glib::ustring app_icon = Glib::VariantBase::cast_dynamic>(app_icon_var).get(); + Glib::ustring summary = Glib::VariantBase::cast_dynamic>(summary_var).get(); + Glib::ustring body = Glib::VariantBase::cast_dynamic>(body_var).get(); + std::vector actions = Glib::VariantBase::cast_dynamic>>(actions_var).get(); std::map hints = Glib::VariantBase::cast_dynamic>>(hints_var).get(); - gint32 expire_timeout = Glib::VariantBase::cast_dynamic>(timeout_var).get(); + gint32 expire_timeout = Glib::VariantBase::cast_dynamic>(timeout_var).get(); spdlog::info("Notification Received: {} - {}", summary.raw(), body.raw()); NotifyMessage notify; - notify.app_name = app_name; - notify.replaces_id = replaces_id; - notify.app_icon = app_icon; - notify.summary = static_cast(summary); - notify.body = static_cast(body); + notify.app_name = app_name; + notify.replaces_id = replaces_id; + notify.app_icon = app_icon; + notify.summary = static_cast(summary); + notify.body = static_cast(body); notify.actionInvoked = std::make_shared(false); std::vector actions_converted; actions_converted.reserve(actions.size()); for (ulong i = 0; i < actions.size(); i += 2) { - auto name = static_cast(actions[i]); + auto name = static_cast(actions[i]); auto label = static_cast(actions[i + 1]); if (name == "default") { label = "Open"; } - + actions_converted.push_back(name); actions_converted.push_back(label); } notify.actions = actions_converted; - for (const auto &[key, value] : hints) { if (key == "urgency") { if (value.is_of_type(Glib::VariantType("y"))) { - auto urgency = Glib::VariantBase::cast_dynamic>(value).get(); + auto urgency = Glib::VariantBase::cast_dynamic>(value).get(); notify.urgency = static_cast(urgency); } } @@ -142,15 +141,46 @@ void NotificationService::handle_notify(const Glib::VariantContainerBase ¶me } } } - } + + if (key == "image-path") { + try { + auto image_path_variant = Glib::VariantBase::cast_dynamic>(value); + auto image_path = static_cast(image_path_variant.get()); + + try { + auto pixbuf = Gdk::Pixbuf::create_from_file(image_path); + notify.imageData = pixbuf; + } catch (const Glib::Error &e) { + spdlog::warn("Failed to load image from path {}: {}", image_path, e.what()); + } + } catch (const std::bad_cast &e) { + spdlog::warn("Failed to decode image-path hint: {}", e.what()); + } + } + } notify.expire_timeout = expire_timeout; + guint id = notificationIdCounter++; + if (app_name == "image-copy" ) { + NotificationController::getInstance()->showCopyNotification(notify); + invocation->return_value(Glib::VariantContainerBase::create_tuple( + Glib::Variant::create(id))); + + return; + } + + if (app_name == "ocr-copy") { + NotificationController::getInstance()->showCopyNotification(notify); + invocation->return_value(Glib::VariantContainerBase::create_tuple( + Glib::Variant::create(id))); + return; + } + if (app_name == "Thunderbird") { notify.expire_timeout = 10000; // 10 seconds for email notifications } - guint id = notificationIdCounter++; // Set up the callback to emit ActionInvoked on D-Bus Glib::RefPtr dbus_conn = invocation->get_connection(); notify.on_action = [dbus_conn, id](const std::string &action_id) { diff --git a/src/services/notificationController.cpp b/src/services/notificationController.cpp index f58b464..edc1933 100644 --- a/src/services/notificationController.cpp +++ b/src/services/notificationController.cpp @@ -1,14 +1,14 @@ #include "services/notificationController.hpp" +#include #include #include "services/dbus/messages.hpp" #include "widgets/notification/baseNotification.hpp" +#include "widgets/notification/copyNotification.hpp" #include "widgets/notification/notificationWindow.hpp" #include "widgets/notification/spotifyNotification.hpp" -#include - #include "gdkmm/display.h" #include "sigc++/adaptors/bind.h" @@ -37,7 +37,6 @@ NotificationController::NotificationController() { } } - void NotificationController::showNotificationOnAllMonitors(NotifyMessage notify) { uint64_t id = this->globalNotificationId++; std::vector> notifications; @@ -69,21 +68,41 @@ void NotificationController::showNotificationOnAllMonitors(NotifyMessage notify) } this->activeNotifications[id] = notifications; - } +void NotificationController::showCopyNotification(NotifyMessage notify) { + std::vector> notifications; + uint64_t id = this->globalNotificationId++; + + for (const auto &monitor : this->activeMonitors) { + auto notification = std::make_shared(id, monitor, notify); + + notification->show(); + notifications.push_back(notification); + + notification->signal_close.connect([this, id = notification->getNotificationId()](int) { + closeNotification(id); + }); + + notification->signal_hover_changed.connect([this, id = notification->getNotificationId()](bool hovered) { + updateHoverState(id, hovered); + }); + notification->startAutoClose(DEFAULT_NOTIFICATION_TIMEOUT); + } + + this->activeNotifications[id] = notifications; +} void NotificationController::showSpotifyNotification(MprisPlayer2Message mpris) { std::vector> notifications; uint64_t id = this->globalNotificationId++; - for (const auto &monitor : this->activeMonitors) { auto notification = std::make_shared(id, monitor, mpris); notification->show(); notifications.push_back(notification); - + notification->signal_close.connect([this, id = notification->getNotificationId()](int) { closeNotification(id); }); @@ -92,12 +111,10 @@ void NotificationController::showSpotifyNotification(MprisPlayer2Message mpris) updateHoverState(id, hovered); }); - - notification->startAutoClose(DEFAULT_NOTIFICATION_TIMEOUT); + notification->startAutoClose(DEFAULT_NOTIFICATION_TIMEOUT); } this->activeNotifications[id] = notifications; - } void NotificationController::updateHoverState(uint64_t notificationId, bool isHovered) { diff --git a/src/widgets/controlCenter/mediaControl.cpp b/src/widgets/controlCenter/mediaControl.cpp index 5a53e04..4d66a13 100644 --- a/src/widgets/controlCenter/mediaControl.cpp +++ b/src/widgets/controlCenter/mediaControl.cpp @@ -71,10 +71,19 @@ MediaControlWidget::MediaControlWidget() } if (!this->currentTrackId.empty()) { this->mprisController->set_position(this->currentTrackId, new_position_us); - } else { + if (this->playbackStatus != MprisController::PlaybackStatus::Playing) { + this->schedulePauseAfterSeek(); + } + } else if (this->playbackStatus == MprisController::PlaybackStatus::Playing) { this->mprisController->emit_seeked(new_position_us - this->currentPositionUs); // in us + } else { + return; + } + if (this->playbackStatus == MprisController::PlaybackStatus::Playing) { + this->resetSeekTimer(new_position_us); + } else { + this->setCurrentPosition(new_position_us); } - this->resetSeekTimer(new_position_us); }); this->bottomContainer.set_orientation(Gtk::Orientation::HORIZONTAL); @@ -136,15 +145,25 @@ void MediaControlWidget::onSpotifyMprisUpdated(const MprisPlayer2Message &messag } this->artistLabel.set_text(artistText); this->titleLabel.set_text(message.title); + const bool trackChanged = !this->currentTrackId.empty() && this->currentTrackId != message.track_id; this->currentTrackId = message.track_id; + if (trackChanged) { + this->currentPositionUs = 0; + } + if (auto texture = TextureCacheService::getInstance()->getTexture(message.artwork_url)) { this->backgroundImage.set_paintable(texture); } this->setTotalLength(message.length_ms * 1000); - this->setCurrentPosition(0); - this->resetSeekTimer(0); + this->setCurrentPosition(this->currentPositionUs); + + if (this->playbackStatus == MprisController::PlaybackStatus::Playing) { + this->resetSeekTimer(this->currentPositionUs); + } else if (seekTimerConnection.connected()) { + seekTimerConnection.disconnect(); + } } void MediaControlWidget::setCurrentPosition(int64_t position_us) { @@ -181,6 +200,14 @@ void MediaControlWidget::resetSeekTimer(int64_t start_position_us) { 1000); } +void MediaControlWidget::schedulePauseAfterSeek() { + Glib::signal_timeout().connect_once([this]() { + if (this->playbackStatus != MprisController::PlaybackStatus::Playing) { + this->mprisController->pause(); + } + }, 100); +} + bool MediaControlWidget::onSeekTick() { if (totalLengthUs <= 0) { return true; @@ -195,6 +222,7 @@ bool MediaControlWidget::onSeekTick() { } void MediaControlWidget::onRunningStateChanged(MprisController::PlaybackStatus status) { + this->playbackStatus = status; switch (status) { case MprisController::PlaybackStatus::Playing: this->onPlay(); @@ -215,14 +243,14 @@ void MediaControlWidget::onPlay() { } void MediaControlWidget::onPause() { - this->playPauseButton.set_label("\u23EF"); // Play symbol + this->playPauseButton.set_label("\u23F5"); // Play symbol if (seekTimerConnection.connected()) { seekTimerConnection.disconnect(); } } void MediaControlWidget::onStop() { - this->playPauseButton.set_label("\u23EF"); // Play symbol + this->playPauseButton.set_label("\u23F9"); // stop symbol if (seekTimerConnection.connected()) { seekTimerConnection.disconnect(); } diff --git a/src/widgets/notification/baseNotification.cpp b/src/widgets/notification/baseNotification.cpp index 7b4a62d..4b772c5 100644 --- a/src/widgets/notification/baseNotification.cpp +++ b/src/widgets/notification/baseNotification.cpp @@ -17,7 +17,7 @@ BaseNotification::BaseNotification(uint64_t notificationId, std::shared_ptr monitor) { ensure_notification_css_loaded(); - set_default_size(350, -1); + set_default_size(350, 100); gtk_layer_init_for_window(gobj()); gtk_layer_set_monitor(gobj(), monitor->gobj()); gtk_layer_set_layer(gobj(), GTK_LAYER_SHELL_LAYER_OVERLAY); @@ -72,7 +72,7 @@ void BaseNotification::startAutoClose(int timeoutMs) { } autoCloseRemainingMs = timeoutMs; - autoClosePaused = false; + autoClosePaused = false; start_auto_close_timeout(timeoutMs); } @@ -81,12 +81,12 @@ void BaseNotification::start_auto_close_timeout(int timeoutMs) { autoCloseConnection.disconnect(); } - autoCloseDeadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeoutMs); + autoCloseDeadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeoutMs); autoCloseConnection = Glib::signal_timeout().connect([this]() { this->signal_close.emit(this->notificationId); return false; // Don't repeat }, - timeoutMs); + timeoutMs); } void BaseNotification::pause_auto_close() { @@ -98,10 +98,10 @@ void BaseNotification::pause_auto_close() { autoCloseConnection.disconnect(); } - auto now = std::chrono::steady_clock::now(); - auto remaining = std::chrono::duration_cast(autoCloseDeadline - now).count(); + auto now = std::chrono::steady_clock::now(); + auto remaining = std::chrono::duration_cast(autoCloseDeadline - now).count(); autoCloseRemainingMs = static_cast(std::max(0, remaining)); - autoClosePaused = true; + autoClosePaused = true; } void BaseNotification::resume_auto_close() { @@ -118,7 +118,6 @@ void BaseNotification::resume_auto_close() { start_auto_close_timeout(autoCloseRemainingMs); } - void BaseNotification::ensure_notification_css_loaded() { static bool loaded = false; if (loaded) { diff --git a/src/widgets/notification/copyNotification.cpp b/src/widgets/notification/copyNotification.cpp new file mode 100644 index 0000000..452dcc1 --- /dev/null +++ b/src/widgets/notification/copyNotification.cpp @@ -0,0 +1,162 @@ +#include "widgets/notification/copyNotification.hpp" + +#include +#include + +#include "glibmm/datetime.h" +#include "glibmm/fileutils.h" +#include "glibmm/miscutils.h" +#include "gtkmm/box.h" +#include "gtkmm/button.h" +#include "gtkmm/image.h" +#include "gtkmm/label.h" + +// todo refactor out later + +void copyToClipboard(const std::string &text) { + auto clipboard = Gdk::Display::get_default()->get_clipboard(); + clipboard->set_text(text); +} + +void copyToClipboard(const Glib::RefPtr &image) { + auto clipboard = Gdk::Display::get_default()->get_clipboard(); + + // Wrap the pixbuf in a Value so GTK knows how to handle it + Glib::Value> value; + value.init(Glib::Value>::value_type()); + value.set(image); + + // Create a content provider from the value + auto provider = Gdk::ContentProvider::create(value); + + spdlog::info("Copying image to clipboard with content provider"); + clipboard->set_content(provider); +} + +void createDirectoryIfNotExists(const std::string &dirpath) { + if (!Glib::file_test(dirpath, Glib::FileTest::IS_DIR)) { + try { + g_mkdir_with_parents(dirpath.c_str(), 0755); + } catch (const Glib::Error &e) { + spdlog::error("Failed to create directory {}: {}", dirpath, e.what()); + } + } +} + +void saveImageToFile(const Glib::RefPtr &image, const std::string &filepath, const std::string &filename) { + try { + createDirectoryIfNotExists(filepath); + std::string fullpath = filepath + "/" + filename; + image->save(fullpath, "png"); + } catch (const Glib::Error &e) { + spdlog::error("Failed to save image to file {}: {}", filepath, e.what()); + } +} + +CopyNotification::CopyNotification(uint64_t id, + std::shared_ptr monitor, + NotifyMessage notify) + : BaseNotification(id, monitor) { + + this->set_child(this->mainBox); + this->mainBox.set_orientation(Gtk::Orientation::VERTICAL); + + if (notify.imageData) { + this->createImageNotification(notify); + } else if (!notify.body.empty()) { + this->createTextNotification(notify); + } else { + spdlog::warn("CopyNotification created without valid image or text data."); + } +} + +void CopyNotification::createImageNotification(NotifyMessage notify) { + this->setupTitle("image copied"); + this->type = CopyType::IMAGE; + this->copiedImage = notify.imageData.value(); + + auto contentBox = Gtk::make_managed(); + contentBox->set_orientation(Gtk::Orientation::VERTICAL); + contentBox->set_margin(0); + auto imageWidget = Gtk::make_managed(this->copiedImage); + contentBox->append(*imageWidget); + imageWidget->set_pixel_size(300); + + auto buttonBox = Gtk::make_managed(); + buttonBox->set_spacing(10); + + // material icons unicode + auto saveToClipboardLabel = Gtk::make_managed("\uF0EA"); // content copy icon + auto saveToFileLabel = Gtk::make_managed("\uF1C5"); // save icon + + auto saveToClipboardButton = Gtk::make_managed(); + saveToClipboardButton->set_child(*saveToClipboardLabel); + saveToClipboardButton->signal_clicked().connect([this]() { + copyToClipboard(this->copiedImage); + spdlog::info("Copied image to clipboard"); + + this->signal_close.emit(this->notificationId); + }); + saveToClipboardButton->set_tooltip_text("Copy to Clipboard"); + saveToClipboardButton->add_css_class("notification-button"); + saveToClipboardButton->add_css_class("notification-icon-button"); + + auto saveToFileButton = Gtk::make_managed(); + saveToFileButton->set_child(*saveToFileLabel); + saveToFileButton->signal_clicked().connect([this]() { + // xdg-pic/screenshot // use env + auto xdgPicturesDir = Glib::get_user_special_dir(Glib::UserDirectory::PICTURES); + auto dateStamp = Glib::DateTime::create_now_local().format("%Y%m%d_%H%M%S"); + auto filepath = xdgPicturesDir + "/screenshot" ; + auto filename = dateStamp + ".png"; + saveImageToFile(this->copiedImage, filepath, filename); + spdlog::info("Saved image to {}", filepath.c_str()); + + this->signal_close.emit(this->notificationId); + }); + saveToFileButton->set_tooltip_text("Save to File"); + saveToFileButton->add_css_class("notification-button"); + saveToFileButton->add_css_class("notification-icon-button"); + + buttonBox->append(*saveToClipboardButton); + buttonBox->append(*saveToFileButton); + contentBox->append(*buttonBox); + + this->mainBox.append(*contentBox); + +} + +void CopyNotification::createTextNotification(NotifyMessage notify) { + this->setupTitle("text copied"); + this->type = CopyType::TEXT; + this->copiedText = notify.body; + auto contentBox = Gtk::make_managed(); + contentBox->set_orientation(Gtk::Orientation::VERTICAL); + auto textLabel = Gtk::make_managed(this->copiedText); + textLabel->set_margin_bottom(10); + contentBox->append(*textLabel); + + auto copyToClipboardButton = Gtk::make_managed(); + auto copyToClipboardLabel = Gtk::make_managed("\uF0EA"); // content copy icon + copyToClipboardButton->set_child(*copyToClipboardLabel); + copyToClipboardButton->signal_clicked().connect([this]() { + copyToClipboard(this->copiedText); + this->signal_close.emit(this->notificationId); + }); + copyToClipboardButton->set_tooltip_text("Copy to Clipboard"); + copyToClipboardButton->add_css_class("notification-icon-button"); + copyToClipboardButton->add_css_class("notification-button"); + copyToClipboardButton->set_hexpand(false); + copyToClipboardButton->set_halign(Gtk::Align::START); + contentBox->append(*copyToClipboardButton); + + this->mainBox.append(*contentBox); +} + +void CopyNotification::setupTitle(std::string title) { + this->title = title; + + auto titleLabel = Gtk::make_managed(this->title); + titleLabel->set_margin_bottom(8); + this->mainBox.append(*titleLabel); +}