move around - flatter.
[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 Ecore_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   Ecore_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   Ecore_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 = ecore_list_new();
223   ecore_list_free_cb_set(obj->interfaces, (Ecore_Free_Cb)e_dbus_interface_unref);
224
225   e_dbus_object_interface_attach(obj, introspectable_interface);
226
227   return obj;
228 }
229
230 /**
231  * Free a dbus object
232  *
233  * @param obj the object to free
234  */
235 EAPI void
236 e_dbus_object_free(E_DBus_Object *obj)
237 {
238   if (!obj) return;
239
240   DEBUG(5, "e_dbus_object_free (%s)\n", obj->path);
241   dbus_connection_unregister_object_path(obj->conn->conn, obj->path);
242   e_dbus_connection_close(obj->conn);
243
244   if (obj->path) free(obj->path);
245   ecore_list_destroy(obj->interfaces);
246   if (obj->introspection_data) free(obj->introspection_data);
247
248   free(obj);
249 }
250
251 /**
252  * @brief Fetch the data pointer for a dbus object
253  * @param obj the dbus object
254  */
255 EAPI void *
256 e_dbus_object_data_get(E_DBus_Object *obj)
257 {
258   return obj->data;
259 }
260
261 /**
262  * @brief Sets the callback to fetch properties from an object
263  * @param obj the object
264  * @param func the callback
265  */
266 EAPI void
267 e_dbus_object_property_get_cb_set(E_DBus_Object *obj, E_DBus_Object_Property_Get_Cb func)
268 {
269   obj->cb_property_get = func;
270 }
271
272 /**
273  * @brief Sets the callback to set properties on an object
274  * @param obj the object
275  * @param func the callback
276  */
277 EAPI void
278 e_dbus_object_property_set_cb_set(E_DBus_Object *obj, E_DBus_Object_Property_Set_Cb func)
279 {
280   obj->cb_property_set = func;
281 }
282
283 EAPI void
284 e_dbus_object_interface_attach(E_DBus_Object *obj, E_DBus_Interface *iface)
285 {
286   e_dbus_interface_ref(iface);
287   ecore_list_append(obj->interfaces, iface);
288   obj->introspection_dirty = 1;
289   DEBUG(4, "e_dbus_object_interface_attach (%s, %s) ", obj->path, iface->name);
290 }
291
292 EAPI void
293 e_dbus_object_interface_detach(E_DBus_Object *obj, E_DBus_Interface *iface)
294 {
295   E_DBus_Interface *found;
296
297   DEBUG(4, "e_dbus_object_interface_detach (%s, %s) ", obj->path, iface->name);
298   found = ecore_list_goto(obj->interfaces, iface);
299   if (found == NULL) return;
300
301   ecore_list_remove(obj->interfaces);
302   obj->introspection_dirty = 1;
303   e_dbus_interface_unref(iface);
304 }
305
306 EAPI void
307 e_dbus_interface_ref(E_DBus_Interface *iface)
308 {
309   iface->refcount++;
310   DEBUG(4, "e_dbus_interface_ref (%s) = %d\n", iface->name, iface->refcount);
311 }
312
313 EAPI void
314 e_dbus_interface_unref(E_DBus_Interface *iface)
315 {
316   DEBUG(4, "e_dbus_interface_unref (%s) = %d\n", iface->name, iface->refcount - 1);
317   if (--(iface->refcount) == 0)
318     e_dbus_interface_free(iface);
319 }
320
321 static void
322 e_dbus_interface_free(E_DBus_Interface *iface)
323 {
324   if (iface->name) free(iface->name);
325   if (iface->methods) ecore_list_destroy(iface->methods);
326   free(iface);
327 }
328
329
330 /**
331  * Add a method to an object
332  *
333  * @param iface the E_DBus_Interface to which this method belongs
334  * @param member the name of the method
335  * @param signature  an optional message signature. if provided, then messages
336  *                   with invalid signatures will be automatically rejected 
337  *                   (an Error response will be sent) and introspection data
338  *                   will be available.
339  *
340  * @return 1 if successful, 0 if failed (e.g. no memory)
341  */
342 EAPI int
343 e_dbus_interface_method_add(E_DBus_Interface *iface, const char *member, const char *signature, const char *reply_signature, E_DBus_Method_Cb func)
344 {
345   E_DBus_Method *m;
346
347   m = e_dbus_method_new(member, signature, reply_signature, func);
348   DEBUG(4, "Add method %s: %p\n", member, m);
349   if (!m) return 0;
350
351   ecore_list_append(iface->methods, m);
352   return 1;
353 }
354
355 EAPI E_DBus_Interface *
356 e_dbus_interface_new(const char *interface)
357 {
358   E_DBus_Interface *iface;
359
360   if (!interface) return NULL;
361
362   iface = calloc(1, sizeof(E_DBus_Interface));
363   if (!iface) return NULL;
364
365   iface->refcount = 1;
366   iface->name = strdup(interface);
367   iface->methods = ecore_list_new();
368   ecore_list_free_cb_set(iface->methods, (Ecore_Free_Cb)e_dbus_object_method_free);
369
370   return iface;
371 }
372
373 static E_DBus_Method *
374 e_dbus_method_new(const char *member, const char *signature, const char *reply_signature, E_DBus_Method_Cb func)
375 {
376   E_DBus_Method *m;
377
378   if (!member || !func) return NULL;
379
380   if (signature && !dbus_signature_validate(signature, NULL)) return NULL;
381   if (reply_signature && !dbus_signature_validate(reply_signature, NULL)) return NULL;
382   m = calloc(1, sizeof(E_DBus_Method));
383   if (!m) return NULL;
384
385   m->member = strdup(member);
386   if (signature)
387     m->signature = strdup(signature);
388   if (reply_signature)
389     m->reply_signature = strdup(reply_signature);
390   m->func = func;
391
392   return m;
393 }
394
395 static void
396 e_dbus_object_method_free(E_DBus_Method *m)
397 {
398   if (!m) return;
399   if (m->member) free(m->member);
400   if (m->signature) free(m->signature);
401   if (m->reply_signature) free(m->reply_signature);
402
403   free(m);
404 }
405
406 static E_DBus_Method *
407 e_dbus_object_method_find(E_DBus_Object *obj, const char *interface, const char *member)
408 {
409   E_DBus_Method *m;
410   E_DBus_Interface *iface;
411   if (!obj || !member) return NULL;
412
413   ecore_list_first_goto(obj->interfaces);
414   while ((iface = ecore_list_next(obj->interfaces)))
415   {
416     if (strcmp(interface, iface->name)) continue;
417     ecore_list_first_goto(iface->methods);
418     while ((m = ecore_list_next(iface->methods)))
419     {
420       if (!strcmp(member, m->member))
421         return m;
422     }
423   }
424   return NULL;
425 }
426
427 static DBusHandlerResult
428 e_dbus_object_handler(DBusConnection *conn, DBusMessage *message, void *user_data) 
429 {
430   E_DBus_Object *obj;
431   E_DBus_Method *m;
432   DBusMessage *reply;
433   dbus_uint32_t serial;
434
435   obj = user_data;
436   if (!obj)
437     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
438
439   m = e_dbus_object_method_find(obj, dbus_message_get_interface(message), dbus_message_get_member(message));
440
441   /* XXX should this send an 'invalid method' error instead? */
442   if (!m) 
443     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
444
445   if (m->signature && !dbus_message_has_signature(message, m->signature))
446     reply = dbus_message_new_error_printf(message, "org.enlightenment.InvalidSignature", "Expected signature: %s", m->signature);
447   else
448     reply = m->func(obj, message);
449
450   dbus_connection_send(conn, reply, &serial);
451   dbus_message_unref(reply);
452
453   return DBUS_HANDLER_RESULT_HANDLED;
454 }
455
456 static void
457 e_dbus_object_unregister(DBusConnection *conn, void *user_data)
458 {
459   /* free up the object struct? */
460 }
461
462 Ecore_Strbuf *
463 e_dbus_object_introspect(E_DBus_Object *obj)
464 {
465   Ecore_Strbuf *buf;
466   int level = 0;
467   E_DBus_Interface *iface;
468
469   buf = ecore_strbuf_new();
470
471   /* Doctype */
472   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");
473
474   ecore_strbuf_append(buf, "<node name=\"");
475   ecore_strbuf_append(buf, obj->path);
476   ecore_strbuf_append(buf, "\">\n");
477   level++;
478
479   ecore_list_first_goto(obj->interfaces);
480   while ((iface = ecore_list_next(obj->interfaces)))
481     _introspect_interface_append(buf, iface, level);
482
483   ecore_strbuf_append(buf, "</node>\n");
484   return buf;
485 }
486
487 static void
488 _introspect_indent_append(Ecore_Strbuf *buf, int level)
489 {
490   /* XXX optimize this? */
491   int i = level * 2;
492   while (i-- > 0)
493     ecore_strbuf_append_char(buf, ' ');
494 }
495 static void
496 _introspect_interface_append(Ecore_Strbuf *buf, E_DBus_Interface *iface, int level)
497 {
498   E_DBus_Method *method;
499   _introspect_indent_append(buf, level);
500   ecore_strbuf_append(buf, "<interface name=\"");
501   ecore_strbuf_append(buf, iface->name);
502   ecore_strbuf_append(buf, "\">\n");
503   level++;
504
505   DEBUG(4, "introspect iface: %s\n", iface->name);
506   ecore_list_first_goto(iface->methods);
507   while ((method = ecore_list_next(iface->methods))) 
508     _introspect_method_append(buf, method, level);
509
510   level--;
511   _introspect_indent_append(buf, level);
512   ecore_strbuf_append(buf, "</interface>\n");
513 }
514 static void
515 _introspect_method_append(Ecore_Strbuf *buf, E_DBus_Method *method, int level)
516 {
517   DBusSignatureIter iter;
518   char *type;
519
520   _introspect_indent_append(buf, level);
521   DEBUG(4, "introspect method: %s\n", method->member);
522   ecore_strbuf_append(buf, "<method name=\"");
523   ecore_strbuf_append(buf, method->member);
524   ecore_strbuf_append(buf, "\">\n");
525   level++;
526
527   /* append args */
528   if (method->signature && 
529       method->signature[0] &&
530       dbus_signature_validate(method->signature, NULL))
531   {
532     dbus_signature_iter_init(&iter, method->signature);
533     while ((type = dbus_signature_iter_get_signature(&iter)))
534     {
535       _introspect_arg_append(buf, type, "in", level);
536
537       dbus_free(type);
538       if (!dbus_signature_iter_next(&iter)) break;
539     }
540   }
541
542   /* append reply args */
543   if (method->reply_signature &&
544       method->reply_signature[0] &&
545       dbus_signature_validate(method->reply_signature, NULL))
546   {
547     dbus_signature_iter_init(&iter, method->reply_signature);
548     while ((type = dbus_signature_iter_get_signature(&iter)))
549     {
550       _introspect_arg_append(buf, type, "out", level);
551
552       dbus_free(type);
553       if (!dbus_signature_iter_next(&iter)) break;
554     }
555   }
556
557   level--;
558   _introspect_indent_append(buf, level);
559   ecore_strbuf_append(buf, "</method>\n");
560 }
561
562 static void
563 _introspect_arg_append(Ecore_Strbuf *buf, const char *type, const char *direction, int level)
564 {
565   _introspect_indent_append(buf, level);
566   ecore_strbuf_append(buf, "<arg type=\"");
567   ecore_strbuf_append(buf, type);
568   ecore_strbuf_append(buf, "\" direction=\"");
569   ecore_strbuf_append(buf, direction);
570   ecore_strbuf_append(buf, "\"/>\n");
571 }
572