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