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