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-fdo-notifications"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <gio/gio.h>
9 : : #include <valent.h>
10 : :
11 : : #ifdef HAVE_GLYCIN
12 : : #include <glycin.h>
13 : : #endif /* HAVE_GLYCIN */
14 : :
15 : : #include "valent-fdo-notifications.h"
16 : :
17 : :
18 : : struct _ValentFdoNotifications
19 : : {
20 : : ValentNotificationsAdapter parent_instance;
21 : :
22 : : GDBusInterfaceVTable vtable;
23 : : GDBusNodeInfo *node_info;
24 : : GDBusInterfaceInfo *iface_info;
25 : : GDBusConnection *monitor;
26 : : unsigned int monitor_id;
27 : : char *name_owner;
28 : : unsigned int name_owner_id;
29 : : GDBusConnection *session;
30 : : unsigned int closed_id;
31 : : };
32 : :
33 : : static void g_async_initable_iface_init (GAsyncInitableIface *iface);
34 : :
35 [ + + + - ]: 8 : G_DEFINE_FINAL_TYPE_WITH_CODE (ValentFdoNotifications, valent_fdo_notifications, VALENT_TYPE_NOTIFICATIONS_ADAPTER,
36 : : G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, g_async_initable_iface_init))
37 : :
38 : :
39 : : /*
40 : : * Map of notification-spec urgency to GNotificationPriority
41 : : *
42 : : * See: https://developer-old.gnome.org/notification-spec/#urgency-levels
43 : : */
44 : : static const unsigned int urgencies[] = {
45 : : G_NOTIFICATION_PRIORITY_LOW,
46 : : G_NOTIFICATION_PRIORITY_NORMAL,
47 : : G_NOTIFICATION_PRIORITY_URGENT
48 : : };
49 : :
50 : :
51 : : /*
52 : : * GDBusInterfaceSkeleton
53 : : */
54 : : static const char interface_xml[] =
55 : : "<node>"
56 : : " <interface name='org.freedesktop.Notifications'>"
57 : : " <method name='Notify'>"
58 : : " <arg name='appName' type='s' direction='in'/>"
59 : : " <arg name='replacesId' type='u' direction='in'/>"
60 : : " <arg name='iconName' type='s' direction='in'/>"
61 : : " <arg name='summary' type='s' direction='in'/>"
62 : : " <arg name='body' type='s' direction='in'/>"
63 : : " <arg name='actions' type='as' direction='in'/>"
64 : : " <arg name='hints' type='a{sv}' direction='in'/>"
65 : : " <arg name='timeout' type='i' direction='in'/>"
66 : : " </method>"
67 : : " </interface>"
68 : : "</node>";
69 : :
70 : : static const char *interface_matches[] = {
71 : : "interface='org.freedesktop.Notifications',member='Notify',type='method_call'",
72 : : NULL
73 : : };
74 : :
75 : :
76 : : static GIcon *
77 : 0 : _g_icon_new_for_variant (GVariant *image_data)
78 : : {
79 : : #ifdef HAVE_GLYCIN
80 : 0 : int32_t width, height, rowstride;
81 : 0 : gboolean has_alpha;
82 : 0 : int32_t bits_per_sample, n_channels;
83 : 0 : g_autoptr (GVariant) data_variant = NULL;
84 : 0 : const unsigned char *data = NULL;
85 : 0 : size_t data_len = 0;
86 : 0 : size_t expected_len = 0;
87 : :
88 [ # # ]: 0 : g_autoptr (GlyCreator) creator = NULL;
89 [ # # ]: 0 : g_autoptr (GlyNewFrame) frame = NULL;
90 [ # # ]: 0 : g_autoptr (GlyEncodedImage) image = NULL;
91 [ # # ]: 0 : g_autoptr (GBytes) texture = NULL;
92 [ # # ]: 0 : g_autoptr (GBytes) encoded = NULL;
93 [ # # ]: 0 : g_autoptr (GError) error = NULL;
94 : :
95 : 0 : g_variant_get (image_data, "(iiibii@ay)",
96 : : &width,
97 : : &height,
98 : : &rowstride,
99 : : &has_alpha,
100 : : &bits_per_sample,
101 : : &n_channels,
102 : : &data_variant);
103 : :
104 : 0 : data = g_variant_get_data (data_variant);
105 : 0 : data_len = g_variant_get_size (data_variant);
106 : 0 : expected_len = (height - 1) * rowstride + width
107 : 0 : * ((n_channels * bits_per_sample + 7) / 8);
108 : :
109 [ # # ]: 0 : if (expected_len != data_len)
110 : : {
111 : 0 : g_warning ("Expected image data to be of length %zu not %zu",
112 : : expected_len,
113 : : data_len);
114 : 0 : return NULL;
115 : : }
116 : :
117 [ # # ]: 0 : g_return_val_if_fail (bits_per_sample == 8, NULL);
118 [ # # # # : 0 : g_return_val_if_fail ((has_alpha && n_channels == 4) ||
# # # # ]
119 : : (!has_alpha && n_channels == 3), NULL);
120 : :
121 : 0 : creator = gly_creator_new ("image/png", &error);
122 [ # # ]: 0 : if (creator == NULL)
123 : : {
124 : 0 : g_warning ("%s(): Creating image: %s", G_STRFUNC, error->message);
125 : 0 : return NULL;
126 : : }
127 : :
128 : 0 : texture = g_bytes_new (data, data_len);
129 : 0 : frame = gly_creator_add_frame_with_stride (creator,
130 : : width,
131 : : height,
132 : : rowstride,
133 : : has_alpha
134 [ # # ]: 0 : ? GLY_MEMORY_R8G8B8A8
135 : : : GLY_MEMORY_R8G8B8,
136 : : texture,
137 : : &error);
138 [ # # ]: 0 : if (frame == NULL)
139 : : {
140 : 0 : g_warning ("%s(): Adding frame: %s", G_STRFUNC, error->message);
141 : 0 : return NULL;
142 : : }
143 : :
144 : 0 : image = gly_creator_create (creator, &error);
145 [ # # ]: 0 : if (image == NULL)
146 : : {
147 : 0 : g_warning ("%s(): Encoding image: %s", G_STRFUNC, error->message);
148 : 0 : return NULL;
149 : : }
150 : :
151 : 0 : encoded = gly_encoded_image_get_data (image);
152 : :
153 : 0 : return g_bytes_icon_new (encoded);
154 : : #else
155 : : return NULL;
156 : : #endif /* HAVE_GLYCIN */
157 : : }
158 : :
159 : : static void
160 : 1 : _notification_closed (ValentNotificationsAdapter *adapter,
161 : : GVariant *parameters)
162 : : {
163 : 1 : uint32_t id, reason;
164 : 2 : g_autofree char *id_str = NULL;
165 : :
166 : 1 : g_variant_get (parameters, "(uu)", &id, &reason);
167 : :
168 : 1 : id_str = g_strdup_printf ("%u", id);
169 : 1 : valent_notifications_adapter_notification_removed (adapter, id_str);
170 : 1 : }
171 : :
172 : : static void
173 : 1 : _notify (ValentNotificationsAdapter *adapter,
174 : : GVariant *parameters)
175 : : {
176 : 2 : g_autoptr (ValentNotification) notification = NULL;
177 [ + - ]: 1 : g_autoptr (GIcon) icon = NULL;
178 : :
179 : 1 : const char *app_name;
180 : 1 : uint32_t replaces_id;
181 : 1 : const char *app_icon;
182 : 1 : const char *summary;
183 : 1 : const char *body;
184 [ + - ]: 1 : g_autoptr (GVariant) actions = NULL;
185 [ + - ]: 1 : g_autoptr (GVariant) hints = NULL;
186 : 1 : int32_t expire_timeout;
187 : :
188 [ + - ]: 1 : g_autofree char *replaces_id_str = NULL;
189 : 1 : g_autoptr (GVariant) image_data = NULL;
190 : 1 : const char *image_path;
191 : 1 : unsigned char urgency;
192 : :
193 : : /* Extract what we need from the parameters */
194 : 1 : g_variant_get (parameters, "(&su&s&s&s@as@a{sv}i)",
195 : : &app_name,
196 : : &replaces_id,
197 : : &app_icon,
198 : : &summary,
199 : : &body,
200 : : &actions,
201 : : &hints,
202 : : &expire_timeout);
203 : :
204 : 1 : replaces_id_str = g_strdup_printf ("%u", replaces_id);
205 : :
206 : : /* Deserialize GNotification into ValentNotification */
207 : 1 : notification = valent_notification_new (NULL);
208 : 1 : valent_notification_set_id (notification, replaces_id_str);
209 : 1 : valent_notification_set_application (notification, app_name);
210 : 1 : valent_resource_set_title (VALENT_RESOURCE (notification), summary);
211 : 1 : valent_notification_set_body (notification, body);
212 : :
213 : : /* This ordering is required by the specification.
214 : : * See: https://specifications.freedesktop.org/notification-spec/latest/icons-and-images.html
215 : : */
216 [ + - - + ]: 2 : if (g_variant_lookup (hints, "image-data", "@(iiibiiay)", &image_data) ||
217 : 1 : g_variant_lookup (hints, "image_data", "@(iiibiiay)", &image_data))
218 : : {
219 : 0 : icon = _g_icon_new_for_variant (image_data);
220 : 0 : valent_notification_set_icon (notification, icon);
221 : : }
222 [ + - - + ]: 2 : else if (g_variant_lookup (hints, "image-path", "&s", &image_path) ||
223 : 1 : g_variant_lookup (hints, "image_path", "&s", &image_path))
224 : : {
225 : 0 : icon = g_icon_new_for_string (image_path, NULL);
226 : 0 : valent_notification_set_icon (notification, icon);
227 : : }
228 [ + - ]: 1 : else if (*app_icon != '\0')
229 : : {
230 : 1 : icon = g_icon_new_for_string (app_icon, NULL);
231 : 1 : valent_notification_set_icon (notification, icon);
232 : : }
233 [ # # ]: 0 : else if (g_variant_lookup (hints, "icon_data", "@(iiibiiay)", &image_data))
234 : : {
235 : 0 : icon = _g_icon_new_for_variant (image_data);
236 : 0 : valent_notification_set_icon (notification, icon);
237 : : }
238 : :
239 : : /* Map libnotify urgency to GNotification priority */
240 [ + - + - ]: 1 : if (g_variant_lookup (hints, "urgency", "y", &urgency) && urgency < G_N_ELEMENTS (urgencies))
241 : 1 : valent_notification_set_priority (notification, urgencies[urgency]);
242 : : else
243 : 0 : valent_notification_set_priority (notification, G_NOTIFICATION_PRIORITY_NORMAL);
244 : :
245 : : /* Set a timestamp */
246 : 1 : valent_notification_set_time (notification, valent_timestamp_ms ());
247 : :
248 [ - + ]: 1 : valent_notifications_adapter_notification_added (adapter, notification);
249 : 1 : }
250 : :
251 : : static void
252 : 1 : valent_fdo_notifications_method_call (GDBusConnection *connection,
253 : : const char *sender,
254 : : const char *object_path,
255 : : const char *interface_name,
256 : : const char *method_name,
257 : : GVariant *parameters,
258 : : GDBusMethodInvocation *invocation,
259 : : gpointer user_data)
260 : : {
261 : 1 : ValentNotificationsAdapter *adapter = VALENT_NOTIFICATIONS_ADAPTER (user_data);
262 : 1 : ValentFdoNotifications *self = VALENT_FDO_NOTIFICATIONS (user_data);
263 : 1 : GDBusMessage *message;
264 : 1 : const char *destination;
265 : :
266 [ - + ]: 1 : g_assert (VALENT_IS_NOTIFICATIONS_ADAPTER (adapter));
267 [ + - ]: 1 : g_assert (VALENT_IS_FDO_NOTIFICATIONS (self));
268 : :
269 : 1 : message = g_dbus_method_invocation_get_message (invocation);
270 : 1 : destination = g_dbus_message_get_destination (message);
271 : :
272 : : // TODO: accepting notifications from the well-known name causes duplicates on
273 : : // GNOME Shell where a proxy daemon is run.
274 [ - + - - ]: 1 : if (g_strcmp0 ("org.freedesktop.Notifications", destination) != 0 &&
275 : 0 : g_strcmp0 (self->name_owner, destination) != 0)
276 : 0 : goto out;
277 : :
278 [ - + ]: 1 : if (g_strcmp0 (method_name, "Notify") == 0)
279 : 1 : _notify (adapter, parameters);
280 : :
281 : 0 : out:
282 : 1 : g_object_unref (invocation);
283 : 1 : }
284 : :
285 : : static void
286 : 1 : on_notification_closed (GDBusConnection *connection,
287 : : const char *sender_name,
288 : : const char *object_path,
289 : : const char *interface_name,
290 : : const char *signal_name,
291 : : GVariant *parameters,
292 : : gpointer user_data)
293 : : {
294 : 1 : ValentNotificationsAdapter *adapter = VALENT_NOTIFICATIONS_ADAPTER (user_data);
295 : :
296 [ - + ]: 1 : g_assert (VALENT_IS_NOTIFICATIONS_ADAPTER (adapter));
297 [ + - ]: 1 : g_assert (g_strcmp0 (signal_name, "NotificationClosed") == 0);
298 : :
299 : 1 : _notification_closed (adapter, parameters);
300 : 1 : }
301 : :
302 : : /*
303 : : * Setup
304 : : */
305 : : static void
306 : 1 : on_name_appeared (GDBusConnection *connection,
307 : : const char *name,
308 : : const char *name_owner,
309 : : gpointer user_data)
310 : : {
311 : 1 : ValentFdoNotifications *self = VALENT_FDO_NOTIFICATIONS (user_data);
312 : :
313 [ - + ]: 1 : self->name_owner = g_strdup (name_owner);
314 : 1 : g_set_object (&self->session, connection);
315 : :
316 [ + - ]: 1 : if (self->closed_id == 0)
317 : : {
318 : 1 : self->closed_id =
319 : 1 : g_dbus_connection_signal_subscribe (self->session,
320 : : "org.freedesktop.Notifications",
321 : : "org.freedesktop.Notifications",
322 : : "NotificationClosed",
323 : : "/org/freedesktop/Notifications",
324 : : NULL,
325 : : G_DBUS_SIGNAL_FLAGS_NONE,
326 : : on_notification_closed,
327 : : self, NULL);
328 : : }
329 : :
330 : 1 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
331 : : VALENT_PLUGIN_STATE_ACTIVE,
332 : : NULL);
333 : 1 : }
334 : :
335 : : static void
336 : 0 : on_name_vanished (GDBusConnection *connection,
337 : : const char *name,
338 : : gpointer user_data)
339 : : {
340 : 0 : ValentFdoNotifications *self = VALENT_FDO_NOTIFICATIONS (user_data);
341 : :
342 [ # # ]: 0 : g_clear_pointer (&self->name_owner, g_free);
343 : :
344 [ # # ]: 0 : if (self->closed_id > 0)
345 : : {
346 : 0 : g_dbus_connection_signal_unsubscribe (self->session, self->closed_id);
347 : 0 : self->closed_id = 0;
348 : : }
349 : :
350 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
351 : : VALENT_PLUGIN_STATE_INACTIVE,
352 : : NULL);
353 : 0 : }
354 : :
355 : : static void
356 : 1 : become_monitor_cb (GDBusConnection *connection,
357 : : GAsyncResult *result,
358 : : gpointer user_data)
359 : : {
360 : 1 : g_autoptr (GTask) task = G_TASK (user_data);
361 : 1 : ValentFdoNotifications *self = g_task_get_source_object (task);
362 [ - - + - ]: 1 : g_autoptr (GVariant) reply = NULL;
363 [ - - ]: 1 : g_autoptr (GError) error = NULL;
364 : :
365 : 1 : reply = g_dbus_connection_call_finish (connection, result, &error);
366 : :
367 [ - + ]: 1 : if (reply == NULL)
368 : : {
369 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
370 : : VALENT_PLUGIN_STATE_ERROR,
371 : : error);
372 [ # # ]: 0 : g_clear_object (&self->monitor);
373 : 0 : g_dbus_error_strip_remote_error (error);
374 [ # # ]: 0 : return g_task_return_error (task, g_steal_pointer (&error));
375 : : }
376 : :
377 : : /* Watch the true name owner */
378 : 1 : self->name_owner_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
379 : : "org.freedesktop.Notifications",
380 : : G_BUS_NAME_WATCHER_FLAGS_NONE,
381 : : on_name_appeared,
382 : : on_name_vanished,
383 : : self, NULL);
384 : :
385 : :
386 : : /* Report the adapter as active */
387 : 1 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
388 : : VALENT_PLUGIN_STATE_ACTIVE,
389 : : NULL);
390 [ - + ]: 1 : g_task_return_boolean (task, TRUE);
391 : : }
392 : :
393 : : static void
394 : 1 : new_for_address_cb (GObject *object,
395 : : GAsyncResult *result,
396 : : gpointer user_data)
397 : : {
398 : 1 : g_autoptr (GTask) task = G_TASK (user_data);
399 : 1 : ValentFdoNotifications *self = g_task_get_source_object (task);
400 : 1 : GCancellable *cancellable = g_task_get_cancellable (task);
401 [ - - ]: 1 : g_autoptr (GError) error = NULL;
402 : :
403 : 1 : self->monitor = g_dbus_connection_new_for_address_finish (result, &error);
404 : :
405 [ - + ]: 1 : if (self->monitor == NULL)
406 : : {
407 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
408 : : VALENT_PLUGIN_STATE_ERROR,
409 : : error);
410 : 0 : g_dbus_error_strip_remote_error (error);
411 : 0 : return g_task_return_error (task, g_steal_pointer (&error));
412 : : }
413 : :
414 : : /* Export the monitor interface */
415 : 2 : self->monitor_id =
416 : 1 : g_dbus_connection_register_object (self->monitor,
417 : : "/org/freedesktop/Notifications",
418 : : self->iface_info,
419 : 1 : &self->vtable,
420 : : self, NULL,
421 : : &error);
422 : :
423 [ - + ]: 1 : if (self->monitor_id == 0)
424 : : {
425 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
426 : : VALENT_PLUGIN_STATE_ERROR,
427 : : error);
428 [ # # ]: 0 : g_clear_object (&self->monitor);
429 : 0 : g_dbus_error_strip_remote_error (error);
430 : 0 : return g_task_return_error (task, g_steal_pointer (&error));
431 : : }
432 : :
433 : : /* Become a monitor for notifications */
434 [ - + ]: 1 : g_dbus_connection_call (self->monitor,
435 : : "org.freedesktop.DBus",
436 : : "/org/freedesktop/DBus",
437 : : "org.freedesktop.DBus.Monitoring",
438 : : "BecomeMonitor",
439 : : g_variant_new ("(^asu)", interface_matches, 0),
440 : : NULL,
441 : : G_DBUS_CALL_FLAGS_NONE,
442 : : -1,
443 : : cancellable,
444 : : (GAsyncReadyCallback)become_monitor_cb,
445 : : g_steal_pointer (&task));
446 : : }
447 : :
448 : :
449 : : /*
450 : : * GAsyncInitable
451 : : */
452 : : static void
453 : 1 : valent_fdo_notifications_init_async (GAsyncInitable *initable,
454 : : int io_priority,
455 : : GCancellable *cancellable,
456 : : GAsyncReadyCallback callback,
457 : : gpointer user_data)
458 : : {
459 : 1 : g_autoptr (GTask) task = NULL;
460 [ - - ]: 1 : g_autofree char *address = NULL;
461 : 1 : g_autoptr (GError) error = NULL;
462 : :
463 [ - + ]: 1 : g_assert (VALENT_IS_FDO_NOTIFICATIONS (initable));
464 : :
465 : 1 : task = g_task_new (initable, cancellable, callback, user_data);
466 : 1 : g_task_set_priority (task, io_priority);
467 [ + - ]: 1 : g_task_set_source_tag (task, valent_fdo_notifications_init_async);
468 : :
469 : : /* Get a bus address */
470 : 1 : address = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SESSION,
471 : : cancellable,
472 : : &error);
473 : :
474 [ - + ]: 1 : if (address == NULL)
475 : : {
476 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (initable),
477 : : VALENT_PLUGIN_STATE_ERROR,
478 : : error);
479 [ # # ]: 0 : return g_task_return_error (task, g_steal_pointer (&error));
480 : : }
481 : :
482 : : /* Get a dedicated connection for monitoring */
483 [ - + ]: 1 : g_dbus_connection_new_for_address (address,
484 : : G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
485 : : G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
486 : : NULL,
487 : : cancellable,
488 : : (GAsyncReadyCallback)new_for_address_cb,
489 : : g_steal_pointer (&task));
490 : : }
491 : :
492 : : static void
493 : 2 : g_async_initable_iface_init (GAsyncInitableIface *iface)
494 : : {
495 : 2 : iface->init_async = valent_fdo_notifications_init_async;
496 : 2 : }
497 : :
498 : : /*
499 : : * ValentObject
500 : : */
501 : : static void
502 : 2 : valent_fdo_notifications_destroy (ValentObject *object)
503 : : {
504 : 2 : ValentFdoNotifications *self = VALENT_FDO_NOTIFICATIONS (object);
505 : :
506 [ + + ]: 2 : if (self->closed_id > 0)
507 : : {
508 : 1 : g_dbus_connection_signal_unsubscribe (self->session, self->closed_id);
509 : 1 : self->closed_id = 0;
510 : : }
511 : :
512 [ + + ]: 2 : if (self->name_owner_id > 0)
513 : : {
514 : 1 : g_clear_handle_id (&self->name_owner_id, g_bus_unwatch_name);
515 [ + - ]: 1 : g_clear_pointer (&self->name_owner, g_free);
516 : : }
517 : :
518 [ + + ]: 2 : if (self->monitor_id != 0)
519 : : {
520 : 1 : g_dbus_connection_unregister_object (self->monitor, self->monitor_id);
521 : 1 : self->monitor_id = 0;
522 : : }
523 : :
524 [ + + ]: 2 : g_clear_object (&self->monitor);
525 [ + + ]: 2 : g_clear_object (&self->session);
526 : :
527 : 2 : VALENT_OBJECT_CLASS (valent_fdo_notifications_parent_class)->destroy (object);
528 : 2 : }
529 : :
530 : : /*
531 : : * GObject
532 : : */
533 : : static void
534 : 1 : valent_fdo_notifications_finalize (GObject *object)
535 : : {
536 : 1 : ValentFdoNotifications *self = VALENT_FDO_NOTIFICATIONS (object);
537 : :
538 [ + - ]: 1 : g_clear_pointer (&self->node_info, g_dbus_node_info_unref);
539 : :
540 : 1 : G_OBJECT_CLASS (valent_fdo_notifications_parent_class)->finalize (object);
541 : 1 : }
542 : :
543 : : static void
544 : 2 : valent_fdo_notifications_class_init (ValentFdoNotificationsClass *klass)
545 : : {
546 : 2 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
547 : 2 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
548 : :
549 : 2 : object_class->finalize = valent_fdo_notifications_finalize;
550 : :
551 : 2 : vobject_class->destroy = valent_fdo_notifications_destroy;
552 : : }
553 : :
554 : : static void
555 : 1 : valent_fdo_notifications_init (ValentFdoNotifications *self)
556 : : {
557 : 1 : self->node_info = g_dbus_node_info_new_for_xml (interface_xml, NULL);
558 : 1 : self->iface_info = self->node_info->interfaces[0];
559 : :
560 : 1 : self->vtable.method_call = valent_fdo_notifications_method_call;
561 : 1 : self->vtable.get_property = NULL;
562 : 1 : self->vtable.set_property = NULL;
563 : 1 : }
564 : :
|