timers work, fix crash when vscode window closes after timeout

This commit is contained in:
2026-02-07 22:54:31 +01:00
parent 6be70a7d93
commit a90d1c2f6c
7 changed files with 72 additions and 11 deletions

View File

@@ -67,7 +67,7 @@ class Timer : public Gtk::Box {
} }
void activateTimer() { void activateTimer() {
std::cout << "Timer activated" << std::endl; // std::cout << "Timer activated" << std::endl;
} }
void updateTimeLeft(uint64_t timeValue) { void updateTimeLeft(uint64_t timeValue) {

View File

@@ -41,6 +41,12 @@ class StringHelper {
if (input.length() <= maxSize) { if (input.length() <= maxSize) {
return input; return input;
} }
return input.substr(0, maxSize) + "...";
size_t len = maxSize;
while (len > 0 && (static_cast<unsigned char>(input[len]) & 0xC0) == 0x80) {
len--;
}
return input.substr(0, len) + "...";
} }
}; };

View File

@@ -2,9 +2,12 @@
#include <map> #include <map>
#include <memory> #include <memory>
#include <spdlog/spdlog.h>
#include <sys/types.h> #include <sys/types.h>
#include "glibmm/main.h" #include "glibmm/main.h"
#include "gtkmm/mediafile.h"
#include "gtkmm/mediastream.h"
#include "sigc++/signal.h" #include "sigc++/signal.h"
class TimerService { class TimerService {
struct TimerData { struct TimerData {
@@ -15,7 +18,7 @@ class TimerService {
public: public:
static std::shared_ptr<TimerService> getInstance() { static std::shared_ptr<TimerService> getInstance() {
if (!instance) { if (!instance) {
instance = std::make_shared<TimerService>(); instance = std::shared_ptr<TimerService>(new TimerService());
// add a timer to tick every second // add a timer to tick every second
Glib::signal_timeout().connect([]() { Glib::signal_timeout().connect([]() {
@@ -37,11 +40,28 @@ class TimerService {
void expireTimer(uint64_t timerId) { void expireTimer(uint64_t timerId) {
this->timerExpiredSignal.emit(timerId); this->timerExpiredSignal.emit(timerId);
if (ringingTimerCounter == 0) {
mediaFile->set_volume(1.0);
mediaFile->set_loop(true);
mediaFile->play();
spdlog::info("Playing timer sound");
}
ringingTimerCounter++;
} }
void removeTimer(uint64_t timerId) { void removeTimer(uint64_t timerId) {
ringingTimerCounter--;
this->activeTimers.erase(timerId); this->activeTimers.erase(timerId);
this->timerCancelledSignal.emit(timerId); this->timerCancelledSignal.emit(timerId);
if (ringingTimerCounter <= 0) {
ringingTimerCounter = 0;
mediaFile->pause();
spdlog::info("Paused timer sound");
}
} }
sigc::signal<void(const std::string &, u_int64_t)> &getSignalTimerSet() { sigc::signal<void(const std::string &, u_int64_t)> &getSignalTimerSet() {
@@ -59,7 +79,32 @@ class TimerService {
sigc::signal<void()> tickSignal; sigc::signal<void()> tickSignal;
private: private:
static inline uint64_t timerIdCounter = 0; TimerService() {
this->mediaFile = Gtk::MediaFile::create();
if (this->mediaFile) {
this->mediaFile->set_filename("/usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga");
// Connect to signals to debug playback issues
this->mediaFile->property_error().signal_changed().connect([]() {
if (auto err = mediaFile->get_error()) {
spdlog::error("Timer sound error: {}", err.what());
}
});
this->mediaFile->property_prepared().signal_changed().connect([]() {
if (mediaFile->is_prepared()) {
spdlog::info("Timer sound ready");
}
});
} else {
spdlog::error("Failed to create media file!");
}
}
inline static Glib::RefPtr<Gtk::MediaFile> mediaFile;
static inline uint64_t timerIdCounter = 0;
static inline u_int64_t ringingTimerCounter = 0;
sigc::signal<void(const std::string &, u_int64_t)> timerAddedSignal; sigc::signal<void(const std::string &, u_int64_t)> timerAddedSignal;
sigc::signal<void(u_int64_t)> timerCancelledSignal; sigc::signal<void(u_int64_t)> timerCancelledSignal;
sigc::signal<void(u_int64_t)> timerExpiredSignal; sigc::signal<void(u_int64_t)> timerExpiredSignal;
@@ -73,6 +118,7 @@ class TimerService {
if (timer.duration > 0) { if (timer.duration > 0) {
timer.duration--; timer.duration--;
} else if (timer.duration == 0) { } else if (timer.duration == 0) {
spdlog::info("Timer {} expired", timer.timerId);
expireTimer(timer.timerId); expireTimer(timer.timerId);
timer.duration = -1; // Mark as expired to prevent multiple expirations timer.duration = -1; // Mark as expired to prevent multiple expirations
} }

View File

@@ -2,6 +2,7 @@
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <memory>
#include <sigc++/connection.h> #include <sigc++/connection.h>
#include "gdkmm/monitor.h" #include "gdkmm/monitor.h"
@@ -9,7 +10,7 @@
#define DEFAULT_NOTIFICATION_TIMEOUT 6700 #define DEFAULT_NOTIFICATION_TIMEOUT 6700
class BaseNotification : public Gtk::Window { class BaseNotification : public Gtk::Window, public std::enable_shared_from_this<BaseNotification> {
public: public:
BaseNotification(uint64_t notificationId, std::shared_ptr<Gdk::Monitor> monitor); BaseNotification(uint64_t notificationId, std::shared_ptr<Gdk::Monitor> monitor);

View File

@@ -81,8 +81,8 @@ void BaseNotification::start_auto_close_timeout(int timeoutMs) {
} }
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([self = shared_from_this()]() {
this->getSignalClose().emit(this->notificationId); self->getSignalClose().emit(self->getNotificationId());
return false; // Don't repeat return false; // Don't repeat
}, },
timeoutMs); timeoutMs);

View File

@@ -44,23 +44,29 @@ NotificationWindow::NotificationWindow(uint64_t notificationId, std::shared_ptr<
header_box->append(*img); header_box->append(*img);
} }
auto app_label = Gtk::make_managed<Gtk::Label>(StringHelper::trimToSize(notify.app_name, 24)); auto app_label = Gtk::make_managed<Gtk::Label>(notify.app_name);
app_label->set_halign(Gtk::Align::START); app_label->set_halign(Gtk::Align::START);
app_label->set_ellipsize(Pango::EllipsizeMode::END);
app_label->set_max_width_chars(24);
app_label->set_wrap(false); app_label->set_wrap(false);
app_label->add_css_class("notification-app-name"); app_label->add_css_class("notification-app-name");
header_box->append(*app_label); header_box->append(*app_label);
vbox->append(*header_box); vbox->append(*header_box);
// Summary label // Summary label
auto summary_label = Gtk::make_managed<Gtk::Label>("<b>" + StringHelper::trimToSize(notify.summary, 20) + "</b>"); auto summary_label = Gtk::make_managed<Gtk::Label>("<b>" + notify.summary + "</b>");
summary_label->set_use_markup(true); summary_label->set_use_markup(true);
summary_label->set_halign(Gtk::Align::START); summary_label->set_halign(Gtk::Align::START);
summary_label->set_ellipsize(Pango::EllipsizeMode::END);
summary_label->set_lines(1);
summary_label->set_wrap(true); summary_label->set_wrap(true);
vbox->append(*summary_label); vbox->append(*summary_label);
auto body_label = Gtk::make_managed<Gtk::Label>(StringHelper::trimToSize(notify.body, 100)); auto body_label = Gtk::make_managed<Gtk::Label>(notify.body);
body_label->set_use_markup(true); body_label->set_use_markup(true);
body_label->set_halign(Gtk::Align::START); body_label->set_halign(Gtk::Align::START);
body_label->set_ellipsize(Pango::EllipsizeMode::END);
body_label->set_lines(3);
body_label->set_wrap(true); body_label->set_wrap(true);
vbox->append(*body_label); vbox->append(*body_label);

View File

@@ -36,10 +36,12 @@ SpotifyNotification::SpotifyNotification(uint64_t notificationId, std::shared_pt
auto artistLabel = Gtk::make_managed<Gtk::Label>(); auto artistLabel = Gtk::make_managed<Gtk::Label>();
if (!mpris.artist.empty()) { if (!mpris.artist.empty()) {
artistLabel->set_text(StringHelper::trimToSize(mpris.artist[0], 30)); artistLabel->set_text(mpris.artist[0]);
} else { } else {
artistLabel->set_text("Unknown Artist"); artistLabel->set_text("Unknown Artist");
} }
artistLabel->set_ellipsize(Pango::EllipsizeMode::END);
artistLabel->set_max_width_chars(30);
artistLabel->set_hexpand(true); artistLabel->set_hexpand(true);
artistLabel->set_halign(Gtk::Align::CENTER); artistLabel->set_halign(Gtk::Align::CENTER);