[jscore] Implement the Variant(signature, arg) conversion method for real
[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("DBusDouble", double)
160 MAKE_NUMBER_CLASS_AND_GETTER("DBusByte", byte)
161 MAKE_NUMBER_CLASS_AND_GETTER("DBusUInt64", uint64)
162 MAKE_NUMBER_CLASS_AND_GETTER("DBusInt64", int64)
163 MAKE_NUMBER_CLASS_AND_GETTER("DBusUInt16", uint16)
164 MAKE_NUMBER_CLASS_AND_GETTER("DBusInt16", int16)
165
166 /* Variants */
167
168 static
169 void _variant_finalize (JSObjectRef object)
170 {
171   variant_data_t *data = (variant_data_t *)JSObjectGetPrivate(object);
172   g_assert(data != NULL);
173   g_free(data->signature);
174   JSValueUnprotect(gcontext, data->value);
175   g_free(data);
176 }
177
178 static const JSClassDefinition variant_jsclass_def =
179 {
180   0, kJSClassAttributeNone, "DBusVariant",
181   NULL, NULL, NULL, NULL, _variant_finalize, NULL, NULL,
182   NULL, NULL, NULL, NULL, NULL, NULL, NULL
183 };
184
185 static
186 JSValueRef _construct_variant (JSContextRef context,
187                                JSObjectRef function,
188                                JSObjectRef thisObject,
189                                size_t argumentCount,
190                                const JSValueRef arguments[],
191                                JSValueRef *exception)
192 {
193   variant_data_t *data;
194
195   if (argumentCount < 2)
196   {
197     /* TODO: set exception */
198     return JSValueMakeUndefined(context);
199   }
200
201   data = g_new0(variant_data_t, 1);
202   data->signature = string_from_jsvalue(context, arguments[0]);
203   data->value = arguments[1];
204   JSValueProtect(context, data->value);
205
206   return JSObjectMake(context, (JSClassRef)jsclass_lookup(&variant_jsclass_def), (void*)data);
207 }
208
209 static const JSClassDefinition struct_jsclass_def =
210 {
211   0, kJSClassAttributeNone, "DBusStruct",
212   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
213   NULL, NULL, NULL, NULL, NULL, NULL, NULL
214 };
215
216 static
217 JSValueRef _construct_struct (JSContextRef context,
218                               JSObjectRef function,
219                               JSObjectRef thisObject,
220                               size_t argumentCount,
221                               const JSValueRef arguments[],
222                               JSValueRef *exception)
223 {
224   if (argumentCount != 1)
225   {
226     /* TODO: set exception */
227     return JSValueMakeUndefined(context);
228   }
229
230   /* Just carry the object in private data */
231   return JSObjectMake(context,
232                       (JSClassRef)jsclass_lookup(&struct_jsclass_def),
233                       (void*)arguments[0]);
234 }
235
236 static const JSClassDefinition object_path_jsclass_def =
237 {
238   0, kJSClassAttributeNone, "DBusObjectPath",
239   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
240   NULL, NULL, NULL, NULL, NULL, NULL, NULL
241 };
242
243 static
244 gboolean is_valid_path (const char *path)
245 {
246   const char *this = path;
247   const char *prev = this;
248
249   if (path == NULL || strlen(path) == 0)
250     return FALSE;
251
252   /* MUST begin with zero */
253   if (*this++ != '/')
254     return FALSE;
255
256   /* The path is guranteed to be null-terminated */
257   while (*this != '\0')
258   {
259     /* Two slashes can't be together */
260     if (*this == '/' && *prev == '/')
261     {
262       return FALSE;
263     } else if (!(((*this) >= '0' && (*this) <= '9') ||
264                  ((*this) >= 'A' && (*this) <= 'Z') ||
265                  ((*this) >= 'a' && (*this) <= 'z') ||
266                   (*this) == '_' || (*this) == '/')) {
267       return FALSE;
268     }
269     prev = this;
270     this++;
271   }
272   
273   return TRUE;
274 }
275
276 static
277 JSValueRef _construct_object_path (JSContextRef context,
278                                JSObjectRef function,
279                                JSObjectRef thisObject,
280                                size_t argumentCount,
281                                const JSValueRef arguments[],
282                                JSValueRef *exception)
283 {
284   const char *path;
285
286   if (argumentCount != 1)
287   {
288     /* TODO: set exception */
289     return JSValueMakeUndefined(context);
290   }
291
292   /* D-Bus doesn't like invalid object paths _at all_, so instead of risking
293    * disconnection, we'll validate the path now.
294    */
295   path = string_from_jsvalue(context, arguments[0]);
296   if (!is_valid_path(path))
297   {
298     g_free((gpointer)path);
299     /* TODO: set exception */
300     return JSValueMakeUndefined(context);
301   }
302   g_free((gpointer)path);
303
304   /* Just carry the value in private data */
305   return JSObjectMake(context,
306                       (JSClassRef)jsclass_lookup(&object_path_jsclass_def),
307                       (void*)arguments[0]);
308 }
309
310 static const JSClassDefinition signature_jsclass_def =
311 {
312   0, kJSClassAttributeNone, "DBusSignature",
313   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
314   NULL, NULL, NULL, NULL, NULL, NULL, NULL
315 };
316
317 static
318 JSValueRef _construct_signature (JSContextRef context,
319                                  JSObjectRef function,
320                                  JSObjectRef thisObject,
321                                  size_t argumentCount,
322                                  const JSValueRef arguments[],
323                                  JSValueRef *exception)
324 {
325   if (argumentCount != 1)
326   {
327     /* TODO: set exception */
328     return JSValueMakeUndefined(context);
329   }
330
331   /* Just carry the value in private data */
332   return JSObjectMake(context,
333                       (JSClassRef)jsclass_lookup(&signature_jsclass_def),
334                       (void*)arguments[0]);
335 }
336
337 #define JSVALUE_TO_CONNECTION(ctx, val) (JSValueToNumber(ctx, val, NULL) == DBUS_BUS_SYSTEM) ? system : session
338
339 static
340 JSValueRef getMethod (JSContextRef context,
341                       JSObjectRef function,
342                       JSObjectRef thisObject,
343                       size_t argumentCount,
344                       const JSValueRef arguments[],
345                       JSValueRef *exception)
346 {
347   JSGlobalContextRef global_context = JSObjectGetPrivate(thisObject);
348   
349   if (argumentCount < 4)
350   {
351     /* TODO: set exception */
352     return JSValueMakeUndefined(context);
353   }
354
355   return jscorebus_create_method(global_context,
356                                  JSVALUE_TO_CONNECTION(context, arguments[0]),
357                                  string_from_jsvalue(context, arguments[1]),
358                                  string_from_jsvalue(context, arguments[2]),
359                                  string_from_jsvalue(context, arguments[3]),
360                                  argumentCount > 4 ? 
361                                    string_from_jsvalue(context, arguments[4])
362                                    : NULL,
363                                  argumentCount > 5 ? 
364                                    string_from_jsvalue(context, arguments[5])
365                                    : NULL,
366                                  argumentCount > 6 ? 
367                                    JSValueToObject(context, arguments[6], NULL)
368                                    : NULL,
369                                  exception);
370 }
371
372 static
373 JSValueRef getSignal (JSContextRef context,
374                       JSObjectRef function,
375                       JSObjectRef thisObject,
376                       size_t argumentCount,
377                       const JSValueRef arguments[],
378                       JSValueRef *exception)
379 {
380   JSGlobalContextRef global_context = JSObjectGetPrivate(thisObject);
381
382   if (argumentCount < 3)
383   {
384     /* TODO: set exception */
385     return JSValueMakeUndefined(context);
386   }
387
388   return jscorebus_create_signal(global_context,
389                                  JSVALUE_TO_CONNECTION(context, arguments[0]),
390                                  string_from_jsvalue(context, arguments[1]),
391                                  string_from_jsvalue(context, arguments[2]),
392                                  argumentCount > 3 ? 
393                                    string_from_jsvalue(context, arguments[3])
394                                    : NULL,
395                                  argumentCount > 4 ? 
396                                    string_from_jsvalue(context, arguments[4])
397                                    : NULL,
398                                  argumentCount > 5 ? 
399                                    JSValueToObject(context, arguments[5], NULL)
400                                    : NULL,
401                                  exception);
402 }
403
404 static
405 JSValueRef emitSignal (JSContextRef context,
406                        JSObjectRef function,
407                        JSObjectRef thisObject,
408                        size_t argumentCount,
409                        const JSValueRef arguments[],
410                        JSValueRef *exception)
411 {
412   DBusMessage *message;
413   DBusConnection *connection;
414   char *path;
415   char *interface;
416   char *member;
417   char *signature;
418
419   if (argumentCount < 4)
420   {
421     /* TODO: set exception */
422     g_warning("Not enough arguments for emitSignal");
423     return JSValueMakeBoolean(context, FALSE);
424   }
425
426   connection = JSVALUE_TO_CONNECTION(context, arguments[0]);
427   path       = string_from_jsvalue(context, arguments[1]);
428   interface  = string_from_jsvalue(context, arguments[2]);
429   member     = string_from_jsvalue(context, arguments[3]);
430
431   if (connection == NULL || path == NULL
432    || interface == NULL || member == NULL)
433   {
434     g_free(path);
435     g_free(interface);
436     g_free(member);
437     g_warning("Buggy application: Required emitSignal() argument was null");
438     return JSValueMakeBoolean(context, FALSE);
439   }
440
441   message = dbus_message_new_signal(path, interface, member);
442
443   g_free(path);
444   g_free(interface);
445   g_free(member);
446
447   if (message == NULL)
448   {
449     return JSValueMakeBoolean(context, FALSE);
450   }
451
452   if (argumentCount > 5)
453   {
454     int i, c;
455     JSValueRef *args;
456     DBusMessageIter iter;
457
458     /* "Splice" the array */
459     args = g_new(JSValueRef, argumentCount - 5);
460     c = 0;
461     for (i = 5; i < argumentCount; i++)
462       args[c++] = arguments[i];
463
464     /* Push arguments to the message */
465     signature = string_from_jsvalue(context, arguments[4]);
466     dbus_message_iter_init_append (message, &iter);
467     if (!jsvalue_array_append_to_message_iter(context,
468                                               args, c,
469                                               &iter, signature))
470     {
471       /* TODO: set exception */
472       for (i = 0; i < c; i++)
473         args[i] = NULL;
474       g_free(args);
475       g_free(signature);
476       dbus_message_unref(message);
477       return JSValueMakeBoolean(context, FALSE);
478     }
479
480     for (i = 0; i < c; i++)
481       args[i] = NULL;
482     g_free(args);
483     g_free(signature);
484   }
485
486   if (dbus_connection_send(connection, message, NULL))
487   {
488     dbus_message_unref(message);
489     return JSValueMakeBoolean(context, TRUE);
490   }
491
492   dbus_message_unref(message);
493   return JSValueMakeBoolean(context, FALSE);
494 }
495
496 static
497 void dbus_finalize(JSObjectRef object)
498 {
499   JSObjectSetPrivate(object, NULL);
500 }
501
502 static const
503 JSStaticFunction dbus_jsclass_staticfuncs[] = 
504 {
505   /* Type constructors */
506   { "Int32",   _get_int32, kJSPropertyAttributeReadOnly },
507   { "UInt32",  _get_uint32, kJSPropertyAttributeReadOnly },
508   { "Double",  _get_double, kJSPropertyAttributeReadOnly },
509   { "Byte",    _get_byte, kJSPropertyAttributeReadOnly },
510   { "Int64",   _get_int64, kJSPropertyAttributeReadOnly },
511   { "UInt64",  _get_uint64, kJSPropertyAttributeReadOnly },
512   { "Int16",   _get_int16, kJSPropertyAttributeReadOnly },
513   { "UInt16",  _get_uint16, kJSPropertyAttributeReadOnly },
514   { "ObjectPath", _construct_object_path, kJSPropertyAttributeReadOnly },
515   { "Signature", _construct_signature, kJSPropertyAttributeReadOnly },
516   { "Variant", _construct_variant, kJSPropertyAttributeReadOnly },
517   { "Struct", _construct_struct, kJSPropertyAttributeReadOnly },
518
519   /* Methods */
520   { "getMethod", getMethod, kJSPropertyAttributeReadOnly },
521   { "getSignal", getSignal, kJSPropertyAttributeReadOnly },
522   { "emitSignal", emitSignal, kJSPropertyAttributeReadOnly },
523   { NULL, NULL, 0 }
524 };
525
526 static
527 JSObjectRef dbus_constructor (JSContextRef context,
528                               JSObjectRef constructor,
529                               size_t argumentCount,
530                               const JSValueRef arguments[],
531                               JSValueRef* exception);
532
533 /* The DBus Class */
534 static const
535 JSClassDefinition dbus_jsclass_def =
536 {
537   0,
538   kJSClassAttributeNone,
539   "DBus",
540   NULL,
541
542   dbus_jsclass_staticvalues,
543   dbus_jsclass_staticfuncs,
544   
545   NULL,
546   dbus_finalize,
547   
548   NULL,
549   NULL,
550   NULL,
551   NULL,
552   NULL,
553   
554   NULL,
555   NULL,
556   NULL,
557   NULL
558 };
559
560 static
561 JSObjectRef dbus_constructor (JSContextRef context,
562                               JSObjectRef constructor,
563                               size_t argumentCount,
564                               const JSValueRef arguments[],
565                               JSValueRef* exception)
566 {
567   return JSObjectMake(context,
568                       (JSClassRef)jsclass_lookup(&dbus_jsclass_def),
569                       gcontext);
570 }
571
572 /**
573  * Public API
574  */
575  
576 void jscorebus_init(DBusConnection *psession, DBusConnection *psystem)
577 {
578   session = psession;
579   system = psystem;
580
581   jsclassdef_insert("DBus", &dbus_jsclass_def);
582
583 #define INIT_NUMBER_CLASS(name, def, type, num) \
584   jsclassdef_insert(name, def); \
585   jscorebus_number_class_names[num] = name; \
586   jscorebus_number_class_types[num] = type;
587
588   INIT_NUMBER_CLASS("DBusInt32",  &int32_jsclass_def,  DBUS_TYPE_INT32,  0);
589   INIT_NUMBER_CLASS("DBusUInt32", &uint32_jsclass_def, DBUS_TYPE_UINT32, 1);
590   INIT_NUMBER_CLASS("DBusDouble", &double_jsclass_def, DBUS_TYPE_DOUBLE, 2);
591   INIT_NUMBER_CLASS("DBusByte",   &byte_jsclass_def,   DBUS_TYPE_BYTE,   3);
592   INIT_NUMBER_CLASS("DBusUInt64", &uint64_jsclass_def, DBUS_TYPE_UINT64, 4);
593   INIT_NUMBER_CLASS("DBusInt64",  &int64_jsclass_def,  DBUS_TYPE_INT64,  5);
594   INIT_NUMBER_CLASS("DBusUInt16", &uint16_jsclass_def, DBUS_TYPE_UINT16, 6);
595   INIT_NUMBER_CLASS("DBusInt16",  &int16_jsclass_def,  DBUS_TYPE_INT16,  7);
596
597   jsclassdef_insert("DBusObjectPath", &object_path_jsclass_def);
598   jsclassdef_insert("DBusSignature", &signature_jsclass_def);
599
600   jsclassdef_insert("DBusVariant", &variant_jsclass_def);
601   jsclassdef_insert("DBusStruct", &struct_jsclass_def);
602
603 }
604
605 void jscorebus_export(JSGlobalContextRef context)
606 {
607   JSObjectRef globalObject;
608   JSObjectRef dbus;
609   JSStringRef jsstr;
610
611   dbus = JSObjectMakeConstructor(context,
612                                  (JSClassRef)jsclass_lookup(&dbus_jsclass_def),
613                                  dbus_constructor);
614   gcontext = context;
615
616   globalObject = JSContextGetGlobalObject(context);
617   jsstr = JSStringCreateWithUTF8CString("DBus");
618   JSObjectSetProperty(context, globalObject,
619                       jsstr, dbus,
620                       kJSPropertyAttributeNone, NULL);
621   JSStringRelease(jsstr);
622 }
623