Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-notifications"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <gio/gio.h>
9 : : #include <gio/gdesktopappinfo.h>
10 : : #include <libpeas.h>
11 : : #include <libvalent-core.h>
12 : :
13 : : #include "valent-notification.h"
14 : : #include "valent-notifications.h"
15 : : #include "valent-notifications-adapter.h"
16 : :
17 : :
18 : : /**
19 : : * ValentNotifications:
20 : : *
21 : : * A class for sending and receiving notifications.
22 : : *
23 : : * `ValentNotifications` is an aggregator of notifications, intended for use by
24 : : * [class@Valent.DevicePlugin] implementations.
25 : : *
26 : : * Plugins can implement [class@Valent.NotificationsAdapter] to provide an
27 : : * interface to monitor, send and withdraw notifications.
28 : : *
29 : : * Since: 1.0
30 : : */
31 : : struct _ValentNotifications
32 : : {
33 : : ValentComponent parent_instance;
34 : :
35 : : GVariant *applications;
36 : : };
37 : :
38 [ + + + - ]: 190 : G_DEFINE_FINAL_TYPE (ValentNotifications, valent_notifications, VALENT_TYPE_COMPONENT)
39 : :
40 : : enum {
41 : : NOTIFICATION_ADDED,
42 : : NOTIFICATION_REMOVED,
43 : : N_SIGNALS
44 : : };
45 : :
46 : : static guint signals[N_SIGNALS] = { 0, };
47 : :
48 : :
49 : : static GVariant *
50 : 0 : app_info_serialize (GAppInfo *info)
51 : : {
52 : 0 : GVariantDict dict;
53 : 0 : const char *name;
54 : 0 : GIcon *icon;
55 : :
56 [ # # # # : 0 : g_assert (G_IS_APP_INFO (info));
# # # # ]
57 : :
58 : 0 : g_variant_dict_init (&dict, NULL);
59 : :
60 [ # # ]: 0 : if ((name = g_app_info_get_display_name (info)) != NULL)
61 : 0 : g_variant_dict_insert (&dict, "name", "s", name);
62 : :
63 [ # # ]: 0 : if ((icon = g_app_info_get_icon (info)) != NULL)
64 : : {
65 : 0 : g_autoptr (GVariant) iconv = NULL;
66 : :
67 : 0 : iconv = g_icon_serialize (icon);
68 [ # # ]: 0 : g_variant_dict_insert_value (&dict, "icon", iconv);
69 : : }
70 : :
71 : 0 : return g_variant_dict_end (&dict);
72 : : }
73 : :
74 : : static GVariant *
75 : 11 : notification_serialize (ValentNotification *notification)
76 : : {
77 : 11 : GVariantDict dict;
78 : 11 : const char *app_name;
79 : 11 : GIcon *icon;
80 : :
81 : 11 : g_variant_dict_init (&dict, NULL);
82 : :
83 [ + + ]: 11 : if ((app_name = valent_notification_get_application (notification)) != NULL)
84 : 7 : g_variant_dict_insert (&dict, "name", "s", app_name);
85 : :
86 [ + + ]: 11 : if ((icon = valent_notification_get_icon (notification)) != NULL)
87 : : {
88 : 18 : g_autoptr (GVariant) iconv = NULL;
89 : :
90 : 7 : iconv = g_icon_serialize (icon);
91 [ + - ]: 7 : g_variant_dict_insert_value (&dict, "icon", iconv);
92 : : }
93 : :
94 : 11 : return g_variant_dict_end (&dict);
95 : : }
96 : :
97 : : static void
98 : 7 : query_applications (ValentNotifications *self)
99 : : {
100 : 7 : GVariantDict dict;
101 : 14 : g_autolist (GAppInfo) infos = NULL;
102 : :
103 [ + - ]: 7 : g_assert (VALENT_IS_NOTIFICATIONS (self));
104 : :
105 : 7 : g_variant_dict_init (&dict, NULL);
106 : 7 : infos = g_app_info_get_all ();
107 : :
108 [ - + ]: 7 : for (const GList *iter = infos; iter; iter = iter->next)
109 : : {
110 : 0 : const char *desktop_id;
111 : 0 : const char *name;
112 : :
113 : 0 : desktop_id = g_app_info_get_id (iter->data);
114 : :
115 [ # # # # : 0 : if G_UNLIKELY (g_str_has_prefix (desktop_id, APPLICATION_ID))
# # ]
116 : 0 : continue;
117 : :
118 [ # # ]: 0 : if (!g_desktop_app_info_get_boolean (iter->data, "X-GNOME-UsesNotifications"))
119 : 0 : continue;
120 : :
121 : 0 : name = g_app_info_get_display_name (iter->data);
122 : 0 : g_variant_dict_insert_value (&dict, name, app_info_serialize (iter->data));
123 : : }
124 : :
125 : 7 : self->applications = g_variant_ref_sink (g_variant_dict_end (&dict));
126 : 7 : }
127 : :
128 : : static void
129 : 11 : add_application (ValentNotifications *self,
130 : : GVariant *application)
131 : : {
132 : 11 : GVariantDict dict;
133 : 11 : const char *name;
134 : :
135 [ - + ]: 11 : if (self->applications == NULL)
136 : 0 : query_applications (self);
137 : :
138 : 11 : g_variant_dict_init (&dict, self->applications);
139 : :
140 [ + + ]: 11 : if (g_variant_lookup (application, "name", "&s", &name))
141 : 7 : g_variant_dict_insert_value (&dict, name, g_variant_ref_sink (application));
142 : 11 : g_variant_unref (application);
143 : :
144 [ + - ]: 11 : g_clear_pointer (&self->applications, g_variant_unref);
145 : 11 : self->applications = g_variant_ref_sink (g_variant_dict_end (&dict));
146 : 11 : }
147 : :
148 : :
149 : : /*
150 : : * ValentNotificationsAdapter Callbacks
151 : : */
152 : : static void
153 : 11 : on_notification_added (ValentNotificationsAdapter *adapter,
154 : : ValentNotification *notification,
155 : : ValentNotifications *self)
156 : : {
157 [ + - ]: 11 : g_assert (VALENT_IS_NOTIFICATIONS_ADAPTER (adapter));
158 [ - + ]: 11 : g_assert (VALENT_IS_NOTIFICATION (notification));
159 [ - + ]: 11 : g_assert (VALENT_IS_NOTIFICATIONS (self));
160 : :
161 : 11 : add_application (self, notification_serialize (notification));
162 : :
163 : 11 : g_signal_emit (G_OBJECT (self), signals [NOTIFICATION_ADDED], 0, notification);
164 : 11 : }
165 : :
166 : : static void
167 : 6 : on_notification_removed (ValentNotificationsAdapter *adapter,
168 : : const char *id,
169 : : ValentNotifications *self)
170 : : {
171 [ + - ]: 6 : g_assert (VALENT_IS_NOTIFICATIONS_ADAPTER (adapter));
172 [ - + ]: 6 : g_assert (id != NULL);
173 [ - + ]: 6 : g_assert (VALENT_IS_NOTIFICATIONS (self));
174 : :
175 : 6 : g_signal_emit (G_OBJECT (self), signals [NOTIFICATION_REMOVED], 0, id);
176 : 6 : }
177 : :
178 : :
179 : : /*
180 : : * ValentComponent
181 : : */
182 : : static void
183 : 7 : valent_notifications_bind_extension (ValentComponent *component,
184 : : GObject *extension)
185 : : {
186 : 7 : ValentNotifications *self = VALENT_NOTIFICATIONS (component);
187 : 7 : ValentNotificationsAdapter *adapter = VALENT_NOTIFICATIONS_ADAPTER (extension);
188 : :
189 : 7 : VALENT_ENTRY;
190 : :
191 [ + - ]: 7 : g_assert (VALENT_IS_NOTIFICATIONS (self));
192 [ - + ]: 7 : g_assert (VALENT_IS_NOTIFICATIONS_ADAPTER (adapter));
193 : :
194 : 7 : g_signal_connect_object (adapter,
195 : : "notification-added",
196 : : G_CALLBACK (on_notification_added),
197 : : self, 0);
198 : :
199 : 7 : g_signal_connect_object (adapter,
200 : : "notification-removed",
201 : : G_CALLBACK (on_notification_removed),
202 : : self, 0);
203 : :
204 : 7 : VALENT_EXIT;
205 : : }
206 : :
207 : : static void
208 : 4 : valent_notifications_unbind_extension (ValentComponent *component,
209 : : GObject *extension)
210 : : {
211 : 4 : ValentNotifications *self = VALENT_NOTIFICATIONS (component);
212 : 4 : ValentNotificationsAdapter *adapter = VALENT_NOTIFICATIONS_ADAPTER (extension);
213 : :
214 [ + - ]: 4 : g_assert (VALENT_IS_NOTIFICATIONS (self));
215 [ - + ]: 4 : g_assert (VALENT_IS_NOTIFICATIONS_ADAPTER (adapter));
216 : :
217 : 4 : g_signal_handlers_disconnect_by_func (adapter, on_notification_added, self);
218 : 4 : g_signal_handlers_disconnect_by_func (adapter, on_notification_removed, self);
219 : 4 : }
220 : :
221 : :
222 : : /*
223 : : * GObject
224 : : */
225 : : static void
226 : 5 : valent_notifications_finalize (GObject *object)
227 : : {
228 : 5 : ValentNotifications *self = VALENT_NOTIFICATIONS (object);
229 : :
230 [ + - ]: 5 : g_clear_pointer (&self->applications, g_variant_unref);
231 : :
232 : 5 : G_OBJECT_CLASS (valent_notifications_parent_class)->finalize (object);
233 : 5 : }
234 : :
235 : : static void
236 : 6 : valent_notifications_class_init (ValentNotificationsClass *klass)
237 : : {
238 : 6 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
239 : 6 : ValentComponentClass *component_class = VALENT_COMPONENT_CLASS (klass);
240 : :
241 : 6 : object_class->finalize = valent_notifications_finalize;
242 : :
243 : 6 : component_class->bind_extension = valent_notifications_bind_extension;
244 : 6 : component_class->unbind_extension = valent_notifications_unbind_extension;
245 : :
246 : : /**
247 : : * ValentNotifications::notification-added:
248 : : * @notifications: a `ValentNotifications`
249 : : * @notification: a `ValentNotification`
250 : : *
251 : : * Emitted when a notification is added to a
252 : : * [class@Valent.NotificationsAdapter].
253 : : *
254 : : * Since: 1.0
255 : : */
256 : 12 : signals [NOTIFICATION_ADDED] =
257 : 6 : g_signal_new ("notification-added",
258 : : G_TYPE_FROM_CLASS (klass),
259 : : G_SIGNAL_RUN_FIRST,
260 : : 0,
261 : : NULL, NULL,
262 : : g_cclosure_marshal_VOID__OBJECT,
263 : : G_TYPE_NONE, 1, VALENT_TYPE_NOTIFICATION);
264 : 6 : g_signal_set_va_marshaller (signals [NOTIFICATION_ADDED],
265 : : G_TYPE_FROM_CLASS (klass),
266 : : g_cclosure_marshal_VOID__OBJECTv);
267 : :
268 : : /**
269 : : * ValentNotifications::notification-removed:
270 : : * @notifications: a `ValentNotifications`
271 : : * @id: a notification id
272 : : *
273 : : * Emitted when a notification is removed from a
274 : : * [class@Valent.NotificationsAdapter].
275 : : *
276 : : * Since: 1.0
277 : : */
278 : 12 : signals [NOTIFICATION_REMOVED] =
279 : 6 : g_signal_new ("notification-removed",
280 : : G_TYPE_FROM_CLASS (klass),
281 : : G_SIGNAL_RUN_FIRST,
282 : : 0,
283 : : NULL, NULL,
284 : : g_cclosure_marshal_VOID__STRING,
285 : : G_TYPE_NONE, 1, G_TYPE_STRING);
286 : 6 : g_signal_set_va_marshaller (signals [NOTIFICATION_REMOVED],
287 : : G_TYPE_FROM_CLASS (klass),
288 : : g_cclosure_marshal_VOID__STRINGv);
289 : 6 : }
290 : :
291 : : static void
292 : 7 : valent_notifications_init (ValentNotifications *self)
293 : : {
294 : 7 : query_applications (self);
295 : 7 : }
296 : :
297 : : /**
298 : : * valent_notifications_get_default:
299 : : *
300 : : * Get the default [class@Valent.Notifications].
301 : : *
302 : : * Returns: (transfer none) (not nullable): a `ValentNotifications`
303 : : *
304 : : * Since: 1.0
305 : : */
306 : : ValentNotifications *
307 : 45 : valent_notifications_get_default (void)
308 : : {
309 : 45 : static ValentNotifications *default_instance = NULL;
310 : :
311 [ + + ]: 45 : if (default_instance == NULL)
312 : : {
313 : 7 : default_instance = g_object_new (VALENT_TYPE_NOTIFICATIONS,
314 : : "plugin-domain", "notifications",
315 : : "plugin-type", VALENT_TYPE_NOTIFICATIONS_ADAPTER,
316 : : NULL);
317 : 7 : g_object_add_weak_pointer (G_OBJECT (default_instance),
318 : : (gpointer)&default_instance);
319 : : }
320 : :
321 : 45 : return default_instance;
322 : : }
323 : :
324 : : /**
325 : : * valent_notifications_get_applications:
326 : : * @notifications: (nullable): a `ValentNotifications`
327 : : *
328 : : * Get a dictionary of applications that are known to send notifications.
329 : : *
330 : : * Returns: (transfer none): a `GVariant`
331 : : *
332 : : * Since: 1.0
333 : : */
334 : : GVariant *
335 : 1 : valent_notifications_get_applications (ValentNotifications *notifications)
336 : : {
337 [ - + - - ]: 1 : g_return_val_if_fail (notifications == NULL || VALENT_IS_NOTIFICATIONS (notifications), NULL);
338 : :
339 [ + - ]: 1 : if (notifications == NULL)
340 : 1 : notifications = valent_notifications_get_default ();
341 : :
342 [ - + ]: 1 : if (notifications->applications == NULL)
343 : 0 : query_applications (notifications);
344 : :
345 : 1 : return notifications->applications;
346 : : }
347 : :
|