close one notification to close all

This commit is contained in:
2026-02-02 21:46:50 +01:00
parent 49ac6cec90
commit ed1a9a8605
21 changed files with 352 additions and 146 deletions

View File

@@ -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);
}

View File

@@ -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() {

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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");
}
}