[jscore] Implement DBus.emitSignal()
[browser-dbus-bridge.git] / jscorebus / jscorebus.c
1 /**
2  * Browser D-Bus Bridge, JavaScriptCore version
3  *
4  * Copyright © 2008 Movial Creative Technologies Inc
5  *  Contact: Movial Creative Technologies Inc, <info@movial.com>
6  *  Authors: Kalle Vahlman, <kalle.vahlman@movial.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
20  *
21  */
22
23 #include <string.h>
24  
25 #include <glib.h>
26 #include <dbus/dbus.h>
27 #include <JavaScriptCore/JavaScript.h>
28
29 #include "jscorebus-marshal.h"
30 #include "jscorebus-method.h"
31 #include "jscorebus-signal.h"
32
33 /* Globals */
34 static DBusConnection *session;
35 static DBusConnection *system;
36
37
38 /* Getters for the bus type properties */
39 static
40 JSValueRef _get_bus_type (JSContextRef context,
41                           JSObjectRef object,
42                           JSStringRef propertyName,
43                           JSValueRef *exception)
44 {
45   if (JSStringIsEqualToUTF8CString(propertyName, "SESSION"))
46   {
47     return JSValueMakeNumber(context, DBUS_BUS_SESSION);
48   }
49   if (JSStringIsEqualToUTF8CString(propertyName, "SYSTEM"))
50   {
51     return JSValueMakeNumber(context, DBUS_BUS_SYSTEM);
52   }
53   
54   return JSValueMakeUndefined(context);
55 }
56
57 /* Static members */
58 static const
59 JSStaticValue dbus_jsclass_staticvalues[] = 
60 {
61   { "SESSION", _get_bus_type, NULL, kJSPropertyAttributeReadOnly },
62   { "SYSTEM",  _get_bus_type, NULL, kJSPropertyAttributeReadOnly },
63   { NULL, NULL, NULL, 0 }
64 };
65
66 static
67 void _number_finalize (JSObjectRef object)
68 {
69   g_free(JSObjectGetPrivate(object));
70 }
71
72 static inline
73 JSValueRef _get_number_object (JSContextRef context,
74                                JSObjectRef function,
75                                JSObjectRef thisObject,
76                                size_t argumentCount,
77                                const JSValueRef arguments[],
78                                JSValueRef *exception,
79                                JSClassRef number_class)
80 {
81   dbus_uint64_t *value;
82   
83   if (argumentCount != 1)
84   {
85     /* TODO: set exception */
86     return JSValueMakeUndefined(context);
87   }
88
89   /* dbus_uint64_t should be large enough to hold any number so we just
90    * carry the value in private data as a pointer to dbus_uint64_t.
91    * The object class tells us later which type it is.
92    */
93   value = g_new0(dbus_uint64_t, 1);
94   *value = (dbus_uint64_t)JSValueToNumber(context, arguments[0], NULL);
95
96   return JSObjectMake(context, number_class, value);
97 }
98
99 JSValueRef _convert_number_object(JSContextRef context,
100                                   JSObjectRef object,
101                                   JSType type,
102                                   JSValueRef* exception)
103 {
104   /* FIXME: this isn't called at all... */
105   g_debug("%s(%p to %d)", __FUNCTION__, object, type);
106 #if 0
107   switch (type)
108   {
109     case kJSTypeNumber:
110       {
111         dbus_uint64_t value = jsvalue_to_number_value(context, object, NULL);
112         return JSValueMakeNumber(context, (double)value);
113       }
114     case kJSTypeString:
115       {
116         JSValueRef jsvalue;
117         JSStringRef jsstr;
118         char *str;
119         dbus_uint64_t value = jsvalue_to_number_value(context, object, NULL);
120         str = g_strdup_printf("%llu", value);
121         jsstr = JSStringCreateWithUTF8CString(str);
122         g_free(str);
123         jsvalue = JSValueMakeString(context, jsstr);
124         JSStringRelease(jsstr);
125         return jsvalue;
126       }
127     default:
128       break;
129   }
130 #endif
131   return NULL;
132 }
133
134 #define MAKE_NUMBER_CLASS_AND_GETTER(classname, shortname) \
135   static const JSClassDefinition shortname ##_jsclass_def = \
136   { \
137     0, kJSClassAttributeNone, classname, \
138     NULL, NULL, NULL, NULL, _number_finalize, NULL, NULL, \
139     NULL, NULL, NULL, NULL, NULL, NULL, _convert_number_object \
140   }; \
141   \
142   static JSValueRef _get_ ##shortname (JSContextRef context, \
143                                     JSObjectRef function, \
144                                     JSObjectRef thisObject, \
145                                     size_t argumentCount, \
146                                     const JSValueRef arguments[], \
147                                     JSValueRef *exception) \
148   { \
149     return _get_number_object(context, function, thisObject, \
150                               argumentCount, arguments, exception, \
151                               (JSClassRef)jsclass_lookup(&shortname ##_jsclass_def)); \
152   }
153
154 MAKE_NUMBER_CLASS_AND_GETTER("DBusUInt32", uint32)
155 MAKE_NUMBER_CLASS_AND_GETTER("DBusInt32", int32)
156 MAKE_NUMBER_CLASS_AND_GETTER("DBusDouble", double)
157 MAKE_NUMBER_CLASS_AND_GETTER("DBusByte", byte)
158 MAKE_NUMBER_CLASS_AND_GETTER("DBusUInt64", uint64)
159 MAKE_NUMBER_CLASS_AND_GETTER("DBusInt64", int64)
160 MAKE_NUMBER_CLASS_AND_GETTER("DBusUInt16", uint16)
161 MAKE_NUMBER_CLASS_AND_GETTER("DBusInt16", int16)
162
163 static const JSClassDefinition variant_jsclass_def =
164 {
165   0, kJSClassAttributeNone, "DBusVariant",
166   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
167   NULL, NULL, NULL, NULL, NULL, NULL, NULL
168 };
169
170 static
171 JSValueRef _construct_variant (JSContextRef context,
172                                JSObjectRef function,
173                                JSObjectRef thisObject,
174                                size_t argumentCount,
175                                const JSValueRef arguments[],
176                                JSValueRef *exception)
177 {
178
179   if (argumentCount != 1)
180   {
181     /* TODO: set exception */
182     return JSValueMakeUndefined(context);
183   }
184
185   /* Just carry the value in private data */
186   return JSObjectMake(context, (JSClassRef)jsclass_lookup(&variant_jsclass_def), (void*)arguments[0]);
187 }
188
189 static const JSClassDefinition struct_jsclass_def =
190 {
191   0, kJSClassAttributeNone, "DBusStruct",
192   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
193   NULL, NULL, NULL, NULL, NULL, NULL, NULL
194 };
195
196 static
197 JSValueRef _construct_struct (JSContextRef context,
198                               JSObjectRef function,
199                               JSObjectRef thisObject,
200                               size_t argumentCount,
201                               const JSValueRef arguments[],
202                               JSValueRef *exception)
203 {
204   if (argumentCount != 1)
205   {
206     /* TODO: set exception */
207     return JSValueMakeUndefined(context);
208   }
209
210   /* Just carry the object in private data */
211   return JSObjectMake(context,
212                       (JSClassRef)jsclass_lookup(&struct_jsclass_def),
213                       (void*)arguments[0]);
214 }
215
216 static const JSClassDefinition object_path_jsclass_def =
217 {
218   0, kJSClassAttributeNone, "DBusObjectPath",
219   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
220   NULL, NULL, NULL, NULL, NULL, NULL, NULL
221 };
222
223 static
224 gboolean is_valid_path (const char *path)
225 {
226   const char *this = path;
227   const char *prev = this;
228   
229   if (strlen(path) == 0)
230     return FALSE;
231   
232   /* MUST begin with zero */
233   if (*this++ != '/')
234     return FALSE;
235   
236   /* The path is guranteed to be null-terminated */
237   while (*this != '\0')
238   {
239     /* Two slashes can't be together */
240     if (*this == '/' && *prev == '/')
241     {
242       return FALSE;
243     } else if (!(((*this) >= '0' && (*this) <= '9') ||
244                  ((*this) >= 'A' && (*this) <= 'Z') ||
245                  ((*this) >= 'a' && (*this) <= 'z') ||
246                   (*this) == '_' || (*this) == '/')) {
247       return FALSE;
248     }
249     prev = this;
250     this++;
251   }
252   
253   return TRUE;
254 }
255
256 static
257 JSValueRef _construct_object_path (JSContextRef context,
258                                JSObjectRef function,
259                                JSObjectRef thisObject,
260                                size_t argumentCount,
261                                const JSValueRef arguments[],
262                                JSValueRef *exception)
263 {
264   const char *path;
265
266   if (argumentCount != 1)
267   {
268     /* TODO: set exception */
269     return JSValueMakeUndefined(context);
270   }
271
272   /* D-Bus doesn't like invalid object paths _at all_, so instead of risking
273    * disconnection, we'll validate the path now.
274    */
275   path = string_from_jsvalue(context, arguments[0]);
276   if (!is_valid_path(path))
277   {
278     g_free((gpointer)path);
279     /* TODO: set exception */
280     return JSValueMakeUndefined(context);
281   }
282   g_free((gpointer)path);
283
284   /* Just carry the value in private data */
285   return JSObjectMake(context,
286                       (JSClassRef)jsclass_lookup(&object_path_jsclass_def),
287                       (void*)arguments[0]);
288 }
289
290 static const JSClassDefinition signature_jsclass_def =
291 {
292   0, kJSClassAttributeNone, "DBusSignature",
293   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
294   NULL, NULL, NULL, NULL, NULL, NULL, NULL
295 };
296
297 static
298 JSValueRef _construct_signature (JSContextRef context,
299                                  JSObjectRef function,
300                                  JSObjectRef thisObject,
301                                  size_t argumentCount,
302                                  const JSValueRef arguments[],
303                                  JSValueRef *exception)
304 {
305   if (argumentCount != 1)
306   {
307     /* TODO: set exception */
308     return JSValueMakeUndefined(context);
309   }
310
311   /* Just carry the value in private data */
312   return JSObjectMake(context,
313                       (JSClassRef)jsclass_lookup(&signature_jsclass_def),
314                       (void*)arguments[0]);
315 }
316
317 #define JSVALUE_TO_CONNECTION(ctx, val) (JSValueToNumber(ctx, val, NULL) == DBUS_BUS_SYSTEM) ? system : session
318
319 static
320 JSValueRef getMethod (JSContextRef context,
321                       JSObjectRef function,
322                       JSObjectRef thisObject,
323                       size_t argumentCount,
324                       const JSValueRef arguments[],
325                       JSValueRef *exception)
326 {
327   JSGlobalContextRef global_context = JSObjectGetPrivate(thisObject);
328   
329   if (argumentCount < 4)
330   {
331     /* TODO: set exception */
332     return JSValueMakeUndefined(context);
333   }
334
335   return jscorebus_create_method(global_context,
336                                  JSVALUE_TO_CONNECTION(context, arguments[0]),
337                                  string_from_jsvalue(context, arguments[1]),
338                                  string_from_jsvalue(context, arguments[2]),
339                                  string_from_jsvalue(context, arguments[3]),
340                                  argumentCount > 4 ? 
341                                    string_from_jsvalue(context, arguments[4])
342                                    : NULL,
343                                  argumentCount > 5 ? 
344                                    string_from_jsvalue(context, arguments[5])
345                                    : NULL,
346                                  argumentCount > 6 ? 
347                                    JSValueToObject(context, arguments[6], NULL)
348                                    : NULL,
349                                  exception);
350 }
351
352 static
353 JSValueRef getSignal (JSContextRef context,
354                       JSObjectRef function,
355                       JSObjectRef thisObject,
356                       size_t argumentCount,
357                       const JSValueRef arguments[],
358                       JSValueRef *exception)
359 {
360   JSGlobalContextRef global_context = JSObjectGetPrivate(thisObject);
361
362   if (argumentCount < 3)
363   {
364     /* TODO: set exception */
365     return JSValueMakeUndefined(context);
366   }
367
368   return jscorebus_create_signal(global_context,
369                                  JSVALUE_TO_CONNECTION(context, arguments[0]),
370                                  string_from_jsvalue(context, arguments[1]),
371                                  string_from_jsvalue(context, arguments[2]),
372                                  argumentCount > 3 ? 
373                                    string_from_jsvalue(context, arguments[3])
374                                    : NULL,
375                                  argumentCount > 4 ? 
376                                    string_from_jsvalue(context, arguments[4])
377                                    : NULL,
378                                  argumentCount > 5 ? 
379                                    JSValueToObject(context, arguments[5], NULL)
380                                    : NULL,
381                                  exception);
382 }
383
384 static
385 JSValueRef emitSignal (JSContextRef context,
386                        JSObjectRef function,
387                        JSObjectRef thisObject,
388                        size_t argumentCount,
389                        const JSValueRef arguments[],
390                        JSValueRef *exception)
391 {
392   DBusMessage *message;
393   DBusConnection *connection;
394   char *path;
395   char *interface;
396   char *member;
397   char *signature;
398
399   if (argumentCount < 4)
400   {
401     /* TODO: set exception */
402     g_warning("Not enough arguments for emitSignal");
403     return JSValueMakeBoolean(context, FALSE);
404   }
405
406   connection = JSVALUE_TO_CONNECTION(context, arguments[0]);
407   path       = string_from_jsvalue(context, arguments[1]);
408   interface  = string_from_jsvalue(context, arguments[2]);
409   member     = string_from_jsvalue(context, arguments[3]);
410
411   message = dbus_message_new_signal(path, interface, member);
412
413   g_free(path);
414   g_free(interface);
415   g_free(member);
416
417   if (message == NULL)
418   {
419     return JSValueMakeBoolean(context, FALSE);
420   }
421
422   if (argumentCount > 5)
423   {
424     int i, c;
425     JSValueRef *args;
426     DBusMessageIter iter;
427
428     /* "Splice" the array */
429     args = g_new(JSValueRef, argumentCount - 5);
430     c = 0;
431     for (i = 5; i < argumentCount; i++)
432       args[c++] = arguments[i];
433
434     /* Push arguments to the message */
435     signature = string_from_jsvalue(context, arguments[4]);
436     dbus_message_iter_init_append (message, &iter);
437     if (!jsvalue_array_append_to_message_iter(context,
438                                               args, c,
439                                               &iter, signature))
440     {
441       /* TODO: set exception */
442       for (i = 0; i < c; i++)
443         args[i] = NULL;
444       g_free(args);
445       g_free(signature);
446       dbus_message_unref(message);
447       return JSValueMakeBoolean(context, FALSE);
448     }
449
450     for (i = 0; i < c; i++)
451       args[i] = NULL;
452     g_free(args);
453     g_free(signature);
454   }
455
456   if (dbus_connection_send(connection, message, NULL))
457   {
458     dbus_message_unref(message);
459     return JSValueMakeBoolean(context, TRUE);
460   }
461
462   dbus_message_unref(message);
463   return JSValueMakeBoolean(context, FALSE);
464 }
465
466 static
467 void dbus_finalize(JSObjectRef object)
468 {
469   g_debug(G_STRFUNC);
470   JSObjectSetPrivate(object, NULL);
471 }
472
473 static const
474 JSStaticFunction dbus_jsclass_staticfuncs[] = 
475 {
476   /* Type constructors */
477   { "Int32",   _get_int32, kJSPropertyAttributeReadOnly },
478   { "UInt32",  _get_uint32, kJSPropertyAttributeReadOnly },
479   { "Double",  _get_double, kJSPropertyAttributeReadOnly },
480   { "Byte",    _get_byte, kJSPropertyAttributeReadOnly },
481   { "Int64",   _get_int64, kJSPropertyAttributeReadOnly },
482   { "UInt64",  _get_uint64, kJSPropertyAttributeReadOnly },
483   { "Int16",   _get_int16, kJSPropertyAttributeReadOnly },
484   { "UInt16",  _get_uint16, kJSPropertyAttributeReadOnly },
485   { "ObjectPath", _construct_object_path, kJSPropertyAttributeReadOnly },
486   { "Signature", _construct_signature, kJSPropertyAttributeReadOnly },
487   { "Variant", _construct_variant, kJSPropertyAttributeReadOnly },
488   { "Struct", _construct_struct, kJSPropertyAttributeReadOnly },
489
490   /* Methods */
491   { "getMethod", getMethod, kJSPropertyAttributeReadOnly },
492   { "getSignal", getSignal, kJSPropertyAttributeReadOnly },
493   { "emitSignal", emitSignal, kJSPropertyAttributeReadOnly },
494   { NULL, NULL, 0 }
495 };
496
497 /* The DBus Class */
498 static const
499 JSClassDefinition dbus_jsclass_def =
500 {
501   0,
502   kJSClassAttributeNone,
503   "DBus",
504   NULL,
505
506   dbus_jsclass_staticvalues,
507   dbus_jsclass_staticfuncs,
508   
509   NULL,
510   dbus_finalize,
511   
512   NULL,
513   NULL,
514   NULL,
515   NULL,
516   NULL,
517   
518   NULL,
519   NULL,
520   NULL,
521   NULL
522 };
523
524 /**
525  * Public API
526  */
527  
528 void jscorebus_init(DBusConnection *psession, DBusConnection *psystem)
529 {
530   session = psession;
531   system = psystem;
532
533 #define INIT_NUMBER_CLASS(name, def, type, num) \
534   jsclassdef_insert(name, def); \
535   jscorebus_number_class_names[num] = name; \
536   jscorebus_number_class_types[num] = type;
537
538   INIT_NUMBER_CLASS("DBusInt32",  &int32_jsclass_def,  DBUS_TYPE_INT32,  0);
539   INIT_NUMBER_CLASS("DBusUInt32", &uint32_jsclass_def, DBUS_TYPE_UINT32, 1);
540   INIT_NUMBER_CLASS("DBusDouble", &double_jsclass_def, DBUS_TYPE_DOUBLE, 2);
541   INIT_NUMBER_CLASS("DBusByte",   &byte_jsclass_def,   DBUS_TYPE_BYTE,   3);
542   INIT_NUMBER_CLASS("DBusUInt64", &uint64_jsclass_def, DBUS_TYPE_UINT64, 4);
543   INIT_NUMBER_CLASS("DBusInt64",  &int64_jsclass_def,  DBUS_TYPE_INT64,  5);
544   INIT_NUMBER_CLASS("DBusUInt16", &uint16_jsclass_def, DBUS_TYPE_UINT16, 6);
545   INIT_NUMBER_CLASS("DBusInt16",  &int16_jsclass_def,  DBUS_TYPE_INT16,  7);
546
547   jsclassdef_insert("DBusObjectPath", &object_path_jsclass_def);
548   jsclassdef_insert("DBusSignature", &signature_jsclass_def);
549
550   jsclassdef_insert("DBusVariant", &variant_jsclass_def);
551   jsclassdef_insert("DBusStruct", &struct_jsclass_def);
552
553 }
554
555 void jscorebus_export(JSGlobalContextRef context)
556 {
557   JSObjectRef globalObject;
558   JSObjectRef dbus;
559   JSStringRef jsstr;
560   JSClassRef dbus_jsclass;
561
562 //  global_context = context;
563     
564   dbus_jsclass = JSClassCreate(&dbus_jsclass_def);
565   dbus = JSObjectMake(context, dbus_jsclass, context);
566
567   globalObject = JSContextGetGlobalObject(context);
568   jsstr = JSStringCreateWithUTF8CString("DBus");
569   JSObjectSetProperty(context, globalObject,
570                       jsstr, dbus,
571                       kJSPropertyAttributeNone, NULL);
572 }
573