/* rTerm - Ajaxterm Window Widget for Extjs (Ext.ux.rTerm) * Version: 1.0 * * Copyright 2008 (c) David W Davis * xantus@xantus.org * http://xant.us/ * * License: Same as Extjs 2.0 * * Please do not remove this header */ Ext.namespace( 'Ext.ux', 'QoDesk' ); // Can be used as a qwikioffice app module // install in system/modules/rterm if ( Ext.app && Ext.app.Module ) { QoDesk.rTerm = Ext.extend(Ext.app.Module, { moduleType: 'app', moduleId: 'rterm', init: function(){ this.launcher = { text: 'rTerm', iconCls:'x-rterm-icon', handler : this.createWindow, shortcutIconCls: 'x-rterm-shortcut', text: 'rTerm', tooltip: 'rTerm
Remote Terminal - (c) 2008 David Davis, Xantus', scope: this }; }, createWindow : function() { new Ext.ux.rTerm.App(); } }); } (function(){ var log; if ( window.console ) { log = function(m) { window.console.log(m); }; } else if ( Ext.log ) { log = window.Ext.log; } else { log = Ext.emptyFn; } Ext.ux.rTerm = function( config ) { if ( config === undefined ) config = {}; if ( !config.hostname ) config.hostname = 'localhost'; Ext.apply( this, config ); Ext.ux.rTerm.superclass.constructor.apply(this,arguments); this.initialize( config ); }; Ext.ux.rTerm.version = '1.0'; Ext.extend( Ext.ux.rTerm, Ext.Window, { initialize: function( config ) { log('init rterm window'); if ( !config.iconCls ) this.setIconClass( 'x-rterm-icon' ); if ( !Ext.ux.rTerm.keyManager ) Ext.ux.rTerm.keyManager = new Ext.ux.rTerm.KeyManager(); Ext.ux.rTerm.keyManager.register( this ); this.on( 'documentKeypress', this._keypress, this ); this.show(); this._setupTerm(); // TODO onshow this.body.addClass( 'x-rterm-content' ); this.body.mask(); this.socket.on( 'socketData', this._socketData, this ); this.socket.on( 'connect', this._socketConnected, this ); this.socket.on( 'close', this._socketDisconnected, this ); this.on( 'close', this.socket.destroy, this.socket ); this._connecting = true; this.socket.connect(); /* this.contentId = Ext.id(); this.add({ layout: 'border', border: false, items: [{ width: '100%', height: '100%', region: 'center', layout: 'fit', border: false, margins: '0', items: [{ html: '
testing
' }] }] }); */ }, initComponent: function(){ Ext.apply(this, { height: '100%', tbar: [ { text: 'Colors', window: this, pressed: true, enableToggle: true, scope: this, iconCls: 'color-button', Qtip: 'Enable/Display Colors in Terminal', toggleHandler: function(btn,state){ if(state){ this.color = true; }else{ this.color = false; } } }, '-', { text: 'Get', scope: this, iconCls: 'get-button', Qtip: 'Toggle to GET HTTP Method', enableToggle: true, toggleGroup: 'http-method', handler: function(){this.socket.method = 'GET'} }, { text: 'Post', scope: this, iconCls: 'post-button', Qtip: 'Toggle to GET HTTP Method', pressed: true, enableToggle: true, toggleGroup: 'http-method', handler: function(){this.socket.method = 'POST'} }, '->', { text: 'Copy', scope: this, iconCls: 'copy-button', Qtip: 'Copy to Browser Clipboard', handler: function(){Ext.Msg('TODO', 'Copy');} }, { text: 'Paste', scope: this, iconCls: 'paste-button', Qtip: 'Paste to Terminal', handler: function(){Ext.Msg('TODO', 'Paste');} } ] }); Ext.ux.rTerm.superclass.initComponent.apply(this, arguments); }, _socketData: function( socket, chunks ) { try { for ( var i = 0, len = chunks.length; i < len; i++ ) { var o = chunks[ i ]; if ( !o.html ) continue; if ( o.html == '' ) continue; this._dterm.innerHTML = o.html; } } catch(e) { log( 'error parsing data from server:'+e.message ); }; }, _socketConnected: function( socket ) { this.body.unmask(); log('connected'); this._connecting = false; this._update(); }, _socketDisconnected: function( socket ) { log('disconnected'); this.body.mask(); }, _setupTerm: function() { if ( this.sid ) return; log('setup term'); this.sid = "" + Math.round(Math.random()*1000000000); Ext.applyIf( this, { _queueTimeout: 15, // ms cols: 80, rows: 25, color: true }); this._timeout = null; this.error_timeout = null; this.keybuf = []; this.rmax = 1; var div = this.body.dom; // this.dstat = document.createElement('pre'); // this.sdebug = document.createElement('span'); this._dterm = document.createElement('div'); /* this.sled = document.createElement('span'); this.opt_color = document.createElement('a'); var opt_paste = document.createElement('a'); this.sled.appendChild(document.createTextNode('\xb7')); this.sled.className='off'; this.dstat.appendChild(this.sled); this.dstat.appendChild(document.createTextNode(' ')); this._opt_add(this.opt_color,'Colors'); this.opt_color.className='on'; this._opt_add(opt_paste,'Paste'); */ // this.dstat.appendChild(this.sdebug); // this.dstat.className='stat'; // div.appendChild(this.dstat); div.appendChild(this._dterm); /* if(this.opt_color.addEventListener) { this.opt_color.addEventListener('click',this._do_color.createDelegate( this ),true); opt_paste.addEventListener('click',this._do_paste.createDelegate( this ),true); } else { this.opt_color.attachEvent("onclick", this._do_color.createDelegate( this )); opt_paste.attachEvent("onclick", this._do_paste.createDelegate( this )); } */ }, _update: function() { if ( this._connecting ) return; send_obj = { s: this.sid, w: this.cols, h: this.rows, k: this.keybuf.join('') }; if(this.color == true) send_obj.c = true; this.socket.send([send_obj]); this.keybuf = []; }, _queue: function(s) { this.keybuf.push(s); log('buffer:'+this.keybuf.join('')); if ( this._connecting ) return; if ( this._timeout ) window.clearTimeout(this._timeout); this._timeout = this._update.defer( this._queueTimeout, this ); }, _iekeys: { 9:1,8:1,27:1,33:1,34:1,35:1,36:1,37:1,38:1, 39:1,40:1,45:1,46:1,112:1,113:1,114:1,115:1, 116:1,117:1,118:1,119:1,120:1,121:1,122:1,123:1 }, _keypress: function(ev) { // log( 'keypress:'+ev.keyCode+' '+ev.which+' '+ev.button); if (!ev) ev = window.event; if ( Ext.isIE ) { // keydown // log("kd keyCode="+ev.keyCode+" which="+ev.which+" shiftKey="+ev.shiftKey+" ctrlKey="+ev.ctrlKey+" altKey="+ev.altKey); if (this._iekeys[ev.keyCode] || ev.ctrlKey || ev.altKey) { ev.which = 0; } else return; } // log("kp keyCode="+ev.keyCode+" which="+ev.which+" shiftKey="+ev.shiftKey+" ctrlKey="+ev.ctrlKey+" altKey="+ev.altKey); // return false; // else // if (!ev.ctrlKey || ev.keyCode==17) // return; // var kc; var k=""; if (ev.keyCode) kc=ev.keyCode; if (ev.which) kc=ev.which; if (ev.button >= 0) kc=ev.button + 1; if (ev.altKey) { if (kc>=65 && kc<=90) kc+=32; if (kc>=97 && kc<=122) { k=String.fromCharCode(27)+String.fromCharCode(kc); } } else if (ev.ctrlKey) { if (kc>=65 && kc<=90) k=String.fromCharCode(kc-64); // Ctrl-A..Z else if (kc>=97 && kc<=122) k=String.fromCharCode(kc-96); // Ctrl-A..Z else if (kc==54) k=String.fromCharCode(30); // Ctrl-^ else if (kc==109) k=String.fromCharCode(31); // Ctrl-_ else if (kc==219) k=String.fromCharCode(27); // Ctrl-[ else if (kc==220) k=String.fromCharCode(28); // Ctrl-\ else if (kc==221) k=String.fromCharCode(29); // Ctrl-] else if (kc==219) k=String.fromCharCode(29); // Ctrl-] else if (kc==219) k=String.fromCharCode(0); // Ctrl-@ } else if (ev.which==0) { if (kc==9) k=String.fromCharCode(9); // Tab else if (kc==8) k=String.fromCharCode(127); // Backspace else if (kc==27) k=String.fromCharCode(27); // Escape else { if (kc==33) k="[5~"; // PgUp else if (kc==34) k="[6~"; // PgDn else if (kc==35) k="[4~"; // End else if (kc==36) k="[1~"; // Home else if (kc==37) k="[D"; // Left else if (kc==38) k="[A"; // Up else if (kc==39) k="[C"; // Right else if (kc==40) k="[B"; // Down else if (kc==45) k="[2~"; // Ins else if (kc==46) k="[3~"; // Del else if (kc==112) k="[[A"; // F1 else if (kc==113) k="[[B"; // F2 else if (kc==114) k="[[C"; // F3 else if (kc==115) k="[[D"; // F4 else if (kc==116) k="[[E"; // F5 else if (kc==117) k="[17~"; // F6 else if (kc==118) k="[18~"; // F7 else if (kc==119) k="[19~"; // F8 else if (kc==120) k="[20~"; // F9 else if (kc==121) k="[21~"; // F10 else if (kc==122) k="[23~"; // F11 else if (kc==123) k="[24~"; // F12 if (k.length) { k=String.fromCharCode(27)+k; } } } else { if (kc==8) k=String.fromCharCode(127); // Backspace else k=String.fromCharCode(kc); } if(k.length) { if(k=="+") { this._queue("%2B"); } else { // this._queue(escape(k)); this._queue(k); } } ev.cancelBubble=true; if (ev.stopPropagation) ev.stopPropagation(); if (ev.preventDefault) ev.preventDefault(); return false; }, _error: function() { //this.sled.className='off'; log("Connection lost timeout ts:"+((new Date).getTime())); }, _opt_add: function(opt,name) { opt.className='off'; opt.innerHTML=' '+name+' '; this.dstat.appendChild(opt); this.dstat.appendChild(document.createTextNode(' ')); }, /* _do_color: function(event) { var o = this.opt_color.className=(this.opt_color.className=='off')?'on':'off'; this.color = ( o == 'on' ) ? true : false; log('Color '+this.opt_color.className); }, */ _mozilla_clipboard: function() { // mozilla sucks try { netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); } catch (err) { //log('Access denied, more info'); return undefined; } var clip = Components.classes["@mozilla.org/widget/clipboard;1"].createInstance(Components.interfaces.nsIClipboard); var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable); if (!clip || !trans) { return undefined; } trans.addDataFlavor("text/unicode"); clip.getData(trans,clip.kGlobalClipboard); var str=new Object(); var strLength=new Object(); try { trans.getTransferData("text/unicode",str,strLength); } catch(err) { return ""; } if (str) str=str.value.QueryInterface(Components.interfaces.nsISupportsString); return str ? str.data.substring(0,strLength.value / 2) : ""; }, _do_paste: function(event) { var p; if (window.clipboardData) { p = window.clipboardData.getData("Text"); } else if(window.netscape) { p = this._mozilla_clipboard(); } if (p) { log('Pasted'); this._queue(encodeURIComponent(p)); } } }); Ext.reg('rterm', Ext.ux.rTerm); Ext.ux.rTerm.Filter = function( config ) { Ext.ux.rTerm.Filter.superclass.constructor.apply(this,arguments); }; Ext.extend( Ext.ux.rTerm.Filter, Ext.ux.Sprocket.Filter.Stream, { get: function( ) { var chunks = Ext.ux.rTerm.Filter.superclass.get.apply( this, arguments ); // filter xml blocks off the front for ( var i = 0, len = chunks.length; i < len; i++ ) chunks[ i ] = { html: chunks[ i ].replace( /^<\?xml[^>]+>/, '' ) }; return chunks; }, put: function( data ) { return data; } }); Ext.ux.rTerm.AjaxtermSocket = function( config ) { if ( config === undefined ) config = {}; Ext.apply( this, config ); this.initialize( config ); }; Ext.extend( Ext.ux.rTerm.AjaxtermSocket, Ext.util.Observable, { initialize: function( config ) { this.state = 'disconnected'; this.reqFailures = 0; this.sending = false; this.queue = []; this.defaultParams = {}; this.waitcount = 0; this.filter = new Ext.ux.rTerm.Filter(); Ext.applyIf( this, { uri: '/ajaxterm/u', method: 'POST', waitlimit: 50, } ); this.addEvents({ /** * @event socketData * Fires when a new color selected * @param {Ext.ux.rTerm.AjaxtermSocket} this * @param {Array} chunks */ socketData: true, /** * @event connect * Fires when connected * @param {Ext.ux.rTerm.AjaxtermSocket} this */ connect: true, /** * @event close * Fires when disconnected * @param {Ext.ux.rTerm.AjaxtermSocket} this */ close: true, /** * @event ioError * Fires when an input/output error occurs * @param {Ext.ux.rTerm.AjaxtermSocket} this * @param {String} error */ ioError: true, /** * @event securityError * Fires when a call to connect fails due to a security error * @param {Ext.ux.rTerm.AjaxtermSocket} this * @param {String} error */ securityError: true }); }, destroy: function() { if ( this.timer ) window.clearTimeout( this.timer ); this.disconnect(); this.purgeListeners(); }, /* methods */ connect: function() { if ( this.state == 'disconnected' ) this.onConnected(); }, disconnect: function() { this.onDisconnected(); }, send: function( chunks ) { //var chunks = this.filter.put( data ); //if ( chunks ) // this.doRequest( chunks ); for ( var i = 0, len = chunks.length; i < len; i++ ) this.queue.push( chunks[ i ] ); if ( this.timer ) window.clearTimeout( this.timer ); this.timer = this.doRequest.defer( 15, this ); }, /* same as send */ write: function() { this.send.apply( this, arguments ); }, flush: function() { if ( this.timer ) window.clearTimeout( this.timer ); this.doRequest(); }, onSocketData: function( data ) { var chunks; try { chunks = this.filter.get( data ); } catch(e) { log('Error in filter get call: '+e.message); }; try { if ( chunks.length > 0 ) this.fireEvent( 'socketData', this, chunks ); } catch(e) { log('Error in socketData event call: '+e.message); }; }, _getParams: function() { if ( this.queue.length > 0 ) { var l = this.queue.length - 1; this.defaultParams.s = this.queue[ l ].s; this.defaultParams.w = this.queue[ l ].w; this.defaultParams.h = this.queue[ l ].h; this.defaultParams.c = this.queue[ l ].c; // Ext.apply( this.defaultParams, this.queue[ this.queue.length - 1 ] ); // delete this.defaultParams.k; } var params = { k: '' }; Ext.apply( params, this.defaultParams ); for ( var i = 0, len = this.queue.length; i < len; i++ ) { if ( this.queue[ i ].k !== undefined ) params.k += this.queue[ i ].k; } this.queue = []; // log('params: '+Ext.encode(params)); return params; }, doRequest: function( data ) { if ( this.sending || this.state == 'disconnected' ) return; if ( this.queue.length == 0 ) { this.waitcount++; if ( this.waitcount < this.waitlimit ) { // log('waiting, count:'+this.waitcount); return this.timer = this.doRequest.defer( 10, this ); } } this.sending = true; this.waitcount = 0; Ext.Ajax.request({ url: this.uri, method: this.method, success: this._requestSuccess, failure: this._requestFailure, scope: this, headers: { 'If-Modified-Since': 'Sat, 1 Jan 2000 00:00:00 GMT' }, params: this._getParams() }); }, _requestSuccess: function( r, o ) { this.reqFailures = 0; this.onSocketData( r.responseText ); this.sending = false; if ( this.timer ) window.clearTimeout( this.timer ); this.timer = this.doRequest.defer( 10, this ); }, _requestFailure: function( r, o ) { this.reqFailures++; this.sending = false; if ( r.status != 200 ) { log('Connection error '+r.status+' '+r.statusText); } else { log('Connection error '+r); } if ( this.reqFailures > 10 ) { // give up this.onIOError( r.responseText ); this.onDisconnected(); } else { //this.onIOError( r.responseText ); // log('requeing data:'+Ext.encode(o.params)); log('requeing after error'); this.queue.unshift( o.params ); if ( this.timer ) window.clearTimeout( this.timer ); this.timer = this.doRequest.defer( 500, this ); } }, onConnected: function() { this.state = 'connected'; this.fireEvent( 'connect', this ); }, onDisconnected: function() { this.state = 'disconnected'; this.fireEvent( 'close', this ); }, onIOError: function( error ) { this.fireEvent( 'ioError', this, error ); }, // not used ATM onSecurityError: function( error ) { this.fireEvent( 'securityError', this, error ); } }); Ext.ux.rTerm.App = function( config ) { if ( config === undefined ) config = {}; Ext.apply( this, config ); this.initialize( config ); }; Ext.extend( Ext.ux.rTerm.App, Ext.util.Observable, { initialize: function( config ) { // TODO } }); Ext.ux.rTerm.KeyManager = function( config ) { if ( config === undefined ) config = {}; Ext.apply( this, config ); this.initialize( config ); }; Ext.extend( Ext.ux.rTerm.KeyManager, Ext.util.Observable, { initialize: function( config ) { this.terms = []; this.active = false; // Ext.EventManager.on( document, 'keydown', this.keyEvent, this ); if (Ext.isIE) document.onkeydown = this.keyEvent.createDelegate( this ); else document.onkeypress = this.keyEvent.createDelegate( this ); // Ext.EventManager.on( document, 'keypress', this.keyEvent, this ); }, keyEvent: function( ev ) { if ( !this.active ) return; if ( !this.activeWin ) { this.active = false; log('active win is gone!'); return; } return this.activeWin.fireEvent( 'documentKeypress', ev); }, register: function( win ) { // where is [].add()? for ( var i = 0, len = this.terms.length; i < len; i++ ) if ( this.terms[ i ] === win ) return; log('registered new rTerm window'); win.addEvents({ /** * @event documentKeypress * Fires when a keypress on the document occurs and the window is active * @param {Ext.ux.rTerm.Window} this * @param {Object} event */ documentKeypress: true }); this.terms.push( win ); win.on('close',this.windowClose, this ); win.on('activate',this.windowActivate, this ); win.on('deactivate',this.windowDeactivate, this ); }, windowActivate: function( win ) { log('activate win'); this.active = true; this.activeWin = win; }, windowDeactivate: function( win ) { log('deactivate win'); this.active = false; this.activeWin = null; }, windowClose: function( win ) { log('window closed'); this.unregister( win ); }, unregister: function( win ) { if ( this.activeWin === win ) { this.active = false; this.activeWin = null; } this.terms.remove( win ); } }); })();