diff options
| -rw-r--r-- | data/org.mate.NotificationDaemon.gschema.xml.in | 5 | ||||
| -rw-r--r-- | src/capplet/mate-notification-properties.c | 31 | ||||
| -rw-r--r-- | src/capplet/mate-notification-properties.ui | 16 | ||||
| -rw-r--r-- | src/common/constants.h | 1 | ||||
| -rw-r--r-- | src/daemon/daemon.c | 117 | ||||
| -rw-r--r-- | src/daemon/mnd-daemon.c | 13 | ||||
| -rw-r--r-- | src/themes/coco/coco-theme.c | 11 | ||||
| -rw-r--r-- | src/themes/nodoka/nodoka-theme.c | 11 | ||||
| -rw-r--r-- | src/themes/slider/theme.c | 9 | ||||
| -rw-r--r-- | src/themes/standard/theme.c | 9 |
10 files changed, 179 insertions, 44 deletions
diff --git a/data/org.mate.NotificationDaemon.gschema.xml.in b/data/org.mate.NotificationDaemon.gschema.xml.in index 80beac4..a28edc3 100644 --- a/data/org.mate.NotificationDaemon.gschema.xml.in +++ b/data/org.mate.NotificationDaemon.gschema.xml.in @@ -51,5 +51,10 @@ <summary>Enable notification history</summary> <description>When enabled, notifications are stored in history for later viewing. When disabled, no notification history is kept for privacy. Note: This only controls MATE's notification daemon storage; other applications may still have access to notifications through system logs or other means.</description> </key> + <key name="force-display" type="b"> + <default>false</default> + <summary>Force display all notifications</summary> + <description>When enabled, all notifications are shown regardless of urgency, fullscreen, or screensaver state. Do Not Disturb still takes precedence.</description> + </key> </schema> </schemalist> diff --git a/src/capplet/mate-notification-properties.c b/src/capplet/mate-notification-properties.c index f81dd2c..a27eb91 100644 --- a/src/capplet/mate-notification-properties.c +++ b/src/capplet/mate-notification-properties.c @@ -49,6 +49,7 @@ typedef struct { GtkWidget* timeout_spin; GtkWidget* persistence_checkbox; GtkWidget* countdown_checkbox; + GtkWidget* force_display_checkbox; NotifyNotification* preview1; NotifyNotification* preview2; @@ -502,6 +503,7 @@ static gboolean notification_properties_dialog_init(NotificationAppletDialog* di dialog->timeout_spin = GTK_WIDGET(gtk_builder_get_object(builder, "timeout_spin")); dialog->persistence_checkbox = GTK_WIDGET(gtk_builder_get_object(builder, "enable_persistence_check")); dialog->countdown_checkbox = GTK_WIDGET(gtk_builder_get_object(builder, "show_countdown_check")); + dialog->force_display_checkbox = GTK_WIDGET(gtk_builder_get_object(builder, "force_display_check")); g_object_unref (builder); @@ -518,6 +520,7 @@ static gboolean notification_properties_dialog_init(NotificationAppletDialog* di g_settings_bind (dialog->gsettings, GSETTINGS_KEY_DEFAULT_TIMEOUT, timeout_adjustment, "value", G_SETTINGS_BIND_DEFAULT); g_settings_bind (dialog->gsettings, GSETTINGS_KEY_ENABLE_PERSISTENCE, dialog->persistence_checkbox, "active", G_SETTINGS_BIND_DEFAULT); g_settings_bind (dialog->gsettings, GSETTINGS_KEY_SHOW_COUNTDOWN, dialog->countdown_checkbox, "active", G_SETTINGS_BIND_DEFAULT); + g_settings_bind (dialog->gsettings, GSETTINGS_KEY_FORCE_DISPLAY, dialog->force_display_checkbox, "active", G_SETTINGS_BIND_DEFAULT); notification_properties_dialog_setup_themes (dialog); notification_properties_dialog_setup_positions (dialog); @@ -562,6 +565,14 @@ static void notification_properties_dialog_finalize(NotificationAppletDialog* di int main(int argc, char** argv) { NotificationAppletDialog *dialog; + static gboolean show_version = FALSE; + GOptionContext *option_context; + GError *error = NULL; + + static GOptionEntry option_entries[] = { + { "version", 'v', 0, G_OPTION_ARG_NONE, &show_version, N_("Version of this application"), NULL }, + { NULL } + }; #ifdef ENABLE_NLS bindtextdomain(GETTEXT_PACKAGE, NOTIFICATION_LOCALEDIR); @@ -569,7 +580,25 @@ int main(int argc, char** argv) textdomain(GETTEXT_PACKAGE); #endif /* ENABLE_NLS */ - gtk_init(&argc, &argv); + option_context = g_option_context_new (NULL); + g_option_context_add_main_entries (option_context, option_entries, GETTEXT_PACKAGE); + g_option_context_add_group (option_context, gtk_get_option_group (TRUE)); + + if (!g_option_context_parse (option_context, &argc, &argv, &error)) + { + g_warning ("%s", error->message); + g_error_free (error); + g_option_context_free (option_context); + return 1; + } + + g_option_context_free (option_context); + + if (show_version) + { + g_print ("%s %s\n", PACKAGE, VERSION); + return 0; + } notify_init("mate-notification-properties"); diff --git a/src/capplet/mate-notification-properties.ui b/src/capplet/mate-notification-properties.ui index 9b461bf..0c802e5 100644 --- a/src/capplet/mate-notification-properties.ui +++ b/src/capplet/mate-notification-properties.ui @@ -347,6 +347,22 @@ <property name="position">2</property> </packing> </child> + <child> + <object class="GtkCheckButton" id="force_display_check"> + <property name="label" translatable="yes">Force Display All Notifications</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="halign">start</property> + <property name="draw_indicator">True</property> + <property name="tooltip_text" translatable="yes">Show all notifications regardless of urgency, fullscreen, or screensaver state. Do Not Disturb still takes precedence.</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> </object> </child> </object> diff --git a/src/common/constants.h b/src/common/constants.h index 1a81977..972cd7f 100644 --- a/src/common/constants.h +++ b/src/common/constants.h @@ -32,6 +32,7 @@ #define GSETTINGS_KEY_ENABLE_PERSISTENCE "enable-persistence" #define GSETTINGS_KEY_SHOW_COUNTDOWN "show-countdown" #define GSETTINGS_KEY_HISTORY_ENABLED "history-enabled" +#define GSETTINGS_KEY_FORCE_DISPLAY "force-display" #define NOTIFICATION_BUS_NAME "org.freedesktop.Notifications" #define NOTIFICATION_BUS_PATH "/org/freedesktop/Notifications" diff --git a/src/daemon/daemon.c b/src/daemon/daemon.c index e5033de..473a598 100644 --- a/src/daemon/daemon.c +++ b/src/daemon/daemon.c @@ -32,6 +32,8 @@ #include <glib/gi18n.h> #include <glib.h> #include <glib-object.h> +#include <gio/gio.h> +#include <gio/gdesktopappinfo.h> #include <gtk/gtk.h> #ifdef HAVE_X11 @@ -756,8 +758,14 @@ static void _notification_destroyed_cb(GtkWindow* nw, NotifyDaemon* daemon) /* * This usually won't happen, but can if notification-daemon dies before * all notifications are closed. Mark them as expired. + * + * But if instead the notification's close button was clicked, the + * "_user_closed" flag will be set on the window, so we send the + * correct reason. */ - _close_notification(daemon, NW_GET_NOTIFY_ID(nw), FALSE, NOTIFYD_CLOSED_EXPIRED); + gboolean user_closed = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(nw), "_user_closed")); + _close_notification(daemon, NW_GET_NOTIFY_ID(nw), FALSE, + user_closed ? NOTIFYD_CLOSED_USER : NOTIFYD_CLOSED_EXPIRED); } #ifdef HAVE_X11 @@ -960,7 +968,7 @@ static gboolean _check_expiration(NotifyDaemon* daemon) return has_more_timeouts; } -static void _calculate_timeout(NotifyDaemon* daemon, NotifyTimeout* nt, int timeout, gboolean resident) +static void _calculate_timeout(NotifyDaemon* daemon, NotifyTimeout* nt, int timeout, gboolean resident, guint urgency) { GSettings *gsettings = g_settings_new ("org.mate.NotificationDaemon"); gboolean persistence_enabled = g_settings_get_boolean (gsettings, "enable-persistence"); @@ -975,7 +983,9 @@ static void _calculate_timeout(NotifyDaemon* daemon, NotifyTimeout* nt, int time g_object_unref (gsettings); } - if (timeout == 0 || nt->resident) + /* Per the spec, critical notifications should not automatically + * expire. They should only be closed by user action. */ + if (timeout == 0 || nt->resident || urgency == URGENCY_CRITICAL) { nt->has_timeout = FALSE; } @@ -1046,7 +1056,7 @@ static NotifyTimeout* _store_notification(NotifyDaemon* daemon, GtkWindow* nw, nt->timestamp = g_get_real_time(); nt->close_reason = NOTIFYD_CLOSED_EXPIRED; /* Default to expired, since we don't care about active notifications */ - _calculate_timeout(daemon, nt, timeout, resident); + _calculate_timeout(daemon, nt, timeout, resident, urgency); #if GLIB_CHECK_VERSION (2, 68, 0) g_hash_table_insert(daemon->notification_hash, g_memdup2(&id, sizeof(guint)), nt); @@ -1636,36 +1646,47 @@ static gboolean notify_daemon_notify_handler(NotifyDaemonNotifications *object, * 3. icon (which falls back to the desktop entry) * 4. icon_data (for backward compatibility) */ - if (g_variant_lookup(hints, "image-data", "@(iiibiiay)", &data)) + if (pixbuf == NULL && g_variant_lookup(hints, "image-data", "@(iiibiiay)", &data)) { pixbuf = _notify_daemon_pixbuf_from_data_hint (data); g_variant_unref(data); } - else if (g_variant_lookup(hints, "image_data", "@(iiibiiay)", &data)) + + if (pixbuf == NULL && g_variant_lookup(hints, "image_data", "@(iiibiiay)", &data)) { pixbuf = _notify_daemon_pixbuf_from_data_hint (data); g_variant_unref(data); } - else if (g_variant_lookup(hints, "image-path", "@s", &data)) + + if (pixbuf == NULL && g_variant_lookup(hints, "image-path", "@s", &data)) { const char *path = g_variant_get_string (data, NULL); pixbuf = _notify_daemon_pixbuf_from_path (path); - resolved_icon = g_strdup(path); + if (pixbuf != NULL) { + resolved_icon = g_strdup(path); + } g_variant_unref(data); } - else if (g_variant_lookup(hints, "image_path", "@s", &data)) + + if (pixbuf == NULL && g_variant_lookup(hints, "image_path", "@s", &data)) { const char *path = g_variant_get_string (data, NULL); pixbuf = _notify_daemon_pixbuf_from_path (path); - resolved_icon = g_strdup(path); + if (pixbuf != NULL) { + resolved_icon = g_strdup(path); + } g_variant_unref(data); } - else if (*icon != '\0') + + if (pixbuf == NULL && icon != NULL && *icon != '\0') { pixbuf = _notify_daemon_pixbuf_from_path (icon); - resolved_icon = g_strdup(icon); + if (pixbuf != NULL) { + resolved_icon = g_strdup(icon); + } } - else if (desktop_entry != NULL && *desktop_entry != '\0') + + if (pixbuf == NULL && desktop_entry != NULL && *desktop_entry != '\0') { /* Use desktop-entry to resolve application icon as fallback */ gchar *desktop_file = g_strdup_printf("%s.desktop", desktop_entry); @@ -1691,7 +1712,7 @@ static gboolean notify_daemon_notify_handler(NotifyDaemonNotifications *object, { pixbuf = _notify_daemon_pixbuf_from_path (icon_name); - if (!resolved_icon && (*icon == '\0' || icon == NULL)) { + if (pixbuf != NULL && !resolved_icon) { resolved_icon = g_strdup(icon_name); } @@ -1701,7 +1722,43 @@ static gboolean notify_daemon_notify_handler(NotifyDaemonNotifications *object, g_key_file_free(key_file); g_free(desktop_file); } - else if (g_variant_lookup(hints, "icon_data", "@(iiibiiay)", &data)) + + if (pixbuf == NULL && app_name != NULL && *app_name != '\0') + { + /* Fallback: Try to derive desktop entry from app_name */ + gchar *desktop_key = g_ascii_strdown(app_name, -1); + for (gchar *p = desktop_key; *p != '\0'; p++) { + if (*p == ' ') *p = '-'; + } + gchar *desktop_id = g_strdup_printf("%s.desktop", desktop_key); + GDesktopAppInfo *app_info = g_desktop_app_info_new(desktop_id); + + if (app_info != NULL) + { + GIcon *gicon = g_app_info_get_icon(G_APP_INFO(app_info)); + if (gicon != NULL) + { + GtkIconInfo *icon_info = gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_default(), + gicon, + IMAGE_SIZE, + GTK_ICON_LOOKUP_USE_BUILTIN); + if (icon_info != NULL) + { + pixbuf = gtk_icon_info_load_icon(icon_info, NULL); + if (pixbuf != NULL && !resolved_icon) { + resolved_icon = g_strdup(g_icon_to_string(gicon)); + } + g_object_unref(icon_info); + } + } + g_object_unref(app_info); + } + + g_free(desktop_id); + g_free(desktop_key); + } + + if (pixbuf == NULL && g_variant_lookup(hints, "icon_data", "@(iiibiiay)", &data)) { g_warning("\"icon_data\" hint is deprecated, please use \"image_data\" instead"); pixbuf = _notify_daemon_pixbuf_from_data_hint (data); @@ -1832,22 +1889,10 @@ static gboolean notify_daemon_notify_handler(NotifyDaemonNotifications *object, #endif /* HAVE_X11 */ /* fullscreen_window is assumed to be false on Wayland, as there is no trivial way to check */ - /* If there is no timeout, show the notification also if screensaver - * is active or there are fullscreen windows - */ - if (!nt->has_timeout || (!screensaver_active (GTK_WIDGET (nw)) && !fullscreen_window)) - { - if (!do_not_disturb) - { - theme_show_notification (nw); + gboolean force_display = g_settings_get_boolean (daemon->gsettings, GSETTINGS_KEY_FORCE_DISPLAY); - if (sound_file != NULL) - { - sound_play_file (GTK_WIDGET (nw), sound_file); - } - } - } - else + /* If on DnD or screensaver, suppress and close notification */ + if (do_not_disturb || (!force_display && screensaver_active (GTK_WIDGET (nw)) && urgency != URGENCY_CRITICAL)) { _NotifyPendingClose *notification_data; @@ -1858,6 +1903,16 @@ static gboolean notify_daemon_notify_handler(NotifyDaemonNotifications *object, notification_data->daemon = g_object_ref (daemon); g_idle_add ((GSourceFunc) _close_notification_not_shown, notification_data); } + else + { + /* Show if critical, force-display, or no fullscreen window */ + if (force_display || urgency == URGENCY_CRITICAL || !fullscreen_window) + theme_show_notification (nw); + + /* Play sound unless low urgency during fullscreen (when not force-display) */ + if (sound_file != NULL && (force_display || !(fullscreen_window && urgency == URGENCY_LOW))) + sound_play_file (GTK_WIDGET (nw), sound_file); + } g_free (sound_file); @@ -1865,7 +1920,7 @@ static gboolean notify_daemon_notify_handler(NotifyDaemonNotifications *object, if (nt) { - _calculate_timeout (daemon, nt, timeout, resident && !transient); + _calculate_timeout (daemon, nt, timeout, resident && !transient, urgency); } if (resolved_icon) { diff --git a/src/daemon/mnd-daemon.c b/src/daemon/mnd-daemon.c index 59921c7..cfaedfd 100644 --- a/src/daemon/mnd-daemon.c +++ b/src/daemon/mnd-daemon.c @@ -32,6 +32,7 @@ static gboolean debug = FALSE; static gboolean replace = FALSE; static gboolean idle_exit = FALSE; +static gboolean show_version = FALSE; static GOptionEntry entries[] = { @@ -54,6 +55,12 @@ static GOptionEntry entries[] = NULL }, { + "version", 'v', G_OPTION_FLAG_NONE, + G_OPTION_ARG_NONE, &show_version, + "Version of this application", + NULL + }, + { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL } }; @@ -102,6 +109,12 @@ int main (int argc, char *argv[]) if (!parse_arguments (&argc, &argv)) return EXIT_FAILURE; + if (show_version) + { + g_print ("%s %s\n", PACKAGE, VERSION); + return EXIT_SUCCESS; + } + daemon = notify_daemon_new (replace, idle_exit); gtk_main(); diff --git a/src/themes/coco/coco-theme.c b/src/themes/coco/coco-theme.c index b131283..08cfa44 100644 --- a/src/themes/coco/coco-theme.c +++ b/src/themes/coco/coco-theme.c @@ -90,19 +90,13 @@ void set_notification_hints(GtkWindow *nw, GVariant *hints); void notification_tick(GtkWindow *nw, glong remaining); static void create_pie_countdown(WindowData* windata); -#define STRIPE_WIDTH 32 #define WIDTH 300 #define IMAGE_SIZE 32 -#define IMAGE_PADDING 10 -#define SPACER_LEFT 30 +#define IMAGE_PADDING 24 #define PIE_RADIUS 12 #define PIE_WIDTH (2 * PIE_RADIUS) #define PIE_HEIGHT (2 * PIE_RADIUS) -#define BODY_X_OFFSET (IMAGE_SIZE + 8) -#define DEFAULT_ARROW_OFFSET (SPACER_LEFT + 12) -#define DEFAULT_ARROW_HEIGHT 14 -#define DEFAULT_ARROW_WIDTH 22 -#define DEFAULT_ARROW_SKEW -6 +#define BODY_X_OFFSET (IMAGE_SIZE + IMAGE_PADDING) #define BACKGROUND_OPACITY 0.9 #define GRADIENT_CENTER 0.7 @@ -464,6 +458,7 @@ create_notification(UrlClickedCb url_clicked) gtk_widget_set_halign (windata->main_hbox, GTK_ALIGN_START); gtk_widget_set_valign (windata->main_hbox, GTK_ALIGN_START); gtk_widget_set_margin_top (windata->main_hbox, 8); + gtk_widget_set_margin_bottom (windata->main_hbox, 8); gtk_widget_set_margin_end (windata->main_hbox, 8); gtk_widget_show (windata->main_hbox); gtk_box_pack_start (GTK_BOX(main_vbox), windata->main_hbox, FALSE, FALSE, 0); diff --git a/src/themes/nodoka/nodoka-theme.c b/src/themes/nodoka/nodoka-theme.c index 8600d60..4de4d5d 100644 --- a/src/themes/nodoka/nodoka-theme.c +++ b/src/themes/nodoka/nodoka-theme.c @@ -748,6 +748,13 @@ get_theme_info(char **theme_name, *homepage = g_strdup("https://nodoka.fedorahosted.org/"); } +static void +close_button_clicked_cb(GtkButton* button, GtkWidget* win) +{ + g_object_set_data(G_OBJECT(win), "_user_closed", GINT_TO_POINTER(1)); + gtk_widget_destroy(win); +} + /* Create new notification */ GtkWindow * create_notification(UrlClickedCb url_clicked) @@ -860,8 +867,8 @@ create_notification(UrlClickedCb url_clicked) gtk_button_set_relief(GTK_BUTTON(close_button), GTK_RELIEF_NONE); gtk_container_set_border_width(GTK_CONTAINER(close_button), 0); gtk_widget_set_size_request(close_button, 24, 24); - g_signal_connect_swapped(G_OBJECT(close_button), "clicked", - G_CALLBACK(gtk_widget_destroy), win); + g_signal_connect(G_OBJECT(close_button), "clicked", + G_CALLBACK(close_button_clicked_cb), win); atkobj = gtk_widget_get_accessible(close_button); atk_action_set_description(ATK_ACTION(atkobj), 0, diff --git a/src/themes/slider/theme.c b/src/themes/slider/theme.c index 7672a8d..c75967a 100644 --- a/src/themes/slider/theme.c +++ b/src/themes/slider/theme.c @@ -306,6 +306,13 @@ static void on_composited_changed(GtkWidget* window, WindowData* windata) gtk_widget_queue_draw (windata->win); } +static void +close_button_clicked_cb(GtkButton* button, GtkWidget* win) +{ + g_object_set_data(G_OBJECT(win), "_user_closed", GINT_TO_POINTER(1)); + gtk_widget_destroy(win); +} + GtkWindow* create_notification(UrlClickedCb url_clicked) { GtkWidget* win; @@ -400,7 +407,7 @@ GtkWindow* create_notification(UrlClickedCb url_clicked) gtk_button_set_relief(GTK_BUTTON(close_button), GTK_RELIEF_NONE); gtk_container_set_border_width(GTK_CONTAINER(close_button), 0); - g_signal_connect_swapped(G_OBJECT(close_button), "clicked", G_CALLBACK(gtk_widget_destroy), win); + g_signal_connect(G_OBJECT(close_button), "clicked", G_CALLBACK(close_button_clicked_cb), win); atkobj = gtk_widget_get_accessible(close_button); atk_action_set_description(ATK_ACTION(atkobj), 0, diff --git a/src/themes/standard/theme.c b/src/themes/standard/theme.c index 90171c6..b670f5b 100644 --- a/src/themes/standard/theme.c +++ b/src/themes/standard/theme.c @@ -642,6 +642,13 @@ static gboolean activate_link(GtkLabel* label, const char* url, WindowData* wind return TRUE; } +static void +close_button_clicked_cb(GtkButton* button, GtkWidget* win) +{ + g_object_set_data(G_OBJECT(win), "_user_closed", GINT_TO_POINTER(1)); + gtk_widget_destroy(win); +} + GtkWindow* create_notification(UrlClickedCb url_clicked) { GtkWidget* spacer; @@ -754,7 +761,7 @@ GtkWindow* create_notification(UrlClickedCb url_clicked) gtk_button_set_relief(GTK_BUTTON(close_button), GTK_RELIEF_NONE); gtk_container_set_border_width(GTK_CONTAINER(close_button), 0); //gtk_widget_set_size_request(close_button, 20, 20); - g_signal_connect_swapped(G_OBJECT(close_button), "clicked", G_CALLBACK(gtk_widget_destroy), win); + g_signal_connect(G_OBJECT(close_button), "clicked", G_CALLBACK(close_button_clicked_cb), win); atkobj = gtk_widget_get_accessible(close_button); atk_action_set_description(ATK_ACTION(atkobj), 0, |
