bea3193449b963b4c8d8abbc8e791d462eddcdd6
[browser-dbus-bridge.git] / html / dbus.js
1 /*
2  * DBus.js, a JS wrapper for the Browser D-Bus Bridge
3  * Copyright (c) 2008-2009  Movial Creative Technologies Inc
4  *
5  * Contact: Movial Creative Technologies Inc, <info@movial.com>
6  * Authors: Lauri Mylläri, <lauri.myllari@movial.fi>
7  *          Kalle Vahlman, <kalle.vahlman@movial.com>
8  *          Ehsun Amanolahi, <ehsun.amanolahi@movial.com>
9  *
10  * Permission is hereby granted, free of charge, to any person
11  * obtaining a copy of this software and associated documentation
12  * files (the "Software"), to deal in the Software without
13  * restriction, including without limitation the rights to use,
14  * copy, modify, merge, publish, distribute, sublicense, and/or sell
15  * copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following
17  * conditions:
18  *
19  * The above copyright notice and this permission notice shall be
20  * included in all copies or substantial portions of the Software.
21  *
22  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
24  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
26  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
27  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
29  * OTHER DEALINGS IN THE SOFTWARE.
30  */
31
32 if (!window.DBus)
33 {
34     /* This is the Gecko wrapping for the Browser D-Bus Bridge API
35      * You can use the XPCOM API directly, but the Bridge API
36      *  - is easier to use
37      *  - has few utility features over the "raw" API (like introspection)
38      *  - generally feels a bit more JavaScriptish (hopefully)
39      */
40
41     /* For readability */
42     var ep = netscape.security.PrivilegeManager.enablePrivilege;
43     ep("UniversalXPConnect");
44     var cc = Components.classes;
45     var ci = Components.interfaces;
46
47     /* Utility function that creates a wrapper function which handles signature
48      * conversion from wrapper(argarray) to method(arg1, ..., argN) and sets
49      * 'this' to point to the user-specified object. This is mainly used to
50      * convert the variant arrays from the XPCOM service to callback arguments.
51      */
52     var unWrap = function(method, user_data) {
53         return function() {
54             if (method){
55                 method.apply(user_data, arguments[0]);
56             }
57         };
58     };
59
60     var makeVariantMaker = function(variant_type) {
61         return function(val) {
62             ep("UniversalXPConnect");
63             var variant = cc["@mozilla.org/variant;1"].createInstance(ci.nsIWritableVariant);
64             variant['setAs' + variant_type](val);
65             return variant;
66         };
67         return ret;
68     };
69
70     // Bridge API: The DBus singleton class
71     window.DBus = function() {
72
73         ep("UniversalXPConnect");
74         this.dbus = cc["@movial.fi/dbus/service;1"].getService();
75         this.dbus = this.dbus.QueryInterface(Components.interfaces.IDBusService);
76
77         // Mirror static properties
78         this.SYSTEM = this.dbus.SYSTEM;
79         this.SESSION = this.dbus.SESSION;
80
81         // Bridge API: DBus.[some type]()
82         // These are types we can wrap to variants directly for the
83         // DBus.Int32() etc conversion methods
84         var types = {
85             Int32:  "Int32",
86             UInt32: "Uint32",
87             Int16:  "Int16",
88             UInt16: "Uint16",
89             Int64:  "Int64",
90             UInt64: "Uint64",
91             Double: "Double",
92             Byte:   "Int8"
93         };
94
95         for (var type in types) {
96             if(type !== null ){
97                 this[type] = makeVariantMaker(types[type]);
98             }
99         }
100
101     };
102
103     // Methods for non-variant and non-automatic type conversions
104     window.DBus.prototype.Signature = function(val) {
105       ep("UniversalXPConnect");
106       var carrier = cc["@movial.fi/dbus/datacarrier;1"].createInstance(ci.IDBusDataCarrier);
107       carrier.type = "g";
108       carrier.value = val;
109       return carrier;
110     };
111
112     window.DBus.prototype.ObjectPath = function(val) {
113       ep("UniversalXPConnect");
114       var carrier = cc["@movial.fi/dbus/datacarrier;1"].createInstance(ci.IDBusDataCarrier);
115       carrier.type = "o";
116       carrier.value = val;
117       return carrier;
118     };
119
120     window.DBus.prototype.Variant = function(sig, val) {
121       ep("UniversalXPConnect");
122       var carrier = cc["@movial.fi/dbus/datacarrier;1"].createInstance(ci.IDBusDataCarrier);
123       carrier.type = "v";
124       carrier.signature = sig;
125       carrier.value = val;
126       return carrier;
127     };
128
129     window.DBus.prototype.Struct = function(val) {
130       ep("UniversalXPConnect");
131       var carrier = cc["@movial.fi/dbus/datacarrier;1"].createInstance(ci.IDBusDataCarrier);
132       carrier.type = "r";
133       carrier.value = val;
134       return carrier;
135     };
136
137     // Bridge API: dbus.getMethod()
138     window.DBus.prototype.getMethod = function(bustype,
139                                                destination,
140                                                object_path,
141                                                method_name,
142                                                inter,
143                                                signature,
144                                                user_data) {
145
146         /* A wrapper function for method calls
147          * Executes the actual call through the XPCOM interface
148          */
149         var method = function() {
150             ep("UniversalXPConnect");
151             try {
152               // The Array.prototype dance converts the "sort of Array but not
153               // quite" 'arguments' to a real Array object
154               method.dbusmethod.doCall(Array.prototype.slice.call(arguments),
155                                        arguments.length);
156             } catch (e) {
157               if (method._onerror) {
158                 method._onerror.apply(method.user_data, [e.toString()]);
159               }
160             }
161         };
162
163         // Store the user specified "this" object
164         method.user_data = user_data;
165
166         // Create the XPCOM method object
167         ep("UniversalXPConnect");
168         method.dbusmethod = this.dbus.getMethod(bustype,
169                                                 destination,
170                                                 object_path,
171                                                 method_name,
172                                                 inter,
173                                                 signature);
174
175         /* XXX: Hmm? onreply and onerror are undefined here, no? */
176         method.dbusmethod.onReply = unWrap(method.onreply, method.user_data);
177         method.dbusmethod.onError = unWrap(method.onerror, method.user_data);
178
179         // Watch the onreply property and assign the callback through our
180         // wrapper
181         method.watch("onreply", function(id, oldval, newval) {
182             ep("UniversalXPConnect");
183             method.dbusmethod.onReply = unWrap(newval, method.user_data);
184         });
185
186         // Ditto for onerror
187         method.watch("onerror", function(id, oldval, newval) {
188             ep("UniversalXPConnect");
189             method.dbusmethod.onError = unWrap(newval, method.user_data);
190             // We need to shadow the property for some reason...
191             method._onerror = newval;
192         });
193
194         // Mirror the async property
195         method.async = true;
196         method.watch("async", function(id, oldval, newval) {
197             ep("UniversalXPConnect");
198             method.dbusmethod.async = newval;
199         });
200
201         return method;
202     };
203
204     // Bridge API: dbus.getSignal()
205     window.DBus.prototype.getSignal = function(bustype,
206                                                inter,
207                                                signal_name,
208                                                sender,
209                                                object_path,
210                                                user_data) {
211         var signal = {};
212
213         // Create the XPCOM signal object
214         ep("UniversalXPConnect");
215         signal.dbussignal = this.dbus.getSignal(bustype,
216                                                 inter,
217                                                 signal_name,
218                                                 sender,
219                                                 object_path);
220
221         // Store user-specified "this" for callbacks
222         signal.user_data = user_data;
223
224         // XXX: ummm.... onemit == undefined?
225         signal.dbussignal.onEmit = unWrap(signal.onemit, signal.user_data);
226
227         // Watch the onemit property for wrapping
228         signal.watch("onemit", function(id, oldval, newval) {
229             ep("UniversalXPConnect");
230             signal.dbussignal.onEmit = unWrap(newval, signal.user_data);
231         });
232
233         // Watch the enabled property for wrapping
234         signal.enabled = false;
235         signal.watch("enabled", function(id, oldval, newval) {
236             ep("UniversalXPConnect");
237             signal.dbussignal.enabled = newval;
238         });
239
240         return signal;
241     };
242
243     // Bridge API: dbus.emitSignal()
244     window.DBus.prototype.emitSignal = function() {
245         var args = arguments.splice(5);
246         ep("UniversalXPConnect");
247         this.dbus.emitSignal(arguments[0], arguments[1],
248                              arguments[2], arguments[3],
249                              arguments[4], args, args.length);
250     };
251
252 }
253
254 // Instantiate the Bridge API singleton
255 try {
256   window.dbus = new window.DBus();
257 } catch (e) {
258   window.dbus = null;
259 }
260
261 /**
262  *  @private
263  */
264
265 DBus.prototype.parseInterface = function(doc, dest, path, inter){
266     var ret = {};
267     var temp = doc.getElementsByTagName("interface");
268     //interesting interface is last
269     for( var i = temp.length - 1; i >=0; i--){
270         if( temp[i].getAttribute("name") != inter ) { continue; }
271         //method name
272         var methods = temp[i].getElementsByTagName("method");
273         for ( var j = 0, jl = methods.length; j < jl; j ++){
274             var name = methods[j].getAttribute("name");
275             //params
276             var args = methods[j].getElementsByTagName("arg");
277             var type = [];
278
279             for( var z = 0, zl = args.length; z < zl; z ++){
280
281                 if(  args[z].getAttribute("direction") == "in"){
282                     type.push(args[z].getAttribute("type") );
283                 }
284
285             }
286             ret[name] = dbus.getMethod(window.DBus.SESSION, dest, path,
287                                        name, inter, type.join(""), ret );
288         }
289
290         //signals
291         var signals = temp[i].getElementsByTagName("signal");
292         for ( var j = 0, jl = signals.length; j < jl; j ++){
293             name = signals[j].getAttribute("name");
294             ret[name] = dbus.getSignal(window.DBus.SESSION,
295                                        inter, name, null,
296                                        this.object_path, ret);
297             ret[name].enabled = false;
298             ret[name].onemit = null;
299         }
300         ret.xml = (new XMLSerializer()).serializeToString(doc);
301         return ret;
302         // break; //interface found
303     }
304     return null;
305 };
306
307 /**
308     *  @constructor
309     *  @class This method uses the <code>Introspect</code> method on the standard
310     *  <code>org.freedesktop.DBus.Introspectable</code> interface to map a given
311     *  D-Bus interface to a JavaScript object. The produced object works as
312     *  <code>this</code> in all its methods.<br/> Note that introspecting is
313     *  not done asynchronously so the function call will block until the service
314     *  responds.
315     *  <br/>
316     *  for example:<br/>
317     *  <code>
318     *  var myinterface = DBus.getInterface( mydest, mypath, myinterface);<br/>
319     *  var myname = "";<br/>
320     *  myinterface.getName.onreply = function(name){<br/>
321     *  &nbsp;&nbsp;myname = name;<br/>
322     *  };<br/>
323     *  myinterface.getName.onerror = function(message){<br/>
324     *  &nbsp;&nbsp;alert('name getting failed:'+message );<br/>
325     *  };<br/>
326     *  myinterface.getName( me );<br/>
327     *  </code>
328     *  @param dest {String} destination
329     *  @param path {String} object path
330     *  @param inter {String} interface
331     */
332 DBus.prototype.getInterface = function(dest, path, inter){
333     var doc = null;
334     var introspect = dbus.getMethod(dbus.SESSION, dest, path,
335                                     "Introspect",
336                                     "org.freedesktop.DBus.Introspectable" );
337     introspect.onreply = function(s){
338         doc = s.replace(/^\s*|\s*$/g,"");
339     };
340     introspect.onerror = function(s){
341         doc = null;
342     };
343     introspect.async = false;
344     introspect();
345
346     if (doc === null) {
347         return null;
348     }
349
350     doc = (new DOMParser()).parseFromString(doc, "text/xml");
351
352     return this.parseInterface(doc, dest, path, inter);
353 };
354
355 /* Conversion functions for default conversion types
356  * These are offered mainly just for completeness, there is usually no point in
357  * actually using them...
358  */
359 DBus.prototype.Boolean = function (val) { return val; };
360 DBus.prototype.Double = function (val) { return val; };
361 DBus.prototype.String = function (val) { return val; };
362 DBus.prototype.Array = function (val) { return val; };
363 DBus.prototype.Dict = function (val) { return val; };