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