14630685c37463c7ab47b1966a252f7c52c52715
[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   if (connection == NULL || path == NULL
412    || interface == NULL || member == NULL)
413   {
414     g_free(path);
415     g_free(interface);
416     g_free(member);
417     g_warning("Buggy application: Required emitSignal() argument was null");
418     return JSValueMakeBoolean(context, FALSE);
419   }
420
421   message = dbus_message_new_signal(path, interface, member);
422
423   g_free(path);
424   g_free(interface);
425   g_free(member);
426
427   if (message == NULL)
428   {
429     return JSValueMakeBoolean(context, FALSE);
430   }
431
432   if (argumentCount > 5)
433   {
434     int i, c;
435     JSValueRef *args;
436     DBusMessageIter iter;
437
438     /* "Splice" the array */
439     args = g_new(JSValueRef, argumentCount - 5);
440     c = 0;
441     for (i = 5; i < argumentCount; i++)
442       args[c++] = arguments[i];
443
444     /* Push arguments to the message */
445     signature = string_from_jsvalue(context, arguments[4]);
446     dbus_message_iter_init_append (message, &iter);
447     if (!jsvalue_array_append_to_message_iter(context,
448                                               args, c,
449                                               &iter, signature))
450     {
451       /* TODO: set exception */
452       for (i = 0; i < c; i++)
453         args[i] = NULL;
454       g_free(args);
455       g_free(signature);
456       dbus_message_unref(message);
457       return JSValueMakeBoolean(context, FALSE);
458     }
459
460     for (i = 0; i < c; i++)
461       args[i] = NULL;
462     g_free(args);
463     g_free(signature);
464   }
465
466   if (dbus_connection_send(connection, message, NULL))
467   {
468     dbus_message_unref(message);
469     return JSValueMakeBoolean(context, TRUE);
470   }
471
472   dbus_message_unref(message);
473   return JSValueMakeBoolean(context, FALSE);
474 }
475
476 static
477 void dbus_finalize(JSObjectRef object)
478 {
479   g_debug(G_STRFUNC);
480   JSObjectSetPrivate(object, NULL);
481 }
482
483 static const
484 JSStaticFunction dbus_jsclass_staticfuncs[] = 
485 {
486   /* Type constructors */
487   { "Int32",   _get_int32, kJSPropertyAttributeReadOnly },
488   { "UInt32",  _get_uint32, kJSPropertyAttributeReadOnly },
489   { "Double",  _get_double, kJSPropertyAttributeReadOnly },
490   { "Byte",    _get_byte, kJSPropertyAttributeReadOnly },
491   { "Int64",   _get_int64, kJSPropertyAttributeReadOnly },
492   { "UInt64",  _get_uint64, kJSPropertyAttributeReadOnly },
493   { "Int16",   _get_int16, kJSPropertyAttributeReadOnly },
494   { "UInt16",  _get_uint16, kJSPropertyAttributeReadOnly },
495   { "ObjectPath", _construct_object_path, kJSPropertyAttributeReadOnly },
496   { "Signature", _construct_signature, kJSPropertyAttributeReadOnly },
497   { "Variant", _construct_variant, kJSPropertyAttributeReadOnly },
498   { "Struct", _construct_struct, kJSPropertyAttributeReadOnly },
499
500   /* Methods */
501   { "getMethod", getMethod, kJSPropertyAttributeReadOnly },
502   { "getSignal", getSignal, kJSPropertyAttributeReadOnly },
503   { "emitSignal", emitSignal, kJSPropertyAttributeReadOnly },
504   { NULL, NULL, 0 }
505 };
506
507 /* The DBus Class */
508 static const
509 JSClassDefinition dbus_jsclass_def =
510 {
511   0,
512   kJSClassAttributeNone,
513   "DBus",
514   NULL,
515
516   dbus_jsclass_staticvalues,
517   dbus_jsclass_staticfuncs,
518   
519   NULL,
520   dbus_finalize,
521   
522   NULL,
523   NULL,
524   NULL,
525   NULL,
526   NULL,
527   
528   NULL,
529   NULL,
530   NULL,
531   NULL
532 };
533
534 /**
535  * Public API
536  */
537  
538 void jscorebus_init(DBusConnection *psession, DBusConnection *psystem)
539 {
540   session = psession;
541   system = psystem;
542
543 #define INIT_NUMBER_CLASS(name, def, type, num) \
544   jsclassdef_insert(name, def); \
545   jscorebus_number_class_names[num] = name; \
546   jscorebus_number_class_types[num] = type;
547
548   INIT_NUMBER_CLASS("DBusInt32",  &int32_jsclass_def,  DBUS_TYPE_INT32,  0);
549   INIT_NUMBER_CLASS("DBusUInt32", &uint32_jsclass_def, DBUS_TYPE_UINT32, 1);
550   INIT_NUMBER_CLASS("DBusDouble", &double_jsclass_def, DBUS_TYPE_DOUBLE, 2);
551   INIT_NUMBER_CLASS("DBusByte",   &byte_jsclass_def,   DBUS_TYPE_BYTE,   3);
552   INIT_NUMBER_CLASS("DBusUInt64", &uint64_jsclass_def, DBUS_TYPE_UINT64, 4);
553   INIT_NUMBER_CLASS("DBusInt64",  &int64_jsclass_def,  DBUS_TYPE_INT64,  5);
554   INIT_NUMBER_CLASS("DBusUInt16", &uint16_jsclass_def, DBUS_TYPE_UINT16, 6);
555   INIT_NUMBER_CLASS("DBusInt16",  &int16_jsclass_def,  DBUS_TYPE_INT16,  7);
556
557   jsclassdef_insert("DBusObjectPath", &object_path_jsclass_def);
558   jsclassdef_insert("DBusSignature", &signature_jsclass_def);
559
560   jsclassdef_insert("DBusVariant", &variant_jsclass_def);
561   jsclassdef_insert("DBusStruct", &struct_jsclass_def);
562
563 }
564
565 void jscorebus_export(JSGlobalContextRef context)
566 {
567   JSObjectRef globalObject;
568   JSObjectRef dbus;
569   JSStringRef jsstr;
570   JSClassRef dbus_jsclass;
571
572 //  global_context = context;
573     
574   dbus_jsclass = JSClassCreate(&dbus_jsclass_def);
575   dbus = JSObjectMake(context, dbus_jsclass, context);
576
577   globalObject = JSContextGetGlobalObject(context);
578   jsstr = JSStringCreateWithUTF8CString("DBus");
579   JSObjectSetProperty(context, globalObject,
580                       jsstr, dbus,
581                       kJSPropertyAttributeNone, NULL);
582 }
583