media widget fix
This commit is contained in:
@@ -41,6 +41,7 @@ target_sources(bar_lib
|
|||||||
src/widgets/clock.cpp
|
src/widgets/clock.cpp
|
||||||
src/widgets/date.cpp
|
src/widgets/date.cpp
|
||||||
src/widgets/notification/baseNotification.cpp
|
src/widgets/notification/baseNotification.cpp
|
||||||
|
src/widgets/notification/copyNotification.cpp
|
||||||
src/widgets/notification/notificationWindow.cpp
|
src/widgets/notification/notificationWindow.cpp
|
||||||
src/widgets/notification/spotifyNotification.cpp
|
src/widgets/notification/spotifyNotification.cpp
|
||||||
src/widgets/volumeWidget.cpp
|
src/widgets/volumeWidget.cpp
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class MprisController {
|
|||||||
static std::shared_ptr<MprisController> getInstance();
|
static std::shared_ptr<MprisController> getInstance();
|
||||||
|
|
||||||
void toggle_play();
|
void toggle_play();
|
||||||
|
void pause();
|
||||||
void next_song();
|
void next_song();
|
||||||
void previous_song();
|
void previous_song();
|
||||||
void emit_seeked(int64_t position_us);
|
void emit_seeked(int64_t position_us);
|
||||||
@@ -57,6 +58,8 @@ class MprisController {
|
|||||||
|
|
||||||
void on_bus_connected(const Glib::RefPtr<Gio::AsyncResult> &result);
|
void on_bus_connected(const Glib::RefPtr<Gio::AsyncResult> &result);
|
||||||
void signalNotification();
|
void signalNotification();
|
||||||
|
void emit_cached_playback_status();
|
||||||
|
void emit_cached_position();
|
||||||
void on_dbus_signal(const Glib::ustring &sender_name,
|
void on_dbus_signal(const Glib::ustring &sender_name,
|
||||||
const Glib::ustring &signal_name,
|
const Glib::ustring &signal_name,
|
||||||
const Glib::VariantContainerBase ¶meters);
|
const Glib::VariantContainerBase ¶meters);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class NotificationController {
|
|||||||
|
|
||||||
void showSpotifyNotification(MprisPlayer2Message mpris);
|
void showSpotifyNotification(MprisPlayer2Message mpris);
|
||||||
void showNotificationOnAllMonitors(NotifyMessage notify);
|
void showNotificationOnAllMonitors(NotifyMessage notify);
|
||||||
|
void showCopyNotification(NotifyMessage notify);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint64_t globalNotificationId = 1;
|
uint64_t globalNotificationId = 1;
|
||||||
|
|||||||
@@ -21,11 +21,13 @@ class MediaControlWidget : public Gtk::Box {
|
|||||||
sigc::connection seekTimerConnection;
|
sigc::connection seekTimerConnection;
|
||||||
bool suppressSeekSignal = false;
|
bool suppressSeekSignal = false;
|
||||||
std::string currentTrackId;
|
std::string currentTrackId;
|
||||||
|
MprisController::PlaybackStatus playbackStatus = MprisController::PlaybackStatus::Stopped;
|
||||||
|
|
||||||
void setCurrentPosition(int64_t position_us);
|
void setCurrentPosition(int64_t position_us);
|
||||||
void setTotalLength(int64_t length_us);
|
void setTotalLength(int64_t length_us);
|
||||||
void resetSeekTimer(int64_t start_position_us);
|
void resetSeekTimer(int64_t start_position_us);
|
||||||
bool onSeekTick();
|
bool onSeekTick();
|
||||||
|
void schedulePauseAfterSeek();
|
||||||
|
|
||||||
Gtk::Box spotifyContainer;
|
Gtk::Box spotifyContainer;
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
#include <sigc++/connection.h>
|
#include <sigc++/connection.h>
|
||||||
|
|
||||||
#include "gdkmm/monitor.h"
|
#include "gdkmm/monitor.h"
|
||||||
|
#include "gtkmm/scrolledwindow.h"
|
||||||
#include "gtkmm/window.h"
|
#include "gtkmm/window.h"
|
||||||
|
|
||||||
#define DEFAULT_NOTIFICATION_TIMEOUT 6700
|
#define DEFAULT_NOTIFICATION_TIMEOUT 6700
|
||||||
|
|
||||||
|
|
||||||
class BaseNotification : public Gtk::Window {
|
class BaseNotification : public Gtk::Window {
|
||||||
public:
|
public:
|
||||||
BaseNotification( uint64_t notificationId, std::shared_ptr<Gdk::Monitor> monitor);
|
BaseNotification(uint64_t notificationId, std::shared_ptr<Gdk::Monitor> monitor);
|
||||||
|
|
||||||
void pauseAutoClose();
|
void pauseAutoClose();
|
||||||
void resumeAutoClose();
|
void resumeAutoClose();
|
||||||
@@ -28,6 +27,7 @@ class BaseNotification : public Gtk::Window {
|
|||||||
uint64_t getNotificationId() const {
|
uint64_t getNotificationId() const {
|
||||||
return this->notificationId;
|
return this->notificationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ensure_notification_css_loaded();
|
void ensure_notification_css_loaded();
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ class BaseNotification : public Gtk::Window {
|
|||||||
protected:
|
protected:
|
||||||
uint64_t notificationId;
|
uint64_t notificationId;
|
||||||
|
|
||||||
bool autoClosePaused = false;
|
bool autoClosePaused = false;
|
||||||
int autoCloseRemainingMs = 0;
|
int autoCloseRemainingMs = 0;
|
||||||
std::chrono::steady_clock::time_point autoCloseDeadline;
|
std::chrono::steady_clock::time_point autoCloseDeadline;
|
||||||
sigc::connection autoCloseConnection;
|
sigc::connection autoCloseConnection;
|
||||||
|
|||||||
34
include/widgets/notification/copyNotification.hpp
Normal file
34
include/widgets/notification/copyNotification.hpp
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#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<Gdk::Monitor> monitor,
|
||||||
|
NotifyMessage notify);
|
||||||
|
virtual ~CopyNotification() = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
private:
|
||||||
|
CopyType type;
|
||||||
|
std::string copiedText;
|
||||||
|
Glib::RefPtr<Gdk::Pixbuf> copiedImage;
|
||||||
|
Gtk::Box mainBox;
|
||||||
|
|
||||||
|
std::string title;
|
||||||
|
|
||||||
|
void createImageNotification(NotifyMessage notify);
|
||||||
|
void createTextNotification(NotifyMessage notify);
|
||||||
|
|
||||||
|
void setupTitle(std::string title);
|
||||||
|
};
|
||||||
@@ -122,6 +122,8 @@ void MprisController::handle_player_registered(const std::string &bus_name) {
|
|||||||
m_proxy->signal_properties_changed().connect(
|
m_proxy->signal_properties_changed().connect(
|
||||||
sigc::mem_fun(*this, &MprisController::on_properties_changed));
|
sigc::mem_fun(*this, &MprisController::on_properties_changed));
|
||||||
signalNotification();
|
signalNotification();
|
||||||
|
emit_cached_playback_status();
|
||||||
|
emit_cached_position();
|
||||||
}
|
}
|
||||||
} catch (const Glib::Error &ex) {
|
} catch (const Glib::Error &ex) {
|
||||||
spdlog::error("DBus Connection Error: {}", ex.what());
|
spdlog::error("DBus Connection Error: {}", ex.what());
|
||||||
@@ -237,6 +239,42 @@ void MprisController::signalNotification() {
|
|||||||
mprisUpdatedSignal.emit(mpris);
|
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<Glib::Variant<Glib::ustring>>(status_var).get();
|
||||||
|
auto parsedStatusIt = playbackStatusMap.find(static_cast<std::string>(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<Glib::Variant<gint64>>(position_var).get();
|
||||||
|
playbackPositionChangedSignal.emit(static_cast<int64_t>(position));
|
||||||
|
}
|
||||||
|
|
||||||
void MprisController::on_properties_changed(const Gio::DBus::Proxy::MapChangedProperties &changed_properties,
|
void MprisController::on_properties_changed(const Gio::DBus::Proxy::MapChangedProperties &changed_properties,
|
||||||
const std::vector<Glib::ustring> &) {
|
const std::vector<Glib::ustring> &) {
|
||||||
|
|
||||||
@@ -282,6 +320,12 @@ void MprisController::toggle_play() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MprisController::pause() {
|
||||||
|
if (m_proxy) {
|
||||||
|
m_proxy->call("Pause");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MprisController::next_song() {
|
void MprisController::next_song() {
|
||||||
if (m_proxy) {
|
if (m_proxy) {
|
||||||
m_proxy->call("Next");
|
m_proxy->call("Next");
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include "helpers/string.hpp"
|
#include "helpers/string.hpp"
|
||||||
#include "services/notificationController.hpp"
|
#include "services/notificationController.hpp"
|
||||||
|
#include "widgets/notification/copyNotification.hpp"
|
||||||
|
|
||||||
#include "glib.h"
|
#include "glib.h"
|
||||||
#include "glibconfig.h"
|
#include "glibconfig.h"
|
||||||
@@ -39,8 +40,7 @@ void NotificationService::on_method_call(const Glib::RefPtr<Gio::DBus::Connectio
|
|||||||
handle_notify(parameters, invocation);
|
handle_notify(parameters, invocation);
|
||||||
} else if (method_name == "GetCapabilities") {
|
} else if (method_name == "GetCapabilities") {
|
||||||
auto caps = std::vector<Glib::ustring>{
|
auto caps = std::vector<Glib::ustring>{
|
||||||
"body", "actions", "actions-icons", "persistence", "icon-static"
|
"body", "actions", "actions-icons", "persistence", "icon-static"};
|
||||||
};
|
|
||||||
invocation->return_value(Glib::VariantContainerBase::create_tuple(
|
invocation->return_value(Glib::VariantContainerBase::create_tuple(
|
||||||
Glib::Variant<std::vector<Glib::ustring>>::create(caps)));
|
Glib::Variant<std::vector<Glib::ustring>>::create(caps)));
|
||||||
} else if (method_name == "GetServerInformation") {
|
} 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(hints_var, 6);
|
||||||
parameters.get_child(timeout_var, 7);
|
parameters.get_child(timeout_var, 7);
|
||||||
|
|
||||||
Glib::ustring app_name = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(app_name_var).get();
|
Glib::ustring app_name = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(app_name_var).get();
|
||||||
guint32 replaces_id = Glib::VariantBase::cast_dynamic<Glib::Variant<guint32>>(replaces_id_var).get();
|
guint32 replaces_id = Glib::VariantBase::cast_dynamic<Glib::Variant<guint32>>(replaces_id_var).get();
|
||||||
Glib::ustring app_icon = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(app_icon_var).get();
|
Glib::ustring app_icon = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(app_icon_var).get();
|
||||||
Glib::ustring summary = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(summary_var).get();
|
Glib::ustring summary = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(summary_var).get();
|
||||||
Glib::ustring body = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(body_var).get();
|
Glib::ustring body = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(body_var).get();
|
||||||
std::vector<Glib::ustring> actions = Glib::VariantBase::cast_dynamic<Glib::Variant<std::vector<Glib::ustring>>>(actions_var).get();
|
std::vector<Glib::ustring> actions = Glib::VariantBase::cast_dynamic<Glib::Variant<std::vector<Glib::ustring>>>(actions_var).get();
|
||||||
std::map<Glib::ustring, Glib::VariantBase> hints = Glib::VariantBase::cast_dynamic<Glib::Variant<std::map<Glib::ustring, Glib::VariantBase>>>(hints_var).get();
|
std::map<Glib::ustring, Glib::VariantBase> hints = Glib::VariantBase::cast_dynamic<Glib::Variant<std::map<Glib::ustring, Glib::VariantBase>>>(hints_var).get();
|
||||||
gint32 expire_timeout = Glib::VariantBase::cast_dynamic<Glib::Variant<gint32>>(timeout_var).get();
|
gint32 expire_timeout = Glib::VariantBase::cast_dynamic<Glib::Variant<gint32>>(timeout_var).get();
|
||||||
|
|
||||||
spdlog::info("Notification Received: {} - {}", summary.raw(), body.raw());
|
spdlog::info("Notification Received: {} - {}", summary.raw(), body.raw());
|
||||||
|
|
||||||
NotifyMessage notify;
|
NotifyMessage notify;
|
||||||
notify.app_name = app_name;
|
notify.app_name = app_name;
|
||||||
notify.replaces_id = replaces_id;
|
notify.replaces_id = replaces_id;
|
||||||
notify.app_icon = app_icon;
|
notify.app_icon = app_icon;
|
||||||
notify.summary = static_cast<std::string>(summary);
|
notify.summary = static_cast<std::string>(summary);
|
||||||
notify.body = static_cast<std::string>(body);
|
notify.body = static_cast<std::string>(body);
|
||||||
notify.actionInvoked = std::make_shared<bool>(false);
|
notify.actionInvoked = std::make_shared<bool>(false);
|
||||||
|
|
||||||
std::vector<std::string> actions_converted;
|
std::vector<std::string> actions_converted;
|
||||||
actions_converted.reserve(actions.size());
|
actions_converted.reserve(actions.size());
|
||||||
for (ulong i = 0; i < actions.size(); i += 2) {
|
for (ulong i = 0; i < actions.size(); i += 2) {
|
||||||
auto name = static_cast<std::string>(actions[i]);
|
auto name = static_cast<std::string>(actions[i]);
|
||||||
auto label = static_cast<std::string>(actions[i + 1]);
|
auto label = static_cast<std::string>(actions[i + 1]);
|
||||||
|
|
||||||
if (name == "default") {
|
if (name == "default") {
|
||||||
label = "Open";
|
label = "Open";
|
||||||
}
|
}
|
||||||
|
|
||||||
actions_converted.push_back(name);
|
actions_converted.push_back(name);
|
||||||
actions_converted.push_back(label);
|
actions_converted.push_back(label);
|
||||||
}
|
}
|
||||||
notify.actions = actions_converted;
|
notify.actions = actions_converted;
|
||||||
|
|
||||||
|
|
||||||
for (const auto &[key, value] : hints) {
|
for (const auto &[key, value] : hints) {
|
||||||
if (key == "urgency") {
|
if (key == "urgency") {
|
||||||
if (value.is_of_type(Glib::VariantType("y"))) {
|
if (value.is_of_type(Glib::VariantType("y"))) {
|
||||||
auto urgency = Glib::VariantBase::cast_dynamic<Glib::Variant<guint8>>(value).get();
|
auto urgency = Glib::VariantBase::cast_dynamic<Glib::Variant<guint8>>(value).get();
|
||||||
notify.urgency = static_cast<NotificationUrgency>(urgency);
|
notify.urgency = static_cast<NotificationUrgency>(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<Glib::Variant<Glib::ustring>>(value);
|
||||||
|
auto image_path = static_cast<std::string>(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;
|
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<guint>::create(id)));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app_name == "ocr-copy") {
|
||||||
|
NotificationController::getInstance()->showCopyNotification(notify);
|
||||||
|
invocation->return_value(Glib::VariantContainerBase::create_tuple(
|
||||||
|
Glib::Variant<guint>::create(id)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (app_name == "Thunderbird") {
|
if (app_name == "Thunderbird") {
|
||||||
notify.expire_timeout = 10000; // 10 seconds for email notifications
|
notify.expire_timeout = 10000; // 10 seconds for email notifications
|
||||||
}
|
}
|
||||||
|
|
||||||
guint id = notificationIdCounter++;
|
|
||||||
// Set up the callback to emit ActionInvoked on D-Bus
|
// Set up the callback to emit ActionInvoked on D-Bus
|
||||||
Glib::RefPtr<Gio::DBus::Connection> dbus_conn = invocation->get_connection();
|
Glib::RefPtr<Gio::DBus::Connection> dbus_conn = invocation->get_connection();
|
||||||
notify.on_action = [dbus_conn, id](const std::string &action_id) {
|
notify.on_action = [dbus_conn, id](const std::string &action_id) {
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
#include "services/notificationController.hpp"
|
#include "services/notificationController.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "services/dbus/messages.hpp"
|
#include "services/dbus/messages.hpp"
|
||||||
#include "widgets/notification/baseNotification.hpp"
|
#include "widgets/notification/baseNotification.hpp"
|
||||||
|
#include "widgets/notification/copyNotification.hpp"
|
||||||
#include "widgets/notification/notificationWindow.hpp"
|
#include "widgets/notification/notificationWindow.hpp"
|
||||||
#include "widgets/notification/spotifyNotification.hpp"
|
#include "widgets/notification/spotifyNotification.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include "gdkmm/display.h"
|
#include "gdkmm/display.h"
|
||||||
#include "sigc++/adaptors/bind.h"
|
#include "sigc++/adaptors/bind.h"
|
||||||
|
|
||||||
@@ -37,7 +37,6 @@ NotificationController::NotificationController() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void NotificationController::showNotificationOnAllMonitors(NotifyMessage notify) {
|
void NotificationController::showNotificationOnAllMonitors(NotifyMessage notify) {
|
||||||
uint64_t id = this->globalNotificationId++;
|
uint64_t id = this->globalNotificationId++;
|
||||||
std::vector<std::shared_ptr<BaseNotification>> notifications;
|
std::vector<std::shared_ptr<BaseNotification>> notifications;
|
||||||
@@ -69,21 +68,41 @@ void NotificationController::showNotificationOnAllMonitors(NotifyMessage notify)
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->activeNotifications[id] = notifications;
|
this->activeNotifications[id] = notifications;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NotificationController::showCopyNotification(NotifyMessage notify) {
|
||||||
|
std::vector<std::shared_ptr<BaseNotification>> notifications;
|
||||||
|
uint64_t id = this->globalNotificationId++;
|
||||||
|
|
||||||
|
for (const auto &monitor : this->activeMonitors) {
|
||||||
|
auto notification = std::make_shared<CopyNotification>(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) {
|
void NotificationController::showSpotifyNotification(MprisPlayer2Message mpris) {
|
||||||
std::vector<std::shared_ptr<BaseNotification>> notifications;
|
std::vector<std::shared_ptr<BaseNotification>> notifications;
|
||||||
uint64_t id = this->globalNotificationId++;
|
uint64_t id = this->globalNotificationId++;
|
||||||
|
|
||||||
|
|
||||||
for (const auto &monitor : this->activeMonitors) {
|
for (const auto &monitor : this->activeMonitors) {
|
||||||
auto notification = std::make_shared<SpotifyNotification>(id, monitor, mpris);
|
auto notification = std::make_shared<SpotifyNotification>(id, monitor, mpris);
|
||||||
|
|
||||||
notification->show();
|
notification->show();
|
||||||
notifications.push_back(notification);
|
notifications.push_back(notification);
|
||||||
|
|
||||||
notification->signal_close.connect([this, id = notification->getNotificationId()](int) {
|
notification->signal_close.connect([this, id = notification->getNotificationId()](int) {
|
||||||
closeNotification(id);
|
closeNotification(id);
|
||||||
});
|
});
|
||||||
@@ -92,12 +111,10 @@ void NotificationController::showSpotifyNotification(MprisPlayer2Message mpris)
|
|||||||
updateHoverState(id, hovered);
|
updateHoverState(id, hovered);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
notification->startAutoClose(DEFAULT_NOTIFICATION_TIMEOUT);
|
||||||
notification->startAutoClose(DEFAULT_NOTIFICATION_TIMEOUT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this->activeNotifications[id] = notifications;
|
this->activeNotifications[id] = notifications;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationController::updateHoverState(uint64_t notificationId, bool isHovered) {
|
void NotificationController::updateHoverState(uint64_t notificationId, bool isHovered) {
|
||||||
|
|||||||
@@ -71,10 +71,19 @@ MediaControlWidget::MediaControlWidget()
|
|||||||
}
|
}
|
||||||
if (!this->currentTrackId.empty()) {
|
if (!this->currentTrackId.empty()) {
|
||||||
this->mprisController->set_position(this->currentTrackId, new_position_us);
|
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
|
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);
|
this->bottomContainer.set_orientation(Gtk::Orientation::HORIZONTAL);
|
||||||
@@ -136,15 +145,25 @@ void MediaControlWidget::onSpotifyMprisUpdated(const MprisPlayer2Message &messag
|
|||||||
}
|
}
|
||||||
this->artistLabel.set_text(artistText);
|
this->artistLabel.set_text(artistText);
|
||||||
this->titleLabel.set_text(message.title);
|
this->titleLabel.set_text(message.title);
|
||||||
|
const bool trackChanged = !this->currentTrackId.empty() && this->currentTrackId != message.track_id;
|
||||||
this->currentTrackId = message.track_id;
|
this->currentTrackId = message.track_id;
|
||||||
|
|
||||||
|
if (trackChanged) {
|
||||||
|
this->currentPositionUs = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (auto texture = TextureCacheService::getInstance()->getTexture(message.artwork_url)) {
|
if (auto texture = TextureCacheService::getInstance()->getTexture(message.artwork_url)) {
|
||||||
this->backgroundImage.set_paintable(texture);
|
this->backgroundImage.set_paintable(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->setTotalLength(message.length_ms * 1000);
|
this->setTotalLength(message.length_ms * 1000);
|
||||||
this->setCurrentPosition(0);
|
this->setCurrentPosition(this->currentPositionUs);
|
||||||
this->resetSeekTimer(0);
|
|
||||||
|
if (this->playbackStatus == MprisController::PlaybackStatus::Playing) {
|
||||||
|
this->resetSeekTimer(this->currentPositionUs);
|
||||||
|
} else if (seekTimerConnection.connected()) {
|
||||||
|
seekTimerConnection.disconnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaControlWidget::setCurrentPosition(int64_t position_us) {
|
void MediaControlWidget::setCurrentPosition(int64_t position_us) {
|
||||||
@@ -181,6 +200,14 @@ void MediaControlWidget::resetSeekTimer(int64_t start_position_us) {
|
|||||||
1000);
|
1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MediaControlWidget::schedulePauseAfterSeek() {
|
||||||
|
Glib::signal_timeout().connect_once([this]() {
|
||||||
|
if (this->playbackStatus != MprisController::PlaybackStatus::Playing) {
|
||||||
|
this->mprisController->pause();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
bool MediaControlWidget::onSeekTick() {
|
bool MediaControlWidget::onSeekTick() {
|
||||||
if (totalLengthUs <= 0) {
|
if (totalLengthUs <= 0) {
|
||||||
return true;
|
return true;
|
||||||
@@ -195,6 +222,7 @@ bool MediaControlWidget::onSeekTick() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MediaControlWidget::onRunningStateChanged(MprisController::PlaybackStatus status) {
|
void MediaControlWidget::onRunningStateChanged(MprisController::PlaybackStatus status) {
|
||||||
|
this->playbackStatus = status;
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case MprisController::PlaybackStatus::Playing:
|
case MprisController::PlaybackStatus::Playing:
|
||||||
this->onPlay();
|
this->onPlay();
|
||||||
@@ -215,14 +243,14 @@ void MediaControlWidget::onPlay() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MediaControlWidget::onPause() {
|
void MediaControlWidget::onPause() {
|
||||||
this->playPauseButton.set_label("\u23EF"); // Play symbol
|
this->playPauseButton.set_label("\u23F5"); // Play symbol
|
||||||
if (seekTimerConnection.connected()) {
|
if (seekTimerConnection.connected()) {
|
||||||
seekTimerConnection.disconnect();
|
seekTimerConnection.disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaControlWidget::onStop() {
|
void MediaControlWidget::onStop() {
|
||||||
this->playPauseButton.set_label("\u23EF"); // Play symbol
|
this->playPauseButton.set_label("\u23F9"); // stop symbol
|
||||||
if (seekTimerConnection.connected()) {
|
if (seekTimerConnection.connected()) {
|
||||||
seekTimerConnection.disconnect();
|
seekTimerConnection.disconnect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
BaseNotification::BaseNotification(uint64_t notificationId, std::shared_ptr<Gdk::Monitor> monitor) {
|
BaseNotification::BaseNotification(uint64_t notificationId, std::shared_ptr<Gdk::Monitor> monitor) {
|
||||||
ensure_notification_css_loaded();
|
ensure_notification_css_loaded();
|
||||||
set_default_size(350, -1);
|
set_default_size(350, 100);
|
||||||
gtk_layer_init_for_window(gobj());
|
gtk_layer_init_for_window(gobj());
|
||||||
gtk_layer_set_monitor(gobj(), monitor->gobj());
|
gtk_layer_set_monitor(gobj(), monitor->gobj());
|
||||||
gtk_layer_set_layer(gobj(), GTK_LAYER_SHELL_LAYER_OVERLAY);
|
gtk_layer_set_layer(gobj(), GTK_LAYER_SHELL_LAYER_OVERLAY);
|
||||||
@@ -72,7 +72,7 @@ void BaseNotification::startAutoClose(int timeoutMs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
autoCloseRemainingMs = timeoutMs;
|
autoCloseRemainingMs = timeoutMs;
|
||||||
autoClosePaused = false;
|
autoClosePaused = false;
|
||||||
start_auto_close_timeout(timeoutMs);
|
start_auto_close_timeout(timeoutMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,12 +81,12 @@ void BaseNotification::start_auto_close_timeout(int timeoutMs) {
|
|||||||
autoCloseConnection.disconnect();
|
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]() {
|
autoCloseConnection = Glib::signal_timeout().connect([this]() {
|
||||||
this->signal_close.emit(this->notificationId);
|
this->signal_close.emit(this->notificationId);
|
||||||
return false; // Don't repeat
|
return false; // Don't repeat
|
||||||
},
|
},
|
||||||
timeoutMs);
|
timeoutMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseNotification::pause_auto_close() {
|
void BaseNotification::pause_auto_close() {
|
||||||
@@ -98,10 +98,10 @@ void BaseNotification::pause_auto_close() {
|
|||||||
autoCloseConnection.disconnect();
|
autoCloseConnection.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto now = std::chrono::steady_clock::now();
|
auto now = std::chrono::steady_clock::now();
|
||||||
auto remaining = std::chrono::duration_cast<std::chrono::milliseconds>(autoCloseDeadline - now).count();
|
auto remaining = std::chrono::duration_cast<std::chrono::milliseconds>(autoCloseDeadline - now).count();
|
||||||
autoCloseRemainingMs = static_cast<int>(std::max<int64_t>(0, remaining));
|
autoCloseRemainingMs = static_cast<int>(std::max<int64_t>(0, remaining));
|
||||||
autoClosePaused = true;
|
autoClosePaused = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseNotification::resume_auto_close() {
|
void BaseNotification::resume_auto_close() {
|
||||||
@@ -118,7 +118,6 @@ void BaseNotification::resume_auto_close() {
|
|||||||
start_auto_close_timeout(autoCloseRemainingMs);
|
start_auto_close_timeout(autoCloseRemainingMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void BaseNotification::ensure_notification_css_loaded() {
|
void BaseNotification::ensure_notification_css_loaded() {
|
||||||
static bool loaded = false;
|
static bool loaded = false;
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
|
|||||||
162
src/widgets/notification/copyNotification.cpp
Normal file
162
src/widgets/notification/copyNotification.cpp
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
#include "widgets/notification/copyNotification.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#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<Gdk::Pixbuf> &image) {
|
||||||
|
auto clipboard = Gdk::Display::get_default()->get_clipboard();
|
||||||
|
|
||||||
|
// Wrap the pixbuf in a Value so GTK knows how to handle it
|
||||||
|
Glib::Value<Glib::RefPtr<Gdk::Pixbuf>> value;
|
||||||
|
value.init(Glib::Value<Glib::RefPtr<Gdk::Pixbuf>>::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<Gdk::Pixbuf> &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<Gdk::Monitor> 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<Gtk::Box>();
|
||||||
|
contentBox->set_orientation(Gtk::Orientation::VERTICAL);
|
||||||
|
contentBox->set_margin(0);
|
||||||
|
auto imageWidget = Gtk::make_managed<Gtk::Image>(this->copiedImage);
|
||||||
|
contentBox->append(*imageWidget);
|
||||||
|
imageWidget->set_pixel_size(300);
|
||||||
|
|
||||||
|
auto buttonBox = Gtk::make_managed<Gtk::Box>();
|
||||||
|
buttonBox->set_spacing(10);
|
||||||
|
|
||||||
|
// material icons unicode
|
||||||
|
auto saveToClipboardLabel = Gtk::make_managed<Gtk::Label>("\uF0EA"); // content copy icon
|
||||||
|
auto saveToFileLabel = Gtk::make_managed<Gtk::Label>("\uF1C5"); // save icon
|
||||||
|
|
||||||
|
auto saveToClipboardButton = Gtk::make_managed<Gtk::Button>();
|
||||||
|
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<Gtk::Button>();
|
||||||
|
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<Gtk::Box>();
|
||||||
|
contentBox->set_orientation(Gtk::Orientation::VERTICAL);
|
||||||
|
auto textLabel = Gtk::make_managed<Gtk::Label>(this->copiedText);
|
||||||
|
textLabel->set_margin_bottom(10);
|
||||||
|
contentBox->append(*textLabel);
|
||||||
|
|
||||||
|
auto copyToClipboardButton = Gtk::make_managed<Gtk::Button>();
|
||||||
|
auto copyToClipboardLabel = Gtk::make_managed<Gtk::Label>("\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<Gtk::Label>(this->title);
|
||||||
|
titleLabel->set_margin_bottom(8);
|
||||||
|
this->mainBox.append(*titleLabel);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user