689 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			689 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| var util = Require('util'); // util.inherits
 | |
| var net = Require('net');
 | |
| 
 | |
| var handshake = Require('x11/core/handshake');
 | |
| //var xevents = require('./xevents');
 | |
| 
 | |
| var EventEmitter = Require('events').EventEmitter;
 | |
| var PackStream = Require('x11/core/unpackstream');
 | |
| var hexy = Require('x11/core/hexy').hexy;
 | |
| 
 | |
| var Buffer = Require('buffer').Buffer;
 | |
| // add 'unpack' method for buffer
 | |
| Require('x11/core/unpackbuffer').addUnpack(Buffer);
 | |
| 
 | |
| var os = require('os');
 | |
| 
 | |
| var xerrors = Require('x11/core/xerrors');
 | |
| var coreRequests = Require('x11/core/corereqs');
 | |
| var stdatoms = Require('x11/core/stdatoms');
 | |
| var em = Require('x11/core/eventmask').eventMask;
 | |
| 
 | |
| function stash ()
 | |
| {
 | |
|     Require('x11/core/ext/apple-wm');
 | |
|     Require('x11/core/ext/big-requests');
 | |
|     Require('x11/core/ext/composite');
 | |
|     Require('x11/core/ext/damage');
 | |
|     Require('x11/core/ext/dpms');
 | |
|     Require('x11/core/ext/fixes');
 | |
|     Require('x11/core/ext/glxconstants');
 | |
|     Require('x11/core/ext/glx');
 | |
|     Require('x11/core/ext/glxrender');
 | |
|     Require('x11/core/ext/randr');
 | |
|     Require('x11/core/ext/render');
 | |
|     Require('x11/core/ext/screen-saver');
 | |
|     Require('x11/core/ext/shape');
 | |
|     Require('x11/core/ext/xc-misc');
 | |
|     Require('x11/core/ext/xtest');
 | |
| }
 | |
| if (process.argv.indexOf('-buildx11')!=-1) stash();
 | |
| 
 | |
| function XClient(displayNum, screenNum, options)
 | |
| {
 | |
|     EventEmitter.call(this);
 | |
|     this.options = options ? options : {};
 | |
| 
 | |
|     // TODO: this is probably not used
 | |
|     this.core_requests = {};
 | |
|     this.ext_requests = {};
 | |
| 
 | |
|     this.displayNum = displayNum;
 | |
|     this.screenNum = screenNum;
 | |
| }
 | |
| util.inherits(XClient, EventEmitter);
 | |
| 
 | |
| XClient.prototype.init = function(stream)
 | |
