/* mate-notification-applet.c - MATE Notification Applet * * Copyright (c) 2020 Robert Buj * Copyright (C) 2020-2021 MATE Developers * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "config.h" #include #include #include #include #include #define MATE_DESKTOP_USE_UNSTABLE_API #include #include "constants.h" #include "mate-notification-applet-dbus.h" #include "mate-notification-applet-history.h" typedef struct { MatePanelApplet *applet; GtkWidget *image_on; GtkWidget *image_off; GtkWidget *overlay; GtkWidget *count_label; GtkActionGroup *action_group; GSettings *settings; guint unread_count; guint update_timer_id; MateNotificationDBusContext *dbus_context; MateNotificationHistoryContext *history_context; } MateNotificationApplet; static void show_about (GtkAction *action, MateNotificationApplet *applet); static void call_properties (GtkAction *action, MateNotificationApplet *applet); static void call_show_history (GtkAction *action, MateNotificationApplet *applet); void call_clear_history (GtkAction *action, MateNotificationApplet *applet); static void call_mark_all_read (GtkAction *action, MateNotificationApplet *applet); /* D-Bus and notification history functions */ static void setup_daemon_connection (MateNotificationApplet *applet); static void update_unread_count (MateNotificationApplet *applet); static void update_applet_display (MateNotificationApplet *applet); static void update_count_badge (MateNotificationApplet *applet); static gboolean periodic_update_count (MateNotificationApplet *applet); /* Click handler functions */ static gboolean applet_button_press_cb (GtkWidget *widget, GdkEventButton *event, MateNotificationApplet *applet); static void toggle_do_not_disturb (MateNotificationApplet *applet); static const GtkActionEntry applet_menu_actions [] = { { "ShowHistory", "document-open-recent", N_("_Show History"), NULL, NULL, G_CALLBACK (call_show_history) }, { "ClearHistory", "edit-clear", N_("_Clear History"), NULL, NULL, G_CALLBACK (call_clear_history) }, { "MarkAllRead", "edit-select-all", N_("_Mark All as Read"), NULL, NULL, G_CALLBACK (call_mark_all_read) }, { "Preferences", "document-properties", N_("_Preferences"), NULL, NULL, G_CALLBACK (call_properties) }, { "About", "help-about", N_("_About"), NULL, NULL, G_CALLBACK (show_about) } }; static const char* notification_properties[] = { "mate-notification-properties", }; static void applet_destroy (MatePanelApplet *applet_widget, MateNotificationApplet *applet) { g_assert (applet); if (applet->update_timer_id > 0) g_source_remove (applet->update_timer_id); if (applet->dbus_context) { dbus_context_free (applet->dbus_context); } if (applet->history_context) { history_context_free (applet->history_context); } g_object_unref (applet->settings); g_object_unref (applet->action_group); g_free (applet); } static void call_show_history (GtkAction *action, MateNotificationApplet *applet) { (void) action; if (applet->history_context) show_notification_history (applet->history_context); } void call_clear_history (GtkAction *action, MateNotificationApplet *applet) { (void) action; if (dbus_context_clear_notification_history (applet->dbus_context)) update_unread_count (applet); } static void call_mark_all_read (GtkAction *action, MateNotificationApplet *applet) { (void) action; /* Update count after marking all as read */ if (dbus_context_mark_all_notifications_as_read (applet->dbus_context)) update_unread_count (applet); } static void call_properties (GtkAction *action, MateNotificationApplet *applet) { gsize i; (void) action; for (i = 0; i < G_N_ELEMENTS (notification_properties); i++) { char *programpath = g_find_program_in_path (notification_properties[i]); if (programpath != NULL) { g_free (programpath); mate_gdk_spawn_command_line_on_screen (gtk_widget_get_screen (GTK_WIDGET (applet->applet)), notification_properties[i], NULL); return; } } } static void show_about (GtkAction *action, MateNotificationApplet *applet) { static const char *authors[] = { "MATE Developers", "Robert Buj ", NULL }; (void) action; (void) applet; gtk_show_about_dialog (NULL, "title", _("About Do Not Disturb"), "version", VERSION, "copyright", _("Copyright \xc2\xa9 2021 MATE developers"), "comments", _("Activate the do not disturb mode quickly."), "authors", authors, "translator-credits", _("translator-credits"), "logo_icon_name", "mate-notification-properties", NULL); } static void set_status_image (MateNotificationApplet *applet, gboolean active) { gtk_widget_show_all (GTK_WIDGET (applet->applet)); if (active) gtk_widget_hide (applet->image_on); else gtk_widget_hide (applet->image_off); } static void settings_changed (GSettings *settings, gchar *key, MateNotificationApplet *applet) { if (g_strcmp0 (GSETTINGS_KEY_DO_NOT_DISTURB, key) == 0) update_applet_display (applet); } static void applet_draw_icon (MatePanelApplet *applet_widget, int arg1, MateNotificationApplet *applet) { gint size, scale; g_assert (applet); size = (gint) mate_panel_applet_get_size (applet_widget); scale = gtk_widget_get_scale_factor (GTK_WIDGET (applet_widget)); cairo_surface_t *image_on = gtk_icon_theme_load_surface (gtk_icon_theme_get_default (), "user-available", size, scale, NULL, 0, NULL); cairo_surface_t *image_off = gtk_icon_theme_load_surface (gtk_icon_theme_get_default (), "user-invisible", size, scale, NULL, 0, NULL); gtk_image_set_from_surface (GTK_IMAGE (applet->image_on), image_on); gtk_image_set_from_surface (GTK_IMAGE (applet->image_off), image_off); cairo_surface_destroy (image_on); cairo_surface_destroy (image_off); } static void setup_daemon_connection (MateNotificationApplet *applet) { if (dbus_context_connect (applet->dbus_context)) { update_unread_count (applet); } else { applet->unread_count = 0; } /* Update history context with new daemon connection */ if (applet->history_context) { history_context_update_dbus (applet->history_context, applet->dbus_context); } } static void update_unread_count (MateNotificationApplet *applet) { applet->unread_count = dbus_context_get_notification_count (applet->dbus_context); update_applet_display (applet); } static void update_applet_display (MateNotificationApplet *applet) { gchar *tooltip_text; gboolean dnd_active; dnd_active = g_settings_get_boolean (applet->settings, GSETTINGS_KEY_DO_NOT_DISTURB); /* Update tooltip based on number of unread notifications and user state */ if (applet->unread_count > 99) tooltip_text = g_strdup (_("(99+ unread)")); else if (applet->unread_count > 0) tooltip_text = g_strdup_printf ("(%d unread)", applet->unread_count); else tooltip_text = g_strdup (""); if (dnd_active) tooltip_text = g_strdup_printf ("Do Not Disturb %s", tooltip_text); else if (!dbus_context_is_available (applet->dbus_context)) tooltip_text = g_strdup (_("Notifications (daemon unavailable)")); else tooltip_text = g_strdup_printf ("Notifications %s", tooltip_text); gtk_widget_set_tooltip_text (GTK_WIDGET (applet->applet), tooltip_text); g_free (tooltip_text); set_status_image (applet, dnd_active); update_count_badge (applet); } static gboolean applet_button_press_cb (GtkWidget *widget, GdkEventButton *event, MateNotificationApplet *applet) { switch (event->button) { case 1: /* Left click */ if (applet->history_context) { show_notification_history (applet->history_context); } return TRUE; case 2: /* Middle click */ toggle_do_not_disturb (applet); return TRUE; case 3: /* Right click handled by context menu */ default: break; } return FALSE; } static void toggle_do_not_disturb (MateNotificationApplet *applet) { gboolean current_state; current_state = g_settings_get_boolean (applet->settings, GSETTINGS_KEY_DO_NOT_DISTURB); g_settings_set_boolean (applet->settings, GSETTINGS_KEY_DO_NOT_DISTURB, !current_state); } static void update_count_badge (MateNotificationApplet *applet) { gchar *count_text; if (applet->unread_count > 0) { /* Show badge with count */ if (applet->unread_count > 99) { count_text = g_strdup ("99+"); } else { count_text = g_strdup_printf ("%u", applet->unread_count); } gtk_label_set_text (GTK_LABEL (applet->count_label), count_text); gtk_widget_show (applet->count_label); g_free (count_text); } else { /* Hide badge when no unread notifications */ gtk_widget_hide (applet->count_label); } } static gboolean periodic_update_count (MateNotificationApplet *applet) { if (applet && dbus_context_is_available (applet->dbus_context)) { update_unread_count (applet); } return TRUE; /* Continue periodic updates */ } static MateNotificationApplet* applet_main (MatePanelApplet *applet_widget) { MateNotificationApplet *applet; GtkWidget *box; #ifndef ENABLE_IN_PROCESS g_set_application_name (_("Do Not Disturb")); #endif gtk_window_set_default_icon_name ("mate-notification-properties"); applet = g_new (MateNotificationApplet, 1); applet->applet = applet_widget; applet->settings = g_settings_new (GSETTINGS_SCHEMA); /* Initialize D-Bus context and notification state */ applet->dbus_context = dbus_context_new (); applet->unread_count = 0; applet->update_timer_id = 0; #ifndef ENABLE_IN_PROCESS /* needed to clamp ourselves to the panel size */ mate_panel_applet_set_flags (MATE_PANEL_APPLET (applet), MATE_PANEL_APPLET_EXPAND_MINOR); #endif /* Create overlay for icons and count badge */ applet->overlay = gtk_overlay_new (); /* Create container for the two state icons */ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); applet->image_on = gtk_image_new (); applet->image_off = gtk_image_new (); applet_draw_icon (applet_widget, 0, applet); gtk_box_pack_start (GTK_BOX (box), applet->image_on, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (box), applet->image_off, TRUE, TRUE, 0); /* Add icon box as main overlay child */ gtk_container_add (GTK_CONTAINER (applet->overlay), box); /* Create count badge label */ applet->count_label = gtk_label_new (""); gtk_widget_set_name (applet->count_label, "notification-count-badge"); gtk_widget_set_halign (applet->count_label, GTK_ALIGN_END); gtk_widget_set_valign (applet->count_label, GTK_ALIGN_END); GtkCssProvider *css_provider = gtk_css_provider_new (); const gchar *css_data = "#notification-count-badge {" " background-color: rgba(255,255,255,0.9);" " color: #000000;" " border-radius: 8px;" " min-width: 12px;" " min-height: 4px;" " padding: 1px 1px;" " font-size: 10px;" " font-weight: bold;" " text-shadow: none;" " border: 1px solid rgba(0,0,0,0.1);" "}"; gtk_css_provider_load_from_data (css_provider, css_data, -1, NULL); gtk_style_context_add_provider (gtk_widget_get_style_context (applet->count_label), GTK_STYLE_PROVIDER (css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); g_object_unref (css_provider); /* Add count label as overlay */ gtk_overlay_add_overlay (GTK_OVERLAY (applet->overlay), applet->count_label); /* Add overlay to applet */ gtk_container_add (GTK_CONTAINER (applet_widget), applet->overlay); set_status_image (applet, g_settings_get_boolean (applet->settings, GSETTINGS_KEY_DO_NOT_DISTURB)); /* click handling */ gtk_widget_add_events (GTK_WIDGET (applet_widget), GDK_BUTTON_PRESS_MASK); g_signal_connect (G_OBJECT (applet_widget), "button-press-event", G_CALLBACK (applet_button_press_cb), applet); /* set up context menu */ applet->action_group = gtk_action_group_new ("Do Not Disturb Actions"); #ifdef ENABLE_NLS gtk_action_group_set_translation_domain (applet->action_group, GETTEXT_PACKAGE); #endif /* ENABLE_NLS */ gtk_action_group_add_actions (applet->action_group, applet_menu_actions, G_N_ELEMENTS (applet_menu_actions), applet); GtkToggleAction *do_not_disturb_toggle_action = gtk_toggle_action_new ("DoNotDisturb", _("_Do not disturb"), _("Enable/Disable do-not-disturb mode."), NULL); gtk_action_group_add_action (applet->action_group, GTK_ACTION (do_not_disturb_toggle_action)); mate_panel_applet_setup_menu_from_resource (applet->applet, RESOURCE_PATH "menu.xml", applet->action_group); g_settings_bind (applet->settings, "do-not-disturb", do_not_disturb_toggle_action, "active", G_SETTINGS_BIND_DEFAULT); g_signal_connect (G_OBJECT (applet->applet), "destroy", G_CALLBACK (applet_destroy), applet); /* GSettings callback */ g_signal_connect (G_OBJECT (applet->settings), "changed::" GSETTINGS_KEY_DO_NOT_DISTURB, G_CALLBACK (settings_changed), applet); /* Create new history context */ applet->history_context = history_context_new (applet->dbus_context, GTK_WIDGET (applet->applet), G_CALLBACK (update_unread_count), applet, applet->settings); setup_daemon_connection (applet); /* Set up periodic updates every few seconds */ #define NOTIFICATION_UPDATE_COUNT 5 applet->update_timer_id = g_timeout_add_seconds (NOTIFICATION_UPDATE_COUNT, (GSourceFunc) periodic_update_count, applet); return applet; } static gboolean applet_factory (MatePanelApplet *applet_widget, const gchar *iid, gpointer data) { MateNotificationApplet *applet; (void) data; if (!strcmp (iid, "MateNotificationApplet")) { applet = applet_main (applet_widget); g_signal_connect (G_OBJECT (applet_widget), "change_size", G_CALLBACK (applet_draw_icon), (gpointer) applet); return TRUE; } return FALSE; } PANEL_APPLET_FACTORY ("MateNotificationAppletFactory", PANEL_TYPE_APPLET, "Do Not Disturb Applet", applet_factory, NULL)