* estickies,
[framework/uifw/edbus.git] / src / lib / dbus / e_dbus_object.c
1 #include "E_DBus.h"
2 #include "e_dbus_private.h"
3 #include <Ecore_Data.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7
8 static E_DBus_Interface *introspectable_interface = NULL;
9 static E_DBus_Interface *properties_interface = NULL;
10
11 typedef struct E_DBus_Method E_DBus_Method;
12
13 Ecore_Strbuf * e_dbus_object_introspect(E_DBus_Object *obj);
14
15 static void e_dbus_object_unregister(DBusConnection *conn, void *user_data);
16 static DBusHandlerResult e_dbus_object_handler(DBusConnection *conn, DBusMessage *message, void *user_data);
17
18 static void e_dbus_interface_free(E_DBus_Interface *iface);
19
20 static E_DBus_Method *e_dbus_method_new(const char *member, const char *signature, const char *reply_signature, E_DBus_Method_Cb func);
21 static void e_dbus_object_method_free(E_DBus_Method *m);
22
23 static void _introspect_indent_append(Ecore_Strbuf *buf, int level);
24 static void _introspect_interface_append(Ecore_Strbuf *buf, E_DBus_Interface *iface, int level);
25 static void _introspect_method_append(Ecore_Strbuf *buf, E_DBus_Method *method, int level);
26 static void _introspect_arg_append(Ecore_Strbuf *buf, const char *type, const char *direction, int level);
27
28
29 //static Eina_List *standard_methods = NULL;
30
31
32 static DBusObjectPathVTable vtable = {
33   e_dbus_object_unregister,
34   e_dbus_object_handler,
35   NULL,
36   NULL,
37   NULL,
38   NULL
39 };
40
41 struct E_DBus_Object
42 {
43   E_DBus_Connection *conn;
44   char *path;
45   Eina_List *interfaces;
46   char *introspection_data;
47   int introspection_dirty;
48
49   E_DBus_Object_Property_Get_Cb cb_property_get;
50   E_DBus_Object_Property_Set_Cb cb_property_set;
51
52   void *data;
53 };
54
55 struct E_DBus_Interface
56 {
57   char *name;
58   Eina_List *methods;
59   int refcount;
60 };
61
62 struct E_DBus_Method
63 {
64   char *member;
65   char *signature;
66   char *reply_signature;
67   E_DBus_Method_Cb func;
68 };
69
70 static DBusMessage *
71 cb_introspect(E_DBus_Object *obj, DBusMessage *msg)
72 {
73   DBusMessage *ret;
74   Ecore_Strbuf *buf;
75
76   if (obj->introspection_dirty || !obj->introspection_data)
77   {
78     buf = e_dbus_object_introspect(obj);
79     if (!buf)
80     {
81       ret = dbus_message_new_error(msg, "org.enlightenment.NotIntrospectable", "This object does not provide introspection data");
82       return ret;
83     }
84
85     if (obj->introspection_data) free(obj->introspection_data);
86     obj->introspection_data = strdup(ecore_strbuf_string_get(buf));
87     ecore_strbuf_free(buf);
88   }
89   //printf("XML: \n\n%s\n\n", obj->introspection_data);
90   ret = dbus_message_new_method_return(msg);
91   dbus_message_append_args(ret, DBUS_TYPE_STRING, &(obj->introspection_data), DBUS_TYPE_INVALID);
92
93   return ret;
94 }
95
96 static DBusMessage *
97 cb_properties_get(E_DBus_Object *obj, DBusMessage *msg)
98 {
99   DBusMessage *reply;
100   DBusMessageIter iter, sub;
101   DBusError err;
102   int type;
103   void *value;
104   char *property;
105
106   dbus_error_init(&err);
107   dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID);
108
109   if (dbus_error_is_set(&err))
110   {
111     return dbus_message_new_error(msg, err.name, err.message);
112   }
113
114   obj->cb_property_get(obj, property, &type, &value);
115   if (type == DBUS_TYPE_INVALID)
116   {
117     return dbus_message_new_error_printf(msg, "org.enlightenment.DBus.InvalidProperty", "The property '%s' does not exist on this object.", property);
118   }
119
120   if (dbus_type_is_basic(type))
121   {
122     reply = dbus_message_new_method_return(msg);
123     dbus_message_iter_init_append(msg, &iter);
124     dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, e_dbus_basic_type_as_string(type), &sub);
125     dbus_message_iter_append_basic(&sub, type, &value);
126     dbus_message_iter_close_container(&iter, &sub);
127     return reply;
128   }
129   else
130   {
131     return dbus_message_new_error(msg, "org.enlightenment.DBus.UnsupportedType", "E_DBus currently only supports properties of a basic type.");
132   }
133 }
134
135 static DBusMessage *
136 cb_properties_set(E_DBus_Object *obj, DBusMessage *msg)
137 {
138   DBusMessageIter iter, sub;
139   int type;
140   void *value;
141   char *property;
142
143   dbus_message_iter_init(msg, &iter);
144   dbus_message_iter_get_basic(&iter, &property);
145   dbus_message_iter_recurse(&iter, &sub);
146   type = dbus_message_iter_get_arg_type(&sub);
147   if (dbus_type_is_basic(type))
148   {
149     dbus_message_iter_get_basic(&sub, &value);
150     if (obj->cb_property_set(obj, property, type, value))
151     {
152       return dbus_message_new_method_return(msg);
153     }
154     else
155     {
156       return dbus_message_new_error_printf(msg, "org.enlightenment.DBus.InvalidProperty", "The property '%s' does not exist on this object.", property);
157     }
158   }
159   else
160   {
161     return dbus_message_new_error(msg, "org.enlightenment.DBus.UnsupportedType", "E_DBus currently only supports properties of a basic type.");
162   }
163
164 }
165
166 int
167 e_dbus_object_init(void)
168 {
169   introspectable_interface = e_dbus_interface_new("org.freedesktop.DBus.Introspectable");
170   properties_interface = e_dbus_interface_new("org.freedesktop.DBus.Properties");
171   if (!introspectable_interface || !properties_interface)
172   {
173     if (introspectable_interface) e_dbus_interface_unref(introspectable_interface);
174     introspectable_interface = NULL;
175     if (properties_interface) e_dbus_interface_unref(properties_interface);
176     properties_interface = NULL;
177     return 0;
178   }
179
180   e_dbus_interface_method_add(introspectable_interface, "Introspect", "", "s", cb_introspect);
181   e_dbus_interface_method_add(properties_interface, "Get", "s", "v", cb_properties_get);
182   e_dbus_interface_method_add(properties_interface, "Set", "sv", "", cb_properties_set);
183   return 1;
184 }
185
186 void
187 e_dbus_object_shutdown(void)
188 {
189   e_dbus_interface_unref(introspectable_interface);
190   introspectable_interface = NULL;
191
192   e_dbus_interface_unref(properties_interface);
193   properties_interface = NULL;
194 }
195
196 /**
197  * Add a dbus object.
198  *
199  * @param conn the connection on with the object should listen
200  * @param object_path a unique string identifying an object (e.g. org/enlightenment/WindowManager
201  * @param data custom data to set on the object (retrievable via
202  *             e_dbus_object_data_get())
203  */
204 EAPI E_DBus_Object *
205 e_dbus_object_add(E_DBus_Connection *conn, const char *object_path, void *data)
206 {
207   E_DBus_Object *obj;
208
209   obj = calloc(1, sizeof(E_DBus_Object));
210   if (!obj) return NULL;
211
212   if (!dbus_connection_register_object_path(conn->conn, object_path, &vtable, obj))
213   {
214     free(obj);
215     return NULL;
216   }
217
218   obj->conn = conn;
219   e_dbus_connection_ref(conn);
220   obj->path = strdup(object_path);
221   obj->data = data;
222   obj->interfaces = NULL;
223
224   e_dbus_object_interface_attach(obj, introspectable_interface);
225
226   return obj;
227 }
228
229 /**
230  * Free a dbus object
231  *
232  * @param obj the object to free
233  */
234 EAPI void
235 e_dbus_object_free(E_DBus_Object *obj)
236 {
237   E_DBus_Interface *iface;
238
239   if (!obj) return;
240
241   DEBUG(5, "e_dbus_object_free (%s)\n", obj->path);
242   dbus_connection_unregister_object_path(obj->conn->conn, obj->path);
243   e_dbus_connection_close(obj->conn);
244
245   if (obj->path) free(obj->path);
246   while (obj->interfaces)
247     {
248        iface = eina_list_data_get(obj->interfaces);
249        e_dbus_interface_unref(iface);
250        obj->interfaces = eina_list_remove_list(obj->interfaces, obj->interfaces);
251     }
252   if (obj->introspection_data) free(obj->introspection_data);
253
254   free(obj);
255 }
256
257 /**
258  * @brief Fetch the data pointer for a dbus object
259  * @param obj the dbus object
260  */
261 EAPI void *
262 e_dbus_object_data_get(E_DBus_Object *obj)
263 {
264   return obj->data;
265 }
266
267 /**
268  * @brief Sets the callback to fetch properties from an object
269  * @param obj the object
270  * @param func the callback
271  */
272 EAPI void
273 e_dbus_object_property_get_cb_set(E_DBus_Object *obj, E_DBus_Object_Property_Get_Cb func)
274 {
275   obj->cb_property_get = func;
276 }
277
278 /**
279  * @brief Sets the callback to set properties on an object
280  * @param obj the object
281  * @param func the callback
282  */
283 EAPI void
284 e_dbus_object_property_set_cb_set(E_DBus_Object *obj, E_DBus_Object_Property_Set_Cb func)
285 {
286   obj->cb_property_set = func;
287 }
288
289 EAPI void
290 e_dbus_object_interface_attach(E_DBus_Object *obj, E_DBus_Interface *iface)
291 {
292   e_dbus_interface_ref(iface);
293   obj->interfaces = eina_list_append(obj->interfaces, iface);
294   obj->introspection_dirty = 1;
295   DEBUG(4, "e_dbus_object_interface_attach (%s, %s) ", obj->path, iface->name);
296 }
297
298 EAPI void
299 e_dbus_object_interface_detach(E_DBus_Object *obj, E_DBus_Interface *iface)
300 {
301   E_DBus_Interface *found;
302
303   DEBUG(4, "e_dbus_object_interface_detach (%s, %s) ", obj->path, iface->name);
304   found = eina_list_data_find(obj->interfaces, iface);
305   if (found == NULL) return;
306
307   obj->interfaces = eina_list_remove(obj->interfaces, iface);
308   obj->introspection_dirty = 1;
309   e_dbus_interface_unref(iface);
310 }
311
312 EAPI void
313 e_dbus_interface_ref(E_DBus_Interface *iface)
314 {
315   iface->refcount++;
316   DEBUG(4, "e_dbus_interface_ref (%s) = %d\n", iface->name, iface->refcount);
317 }
318
319 EAPI void
320 e_dbus_interface_unref(E_DBus_Interface *iface)
321 {
322   DEBUG(4, "e_dbus_interface_unref (%s) = %d\n", iface->name, iface->refcount - 1);
323   if (--(iface->refcount) == 0)
324     e_dbus_interface_free(iface);
325 }
326
327 static void
328 e_dbus_interface_free(E_DBus_Interface *iface)
329 {
330   E_DBus_Method *m;
331
332   if (iface->name) free(iface->name);
333   while (iface->methods)
334     {
335        m = eina_list_data_get(iface->methods);
336        e_dbus_object_method_free(m);
337        iface->methods = eina_list_remove_list(iface->methods, iface->methods);
338     }
339   free(iface);
340 }
341
342
343 /**
344  * Add a method to an object
345  *
346  * @param iface the E_DBus_Interface to which this method belongs
347  * @param member the name of the method
348  * @param signature  an optional message signature. if provided, then messages
349  *                   with invalid signatures will be automatically rejected 
350  *                   (an Error response will be sent) and introspection data
351  *                   will be available.
352  *
353  * @return 1 if successful, 0 if failed (e.g. no memory)
354  */
355 EAPI int
356 e_dbus_interface_method_add(E_DBus_Interface *iface, const char *member, const char *signature, const char *reply_signature, E_DBus_Method_Cb func)
357 {
358   E_DBus_Method *m;
359
360   m = e_dbus_method_new(member, signature, reply_signature, func);
361   DEBUG(4, "Add method %s: %p\n", member, m);
362   if (!m) return 0;
363
364   iface->methods = eina_list_append(iface->methods, m);
365   return 1;
366 }
367
368 EAPI E_DBus_Interface *
369 e_dbus_interface_new(const char *interface)
370 {
371   E_DBus_Interface *iface;
372
373   if (!interface) return NULL;
374
375   iface = calloc(1, sizeof(E_DBus_Interface));
376   if (!iface) return NULL;
377
378   iface->refcount = 1;
379   iface->name = strdup(interface);
380   iface->methods = NULL;
381
382   return iface;
383 }
384
385 static E_DBus_Method *
386 e_dbus_method_new(const char *member, const char *signature, const char *reply_signature, E_DBus_Method_Cb func)
387 {
388   E_DBus_Method *m;
389
390   if (!member || !func) return NULL;
391
392   if (signature && !dbus_signature_validate(signature, NULL)) return NULL;
393   if (reply_signature && !dbus_signature_validate(reply_signature, NULL)) return NULL;
394   m = calloc(1, sizeof(E_DBus_Method));
395   if (!m) return NULL;
396
397   m->member = strdup(member);
398   if (signature)
399     m->signature = strdup(signature);
400   if (reply_signature)
401     m->reply_signature = strdup(reply_signature);
402   m->func = func;
403
404   return m;
405 }
406
407 static void
408 e_dbus_object_method_free(E_DBus_Method *m)
409 {
410   if (!m) return;
411   if (m->member) free(m->member);
412   if (m->signature) free(m->signature);
413   if (m->reply_signature) free(m->reply_signature);
414
415   free(m);
416 }
417
418 static E_DBus_Method *
419 e_dbus_object_method_find(E_DBus_Object *obj, const char *interface, const char *member)
420 {
421   E_DBus_Method *m;
422   E_DBus_Interface *iface;
423   Eina_List *l, *ll;
424
425   if (!obj || !member) return NULL;
426
427   EINA_LIST_FOREACH(obj->interfaces, l, iface)
428   {
429     if (strcmp(interface, iface->name)) continue;
430     EINA_LIST_FOREACH(iface->methods, ll, m)
431     {
432       if (!strcmp(member, m->member))
433         return m;
434     }
435   }
436   return NULL;
437 }
438
439 static DBusHandlerResult
440 e_dbus_object_handler(DBusConnection *conn, DBusMessage *message, void *user_data) 
441 {
442   E_DBus_Object *obj;
443   E_DBus_Method *m;
444   DBusMessage *reply;
445   dbus_uint32_t serial;
446
447   obj = user_data;
448   if (!obj)
449     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
450
451   m = e_dbus_object_method_find(obj, dbus_message_get_interface(message), dbus_message_get_member(message));
452
453   /* XXX should this send an 'invalid method' error instead? */
454   if (!m) 
455     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
456
457   if (m->signature && !dbus_message_has_signature(message, m->signature))
458     reply = dbus_message_new_error_printf(message, "org.enlightenment.InvalidSignature", "Expected signature: %s", m->signature);
459   else
460     reply = m->func(obj, message);
461
462   dbus_connection_send(conn, reply, &serial);
463   dbus_message_unref(reply);
464
465   return DBUS_HANDLER_RESULT_HANDLED;
466 }
467
468 static void
469 e_dbus_object_unregister(DBusConnection *conn, void *user_data)
470 {
471   /* free up the object struct? */
472 }
473
474 Ecore_Strbuf *
475 e_dbus_object_introspect(E_DBus_Object *obj)
476 {
477   Ecore_Strbuf *buf;
478   int level = 0;
479   E_DBus_Interface *iface;
480   Eina_List *l;
481
482   buf = ecore_strbuf_new();
483
484   /* Doctype */
485   ecore_strbuf_append(buf, "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n");
486
487   ecore_strbuf_append(buf, "<node name=\"");
488   ecore_strbuf_append(buf, obj->path);
489   ecore_strbuf_append(buf, "\">\n");
490   level++;
491
492   EINA_LIST_FOREACH(obj->interfaces, l, iface)
493     _introspect_interface_append(buf, iface, level);
494
495   ecore_strbuf_append(buf, "</node>\n");
496   return buf;
497 }
498
499 static void
500 _introspect_indent_append(Ecore_Strbuf *buf, int level)
501 {
502   /* XXX optimize this? */
503   int i = level * 2;
504   while (i-- > 0)
505     ecore_strbuf_append_char(buf, ' ');
506 }
507 static void
508 _introspect_interface_append(Ecore_Strbuf *buf, E_DBus_Interface *iface, int level)
509 {
510   E_DBus_Method *method;
511   Eina_List *l;
512
513   _introspect_indent_append(buf, level);
514   ecore_strbuf_append(buf, "<interface name=\"");
515   ecore_strbuf_append(buf, iface->name);
516   ecore_strbuf_append(buf, "\">\n");
517   level++;
518
519   DEBUG(4, "introspect iface: %s\n", iface->name);
520   EINA_LIST_FOREACH(iface->methods, l, method)
521     _introspect_method_append(buf, method, level);
522
523   level--;
524   _introspect_indent_append(buf, level);
525   ecore_strbuf_append(buf, "</interface>\n");
526 }
527 static void
528 _introspect_method_append(Ecore_Strbuf *buf, E_DBus_Method *method, int level)
529 {
530   DBusSignatureIter iter;
531   char *type;
532
533   _introspect_indent_append(buf, level);
534   DEBUG(4, "introspect method: %s\n", method->member);
535   ecore_strbuf_append(buf, "<method name=\"");
536   ecore_strbuf_append(buf, method->member);
537   ecore_strbuf_append(buf, "\">\n");
538   level++;
539
540   /* append args */
541   if (method->signature && 
542       method->signature[0] &&
543       dbus_signature_validate(method->signature, NULL))
544   {
545     dbus_signature_iter_init(&iter, method->signature);
546     while ((type = dbus_signature_iter_get_signature(&iter)))
547     {
548       _introspect_arg_append(buf, type, "in", level);
549
550       dbus_free(type);
551       if (!dbus_signature_iter_next(&iter)) break;
552     }
553   }
554
555   /* append reply args */
556   if (method->reply_signature &&
557       method->reply_signature[0] &&
558       dbus_signature_validate(method->reply_signature, NULL))
559   {
560     dbus_signature_iter_init(&iter, method->reply_signature);
561     while ((type = dbus_signature_iter_get_signature(&iter)))
562     {
563       _introspect_arg_append(buf, type, "out", level);
564
565       dbus_free(type);
566       if (!dbus_signature_iter_next(&iter)) break;
567     }
568   }
569
570   level--;
571   _introspect_indent_append(buf, level);
572   ecore_strbuf_append(buf, "</method>\n");
573 }
574
575 static void
576 _introspect_arg_append(Ecore_Strbuf *buf, const char *type, const char *direction, int level)
577 {
578   _introspect_indent_append(buf, level);
579   ecore_strbuf_append(buf, "<arg type=\"");
580   ecore_strbuf_append(buf, type);
581   ecore_strbuf_append(buf, "\" direction=\"");
582   ecore_strbuf_append(buf, direction);
583   ecore_strbuf_append(buf, "\"/>\n");
584 }
585