Branch data Line data Source code
1 : : /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 : : /*
3 : : * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
4 : : * SPDX-FileCopyrightText: 2011 Giovanni Campagna
5 : : */
6 : :
7 : : #include <config.h>
8 : :
9 : : #include <stdbool.h>
10 : : #include <string.h> // for strcmp
11 : :
12 : : #include <gio/gio.h>
13 : : #include <glib-object.h>
14 : : #include <glib.h>
15 : :
16 : : #include "libgjs-private/gjs-gdbus-wrapper.h"
17 : :
18 : : enum {
19 : : PROP_0,
20 : : PROP_G_INTERFACE_INFO,
21 : : PROP_LAST
22 : : };
23 : :
24 : : enum {
25 : : SIGNAL_HANDLE_METHOD,
26 : : SIGNAL_HANDLE_PROPERTY_GET,
27 : : SIGNAL_HANDLE_PROPERTY_SET,
28 : : SIGNAL_LAST,
29 : : };
30 : :
31 : : static unsigned signals[SIGNAL_LAST];
32 : :
33 : : struct _GjsDBusImplementationPrivate {
34 : : GDBusInterfaceVTable vtable;
35 : : GDBusInterfaceInfo *ifaceinfo;
36 : :
37 : : // from char* to GVariant*
38 : : GHashTable *outstanding_properties;
39 : : unsigned idle_id;
40 : : };
41 : :
42 [ + + + - : 218 : G_DEFINE_TYPE_WITH_PRIVATE(GjsDBusImplementation, gjs_dbus_implementation,
+ + ]
43 : : G_TYPE_DBUS_INTERFACE_SKELETON);
44 : :
45 : 3 : static inline GVariant* _g_variant_ref_sink0(void* value) {
46 [ + + ]: 3 : if (value)
47 : 2 : g_variant_ref_sink(value);
48 : :
49 : 3 : return value;
50 : : }
51 : :
52 : 3 : static inline void _g_variant_unref0(void* value) {
53 [ + + ]: 3 : if (value)
54 : 2 : g_variant_unref(value);
55 : 3 : }
56 : :
57 : 72 : static bool gjs_dbus_implementation_check_interface(GjsDBusImplementation* self,
58 : : GDBusConnection* connection,
59 : : const char* object_path,
60 : : const char* interface_name,
61 : : GError** error) {
62 : : const char* exported_object_path;
63 : :
64 [ - + ]: 72 : if (!g_dbus_interface_skeleton_has_connection(
65 : 72 : G_DBUS_INTERFACE_SKELETON(self), connection)) {
66 : 0 : g_set_error_literal(error, G_DBUS_ERROR, G_DBUS_ERROR_DISCONNECTED,
67 : : "Wrong connection");
68 : 0 : return false;
69 : : }
70 : 72 : exported_object_path = g_dbus_interface_skeleton_get_object_path(
71 : 72 : G_DBUS_INTERFACE_SKELETON(self));
72 [ + - - + ]: 72 : if (!exported_object_path || strcmp(object_path, exported_object_path)) {
73 [ # # ]: 0 : g_set_error(
74 : : error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_OBJECT,
75 : : "Wrong object path %s for %s", object_path,
76 : : exported_object_path ? exported_object_path : "unexported object");
77 : 0 : return false;
78 : : }
79 [ - + ]: 72 : if (strcmp(interface_name, self->priv->ifaceinfo->name) != 0) {
80 : 0 : g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_INTERFACE,
81 : : "Unknown interface %s on %s", interface_name,
82 : 0 : self->priv->ifaceinfo->name);
83 : 0 : return false;
84 : : }
85 : 72 : return true;
86 : : }
87 : :
88 : 11 : static bool gjs_dbus_implementation_check_property(GjsDBusImplementation* self,
89 : : const char* interface_name,
90 : : const char* property_name,
91 : : GError** error) {
92 [ - + ]: 11 : if (!g_dbus_interface_info_lookup_property(self->priv->ifaceinfo,
93 : : property_name)) {
94 : 0 : g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY,
95 : : "Unknown property %s on %s", property_name, interface_name);
96 : 0 : return false;
97 : : }
98 : 11 : return true;
99 : : }
100 : :
101 : 61 : static void gjs_dbus_implementation_method_call(
102 : : GDBusConnection* connection, const char* sender G_GNUC_UNUSED,
103 : : const char* object_path, const char* interface_name,
104 : : const char* method_name, GVariant* parameters,
105 : : GDBusMethodInvocation* invocation, void* user_data) {
106 : 61 : GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION (user_data);
107 : 61 : GError* error = NULL;
108 : :
109 [ - + ]: 61 : if (!gjs_dbus_implementation_check_interface(self, connection, object_path,
110 : : interface_name, &error)) {
111 : 0 : g_dbus_method_invocation_take_error(g_steal_pointer(&invocation),
112 : : error);
113 : 0 : return;
114 : : }
115 [ + - - + ]: 122 : if (!G_UNLIKELY(g_dbus_method_invocation_get_method_info(invocation)) ||
116 : 61 : !g_dbus_interface_info_lookup_method(self->priv->ifaceinfo,
117 : : method_name)) {
118 : 0 : g_dbus_method_invocation_return_error(
119 : 0 : g_steal_pointer(&invocation), G_DBUS_ERROR,
120 : : G_DBUS_ERROR_UNKNOWN_METHOD, "Unknown method %s on %s", method_name,
121 : : interface_name);
122 : 0 : return;
123 : : }
124 : :
125 : 61 : g_signal_emit(self, signals[SIGNAL_HANDLE_METHOD], 0, method_name,
126 : 61 : g_steal_pointer(&invocation), parameters);
127 : : }
128 : :
129 : 9 : static GVariant* gjs_dbus_implementation_property_get(
130 : : GDBusConnection* connection, const char* sender G_GNUC_UNUSED,
131 : : const char* object_path, const char* interface_name,
132 : : const char* property_name, GError** error, void* user_data) {
133 : 9 : GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION (user_data);
134 : : GVariant *value;
135 : :
136 [ + - ]: 9 : if (!gjs_dbus_implementation_check_interface(self, connection, object_path,
137 : 9 : interface_name, error) ||
138 [ - + ]: 9 : !gjs_dbus_implementation_check_property(self, interface_name,
139 : : property_name, error))
140 : 0 : return NULL;
141 : :
142 : 9 : g_signal_emit(self, signals[SIGNAL_HANDLE_PROPERTY_GET], 0, property_name, &value);
143 : :
144 : : /* Marshaling GErrors is not supported, so this is the best we can do
145 : : (GIO will assert if value is NULL and error is not set) */
146 [ - + ]: 9 : if (!value)
147 : 0 : g_set_error(error, g_quark_from_static_string("gjs-error-domain"), 0, "Property retrieval failed");
148 : :
149 : 9 : return value;
150 : : }
151 : :
152 : 2 : static gboolean gjs_dbus_implementation_property_set(
153 : : GDBusConnection* connection, const char* sender G_GNUC_UNUSED,
154 : : const char* object_path, const char* interface_name,
155 : : const char* property_name, GVariant* value, GError** error,
156 : : void* user_data) {
157 : 2 : GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION (user_data);
158 : :
159 [ + - ]: 2 : if (!gjs_dbus_implementation_check_interface(self, connection, object_path,
160 : 2 : interface_name, error) ||
161 [ - + ]: 2 : !gjs_dbus_implementation_check_property(self, interface_name,
162 : : property_name, error))
163 : 0 : return FALSE;
164 : :
165 : 2 : g_signal_emit(self, signals[SIGNAL_HANDLE_PROPERTY_SET], 0, property_name, value);
166 : :
167 : 2 : return TRUE;
168 : : }
169 : :
170 : : static void
171 : 1 : gjs_dbus_implementation_init(GjsDBusImplementation *self) {
172 : : GjsDBusImplementationPrivate* priv =
173 : 1 : gjs_dbus_implementation_get_instance_private(self);
174 : :
175 : 1 : self->priv = priv;
176 : :
177 : 1 : priv->vtable.method_call = gjs_dbus_implementation_method_call;
178 : 1 : priv->vtable.get_property = gjs_dbus_implementation_property_get;
179 : 1 : priv->vtable.set_property = gjs_dbus_implementation_property_set;
180 : :
181 : 1 : priv->outstanding_properties = g_hash_table_new_full(g_str_hash,
182 : : g_str_equal,
183 : : g_free,
184 : : _g_variant_unref0);
185 : 1 : }
186 : :
187 : 1 : static void gjs_dbus_implementation_dispose(GObject* object) {
188 : 1 : GjsDBusImplementation* self = GJS_DBUS_IMPLEMENTATION(object);
189 : :
190 [ - + ]: 1 : g_clear_handle_id(&self->priv->idle_id, g_source_remove);
191 : :
192 : 1 : G_OBJECT_CLASS(gjs_dbus_implementation_parent_class)->dispose(object);
193 : 1 : }
194 : :
195 : : static void
196 : 1 : gjs_dbus_implementation_finalize(GObject *object) {
197 : 1 : GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION (object);
198 : :
199 : 1 : g_dbus_interface_info_unref (self->priv->ifaceinfo);
200 : 1 : g_hash_table_destroy(self->priv->outstanding_properties);
201 : :
202 : 1 : G_OBJECT_CLASS(gjs_dbus_implementation_parent_class)->finalize(object);
203 : 1 : }
204 : :
205 : 1 : static void gjs_dbus_implementation_set_property(GObject* object,
206 : : unsigned property_id,
207 : : const GValue* value,
208 : : GParamSpec* pspec) {
209 : 1 : GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION (object);
210 : :
211 [ + - ]: 1 : switch (property_id) {
212 : 1 : case PROP_G_INTERFACE_INFO:
213 : 1 : self->priv->ifaceinfo = (GDBusInterfaceInfo*) g_value_dup_boxed (value);
214 : 1 : break;
215 : 0 : default:
216 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
217 : : }
218 : 1 : }
219 : :
220 : : static GDBusInterfaceInfo *
221 : 1 : gjs_dbus_implementation_get_info (GDBusInterfaceSkeleton *skeleton) {
222 : 1 : GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION (skeleton);
223 : :
224 : 1 : return self->priv->ifaceinfo;
225 : : }
226 : :
227 : : static GDBusInterfaceVTable *
228 : 62 : gjs_dbus_implementation_get_vtable (GDBusInterfaceSkeleton *skeleton) {
229 : 62 : GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION (skeleton);
230 : :
231 : 62 : return &(self->priv->vtable);
232 : : }
233 : :
234 : : static GVariant *
235 : 0 : gjs_dbus_implementation_get_properties (GDBusInterfaceSkeleton *skeleton) {
236 : 0 : GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION (skeleton);
237 : :
238 : 0 : GDBusInterfaceInfo *info = self->priv->ifaceinfo;
239 : : GDBusPropertyInfo **props;
240 : : GVariantBuilder builder;
241 : :
242 : 0 : g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
243 : :
244 [ # # ]: 0 : for (props = info->properties; *props; ++props) {
245 : 0 : GDBusPropertyInfo *prop = *props;
246 : : GVariant *value;
247 : :
248 : : /* If we have a cached value, we use that instead of querying again */
249 [ # # ]: 0 : if ((value = (GVariant*) g_hash_table_lookup(self->priv->outstanding_properties, prop->name))) {
250 : 0 : g_variant_builder_add(&builder, "{sv}", prop->name, value);
251 : 0 : continue;
252 : : }
253 : :
254 : 0 : g_signal_emit(self, signals[SIGNAL_HANDLE_PROPERTY_GET], 0, prop->name, &value);
255 : 0 : g_variant_builder_add(&builder, "{sv}", prop->name, value);
256 : : }
257 : :
258 : 0 : return g_variant_builder_end(&builder);
259 : : }
260 : :
261 : : static void
262 : 1 : gjs_dbus_implementation_flush (GDBusInterfaceSkeleton *skeleton) {
263 : 1 : GjsDBusImplementation *self = GJS_DBUS_IMPLEMENTATION(skeleton);
264 : :
265 : : GVariantBuilder changed_props;
266 : : GVariantBuilder invalidated_props;
267 : : GHashTableIter iter;
268 : : GVariant *val;
269 : :
270 : 1 : g_variant_builder_init(&changed_props, G_VARIANT_TYPE_VARDICT);
271 : 1 : g_variant_builder_init(&invalidated_props, G_VARIANT_TYPE_STRING_ARRAY);
272 : :
273 : : char* prop_name;
274 : 1 : g_hash_table_iter_init(&iter, self->priv->outstanding_properties);
275 [ + + ]: 2 : while (g_hash_table_iter_next(&iter, (void**) &prop_name, (void**) &val)) {
276 [ - + ]: 1 : if (val)
277 : 0 : g_variant_builder_add(&changed_props, "{sv}", prop_name, val);
278 : : else
279 : 1 : g_variant_builder_add(&invalidated_props, "s", prop_name);
280 : : }
281 : :
282 : 1 : GList *connections = g_dbus_interface_skeleton_get_connections(skeleton);
283 : 1 : const char *object_path = g_dbus_interface_skeleton_get_object_path(skeleton);
284 : 1 : GVariant *properties = g_variant_new("(s@a{sv}@as)",
285 : 1 : self->priv->ifaceinfo->name,
286 : : g_variant_builder_end(&changed_props),
287 : : g_variant_builder_end(&invalidated_props));
288 : 1 : g_variant_ref_sink(properties);
289 : :
290 [ + + ]: 2 : for (const GList *iter = connections; iter; iter = iter->next) {
291 : 1 : g_dbus_connection_emit_signal(G_DBUS_CONNECTION(iter->data),
292 : : NULL, /* bus name */
293 : : object_path,
294 : : "org.freedesktop.DBus.Properties",
295 : : "PropertiesChanged",
296 : : properties,
297 : : NULL /* error */);
298 : :
299 : 1 : g_object_unref(iter->data);
300 : : }
301 : 1 : g_variant_unref(properties);
302 : 1 : g_list_free(connections);
303 : :
304 : 1 : g_hash_table_remove_all(self->priv->outstanding_properties);
305 [ + - ]: 1 : g_clear_handle_id(&self->priv->idle_id, g_source_remove);
306 : 1 : }
307 : :
308 : : void
309 : 24 : gjs_dbus_implementation_class_init(GjsDBusImplementationClass *klass) {
310 : 24 : GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
311 : 24 : GDBusInterfaceSkeletonClass *skeleton_class = G_DBUS_INTERFACE_SKELETON_CLASS(klass);
312 : :
313 : 24 : gobject_class->dispose = gjs_dbus_implementation_dispose;
314 : 24 : gobject_class->finalize = gjs_dbus_implementation_finalize;
315 : 24 : gobject_class->set_property = gjs_dbus_implementation_set_property;
316 : :
317 : 24 : skeleton_class->get_info = gjs_dbus_implementation_get_info;
318 : 24 : skeleton_class->get_vtable = gjs_dbus_implementation_get_vtable;
319 : 24 : skeleton_class->get_properties = gjs_dbus_implementation_get_properties;
320 : 24 : skeleton_class->flush = gjs_dbus_implementation_flush;
321 : :
322 : 24 : g_object_class_install_property(gobject_class, PROP_G_INTERFACE_INFO,
323 : : g_param_spec_boxed("g-interface-info",
324 : : "Interface Info",
325 : : "A DBusInterfaceInfo representing the exported object",
326 : : G_TYPE_DBUS_INTERFACE_INFO,
327 : : (GParamFlags) (G_PARAM_STATIC_STRINGS | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)));
328 : :
329 : : /**
330 : : * GjsDBusImplementation::handle-method-call:
331 : : * @self:
332 : : * @method_name:
333 : : * @invocation: (transfer full):
334 : : * @parameters:
335 : : */
336 : 24 : signals[SIGNAL_HANDLE_METHOD] = g_signal_new(
337 : : "handle-method-call", G_TYPE_FROM_CLASS(klass),
338 : : (GSignalFlags)0, /* flags */
339 : : 0, /* closure */
340 : : NULL, /* accumulator */
341 : : NULL, /* accumulator data */
342 : : NULL, /* C marshal */
343 : : G_TYPE_NONE, 3, G_TYPE_STRING, /* method name */
344 : : G_TYPE_DBUS_METHOD_INVOCATION, G_TYPE_VARIANT /* parameters */);
345 : :
346 : : /**
347 : : * GjsDBusImplementation::handle-property-get:
348 : : * @self:
349 : : * @property_name:
350 : : *
351 : : * Return: The property value
352 : : */
353 : 24 : signals[SIGNAL_HANDLE_PROPERTY_GET] = g_signal_new(
354 : : "handle-property-get", G_TYPE_FROM_CLASS(klass),
355 : : (GSignalFlags)0, /* flags */
356 : : 0, /* closure */
357 : : g_signal_accumulator_first_wins, NULL, /* accumulator data */
358 : : NULL, /* C marshal */
359 : : G_TYPE_VARIANT, 1, G_TYPE_STRING /* property name */);
360 : :
361 : : /**
362 : : * GjsDBusImplementation::handle-property-set:
363 : : * @self:
364 : : * @property_name:
365 : : * @value:
366 : : */
367 : 24 : signals[SIGNAL_HANDLE_PROPERTY_SET] =
368 : 24 : g_signal_new("handle-property-set", G_TYPE_FROM_CLASS(klass),
369 : : (GSignalFlags)0, /* flags */
370 : : 0, /* closure */
371 : : NULL, /* accumulator */
372 : : NULL, /* accumulator data */
373 : : NULL, /* C marshal */
374 : : G_TYPE_NONE, 2, G_TYPE_STRING, /* property name */
375 : : G_TYPE_VARIANT /* parameters */);
376 : 24 : }
377 : :
378 : 1 : static gboolean idle_cb(void* data) {
379 : 1 : GDBusInterfaceSkeleton *skeleton = G_DBUS_INTERFACE_SKELETON (data);
380 : :
381 : 1 : g_dbus_interface_skeleton_flush(skeleton);
382 : 1 : return G_SOURCE_REMOVE;
383 : : }
384 : :
385 : : /**
386 : : * gjs_dbus_implementation_emit_property_changed:
387 : : * @self: a #GjsDBusImplementation
388 : : * @property: the name of the property that changed
389 : : * @newvalue: (allow-none): the new value, or %NULL to just invalidate it
390 : : *
391 : : * Queue a PropertyChanged signal for emission, or update the one queued
392 : : * adding @property
393 : : */
394 : 1 : void gjs_dbus_implementation_emit_property_changed(GjsDBusImplementation* self,
395 : : char* property,
396 : : GVariant* newvalue) {
397 : 1 : g_hash_table_replace(self->priv->outstanding_properties,
398 : 1 : g_strdup(property),
399 : 1 : _g_variant_ref_sink0(newvalue));
400 : :
401 [ + - ]: 1 : if (!self->priv->idle_id)
402 : 1 : self->priv->idle_id = g_idle_add(idle_cb, self);
403 : 1 : }
404 : :
405 : : /**
406 : : * gjs_dbus_implementation_emit_signal:
407 : : * @self: a #GjsDBusImplementation
408 : : * @signal_name: the name of the signal
409 : : * @parameters: (allow-none): signal parameters, or %NULL for none
410 : : *
411 : : * Emits a signal named @signal_name from the object and interface represented
412 : : * by @self. This signal has no destination.
413 : : */
414 : 2 : void gjs_dbus_implementation_emit_signal(GjsDBusImplementation* self,
415 : : char* signal_name,
416 : : GVariant* parameters) {
417 : 2 : GDBusInterfaceSkeleton *skeleton = G_DBUS_INTERFACE_SKELETON(self);
418 : 2 : GList *connections = g_dbus_interface_skeleton_get_connections(skeleton);
419 : 2 : const char *object_path = g_dbus_interface_skeleton_get_object_path(skeleton);
420 : :
421 : 2 : _g_variant_ref_sink0(parameters);
422 : :
423 [ + + ]: 4 : for (const GList *iter = connections; iter; iter = iter->next) {
424 : 2 : g_dbus_connection_emit_signal(G_DBUS_CONNECTION(iter->data),
425 : : NULL,
426 : : object_path,
427 : 2 : self->priv->ifaceinfo->name,
428 : : signal_name,
429 : : parameters,
430 : : NULL);
431 : :
432 : 2 : g_object_unref(iter->data);
433 : : }
434 : 2 : _g_variant_unref0(parameters);
435 : :
436 : 2 : g_list_free(connections);
437 : 2 : }
438 : :
439 : : /**
440 : : * gjs_dbus_implementation_unexport:
441 : : * @self: a #GjsDBusImplementation
442 : : *
443 : : * Stops exporting @self on all connections it is exported on.
444 : : *
445 : : * To unexport @self from only a single connection, use
446 : : * gjs_dbus_implementation_skeleton_unexport_from_connection()
447 : : */
448 : : void
449 : 0 : gjs_dbus_implementation_unexport(GjsDBusImplementation *self) {
450 : 0 : GDBusInterfaceSkeleton *skeleton = G_DBUS_INTERFACE_SKELETON(self);
451 : :
452 : 0 : g_hash_table_remove_all(self->priv->outstanding_properties);
453 [ # # ]: 0 : g_clear_handle_id(&self->priv->idle_id, g_source_remove);
454 : :
455 : 0 : g_dbus_interface_skeleton_unexport(skeleton);
456 : 0 : }
457 : :
458 : : /**
459 : : * gjs_dbus_implementation_unexport_from_connection:
460 : : * @self: a #GjsDBusImplementation
461 : : * @connection: a #GDBusConnection
462 : : *
463 : : * Stops exporting @self on @connection.
464 : : *
465 : : * To stop exporting on all connections the interface is exported on,
466 : : * use gjs_dbus_implementation_unexport().
467 : : */
468 : : void
469 : 0 : gjs_dbus_implementation_unexport_from_connection(GjsDBusImplementation *self,
470 : : GDBusConnection *connection) {
471 : 0 : GDBusInterfaceSkeleton *skeleton = G_DBUS_INTERFACE_SKELETON(self);
472 : 0 : GList *connections = g_dbus_interface_skeleton_get_connections(skeleton);
473 : :
474 [ # # ]: 0 : if (g_list_length(connections) <= 1) {
475 : 0 : g_hash_table_remove_all(self->priv->outstanding_properties);
476 [ # # ]: 0 : g_clear_handle_id(&self->priv->idle_id, g_source_remove);
477 : : }
478 : :
479 : 0 : g_list_free_full(connections, g_object_unref);
480 : :
481 : 0 : g_dbus_interface_skeleton_unexport_from_connection(skeleton, connection);
482 : 0 : }
|