| {
 | |
|     this.stream = stream;
 | |
| 
 | |
|     this.authHost = stream.remoteAddress;
 | |
|     // Node v0.10.x does not have stream.remoteFamily, so dig in to find it
 | |
|     this.authFamily = stream._getpeername ? stream._getpeername().family : stream.remoteFamily;
 | |
|     if (!this.authHost || this.authHost === '127.0.0.1' || this.authHost === '::1') {
 | |
|       this.authHost = os.hostname();
 | |
|       this.authFamily = null;
 | |
|     }
 | |
| 
 | |
|     var pack_stream = new PackStream();
 | |
| 
 | |
|     // data received from stream is dispached to
 | |
|     // read requests set by calls to .unpack and .unpackTo
 | |
|     //stream.pipe(pack_stream);
 | |
| 
 | |
|     // pack_stream write requests are buffered and
 | |
|     // flushed to stream as result of call to .flush
 | |
|     // TODO: listen for drain event and flush automatically
 | |
|     //pack_stream.pipe(stream);
 | |
|     var client = this;
 | |
|     pack_stream.on('data', function( data ) {
 | |
|         //console.error(hexy(data, {prefix: 'from packer '}));
 | |
|         //for (var i=0; i < data.length; ++i)
 | |
|         //   console.log('<<< ' + data[i]);
 | |
|         stream.write(data);
 | |
|     });
 | |
|     stream.on('data', function( data ) {
 | |
|         //console.error(hexy(data, {prefix: 'to unpacker '}));
 | |
|         //for (var i=0; i < data.length; ++i)
 | |
|         //   console.log('>>> ' + data[i]);
 | |
|         pack_stream.write(data);
 | |
|     });
 | |
|     stream.on('end', function() {
 | |
|         client.emit('end');
 | |
|     });
 | |
| 
 | |
|     this.pack_stream = pack_stream;
 | |
| 
 | |
|     this.rsrc_id = 0; // generated for each new resource
 | |
|     var cli = this;
 | |
|     if (cli.options.debug) {
 | |
|       this.seq_num_ = 0;
 | |
|       this.seq2stack = {}; // debug: map seq_num to stack at the moment request was issued
 | |
|       Object.defineProperty(cli, "seq_num", {
 | |
|         set : function(v) {
 | |
|           cli.seq_num_ = v;
 | |
|           var err = new Error();
 | |
|           Error.captureStackTrace(err, arguments.callee);
 | |
|           err.timestamp = Date.now();
 | |
|           cli.seq2stack[client.seq_num] = err;
 | |
|         },
 | |
|         get: function() {
 | |
| 	  return cli.seq_num_;
 | |
|         }
 | |
|       });
 | |
|     } else {
 | |
|       this.seq_num = 0;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     // in/out packets indexed by sequence ID
 | |
|     this.replies = {};
 | |
|     this.atoms = stdatoms;
 | |
|     this.atom_names = (function() {
 | |
|         var names = {};
 | |
|         Object.keys(stdatoms).forEach(function(key) {
 | |
|             names[stdatoms[key]] = key;
 | |
|         });
 | |
| 
 | |
|         return names;
 | |
|     })();
 | |
| 
 | |
|     this.eventMask = em;
 | |
| 
 | |
|     this.event_consumers = {}; // maps window id to eventemitter TODO: bad name
 | |
|     this.eventParsers = {};
 | |
|     this.errorParsers = {};
 | |
|     this._extensions = {};
 | |
| 
 | |
|     this.importRequestsFromTemplates(this, coreRequests);
 | |
| 
 | |
|     this.startHandshake();
 | |
|     this._closing = false;
 | |
|     this._unusedIds = [];
 | |
| }
 | |
| 
 | |
| // TODO: close() = set 'closing' flag, watch it in replies and writeQueue, terminate if empty
 | |
| XClient.prototype.terminate = function()
 | |
| {
 | |
|     this.stream.end();
 | |
| }
 | |
| 
 | |
| // GetAtomName used as cheapest non-modifying request with reply
 | |
| // 3 - id for shortest standard atom, "ARC"
 | |
| XClient.prototype.ping = function(cb) {
 | |
|    var start = Date.now();
 | |
|    this.GetAtomName(3, function(err, str) {
 | |
|       if (err) return cb(err);
 | |
|       return cb(null, Date.now() - start);
 | |
|    });
 | |
| }
 | |
| 
 | |
| XClient.prototype.close = function(cb) {
 | |
|     var cli = this;
 | |
|     cli.ping(function(err) {
 | |
|       if (err) return cb(err);
 | |
|       cli.terminate();
 | |
|       if (cb) cb();
 | |
|     });
 | |
|     cli._closing = true;
 | |
| }
 | |
| 
 | |
| XClient.prototype.importRequestsFromTemplates = function(target, reqs)
 | |
| {
 | |
|     var client = this;
 | |
|     this.pending_atoms = {};
 | |
|     for (var r in reqs)
 | |
|     {
 | |
|         // r is request name
 | |
|         target[r] = (function(reqName) {
 | |
| 
 | |
|             var reqFunc = function req_proxy() {
 | |
| 
 | |
|             if (client._closing)
 | |
|                throw new Error('client is in closing state');
 | |
| 
 | |
|             // simple overflow handling (this means that currently there is no way to have more than 65535 requests in the queue
 | |
|             // TODO: edge cases testing
 | |
|             if (client.seq_num == 65535)
 | |
|                client.seq_num = 0;
 | |
|             else
 | |
|                client.seq_num++;
 | |
| 
 | |
|             // is it fast?
 | |
|             var args = Array.prototype.slice.call(req_proxy.arguments);
 | |
| 
 | |
|             var callback = args.length > 0 ? args[args.length - 1] : null;
 | |
|             if (callback && callback.constructor.name != 'Function')
 | |
|                 callback = null;
 | |
| 
 | |
|             // TODO: see how much we can calculate in advance (not in each request)
 | |
|             var reqReplTemplate = reqs[reqName];
 | |
|             var reqTemplate  = reqReplTemplate[0];
 | |
|             var templateType = typeof reqTemplate;
 | |
| 
 | |
|             if (templateType == 'object')
 | |
|                 templateType = reqTemplate.constructor.name;
 | |
| 
 | |
|             if (templateType == 'function')
 | |
|             {
 | |
|                  if (reqName === 'InternAtom') {
 | |
|                      var value = req_proxy.arguments[1];
 | |
|                      if (client.atoms[value]) {
 | |
|                          -- client.seq_num;
 | |
|                          return setImmediate(function() {
 | |
|                              callback(undefined, client.atoms[value]);
 | |
|                          });
 | |
|                      } else {
 | |
|                          client.pending_atoms[client.seq_num] = value;
 | |
|                      }
 | |
|                  }
 | |
| 
 | |
|                  // call template with input arguments (not including callback which is last argument TODO currently with callback. won't hurt)
 | |
|                  //reqPack = reqTemplate.call(args);
 | |
|                  var reqPack = reqTemplate.apply(this, req_proxy.arguments);
 | |
|                  var format = reqPack[0];
 | |
|                  var requestArguments = reqPack[1];
 | |
| 
 | |
|                  if (callback)
 | |
|                      this.replies[this.seq_num] = [reqReplTemplate[1], callback];
 | |
| 
 | |
|                  client.pack_stream.pack(format, requestArguments);
 | |
|                  var b = client.pack_stream.write_queue[0];
 | |
|                  client.pack_stream.flush();
 | |
| 
 | |
|             } else if (templateType == 'Array'){
 | |
|                  if (reqName === 'GetAtomName') {
 | |
|                      var atom = req_proxy.arguments[0];
 | |
|                      if (client.atom_names[atom]) {
 | |
|                          -- client.seq_num;
 | |
|                          return setImmediate(function() {
 | |
|                              callback(undefined, client.atom_names[atom]);
 | |
|                          });
 | |
|                      } else {
 | |
|                          client.pending_atoms[client.seq_num] = atom;
 | |
|                      }
 | |
|                  }
 | |
| 
 | |
|                  var format = reqTemplate[0];
 | |
|                  var requestArguments = [];
 | |
| 
 | |
|                  for (var a = 0; a < reqTemplate[1].length; ++a)
 | |
|                      requestArguments.push(reqTemplate[1][a]);
 | |
|                  for (var a in args)
 | |
|                      requestArguments.push(args[a]);
 | |
| 
 | |
|                  if (callback)
 | |
|                      this.replies[this.seq_num] = [reqReplTemplate[1], callback];
 | |
| 
 | |
|                  client.pack_stream.pack(format, requestArguments);
 | |
|                  client.pack_stream.flush();
 | |
|             } else {
 | |
|                  throw 'unknown request format - ' + templateType;
 | |
|             }
 | |
|         }
 | |
|         return reqFunc;
 | |
|         })(r);
 | |
|     }
 | |
| }
 | |
| 
 | |
| XClient.prototype.AllocID = function()
 | |
| {
 | |
|   if (this._unusedIds.length > 0) {
 | |
|     return this._unusedIds.pop();
 | |
|   }
 | |
|   // TODO: handle overflow (XCMiscGetXIDRange from XC_MISC ext)
 | |
|   this.display.rsrc_id++;
 | |
|   return (this.display.rsrc_id << this.display.rsrc_shift) + this.display.resource_base;
 | |
| };
 | |
| 
 | |
| XClient.prototype.ReleaseID = function(id) {
 | |
|   this._unusedIds.push(id);
 | |
| };
 | |
| 
 | |
| // TODO: move core events unpackers to corereqs.js
 | |
| XClient.prototype.unpackEvent = function(type, seq, extra, code, raw, headerBuf)
 | |
| {
 | |
|     var event = {}; // TODO: constructor & base functions
 | |
|     // Remove the most significant bit. See Chapter 1, Event Format section in X11 protocol
 | |
|     // specification
 | |
|     type = type & 0x7F;
 | |
|     event.type = type;
 | |
|     event.seq = seq;
 | |
| 
 | |
|     var extUnpacker = this.eventParsers[type];
 | |
|     if (extUnpacker)
 | |
|     {
 | |
|         return extUnpacker(type, seq, extra, code, raw);
 | |
|     }
 | |
| 
 | |
|     if (type == 2 || type == 3 || type == 4 || type == 5 || type == 6) { // motion event
 | |
|         var values = raw.unpack('LLLssssSC');
 | |
|         //event.raw = values;
 | |
|         // TODO: use unpackTo???
 | |
|         event.name = [,,'KeyPress', 'KeyRelease', 'ButtonPress', 'ButtonRelease', 'MotionNotify'][type]
 | |
|         event.time = extra;
 | |
|         event.keycode = code;
 | |
|         event.root = values[0];
 | |
|         event.wid = values[1];
 | |
|         event.child = values[2];
 | |
|         event.rootx = values[3];
 | |
|         event.rooty = values[4];
 | |
|         event.x = values[5];
 | |
|         event.y = values[6];
 | |
|         event.buttons = values[7];
 | |
|         event.sameScreen = values[8];
 | |
|     } else if (type == 7 || type == 8) { //EnterNotify || LeaveNotify
 | |
|         event.name = type === 7 ? 'EnterNotify' : 'LeaveNotify';
 | |
|         var values = raw.unpack('LLLssssSC');
 | |
|         event.root = values[0]
 | |
|         event.wid = values[1]
 | |
|         event.child = values[2];
 | |
|         event.rootx = values[3];
 | |
|         event.rooty = values[4];
 | |
|         event.x = values[5];
 | |
|         event.y = values[6];
 | |
|         event.values = values
 | |
| 
 | |
|     } else if (type == 12) { // Expose
 | |
|         var values = raw.unpack('SSSSS');
 | |
|         event.name = 'Expose'
 | |
|         event.wid = extra;
 | |
|         event.x = values[0];
 | |
|         event.y = values[1];
 | |
|         event.width = values[2];
 | |
|         event.height = values[3];
 | |
|         event.count = values[4]; // TODO: ???
 | |
|     } else if (type == 16) { // CreateNotify
 | |
|         var values = raw.unpack('LssSSSc');
 | |
|         event.name = 'CreateNotify'
 | |
|         event.parent = extra;
 | |
|         event.wid = values[0];
 | |
|         event.x = values[1];
 | |
|         event.y = values[2];
 | |
|         event.width = values[3];
 | |
|         event.height = values[4];
 | |
|         event.borderWidth = values[5];
 | |
|         event.overrideRedirect = values[6] ? true : false;
 | |
|         // x, y, width, height, border
 | |
|     } else if (type == 17) { // destroy notify
 | |
|         var values = raw.unpack('L');
 | |
|         event.name = 'DestroyNotify'
 | |
|         event.event = extra;
 | |
|         event.wid = values[0];
 | |
|     } else if (type == 18) { // UnmapNotify
 | |
|         var values = raw.unpack('LC');
 | |
|         event.name = 'UnmapNotify'
 | |
|         event.event = extra;
 | |
|         event.wid = values[0];
 | |
|         event.fromConfigure = values[1] ? true : false;
 | |
|     } else if (type == 19) { // MapNotify
 | |
|         var values = raw.unpack('LC');
 | |
|         event.name = 'MapNotify'
 | |
|         event.event = extra;
 | |
|         event.wid = values[0];
 | |
|         event.overrideRedirect = values[1] ? true : false;
 | |
|     } else if (type == 20) {
 | |
|         var values = raw.unpack('L');
 | |
|         event.name = 'MapRequest'
 | |
|         event.parent = extra;
 | |
|         event.wid = values[0];
 | |
|     } else if (type == 22) {
 | |
|         var values = raw.unpack('LLssSSSC');
 | |
|         event.name = 'ConfigureNotify';
 | |
|         event.wid = extra;
 | |
|         // TODO rename
 | |
|         event.wid1 = values[0];
 | |
|         event.aboveSibling = values[1];
 | |
|         event.x = values[2];
 | |
|         event.y = values[3];
 | |
|         event.width = values[4];
 | |
|         event.height = values[5];
 | |
|         event.borderWidth = values[6];
 | |
|         event.overrideRedirect = values[7];
 | |
|     } else if (type == 23) {
 | |
|         var values = raw.unpack('LLssSSSS');
 | |
|         event.name = 'ConfigureRequest';
 | |
|         event.stackMode = code;
 | |
|         event.parent = extra;
 | |
|         event.wid = values[0];
 | |
|         event.sibling = values[1];
 | |
|         event.x = values[2]
 | |
|         event.y = values[3]
 | |
|         event.width = values[4]
 | |
|         event.height = values[5]
 | |
|         event.borderWidth = values[6];
 | |
|         //
 | |
|         // The value-mask indicates which components were specified in
 | |
|         // the request. The value-mask and the corresponding values are reported as given
 | |
|         // in the request. The remaining values are filled in from the current geometry of the
 | |
|         // window, except in the case of sibling and stack-mode, which are reported as None
 | |
|         // and Above (respectively) if not given in the request.
 | |
|         event.mask = values[6];
 | |
|         // 322, [ 12582925, 0, 0, 484, 316, 1, 12, 0
 | |
|         //console.log([extra, code, values]);
 | |
|     } else if (type == 28) {// PropertyNotify
 | |
|         event.name = 'PropertyNotify';
 | |
|         var values = raw.unpack('LLC');
 | |
|         event.wid = extra;
 | |
|         event.atom = values[0];
 | |
|         event.time = values[1];
 | |
|         event.state = values[2];
 | |
|     } else if (type == 29) {// SelectionClear
 | |
|         event.name = 'SelectionClear';
 | |
|         event.time = extra;
 | |
|         var values = raw.unpack('LL');
 | |
|         event.owner = values[0];
 | |
|         event.selection = values[1];
 | |
|     } else if (type == 30) {// SelectionRequest
 | |
|         event.name = 'SelectionRequest';
 | |
|         // TODO check this
 | |
|         event.time = extra;
 | |
|         var values = raw.unpack('LLLLL');
 | |
|         event.owner = values[0];
 | |
|         event.requestor = values[1];
 | |
|         event.selection = values[2];
 | |
|         event.target = values[3];
 | |
|         event.property = values[4];
 | |
|     } else if (type == 31) {// SelectionNotify
 | |
|         event.name = 'SelectionNotify';
 | |
|         // TODO check this
 | |
|         event.time = extra;
 | |
|         var values = raw.unpack('LLLL');
 | |
|         event.requestor = values[0];
 | |
|         event.selection = values[1];
 | |
|         event.target = values[2];
 | |
|         event.property = values[3];
 | |
|     } else if (type == 33) {// ClientMessage
 | |
|         event.name = 'ClientMessage';
 | |
|         event.format = code;
 | |
|         event.wid = extra;
 | |
|         event.message_type = raw.unpack('L')[0];
 | |
|         var format = (code === 32) ? 'LLLLL' : (code === 16) ? 'SSSSSSSSSS' : 'CCCCCCCCCCCCCCCCCCCC';
 | |
|         event.data = raw.unpack(format, 4);
 | |
|     } else if (type == 34) {
 | |
|         event.name = 'MappingNotify';
 | |
|         event.request = headerBuf[4];
 | |
|         event.firstKeyCode = headerBuf[5];
 | |
|         event.count = headerBuf[6];
 | |
|     }
 | |
|     return event;
 | |
| }
 | |
| 
 | |
| XClient.prototype.expectReplyHeader = function()
 | |
| {
 | |
|     // TODO: move error parsers to corereqs.js
 | |
| 
 | |
|     var client = this;
 | |
|     client.pack_stream.get( 8, function(headerBuf) {
 | |
|             var res = headerBuf.unpack('CCSL');
 | |
|             var type = res[0];
 | |
|             var seq_num = res[2];
 | |
|             var bad_value = res[3];
 | |
| 
 | |
|             if (type == 0)
 | |
|             {
 | |
|                 var error_code = res[1];
 | |
|                 var error = new Error();
 | |
|                 error.error = error_code;
 | |
|                 error.seq = seq_num;
 | |
|                 if (client.options.debug) {
 | |
|                   error.longstack = client.seq2stack[error.seq]
 | |
|                   console.log(client.seq2stack[error.seq].stack);
 | |
|                 }
 | |
| 
 | |
|                 // unpack error packet (32 bytes for all error types, 8 of them in CCSL header)
 | |
|                 client.pack_stream.get(24, function(buf) {
 | |
| 
 | |
| 		    var res = buf.unpack('SC');
 | |
| 	   	    error.message = xerrors.errorText[error_code];
 | |
| 		    error.badParam = bad_value;
 | |
| 		    error.minorOpcode = res[0];
 | |
| 		    error.majorOpcode = res[1];
 | |
| 
 | |
| 	            var extUnpacker = client.errorParsers[error_code];
 | |
|                     if (extUnpacker) {
 | |
|                       extUnpacker(error, error_code, seq_num, bad_value, buf);
 | |
|                     }
 | |
| 
 | |
|                     var handler = client.replies[seq_num];
 | |
|                     if (handler) {
 | |
|                         var callback = handler[1];
 | |
|                         var handled = callback(error);
 | |
|                         if (!handled)
 | |
|                             client.emit('error', error);
 | |
|                         // TODO: should we delete seq2stack and reply even if there is no handler?
 | |
| 			if (client.options.debug)
 | |
|                           delete client.seq2stack[seq_num];
 | |
|                         delete client.replies[seq_num];
 | |
|                     } else
 | |
|                         client.emit('error', error);
 | |
|                     client.expectReplyHeader();
 | |
|                 } );
 | |
|                 return;
 | |
|             } else if (type > 1)
 | |
|             {
 | |
|                 client.pack_stream.get(24, function(buf) {
 | |
|                     var extra = res[3];
 | |
|                     var code = res[1];
 | |
|                     var ev = client.unpackEvent(type, seq_num, extra, code, buf, headerBuf);
 | |
| 
 | |
|                     // raw event 32-bytes packet (primarily for use in SendEvent);
 | |
|                     // TODO: Event::pack based on event parameters, inverse to unpackEvent
 | |
|                     ev.rawData = new Buffer(32);
 | |
|                     headerBuf.copy(ev.rawData);
 | |
|                     buf.copy(ev.rawData, 8);
 | |
| 
 | |
|                     client.emit('event', ev);
 | |
|                     var ee = client.event_consumers[ev.wid];
 | |
|                     if (ee) {
 | |
|                        ee.emit('event', ev);
 | |
|                     }
 | |
|                     if (ev.parent) {
 | |
|                        ee = client.event_consumers[ev.parent];
 | |
|                        if (ee)
 | |
|                          ee.emit('child-event', ev);
 | |
|                     }
 | |
|                     client.expectReplyHeader();
 | |
|                 } );
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             var opt_data = res[1];
 | |
|             var length_total = res[3];            // in 4-bytes units, _including_ this header
 | |
|             var bodylength = 24 + length_total*4; // 24 is rest if 32-bytes header
 | |
| 
 | |
|             client.pack_stream.get( bodylength, function( data ) {
 | |
| 
 | |
|                 var handler = client.replies[seq_num];
 | |
|                 if (handler) {
 | |
|                     var unpack = handler[0];
 | |
|                     if (client.pending_atoms[seq_num]) {
 | |
|                         opt_data = seq_num;
 | |
|                     }
 | |
| 
 | |
|                     var result = unpack.call(client, data, opt_data);
 | |
|                     var callback = handler[1];
 | |
|                     callback(null, result);
 | |
|                     // TODO: add multiple replies flag and delete handler only after last reply (eg ListFontsWithInfo)
 | |
|                     delete client.replies[seq_num];
 | |
|                 }
 | |
|                 // wait for new packet from server
 | |
|                 client.expectReplyHeader();
 | |
|             });
 | |
|         }
 | |
|     );
 | |
| }
 | |
| 
 | |
| XClient.prototype.startHandshake = function() {
 | |
|     var client = this;
 | |
| 
 | |
|     handshake.writeClientHello(this.pack_stream, this.displayNum, this.authHost, this.authFamily);
 | |
|     handshake.readServerHello(this.pack_stream, function(err, display)
 | |
|     {
 | |
|         if (err) {
 | |
|             client.emit('error', err);
 | |
|             return;
 | |
|         }
 | |
|         client.expectReplyHeader();
 | |
|         client.display = display;
 | |
|         display.client = client;
 | |
|         client.emit('connect', display);
 | |
|     });
 | |
| }
 | |
| 
 | |
| XClient.prototype.require = function(extName, callback)
 | |
| {
 | |
|    var self = this;
 | |
|    var ext = this._extensions[extName];
 | |
|    if (ext) {
 | |
|        return process.nextTick(function() {
 | |
|            callback(null, ext);
 | |
|        });
 | |
|    }
 | |
| 
 | |
|    ext = Require('x11/core/ext/' + extName);
 | |
|    ext.requireExt(this.display, function(err, _ext) {
 | |
|        if (err) {
 | |
|            return callback(err);
 | |
|        }
 | |
| 
 | |
|        self._extensions[extName] = _ext;
 | |
|        callback(null, _ext);
 | |
|    });
 | |
| };
 | |
| 
 | |
| module.exports.createClient = function(options, initCb)
 | |
| {
 | |
|     if (typeof options === 'function') {
 | |
|         initCb = options;
 | |
|         options = {};
 | |
|     }
 | |
| 
 | |
|     if (!options) options = {};
 | |
| 
 | |
|     var display = options.display;
 | |
|     if (!display)
 | |
|         display = (process.env.DISPLAY) ? process.env.DISPLAY : ':0';
 | |
| 
 | |
|     var displayMatch = display.match(/^(?:[^:]*?\/)?(.*):(\d+)(?:.(\d+))?$/);
 | |
|     if (!displayMatch)
 | |
|        throw new Error("Cannot parse display");
 | |
| 
 | |
|     var host = displayMatch[1];
 | |
| 
 | |
|     var displayNum = displayMatch[2];
 | |
|     if (!displayNum)
 | |
|         displayNum = 0;
 | |
|     var screenNum = displayMatch[3];
 | |
|     if (!screenNum)
 | |
|         screenNum = 0;
 | |
| 
 | |
|     // open stream
 | |
|     var stream;
 | |
|     var connected = false;
 | |
|     var cbCalled = false;
 | |
|     var socketPath;
 | |
| 
 | |
|     // try local socket on non-windows platforms
 | |
|     if ( ['cygwin', 'win32', 'win64'].indexOf(process.platform) < 0 )
 | |
|     {
 | |
|         if (process.platform == 'darwin' || process.platform == 'mac')
 | |
|         {
 | |
|             // socket path on OSX is /tmp/launch-(some id)/org.x:0
 | |
|             if (display[0] == '/')
 | |
|             {
 | |
|                 socketPath = display;
 | |
|             }
 | |
|         } else if(!host)
 | |
|             socketPath = '/tmp/.X11-unix/X' + displayNum;
 | |
|     }
 | |
|     var client = new XClient(displayNum, screenNum, options);
 | |
| 
 | |
|     var connectStream = function() {
 | |
|         if (socketPath) {
 | |
|             stream = net.createConnection(socketPath);
 | |
|         } else {
 | |
|             stream = net.createConnection(6000 + parseInt(displayNum), host);
 | |
|         }
 | |
|         stream.on('connect', function() {
 | |
|             connected = true;
 | |
|             client.init(stream);
 | |
|         });
 | |
|         stream.on('error', function(err) {
 | |
|             if (!connected && socketPath && err.code === 'ENOENT') {
 | |
|                 // Retry connection with TCP on localhost
 | |
|                 socketPath = null;
 | |
|                 host = 'localhost';
 | |
|                 connectStream();
 | |
|             } else if (initCb && !cbCalled) {
 | |
|                 cbCalled = true;
 | |
|                 initCb(err);
 | |
|             } else {
 | |
|                 client.emit('error', err);
 | |
|             }
 | |
|         });
 | |
|     };
 | |
|     connectStream();
 | |
|     if (initCb)
 | |
|     {
 | |
|         client.on('connect', function(display) {
 | |
|             // opt-in BigReq
 | |
|             if (!options.disableBigRequests) {
 | |
|                 client.require('big-requests', function(err, BigReq) {
 | |
|                     if (err)
 | |
|                         return initCb(err)
 | |
|                     BigReq.Enable(function(err, maxLen) {
 | |
|                         display.max_request_length = maxLen;
 | |
| 	                cbCalled = true;
 | |
|                         initCb(undefined, display);
 | |
|                     });
 | |
|                 });
 | |
|             } else {
 | |
| 	        cbCalled = true;
 | |
|                 initCb(undefined, display);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
|     return client;
 | |
| }
 |