#include "widgets/notification/baseNotification.hpp" #include #include #include #include "helpers/system.hpp" #include "gdkmm/monitor.h" #include "glibmm/main.h" #include "gtk4-layer-shell.h" #include "gtkmm/button.h" #include "gtkmm/cssprovider.h" #include "gtkmm/eventcontrollermotion.h" #include "gtkmm/gestureclick.h" BaseNotification::BaseNotification(uint64_t notificationId, std::shared_ptr monitor) { ensure_notification_css_loaded(); 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); gtk_layer_set_anchor(gobj(), GTK_LAYER_SHELL_EDGE_TOP, TRUE); gtk_layer_set_anchor(gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, TRUE); 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); this->notificationId = notificationId; auto window_click = Gtk::GestureClick::create(); window_click->set_button(GDK_BUTTON_PRIMARY); window_click->set_exclusive(false); window_click->signal_released().connect([this](int, double x, double y) { Gtk::Widget *picked = this->pick(x, y, Gtk::PickFlags::DEFAULT); for (auto *w = picked; w != nullptr; w = w->get_parent()) { if (dynamic_cast(w) != nullptr) { return; } } this->getSignalClose().emit(this->notificationId); }); add_controller(window_click); auto window_motion = Gtk::EventControllerMotion::create(); window_motion->signal_enter().connect([this](double, double) { getSignalHoverChanged().emit(true); pause_auto_close(); }); window_motion->signal_leave().connect([this]() { getSignalHoverChanged().emit(false); resume_auto_close(); }); add_controller(window_motion); } void BaseNotification::pauseAutoClose() { pause_auto_close(); } void BaseNotification::resumeAutoClose() { resume_auto_close(); } void BaseNotification::startAutoClose(int timeoutMs) { if (timeoutMs <= 0) { return; } autoCloseRemainingMs = timeoutMs; autoClosePaused = false; start_auto_close_timeout(timeoutMs); } void BaseNotification::start_auto_close_timeout(int timeoutMs) { if (autoCloseConnection.connected()) { autoCloseConnection.disconnect(); } autoCloseDeadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeoutMs); autoCloseConnection = Glib::signal_timeout().connect([self = shared_from_this()]() { self->getSignalClose().emit(self->getNotificationId()); return false; // Don't repeat }, timeoutMs); } void BaseNotification::pause_auto_close() { if (autoClosePaused || autoCloseRemainingMs <= 0) { return; } if (autoCloseConnection.connected()) { autoCloseConnection.disconnect(); } 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; } void BaseNotification::resume_auto_close() { if (!autoClosePaused) { return; } autoClosePaused = false; if (autoCloseRemainingMs <= 0) { this->getSignalClose().emit(this->notificationId); return; } start_auto_close_timeout(autoCloseRemainingMs); } void BaseNotification::ensure_notification_css_loaded() { static bool loaded = false; if (loaded) { return; } auto css_provider = Gtk::CssProvider::create(); std::string css_path = "resources/notification.css"; const char *home = std::getenv("HOME"); if (home) { std::filesystem::path config_path = std::filesystem::path(home) / ".config/bar/notification.css"; if (std::filesystem::exists(config_path)) { css_path = config_path.string(); } } const std::string css = SystemHelper::read_file_to_string(css_path); css_provider->load_from_data(css); Gtk::StyleContext::add_provider_for_display( Gdk::Display::get_default(), css_provider, GTK_STYLE_PROVIDER_PRIORITY_USER + 2); loaded = true; }