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