mirror of
https://github.com/danbulant/node-x11
synced 2026-06-11 02:30:22 +00:00
293 lines
9.7 KiB
JavaScript
293 lines
9.7 KiB
JavaScript
var util = require('util'); // util.inherits
|
|
var net = require('net');
|
|
|
|
var handshake = require('./handshake');
|
|
//var xevents = require('./xevents');
|
|
|
|
var EventEmitter = require('events').EventEmitter;
|
|
var PackStream = require('./unpackstream');
|
|
var coreRequestsTemplate = require('./corereqs');
|
|
var hexy = require('./hexy').hexy;
|
|
|
|
var Buffer = require('buffer').Buffer;
|
|
// add 'unpack' method for buffer
|
|
require('./unpackbuffer').addUnpack(Buffer);
|
|
|
|
var xerrors = require('./xerrors');
|
|
var coreRequests = require('./corereqs');
|
|
|
|
function XClient(stream)
|
|
{
|
|
EventEmitter.call(this);
|
|
this.stream = stream;
|
|
|
|
this.core_requests = {};
|
|
this.ext_requests = {};
|
|
|
|
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);
|
|
|
|
pack_stream.on('data', function( data ) {
|
|
//console.error(hexy(data, {prefix: 'from packer '}));
|
|
stream.write(data);
|
|
});
|
|
stream.on('data', function( data ) {
|
|
//console.error(hexy(data, {prefix: 'to unpacker '}));
|
|
pack_stream.write(data);
|
|
});
|
|
|
|
this.pack_stream = pack_stream;
|
|
|
|
this.rcrc_id = 0; // generated for each new resource
|
|
this.seq_num = 0; // incremented in each request. (even if we don't expect reply)
|
|
|
|
// in/out packets indexed by sequence ID
|
|
this.requests = {};
|
|
this.replies = {};
|
|
this.events = {};
|
|
|
|
this.importRequestsFromTemplates(this, coreRequests);
|
|
this.startHandshake();
|
|
|
|
// TODO: bad name
|
|
this.event_consumers = {}; // maps window id to eventemitter
|
|
|
|
// import available extentions
|
|
// TODO: lazy import on first call?
|
|
/*
|
|
this.ext = {};
|
|
this.ListExtensions( function(err, extentionsList ) {
|
|
for (ext in extentionsList) {
|
|
var extRequests = require('./ext/' + extentionsList[ext]);
|
|
// TODO: need to call QueryExtention to get [major opcode, first event, first error]
|
|
importRequestsFromTemplates(this, extRequests);
|
|
}
|
|
}
|
|
*/
|
|
// init comon extentions
|
|
|
|
}
|
|
util.inherits(XClient, EventEmitter);
|
|
|
|
// TODO: close() = set 'closing' flag, watch it in replies and writeQueue, terminate if empty
|
|
XClient.prototype.terminate = function()
|
|
{
|
|
this.stream.end();
|
|
}
|
|
|
|
XClient.prototype.importRequestsFromTemplates = function(target, reqs)
|
|
{
|
|
var client = this;
|
|
for (r in reqs)
|
|
{
|
|
// r is request name
|
|
target[r] = (function(reqName) {
|
|
var reqFunc = function req_proxy() {
|
|
client.seq_num++; // TODO: handle overflow (seq should be last 15 (?) bits of the number
|
|
// 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')
|
|
{
|
|
// call template with input arguments (not including callback which is last argument TODO currently with callback. won't hurt)
|
|
//reqPack = reqTemplate.call(args);
|
|
reqPack = reqTemplate.apply(this, req_proxy.arguments);
|
|
var format = reqPack[0];
|
|
var requestArguments = reqPack[1];
|
|
|
|
if (callback)
|
|
this.replies[this.seq_num] = [reqName, callback];
|
|
|
|
//console.error([format, requestArguments]);
|
|
client.pack_stream.pack(format, requestArguments);
|
|
client.pack_stream.flush();
|
|
} else if (templateType == 'Array'){
|
|
var format = reqTemplate[0];
|
|
var requestArguments = reqTemplate[1];
|
|
for (a in args)
|
|
requestArguments.push(args[a]);
|
|
|
|
if (callback)
|
|
this.replies[this.seq_num] = [reqName, callback];
|
|
|
|
//console.error([format, requestArguments]);
|
|
client.pack_stream.pack(format, requestArguments);
|
|
client.pack_stream.flush();
|
|
} else {
|
|
throw 'unknown request format - ' + templateType;
|
|
}
|
|
}
|
|
return reqFunc;
|
|
})(r);
|
|
}
|
|
}
|
|
|
|
XClient.prototype.AllocID = function()
|
|
{
|
|
// TODO: handle overflow (XCMiscGetXIDRange from XC_MISC ext)
|
|
// TODO: unused id buffer
|
|
this.display.rsrc_id++;
|
|
return (this.display.rsrc_id << this.display.rsrc_shift) + this.display.resource_base;
|
|
}
|
|
|
|
XClient.prototype.unpackEvent = function(type, seq, extra, raw)
|
|
{
|
|
var event = {}; // TODO: constructor & base functions
|
|
event.type = type;
|
|
event.seq = seq;
|
|
|
|
if (type == 6) { // motion event
|
|
var values = raw.unpack('LLLSSSSSC'); //TODO: should be LLLLsssssSC
|
|
//event.raw = values;
|
|
// TODO: use unpackTo???
|
|
event.time = extra;
|
|
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 == 12) { // Expose
|
|
var values = raw.unpack('SSSSS');
|
|
event.wid = extra;
|
|
event.x = values[0];
|
|
event.y = values[1];
|
|
event.width = values[2];
|
|
event.height = values[3];
|
|
event.count = values[4]; // TODO: ???
|
|
}
|
|
return event;
|
|
}
|
|
|
|
XClient.prototype.expectReplyHeader = function()
|
|
{
|
|
// TODO: BigReq!!!!
|
|
|
|
var client = this;
|
|
client.pack_stream.unpack(
|
|
'CCSL', function(res) {
|
|
var type = res[0];
|
|
var seq_num = res[2];
|
|
if (type == 0)
|
|
{
|
|
var error_code = res[1];
|
|
var error = {};
|
|
error.code = error_code;
|
|
error.seq = seq_num;
|
|
error.message = xerrors.errorText[error_code];
|
|
|
|
// unpack error packet (32 bytes for all error types, 8 of them in CCSL header)
|
|
client.pack_stream.get(24, function(buf) {
|
|
// TODO: dispatch, use sequence number
|
|
//TODO: add more generic way to read common values
|
|
// if (error_code == 14)
|
|
{
|
|
var res = buf.unpack('LSC');
|
|
error.badParam = res[0]; // id: GC, WinID, Font, Atom etc; Value
|
|
error.minorOpcode = res[1];
|
|
error.majorOpcode = res[2];
|
|
}
|
|
console.log(error);
|
|
//client.emit('error', error);
|
|
client.expectReplyHeader();
|
|
} );
|
|
return;
|
|
} else if (type > 1)
|
|
{
|
|
client.pack_stream.get(24, function(buf) {
|
|
var extra = res[3];
|
|
var ev = client.unpackEvent(type, seq_num, extra, buf);
|
|
client.emit('event', ev);
|
|
var ee = client.event_consumers[ev.wid];
|
|
if (ee) {
|
|
ee.emit('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 reqName = handler[0];
|
|
var req = coreRequests[reqName];
|
|
var unpack = req[1];
|
|
var result = unpack( data );
|
|
var callback = handler[1];
|
|
callback(result);
|
|
}
|
|
|
|
// wait for new packet from server
|
|
client.expectReplyHeader();
|
|
});
|
|
}
|
|
);
|
|
}
|
|
|
|
XClient.prototype.startHandshake = function()
|
|
{
|
|
var client = this;
|
|
|
|
handshake.writeClientHello(this.pack_stream);
|
|
handshake.readServerHello(this.pack_stream, function(display)
|
|
{
|
|
// TODO: readServerHello can set erro state in display
|
|
// emit error in that case
|
|
client.expectReplyHeader();
|
|
client.display = display;
|
|
client.emit('connect', display);
|
|
});
|
|
}
|
|
|
|
var platformDefaultTransport = {
|
|
win32: 'tcp',
|
|
win64: 'tcp',
|
|
cygwin: 'tcp',
|
|
linux: 'unix'
|
|
// TODO: check process.platform on SmartMachine solaris box
|
|
}
|
|
|
|
module.exports.createClient = function()
|
|
{
|
|
// TODO: parse $DISPLAY
|
|
|
|
// open stream
|
|
var stream;
|
|
var defaultTransportName = platformDefaultTransport[process.platform];
|
|
// use tcp if stated explicitly or if not defined at all
|
|
if (!defaultTransportName || defaultTransportName == 'tcp')
|
|
stream = net.createConnection(6000);
|
|
if (defaultTransportName == 'unix')
|
|
stream = net.createConnection('/tmp/.X11-unix/X0');
|
|
|
|
return new XClient(stream);
|
|
}
|