From 0f1bb6461ebfd7eb9d048135061bbecda218d08d Mon Sep 17 00:00:00 2001 From: Hysterical Raisins <hysterical-dev@xs4all.nl> Date: Tue, 17 Sep 2019 21:54:06 +0200 Subject: [PATCH 1/2] Implement notification actions (#165) - needs some cleanup (debugging messages) - needs input on design - dismiss button should at some point be replaced with swipe gesture --- src/notification.c | 185 +++++++++++++++++++++++++++++++++++++++-- src/notification.h | 22 +++-- src/notify-manager.c | 102 +++++++++++++++++++++-- src/shell.c | 16 ++++ src/ui/notification.ui | 45 +++++++++- 5 files changed, 347 insertions(+), 23 deletions(-) diff --git a/src/notification.c b/src/notification.c index 522b64bdd..fdb5645bc 100644 --- a/src/notification.c +++ b/src/notification.c @@ -22,12 +22,16 @@ enum { PROP_SUMMARY, PROP_BODY, PROP_APP_ICON, + PROP_DESKTOP_ID, + PROP_ACTIONS, LAST_PROP }; static GParamSpec *props[LAST_PROP]; enum { + SIGNAL_DEFAULT, SIGNAL_DISMISSED, + SIGNAL_ACTION, N_SIGNALS }; static guint signals[N_SIGNALS]; @@ -40,7 +44,12 @@ typedef struct _PhoshNotification GtkWidget *lbl_summary; GtkWidget *lbl_body; GtkWidget *img_icon; - gchar *app_icon; + GtkWidget *btn_dismiss; + GtkWidget *box_actions; + gchar *app_icon; + gchar *desktop_id; + GStrv actions; + gboolean default_specified : 1; } PhoshNotification; G_DEFINE_TYPE(PhoshNotification, phosh_notification, PHOSH_TYPE_LAYER_SURFACE) @@ -68,6 +77,12 @@ phosh_notification_set_property (GObject *object, case PROP_APP_ICON: phosh_notification_set_app_icon (self, g_value_get_string (value)); break; + case PROP_DESKTOP_ID: + phosh_notification_set_desktop_id (self, g_value_get_string (value)); + break; + case PROP_ACTIONS: + phosh_notification_set_actions (self, g_value_get_pointer (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -96,6 +111,12 @@ phosh_notification_get_property (GObject *object, case PROP_APP_ICON: g_value_set_string (value, self->app_icon); break; + case PROP_DESKTOP_ID: + g_value_set_string (value, self->desktop_id); + break; + case PROP_ACTIONS: + g_value_set_pointer (value, self->actions); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -108,10 +129,29 @@ evbox_button_press_event_cb (PhoshNotification *self, GdkEventButton *event) { g_return_val_if_fail (PHOSH_IS_NOTIFICATION (self), FALSE); - g_signal_emit(self, signals[SIGNAL_DISMISSED], 0); + g_signal_emit (self, signals[SIGNAL_DEFAULT], 0, self->default_specified); return FALSE; } +static void +dismiss_clicked_cb (PhoshNotification *self, GtkButton *button) +{ + g_return_if_fail (PHOSH_IS_NOTIFICATION (self)); + + g_signal_emit (self, signals[SIGNAL_DISMISSED], 0); +} + +static void +action_clicked_cb (PhoshNotification *self, GtkButton *button) +{ + gchar *action = NULL; + + g_return_if_fail (PHOSH_IS_NOTIFICATION (self)); + + action = g_object_get_data (G_OBJECT (button), "action"); + g_debug ("%s: ACTION = '%s'", __func__, action); + g_signal_emit (self, signals[SIGNAL_ACTION], 0, action); +} static void phosh_notification_finalize (GObject *object) @@ -119,6 +159,7 @@ phosh_notification_finalize (GObject *object) PhoshNotification *self = PHOSH_NOTIFICATION (object); g_clear_pointer (&self->app_icon, g_free); + g_clear_pointer (&self->actions, g_strfreev); G_OBJECT_CLASS (phosh_notification_parent_class)->finalize (object); } @@ -166,8 +207,34 @@ phosh_notification_class_init (PhoshNotificationClass *klass) "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + props[PROP_DESKTOP_ID] = + g_param_spec_string ( + "desktop-id", + "Desktop ID", + "The desktop ID of the sender", + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + props[PROP_ACTIONS] = + g_param_spec_pointer ( + "actions", + "Actions", + "The actions connected to this notification", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_properties (object_class, LAST_PROP, props); + /** + * PhoshNotifiation::default: + * + * User invoked default action, this will activate the application, unless + * another default action was specified by the application + */ + signals[SIGNAL_DEFAULT] = g_signal_new ("default", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + /** * PhoshNotifiation::dismissed: * @@ -178,13 +245,26 @@ phosh_notification_class_init (PhoshNotificationClass *klass) G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + /** + * PhoshNotifiation::action: + * + * User invoked action, by clicking its button + */ + signals[SIGNAL_ACTION] = g_signal_new ("action", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, G_TYPE_STRING); + gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/phosh/ui/notification.ui"); gtk_widget_class_bind_template_child (widget_class, PhoshNotification, lbl_app_name); gtk_widget_class_bind_template_child (widget_class, PhoshNotification, lbl_summary); gtk_widget_class_bind_template_child (widget_class, PhoshNotification, lbl_body); gtk_widget_class_bind_template_child (widget_class, PhoshNotification, img_icon); + gtk_widget_class_bind_template_child (widget_class, PhoshNotification, btn_dismiss); + gtk_widget_class_bind_template_child (widget_class, PhoshNotification, box_actions); gtk_widget_class_bind_template_callback (widget_class, evbox_button_press_event_cb); + gtk_widget_class_bind_template_callback (widget_class, dismiss_clicked_cb); } @@ -194,18 +274,21 @@ phosh_notification_init (PhoshNotification *self) gtk_widget_init_template (GTK_WIDGET (self)); } - PhoshNotification * -phosh_notification_new (const char *app_name,const char *summary, - const char *body, const char *app_icon) +phosh_notification_new(const char *app_name, + const char *summary, + const char *body, + const char *app_icon, + const char *desktop_id) { PhoshWayland *wl = phosh_wayland_get_default (); return g_object_new (PHOSH_TYPE_NOTIFICATION, - "app_name", app_name, + "app-name", app_name, "summary", summary, "body", body, - "app_icon", app_icon, + "app-icon", app_icon, + "desktop-id", desktop_id, /* layer surface */ "layer-shell", phosh_wayland_get_zwlr_layer_shell_v1(wl), "anchor", ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, @@ -236,6 +319,20 @@ phosh_notification_set_app_icon (PhoshNotification *self, const gchar *app_icon) g_object_notify_by_pspec (G_OBJECT (self), props[PROP_APP_ICON]); } +void +phosh_notification_set_desktop_id (PhoshNotification *self, const gchar *desktop_id) +{ + g_return_if_fail (PHOSH_IS_NOTIFICATION (self)); + + if (!g_strcmp0 (self->desktop_id, desktop_id)) + return; + + g_free (self->desktop_id); + self->desktop_id = g_strdup (desktop_id); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DESKTOP_ID]); +} + void phosh_notification_set_summary (PhoshNotification *self, const gchar *summary) { @@ -265,3 +362,77 @@ phosh_notification_set_app_name (PhoshNotification *self, const gchar *app_name) g_object_notify_by_pspec (G_OBJECT (self), props[PROP_APP_NAME]); } + +static void +foreach_destroy(GtkWidget *widget, gpointer unused) +{ + gtk_widget_destroy(widget); +} + +void +phosh_notification_set_actions(PhoshNotification *self, + const gchar * const *actions) +{ + GPtrArray *array; + GtkWidget *button; + + g_return_if_fail (PHOSH_IS_NOTIFICATION (self)); + + if (actions && self->actions && g_strv_length(self->actions) == g_strv_length ((GStrv)actions)) { + gboolean unchanged = TRUE; + for (int i = 0; i < g_strv_length (self->actions); i++) { + if (!(actions[i] && g_str_equal (self->actions[i], actions[i]))) { + unchanged = FALSE; + break; + } + } + + if (unchanged) { + g_debug ("Actions unchaged"); + return; + } + } + + /* clean up stale items */ + g_clear_pointer (&self->actions, g_strfreev); + gtk_container_foreach (GTK_CONTAINER (self->box_actions), foreach_destroy, NULL); + self->default_specified = FALSE; + + /* rebuild actions */ + array = g_ptr_array_new(); + + for (int i = 0; actions[i]; i += 2) { + gchar *action = g_strdup (actions[i]); + gchar *label = g_strdup (actions[i + 1]); + + g_ptr_array_add (array, action); + g_ptr_array_add (array, label); + + g_assert (label); + + g_debug ("label = '%s', action = '%s'", label, action); + + if (g_str_equal (action, "default")) { + self->default_specified = TRUE; + + continue; + } + + button = gtk_button_new_with_label (label); + g_object_set_data (G_OBJECT (button), "action", action); + gtk_box_pack_end (GTK_BOX (self->box_actions), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + g_signal_connect_swapped (button, + "clicked", + G_CALLBACK(action_clicked_cb), + self); + } + + /* NULL terminate to become a GStrv */ + g_ptr_array_add(array, NULL); + + self->actions = (GStrv)g_ptr_array_free(array, FALSE); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACTIONS]); +} diff --git a/src/notification.h b/src/notification.h index 041ee7190..84526f556 100644 --- a/src/notification.h +++ b/src/notification.h @@ -20,12 +20,20 @@ enum G_DECLARE_FINAL_TYPE (PhoshNotification, phosh_notification, PHOSH, NOTIFICATION, PhoshLayerSurface) -PhoshNotification *phosh_notification_new (const char *app_name, - const char *summary, - const char *body, - const char *app_icon); -void phosh_notification_set_summary (PhoshNotification *self, const gchar *summary); -void phosh_notification_set_app_name (PhoshNotification *self, const gchar *app_name); -void phosh_notification_set_app_icon (PhoshNotification *self, const gchar *app_icon); +PhoshNotification *phosh_notification_new(const char *app_name, + const char *summary, + const char *body, + const char *app_icon, + const char *desktop_id); +void phosh_notification_set_summary (PhoshNotification *self, + const gchar *summary); +void phosh_notification_set_app_name (PhoshNotification *self, + const gchar *app_name); +void phosh_notification_set_app_icon (PhoshNotification *self, + const gchar *app_icon); +void phosh_notification_set_desktop_id (PhoshNotification *self, + const gchar *desktop_id); +void phosh_notification_set_actions (PhoshNotification *self, + const gchar * const *actions); G_END_DECLS diff --git a/src/notify-manager.c b/src/notify-manager.c index 90c441635..587394fc2 100644 --- a/src/notify-manager.c +++ b/src/notify-manager.c @@ -25,6 +25,12 @@ * See https://developer.gnome.org/notification-spec/ */ +enum { + SIGNAL_RAISE_APP, + N_SIGNALS, +}; +static guint signals[N_SIGNALS]; + #define NOTIFY_DBUS_NAME "org.freedesktop.Notifications" static void phosh_notify_manager_notify_iface_init ( @@ -139,6 +145,34 @@ on_notification_expired (gpointer data) return G_SOURCE_REMOVE; } +static void +on_notification_default (PhoshNotifyManager *self, + gboolean default_specified, + PhoshNotification *notification) +{ + gpointer data; + + /* no default action specified: raising application */ + if (!default_specified) { + g_autofree gchar *desktop_id = NULL; + g_object_get (notification, "desktop-id", &desktop_id, NULL); + g_debug ("%s: 'desktop-id' = '%s'", __func__, desktop_id); + g_signal_emit (self, signals[SIGNAL_RAISE_APP], 0, desktop_id); + return; + } + + g_return_if_fail (PHOSH_IS_NOTIFY_MANAGER (self)); + g_return_if_fail (PHOSH_IS_NOTIFICATION (notification)); + + data = g_object_get_data (G_OBJECT (notification), "notify-id"); + g_return_if_fail (data); + + phosh_notify_dbus_notifications_emit_action_invoked ( + PHOSH_NOTIFY_DBUS_NOTIFICATIONS(self), + GPOINTER_TO_UINT (data), + "default"); +} + static void on_notification_dismissed (PhoshNotifyManager *self, PhoshNotification *notification) { @@ -154,6 +188,25 @@ on_notification_dismissed (PhoshNotifyManager *self, PhoshNotification *notifica PHOSH_NOTIFY_MANAGER_REASON_DISMISSED); } +static void +on_notification_action (PhoshNotifyManager *self, + const gchar *action, + PhoshNotification *notification) +{ + gpointer data; + + g_return_if_fail (PHOSH_IS_NOTIFY_MANAGER (self)); + g_return_if_fail (PHOSH_IS_NOTIFICATION (notification)); + + data = g_object_get_data (G_OBJECT (notification), "notify-id"); + g_return_if_fail (data); + + phosh_notify_dbus_notifications_emit_action_invoked ( + PHOSH_NOTIFY_DBUS_NOTIFICATIONS(self), + GPOINTER_TO_UINT (data), + action); +} + static gboolean handle_notify (PhoshNotifyDbusNotifications *skeleton, @@ -172,6 +225,7 @@ handle_notify (PhoshNotifyDbusNotifications *skeleton, GVariant *item; GVariantIter iter; guint id; + guint actions_length; g_autofree gchar *image_path = NULL; g_autofree gchar *desktop_id = NULL; @@ -208,10 +262,11 @@ handle_notify (PhoshNotifyDbusNotifications *skeleton, (g_strcmp0 (key, "desktop-entry") == 0)) { if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) desktop_id = g_variant_dup_string (value, NULL); + } else { + g_warning ("Unknown notification hint: '%s'", key); } g_variant_unref(item); } - if (expire_timeout == -1) expire_timeout = NOTIFICATION_DEFAULT_TIMEOUT; @@ -221,16 +276,18 @@ handle_notify (PhoshNotifyDbusNotifications *skeleton, if (notification) { id = replaces_id; g_object_set (notification, - "app_name", app_name, + "app-name", app_name, "summary", summary, "body", body, - "app_icon", image_path ?: app_icon, + "app-icon", image_path ?: app_icon, + "desktop-id", desktop_id, NULL); } else { id = self->next_id++; - notification = g_object_ref_sink (phosh_notification_new (app_name, summary, - body, image_path ?: app_icon)); + notification = phosh_notification_new (app_name, summary, body, + image_path ?: app_icon, + desktop_id); g_hash_table_insert (self->notifications, GUINT_TO_POINTER (id), notification); @@ -242,18 +299,44 @@ handle_notify (PhoshNotifyDbusNotifications *skeleton, GUINT_TO_POINTER (id)); } + g_signal_connect_object (notification, + "default", + G_CALLBACK (on_notification_default), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (notification, "dismissed", G_CALLBACK (on_notification_dismissed), self, G_CONNECT_SWAPPED); + g_signal_connect_object (notification, + "action", + G_CALLBACK (on_notification_action), + self, + G_CONNECT_SWAPPED); + if (self->show_banners) gtk_widget_show (GTK_WIDGET (notification)); } - phosh_notify_dbus_notifications_complete_notify ( - skeleton, invocation, id); + actions_length = g_strv_length ((gchar**)actions); + g_assert (actions_length % 2 == 0); + + if (!actions_length) + g_debug ("No actions for '%s'", g_dbus_method_invocation_get_sender (invocation)); + else { + phosh_notification_set_actions(notification, actions); + } + + for (int i = 0; i < actions_length; i += 2) { + g_debug ("'%s': action = '%s', label = '%s'", + g_dbus_method_invocation_get_sender (invocation), + actions[i], actions[i + 1]); + } + + phosh_notify_dbus_notifications_complete_notify (skeleton, invocation, id); return TRUE; } @@ -360,6 +443,11 @@ phosh_notify_manager_class_init (PhoshNotifyManagerClass *klass) object_class->dispose = phosh_notify_manager_dispose; object_class->set_property = phosh_notify_manager_set_property; object_class->get_property = phosh_notify_manager_get_property; + + signals[SIGNAL_RAISE_APP] = g_signal_new ("raise-application", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 1, G_TYPE_STRING); } diff --git a/src/shell.c b/src/shell.c index 2589fa255..d7ce4dc12 100644 --- a/src/shell.c +++ b/src/shell.c @@ -448,6 +448,16 @@ on_toplevel_added (PhoshShell *self, GParamSpec *pspec, PhoshToplevelManager *to phosh_home_set_state (PHOSH_HOME (priv->home), PHOSH_HOME_STATE_FOLDED); } +static void +on_notify_manager_raise_app(PhoshShell *self, + const gchar *desktop_id, + PhoshNotifyManager *notify_manager) +{ + g_return_if_fail (desktop_id); + + g_debug ("Requested to raise application '%s' (raise not implemented)", desktop_id); +} + static gboolean setup_idle_cb (PhoshShell *self) @@ -477,6 +487,12 @@ setup_idle_cb (PhoshShell *self) priv->notify_manager = phosh_notify_manager_get_default (); + g_signal_connect_object (priv->notify_manager, + "raise-application", + G_CALLBACK (on_notify_manager_raise_app), + self, + G_CONNECT_SWAPPED); + return FALSE; } diff --git a/src/ui/notification.ui b/src/ui/notification.ui index 3f5e416b2..a0edb1938 100644 --- a/src/ui/notification.ui +++ b/src/ui/notification.ui @@ -34,10 +34,12 @@ <property name="position">0</property> </packing> </child> - <child> + <child type="center"> <object class="GtkBox"> <property name="visible">True</property> <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">False</property> <property name="orientation">vertical</property> <child> <object class="GtkLabel" id="lbl_app_name"> @@ -72,6 +74,34 @@ <packing> <property name="expand">True</property> <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="btn_dismiss"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="receives_default">False</property> + <property name="halign">end</property> + <property name="valign">start</property> + <property name="relief">none</property> + <signal name="clicked" handler="dismiss_clicked_cb" swapped="yes"/> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">window-close-symbolic</property> + <property name="icon_size">5</property> + <style> + <class name=""destructive-action""/> + </style> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> <property name="position">1</property> </packing> </child> @@ -99,7 +129,18 @@ </packing> </child> <child> - <placeholder/> + <object class="GtkBox" id="box_actions"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> </child> <style> <class name="phosh-notification"/> -- GitLab From 9a2a57cbacddefa1889dd375058d767faf0a6150 Mon Sep 17 00:00:00 2001 From: Hysterical Raisins <hysterical-dev@xs4all.nl> Date: Tue, 17 Sep 2019 23:20:17 +0200 Subject: [PATCH 2/2] Notification actions: plug a leak + minor cleanups --- src/notification.c | 7 ++----- src/notify-manager.c | 10 ++-------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/notification.c b/src/notification.c index fdb5645bc..2bd16ba61 100644 --- a/src/notification.c +++ b/src/notification.c @@ -144,12 +144,11 @@ dismiss_clicked_cb (PhoshNotification *self, GtkButton *button) static void action_clicked_cb (PhoshNotification *self, GtkButton *button) { - gchar *action = NULL; + g_autofree gchar *action = NULL; g_return_if_fail (PHOSH_IS_NOTIFICATION (self)); action = g_object_get_data (G_OBJECT (button), "action"); - g_debug ("%s: ACTION = '%s'", __func__, action); g_signal_emit (self, signals[SIGNAL_ACTION], 0, action); } @@ -378,7 +377,7 @@ phosh_notification_set_actions(PhoshNotification *self, g_return_if_fail (PHOSH_IS_NOTIFICATION (self)); - if (actions && self->actions && g_strv_length(self->actions) == g_strv_length ((GStrv)actions)) { + if (actions && self->actions && g_strv_length (self->actions) == g_strv_length ((GStrv)actions)) { gboolean unchanged = TRUE; for (int i = 0; i < g_strv_length (self->actions); i++) { if (!(actions[i] && g_str_equal (self->actions[i], actions[i]))) { @@ -410,8 +409,6 @@ phosh_notification_set_actions(PhoshNotification *self, g_assert (label); - g_debug ("label = '%s', action = '%s'", label, action); - if (g_str_equal (action, "default")) { self->default_specified = TRUE; diff --git a/src/notify-manager.c b/src/notify-manager.c index 587394fc2..d1a22c614 100644 --- a/src/notify-manager.c +++ b/src/notify-manager.c @@ -156,7 +156,6 @@ on_notification_default (PhoshNotifyManager *self, if (!default_specified) { g_autofree gchar *desktop_id = NULL; g_object_get (notification, "desktop-id", &desktop_id, NULL); - g_debug ("%s: 'desktop-id' = '%s'", __func__, desktop_id); g_signal_emit (self, signals[SIGNAL_RAISE_APP], 0, desktop_id); return; } @@ -267,6 +266,7 @@ handle_notify (PhoshNotifyDbusNotifications *skeleton, } g_variant_unref(item); } + if (expire_timeout == -1) expire_timeout = NOTIFICATION_DEFAULT_TIMEOUT; @@ -321,7 +321,7 @@ handle_notify (PhoshNotifyDbusNotifications *skeleton, gtk_widget_show (GTK_WIDGET (notification)); } - actions_length = g_strv_length ((gchar**)actions); + actions_length = g_strv_length ((GStrv)actions); g_assert (actions_length % 2 == 0); if (!actions_length) @@ -330,12 +330,6 @@ handle_notify (PhoshNotifyDbusNotifications *skeleton, phosh_notification_set_actions(notification, actions); } - for (int i = 0; i < actions_length; i += 2) { - g_debug ("'%s': action = '%s', label = '%s'", - g_dbus_method_invocation_get_sender (invocation), - actions[i], actions[i + 1]); - } - phosh_notify_dbus_notifications_complete_notify (skeleton, invocation, id); return TRUE; -- GitLab