close one notification to close all
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#include "widgets/controlCenter/mediaControl.hpp"
|
||||
|
||||
#include "helpers/string.hpp"
|
||||
#include "services/textureCache.hpp"
|
||||
|
||||
MediaControlWidget::MediaControlWidget()
|
||||
@@ -62,7 +63,7 @@ MediaControlWidget::MediaControlWidget()
|
||||
double fraction = this->seekBar.get_value() / 100.0;
|
||||
int64_t new_position_us =
|
||||
static_cast<int64_t>(fraction * static_cast<double>(this->totalLengthUs));
|
||||
this->mprisController->emit_seeked(new_position_us); // in ms
|
||||
this->mprisController->emit_seeked(new_position_us - this->currentPositionUs); // in us
|
||||
this->resetSeekTimer(new_position_us);
|
||||
});
|
||||
|
||||
@@ -100,6 +101,16 @@ MediaControlWidget::MediaControlWidget()
|
||||
this->mprisController->signal_mpris_updated().connect(
|
||||
sigc::mem_fun(*this, &MediaControlWidget::onSpotifyMprisUpdated));
|
||||
|
||||
this->mprisController->signal_playback_status_changed().connect(
|
||||
[this](MprisController::PlaybackStatus status) {
|
||||
this->onRunningStateChanged(status);
|
||||
});
|
||||
|
||||
this->mprisController->signal_playback_position_changed().connect(
|
||||
[this](int64_t position_us) {
|
||||
this->setCurrentPosition(position_us);
|
||||
});
|
||||
|
||||
this->artistLabel.set_text("Artist Name");
|
||||
this->artistLabel.add_css_class("control-center-spotify-artist-label");
|
||||
this->titleLabel.set_text("Song Title");
|
||||
@@ -109,7 +120,11 @@ MediaControlWidget::MediaControlWidget()
|
||||
}
|
||||
|
||||
void MediaControlWidget::onSpotifyMprisUpdated(const MprisPlayer2Message &message) {
|
||||
this->artistLabel.set_text(message.artist);
|
||||
std::string artistText = "Unknown Artist";
|
||||
if (!message.artist.empty()) {
|
||||
artistText = StringHelper::trimToSize(message.artist[0], 30);
|
||||
}
|
||||
this->artistLabel.set_text(artistText);
|
||||
this->titleLabel.set_text(message.title);
|
||||
|
||||
if (auto texture = TextureCacheService::getInstance()->getTexture(message.artwork_url)) {
|
||||
@@ -164,4 +179,39 @@ bool MediaControlWidget::onSeekTick() {
|
||||
}
|
||||
setCurrentPosition(nextPosition);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MediaControlWidget::onRunningStateChanged(MprisController::PlaybackStatus status) {
|
||||
switch (status) {
|
||||
case MprisController::PlaybackStatus::Playing:
|
||||
this->onPlay();
|
||||
break;
|
||||
case MprisController::PlaybackStatus::Paused:
|
||||
this->onPause();
|
||||
break;
|
||||
case MprisController::PlaybackStatus::Stopped:
|
||||
this->onStop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MediaControlWidget::onPlay() {
|
||||
this->playPauseButton.set_label("\u23F8"); // Pause symbol
|
||||
// strart seek timer if not already running
|
||||
this->resetSeekTimer(currentPositionUs);
|
||||
}
|
||||
|
||||
void MediaControlWidget::onPause() {
|
||||
this->playPauseButton.set_label("\u23EF"); // Play symbol
|
||||
if (seekTimerConnection.connected()) {
|
||||
seekTimerConnection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void MediaControlWidget::onStop() {
|
||||
this->playPauseButton.set_label("\u23EF"); // Play symbol
|
||||
if (seekTimerConnection.connected()) {
|
||||
seekTimerConnection.disconnect();
|
||||
}
|
||||
this->setCurrentPosition(0);
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
BaseNotification::BaseNotification(std::shared_ptr<Gdk::Monitor> monitor) {
|
||||
ensure_notification_css_loaded();
|
||||
set_default_size(300, 100);
|
||||
set_default_size(350, -1);
|
||||
gtk_layer_init_for_window(gobj());
|
||||
gtk_layer_set_monitor(gobj(), monitor->gobj());
|
||||
gtk_layer_set_layer(gobj(), GTK_LAYER_SHELL_LAYER_OVERLAY);
|
||||
@@ -20,6 +20,9 @@ BaseNotification::BaseNotification(std::shared_ptr<Gdk::Monitor> monitor) {
|
||||
gtk_layer_set_margin(gobj(), GTK_LAYER_SHELL_EDGE_TOP, 2);
|
||||
gtk_layer_set_margin(gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, 2);
|
||||
add_css_class("notification-popup");
|
||||
|
||||
this->set_hexpand(false);
|
||||
this->set_vexpand(false);
|
||||
}
|
||||
|
||||
void BaseNotification::ensure_notification_css_loaded() {
|
||||
|
||||
@@ -1,25 +1,51 @@
|
||||
#include "widgets/notification/notification.hpp"
|
||||
#include "widgets/notification/notificationWindow.hpp"
|
||||
#include "helpers/string.hpp"
|
||||
|
||||
#include "gtkmm/box.h"
|
||||
#include "gtkmm/button.h"
|
||||
#include "gtkmm/image.h"
|
||||
#include "gtkmm/label.h"
|
||||
|
||||
NotificationWindow::NotificationWindow(std::shared_ptr<Gdk::Monitor> monitor, NotifyMessage notify) : BaseNotification(monitor) {
|
||||
set_title(notify.summary);
|
||||
|
||||
if (notify.imageData) {
|
||||
auto img = Gtk::make_managed<Gtk::Image>(*(notify.imageData));
|
||||
img->set_pixel_size(64);
|
||||
img->set_halign(Gtk::Align::CENTER);
|
||||
img->set_valign(Gtk::Align::CENTER);
|
||||
img->add_css_class("notification-image");
|
||||
set_child(*img);
|
||||
}
|
||||
|
||||
// Main vertical box
|
||||
auto vbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 8);
|
||||
|
||||
switch (notify.urgency) {
|
||||
case NotificationUrgency::CRITICAL:
|
||||
add_css_class("notification-critical");
|
||||
break;
|
||||
case NotificationUrgency::NORMAL:
|
||||
add_css_class("notification-normal");
|
||||
break;
|
||||
case NotificationUrgency::LOW:
|
||||
add_css_class("notification-low");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Summary label
|
||||
auto summary_label = Gtk::make_managed<Gtk::Label>("<b>" + notify.summary + "</b>");
|
||||
auto summary_label = Gtk::make_managed<Gtk::Label>("<b>" + StringHelper::trimToSize(notify.summary, 20) + "</b>");
|
||||
summary_label->set_use_markup(true);
|
||||
summary_label->set_halign(Gtk::Align::START);
|
||||
summary_label->set_wrap(true);
|
||||
vbox->append(*summary_label);
|
||||
|
||||
// Body label
|
||||
auto body_label = Gtk::make_managed<Gtk::Label>(notify.body);
|
||||
auto body_label = Gtk::make_managed<Gtk::Label>(StringHelper::trimToSize(notify.body, 100));
|
||||
body_label->set_use_markup(true);
|
||||
body_label->set_halign(Gtk::Align::START);
|
||||
body_label->set_wrap(true);
|
||||
vbox->append(*body_label);
|
||||
|
||||
// If actions exist, add buttons
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "widgets/notification/spotifyNotification.hpp"
|
||||
|
||||
#include "helpers/string.hpp"
|
||||
#include "services/textureCache.hpp"
|
||||
|
||||
#include "gtkmm/box.h"
|
||||
@@ -30,7 +31,8 @@ SpotifyNotification::SpotifyNotification(std::shared_ptr<Gdk::Monitor> monitor,
|
||||
title_label->set_halign(Gtk::Align::CENTER);
|
||||
title_label->set_ellipsize(Pango::EllipsizeMode::END);
|
||||
|
||||
auto artistLabel = Gtk::make_managed<Gtk::Label>(mpris.artist);
|
||||
auto artistLabel = Gtk::make_managed<Gtk::Label>();
|
||||
artistLabel->set_text(StringHelper::trimToSize(mpris.artist[0], 30));
|
||||
artistLabel->set_hexpand(true);
|
||||
artistLabel->set_halign(Gtk::Align::CENTER);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <cmath>
|
||||
#include <graphene.h>
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include "components/base/button.hpp"
|
||||
|
||||
namespace {
|
||||
@@ -168,11 +168,13 @@ void log_menu_tree(const std::vector<TrayService::MenuNode> &nodes,
|
||||
if (!node.visible) {
|
||||
continue;
|
||||
}
|
||||
std::cerr << "[TrayIconWidget] menu node id=" << node.id
|
||||
<< " label='" << node.label << "' enabled="
|
||||
<< (node.enabled ? "1" : "0") << " sep="
|
||||
<< (node.separator ? "1" : "0") << " depth=" << depth
|
||||
<< std::endl;
|
||||
spdlog::debug(
|
||||
"[TrayIconWidget] menu node id={} label='{}' enabled={} sep={} depth={}",
|
||||
node.id,
|
||||
node.label,
|
||||
node.enabled ? 1 : 0,
|
||||
node.separator ? 1 : 0,
|
||||
depth);
|
||||
if (!node.children.empty()) {
|
||||
log_menu_tree(node.children, depth + 1);
|
||||
}
|
||||
@@ -299,8 +301,8 @@ void TrayIconWidget::on_primary_released(int /*n_press*/, double x, double y) {
|
||||
}
|
||||
}
|
||||
|
||||
std::cerr << "[TrayIconWidget] Activate primary id=" << id << " x="
|
||||
<< sendX << " y=" << sendY << std::endl;
|
||||
spdlog::debug("[TrayIconWidget] Activate primary id={} x={} y={}", id, sendX,
|
||||
sendY);
|
||||
service.activate(id, sendX, sendY);
|
||||
}
|
||||
|
||||
@@ -317,8 +319,9 @@ void TrayIconWidget::on_middle_released(int /*n_press*/, double x, double y) {
|
||||
}
|
||||
}
|
||||
|
||||
std::cerr << "[TrayIconWidget] SecondaryActivate (middle) id=" << id
|
||||
<< " x=" << sendX << " y=" << sendY << std::endl;
|
||||
spdlog::debug(
|
||||
"[TrayIconWidget] SecondaryActivate (middle) id={} x={} y={}", id,
|
||||
sendX, sendY);
|
||||
service.secondaryActivate(id, sendX, sendY);
|
||||
}
|
||||
|
||||
@@ -329,8 +332,9 @@ void TrayIconWidget::on_secondary_released(int /*n_press*/, double x,
|
||||
// would crash without a mapped surface.
|
||||
GtkWidget *selfWidget = GTK_WIDGET(gobj());
|
||||
if (!gtk_widget_get_mapped(selfWidget) || !has_popup_surface(selfWidget)) {
|
||||
std::cerr << "[TrayIconWidget] Secondary fallback ContextMenu (no surface) id="
|
||||
<< id << std::endl;
|
||||
spdlog::debug(
|
||||
"[TrayIconWidget] Secondary fallback ContextMenu (no surface) id={}",
|
||||
id);
|
||||
service.contextMenu(id, -1, -1);
|
||||
return;
|
||||
}
|
||||
@@ -341,8 +345,7 @@ void TrayIconWidget::on_secondary_released(int /*n_press*/, double x,
|
||||
// Use dbusmenu popover when available and we have a mapped surface; else
|
||||
// fall back to the item's ContextMenu.
|
||||
if (hasRemoteMenu && has_popup_surface(selfWidget)) {
|
||||
std::cerr << "[TrayIconWidget] Requesting dbusmenu for id=" << id
|
||||
<< std::endl;
|
||||
spdlog::debug("[TrayIconWidget] Requesting dbusmenu for id={}", id);
|
||||
menuPopupPending = true;
|
||||
if (menuRequestInFlight) {
|
||||
return;
|
||||
@@ -367,12 +370,12 @@ void TrayIconWidget::on_secondary_released(int /*n_press*/, double x,
|
||||
(void)try_get_global_pointer_coords(GTK_WIDGET(gobj()), sendX, sendY);
|
||||
}
|
||||
if (is_wayland_display(GTK_WIDGET(gobj()))) {
|
||||
std::cerr << "[TrayIconWidget] ContextMenu wayland id=" << id
|
||||
<< " x=-1 y=-1" << std::endl;
|
||||
spdlog::debug(
|
||||
"[TrayIconWidget] ContextMenu wayland id={} x=-1 y=-1", id);
|
||||
service.contextMenu(id, -1, -1);
|
||||
} else {
|
||||
std::cerr << "[TrayIconWidget] ContextMenu id=" << id << " x=" << sendX
|
||||
<< " y=" << sendY << std::endl;
|
||||
spdlog::debug("[TrayIconWidget] ContextMenu id={} x={} y={}", id,
|
||||
sendX, sendY);
|
||||
service.contextMenu(id, sendX, sendY);
|
||||
}
|
||||
}
|
||||
@@ -518,9 +521,8 @@ void TrayIconWidget::on_menu_action(const Glib::VariantBase & /*parameter*/,
|
||||
int32_t sendY = -1;
|
||||
(void)try_get_pending_coords(sendX, sendY);
|
||||
|
||||
std::cerr << "[TrayIconWidget] Menu action id=" << this->id
|
||||
<< " item=" << itemId << " x=" << sendX << " y=" << sendY
|
||||
<< std::endl;
|
||||
spdlog::debug("[TrayIconWidget] Menu action id={} item={} x={} y={}",
|
||||
this->id, itemId, sendX, sendY);
|
||||
|
||||
const uint32_t nowMs = static_cast<uint32_t>(g_get_monotonic_time() / 1000);
|
||||
// Use button 1 for menu activation events; some dbusmenu handlers ignore
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "widgets/volumeWidget.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <regex>
|
||||
#include <sigc++/functors/mem_fun.h>
|
||||
|
||||
@@ -26,8 +26,7 @@ VolumeWidget::VolumeWidget() : Gtk::Box(Gtk::Orientation::HORIZONTAL) {
|
||||
(void)CommandHelper::exec(
|
||||
"wpctl set-mute @DEFAULT_SINK@ toggle");
|
||||
} catch (const std::exception &ex) {
|
||||
std::cerr << "[VolumeWidget] failed to toggle mute: " << ex.what()
|
||||
<< std::endl;
|
||||
spdlog::error("[VolumeWidget] failed to toggle mute: {}", ex.what());
|
||||
}
|
||||
this->update();
|
||||
});
|
||||
@@ -77,8 +76,7 @@ void VolumeWidget::update() {
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
std::cerr << "[VolumeWidget] failed to read volume: " << ex.what()
|
||||
<< std::endl;
|
||||
spdlog::error("[VolumeWidget] failed to read volume: {}", ex.what());
|
||||
label.set_text("N/A");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user