├── .gitignore ├── .jshintrc ├── .todo.txt ├── config ├── done.txt ├── report.txt └── todo.txt ├── ChildProcess.js ├── LICENSE ├── Makefile ├── PTY_README ├── README.md ├── README_electron-rebuild ├── app.js ├── bin └── atomic-terminal ├── childProcessTests ├── echo.js └── node.js ├── documentation.md ├── front ├── css │ ├── common.css │ ├── debug.css │ └── terminal.css ├── js │ ├── .jshintrc │ ├── Terminal.js │ ├── csi.js │ ├── dom.js │ ├── keyboard.js │ ├── osc.js │ └── terminalMain.js └── terminal.html ├── log └── touch ├── package.json ├── sample ├── child_pty-test.js ├── key-test.js ├── random-char.js └── style-test.js ├── test └── atomic-terminal-test.js └── to-support.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Specific # 2 | ############ 3 | 4 | *.local.* 5 | *.local 6 | *.log 7 | *.html.gz 8 | *.css.gz 9 | *.js.gz 10 | *.png 11 | .spellcast 12 | build 13 | _build 14 | _templates 15 | _static 16 | 17 | 18 | # gitignore / Node.gitignore # 19 | ############################## 20 | lib-cov 21 | lcov.info 22 | *.seed 23 | *.log 24 | *.csv 25 | *.dat 26 | *.out 27 | *.pid 28 | *.gz 29 | 30 | pids 31 | logs 32 | results 33 | build 34 | .grunt 35 | 36 | node_modules 37 | 38 | 39 | # OS generated files # 40 | ###################### 41 | .DS_Store 42 | .DS_Store? 43 | ._* 44 | .Spotlight-V100 45 | .Trashes 46 | Icon? 47 | ehthumbs.db 48 | Thumbs.db 49 | 50 | 51 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // Generic options 3 | "maxerr" : 20, 4 | 5 | // Predefined globals (node.js, browser, etc...) 6 | "node" : true, 7 | "browser" : false, 8 | "jquery" : false, 9 | 10 | 11 | // Security 12 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 13 | //"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 14 | "latedef" : false, // true: Require variables/functions to be defined before being used 15 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 16 | "unused" : true, // true: Require all defined variables be used 17 | 18 | // Style 19 | "indent" : 4, // Specify indentation spacing 20 | "camelcase" : true, 21 | // "maxlen" : 120, 22 | "curly" : true, 23 | "newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`. 24 | "noempty" : true, // Prohibit use of empty blocks. 25 | "nonew" : false, // Prohibit use of constructors for side-effects. 26 | "plusplus" : false, // Prohibit use of `++` & `--`. 27 | "sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`. 28 | "smarttabs" : true, // This option suppresses warnings about mixed tabs and spaces when the latter are used for alignmnent only. 29 | "trailing" : true, // Prohibit trailing whitespaces. 30 | "immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 31 | "quotmark" : false, // Quotation mark consistency: 32 | // false : do nothing (default) 33 | // true : ensure whatever is used is consistent 34 | // "single" : require single quotes 35 | // "double" : require double quotes 36 | "maxparams" : 6, // {int} Max number of formal params allowed per function 37 | "maxdepth" : 5, // {int} Max depth of nested blocks (within functions) 38 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 39 | "boss" : false // true: Tolerate assignments where comparisons would be expected 40 | } 41 | -------------------------------------------------------------------------------- /.todo.txt/config: -------------------------------------------------------------------------------- 1 | # === EDIT FILE LOCATIONS BELOW === 2 | 3 | PROJECT_NAME='Atomic Terminal' 4 | 5 | echo -ne "\n\t\e[0;35m~~> \e[1;35m$PROJECT_NAME\e[0;35m todo-list <~~\e[0m\n\n" 6 | 7 | # My config 8 | export TODOTXT_DEFAULT_ACTION=ls 9 | 10 | # Your todo.txt directory 11 | export TODO_DIR=$(dirname "$TODOTXT_CFG_FILE") 12 | 13 | # Your todo/done/report.txt locations 14 | export TODO_FILE="$TODO_DIR/todo.txt" 15 | export DONE_FILE="$TODO_DIR/done.txt" 16 | export REPORT_FILE="$TODO_DIR/report.txt" 17 | 18 | # You can customize your actions directory location 19 | #export TODO_ACTIONS_DIR="$HOME/.todo.actions.d" 20 | 21 | # == EDIT FILE LOCATIONS ABOVE === 22 | 23 | # === COLOR MAP === 24 | 25 | ## Text coloring and formatting is done by inserting ANSI escape codes. 26 | ## If you have re-mapped your color codes, or use the todo.txt 27 | ## output in another output system (like Conky), you may need to 28 | ## over-ride by uncommenting and editing these defaults. 29 | ## If you change any of these here, you also need to uncomment 30 | ## the defaults in the COLORS section below. Otherwise, todo.txt 31 | ## will still use the defaults! 32 | 33 | # export BLACK='\\033[0;30m' 34 | # export RED='\\033[0;31m' 35 | # export GREEN='\\033[0;32m' 36 | # export BROWN='\\033[0;33m' 37 | # export BLUE='\\033[0;34m' 38 | # export PURPLE='\\033[0;35m' 39 | # export CYAN='\\033[0;36m' 40 | # export LIGHT_GREY='\\033[0;37m' 41 | # export DARK_GREY='\\033[1;30m' 42 | # export LIGHT_RED='\\033[1;31m' 43 | # export LIGHT_GREEN='\\033[1;32m' 44 | # export YELLOW='\\033[1;33m' 45 | # export LIGHT_BLUE='\\033[1;34m' 46 | # export LIGHT_PURPLE='\\033[1;35m' 47 | # export LIGHT_CYAN='\\033[1;36m' 48 | # export WHITE='\\033[1;37m' 49 | # export DEFAULT='\\033[0m' 50 | 51 | # === COLORS === 52 | 53 | ## Uncomment and edit to override these defaults. 54 | ## Reference the constants from the color map above, 55 | ## or use $NONE to disable highlighting. 56 | # 57 | # Priorities can be any upper-case letter. 58 | # A,B,C are highlighted; you can add coloring for more. 59 | # 60 | # export PRI_A=$YELLOW # color for A priority 61 | # export PRI_B=$GREEN # color for B priority 62 | # export PRI_C=$LIGHT_BLUE # color for C priority 63 | # export PRI_D=... # define your own 64 | # export PRI_X=$WHITE # color unless explicitly defined 65 | 66 | # There is highlighting for tasks that have been done, 67 | # but haven't been archived yet. 68 | # 69 | # export COLOR_DONE=$LIGHT_GREY 70 | 71 | # There is highlighting for projects and contexts. 72 | # 73 | # export COLOR_PROJECT=$RED 74 | # export COLOR_CONTEXT=$RED 75 | 76 | # === BEHAVIOR === 77 | 78 | ## customize list output 79 | # 80 | # TODOTXT_SORT_COMMAND will filter after line numbers are 81 | # inserted, but before colorization, and before hiding of 82 | # priority, context, and project. 83 | # 84 | # export TODOTXT_SORT_COMMAND='env LC_COLLATE=C sort -f -k2' 85 | 86 | # TODOTXT_FINAL_FILTER will filter list output after colorization, 87 | # priority hiding, context hiding, and project hiding. That is, 88 | # just before the list output is displayed. 89 | # 90 | # export TODOTXT_FINAL_FILTER='cat' 91 | -------------------------------------------------------------------------------- /.todo.txt/done.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cronvel/atomic-terminal/b6a21dda37b55429345997b7116e58c793d097ba/.todo.txt/done.txt -------------------------------------------------------------------------------- /.todo.txt/report.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cronvel/atomic-terminal/b6a21dda37b55429345997b7116e58c793d097ba/.todo.txt/report.txt -------------------------------------------------------------------------------- /.todo.txt/todo.txt: -------------------------------------------------------------------------------- 1 | (B) there is probably a bug in child_pty module, when the client send too many bytes, some data are missing... @bug 2 | -------------------------------------------------------------------------------- /ChildProcess.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Cédric Ronvel 3 | 4 | The MIT License (MIT) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | 27 | // Modules 28 | var events = require( 'events' ) ; 29 | var tree = require( 'tree-kit' ) ; 30 | 31 | 32 | 33 | /* 34 | Important notice: child_pty does not work correctly in the renderer process!!! 35 | Data can be lost. 36 | So it should be run in the browser process. 37 | */ 38 | 39 | 40 | 41 | function ChildProcess() { throw new Error( '[ChildProcess] use ChildProcess.create() instead' ) ; } 42 | ChildProcess.prototype = Object.create( events.EventEmitter.prototype ) ; 43 | ChildProcess.prototype.constructor = ChildProcess ; 44 | 45 | module.exports = ChildProcess ; 46 | 47 | 48 | 49 | ChildProcess.create = function create( command , args , spawn ) 50 | { 51 | var child = Object.create( ChildProcess.prototype ) ; 52 | 53 | child.command = command ; 54 | child.args = args || [] ; 55 | 56 | //child.spawn = spawn || require( 'child_process' ).spawn ; 57 | child.spawn = spawn || require( 'child_pty' ).spawn ; 58 | 59 | return child ; 60 | } ; 61 | 62 | 63 | 64 | ChildProcess.prototype.run = function run() 65 | { 66 | var self = this ; 67 | 68 | this.child = this.spawn( this.command , this.args , { 69 | env: tree.extend( null , {} , process.env , { 70 | TERM: 'xterm-256color' , 71 | COLORTERM: 'atomic-terminal' 72 | } ) , 73 | stdio: [ 'pipe' , 'pipe' , 'pipe' ] , 74 | columns: 80 , 75 | rows: 24 76 | } ) ; 77 | 78 | this.child.stdout.on( 'data' , function( data ) { 79 | 80 | //process.stdout.write( data ) ; 81 | 82 | // Remote Buffer are slow and for some reason, a renderer listener can get data in the bad order... 83 | // So everything is converted to ascii to ensure that all data are sent immediately. 84 | // Remote Buffer does not contains data, only accessors (via IPC) and that sucks big time. 85 | 86 | //self.emit( 'output' , data ) ; 87 | 88 | // I know that converting to binary string is deprecated, but I don't really have the choice here 89 | self.emit( 'output' , data.toString( 'binary' ) ) ; 90 | } ) ; 91 | 92 | /* 93 | // child_pty does not provide any stderr 94 | this.child.stderr.on( 'data', function( data ) { 95 | self.emit( 'output' , data ) ; 96 | } ) ; 97 | //*/ 98 | 99 | this.child.on( 'close', function( code ) { 100 | self.emit( 'close' , code ) ; 101 | } ) ; 102 | } ; 103 | 104 | 105 | 106 | ChildProcess.prototype.input = function input( string ) 107 | { 108 | //console.log( 'debug -- sending: "' + string + '"' ) ; 109 | this.child.stdin.write( string ) ; 110 | } ; 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Cédric Ronvel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # User rules 5 | 6 | # The first rule is the default rule, when invoking "make" without argument... 7 | # Build every buildable things 8 | all: install doc 9 | 10 | # Just install things so it works, basicaly: it just performs a "npm install --production" ATM 11 | install: log/npm-install.log 12 | 13 | # Just install things so it works, basicaly: it just performs a "npm install" ATM 14 | dev-install: log/npm-dev-install.log 15 | 16 | # This run the JsHint & Mocha BDD test, display it to STDOUT & save it to log/mocha.log and log/jshint.log 17 | test: log/jshint.log log/mocha.log 18 | 19 | # This run the JsHint, display it to STDOUT & save it to log/jshint.log 20 | lint: log/jshint.log 21 | 22 | # This run the Mocha BDD test, display it to STDOUT & save it to log/mocha.log 23 | unit: log/mocha.log 24 | 25 | # This build the doc and README.md 26 | doc: README.md 27 | 28 | # This publish to NPM and push to Github, if we are on master branch only 29 | publish: log/npm-publish.log log/github-push.log 30 | 31 | # Clean temporary things, or things that can be automatically regenerated 32 | clean: clean-all 33 | 34 | 35 | 36 | # Variables 37 | 38 | MOCHA=../node_modules/mocha/bin/mocha 39 | JSHINT=./node_modules/jshint/bin/jshint --verbose 40 | 41 | 42 | 43 | # Files rules 44 | 45 | # JsHint STDOUT test 46 | log/jshint.log: log/npm-dev-install.log *.js front/js/*.js test/*.js 47 | ${JSHINT} *.js front/js/*.js test/*.js | tee log/jshint.log ; exit $${PIPESTATUS[0]} 48 | 49 | # Mocha BDD STDOUT test 50 | log/mocha.log: log/npm-dev-install.log *.js front/js/*.js test/*.js 51 | cd test ; ${MOCHA} *.js -R spec | tee ../log/mocha.log ; exit $${PIPESTATUS[0]} 52 | 53 | # README 54 | README.md: documentation.md 55 | cat documentation.md > README.md 56 | 57 | # Mocha Markdown BDD spec 58 | bdd-spec.md: log/npm-dev-install.log *.js front/js/*.js test/*.js 59 | cd test ; ${MOCHA} *.js -R markdown > ../bdd-spec.md 60 | 61 | # Upgrade version in package.json 62 | log/upgrade-package.log: Makefile *.js front/* front/*/*.js test/*.js documentation.md 63 | npm version patch -m "Upgrade package.json version to %s" | tee log/upgrade-package.log ; exit $${PIPESTATUS[0]} 64 | 65 | # Publish to NPM 66 | log/npm-publish.log: check-if-master-branch log/upgrade-package.log 67 | npm publish | tee log/npm-publish.log ; exit $${PIPESTATUS[0]} 68 | 69 | # Push to Github/master 70 | log/github-push.log: *.js front/* front/*/* test/*.js package.json 71 | #'npm version patch' create the git tag by itself... 72 | #git tag v`cat package.json | grep version | sed -r 's/.*"([0-9.]*)".*/\1/'` 73 | git push origin master --tags | tee log/github-push.log ; exit $${PIPESTATUS[0]} 74 | 75 | # NPM install 76 | log/npm-install.log: package.json 77 | npm install --production | tee log/npm-install.log ; exit $${PIPESTATUS[0]} 78 | 79 | # NPM install for developpement usage 80 | log/npm-dev-install.log: package.json 81 | npm install | tee log/npm-dev-install.log ; exit $${PIPESTATUS[0]} 82 | 83 | 84 | 85 | # PHONY rules 86 | 87 | .PHONY: clean-all check-if-master-branch 88 | 89 | # Delete files, mostly log and non-versioned files 90 | clean-all: 91 | rm -rf log/*.log README.md bdd-spec.md node_modules 92 | 93 | # This will fail if we are not on master branch (grep exit 1 if nothing found) 94 | check-if-master-branch: 95 | git branch | grep "^* master$$" 96 | 97 | 98 | -------------------------------------------------------------------------------- /PTY_README: -------------------------------------------------------------------------------- 1 | 2 | Few things to know about PTY, when a child process is created with child_pty instead of child_process. 3 | This can avoid few hours of “WTF is going on?”. 4 | 5 | 6 | 7 | Parent -> PTY STDIN -> Child STDIN 8 | Child STDOUT -> PTY STDOUT -> Parent 9 | 10 | * everything sent to PTY's STDIN is immediately echoed to PTY's STDOUT... 11 | * ... but the Child's STDIN will receive that only when a newline is sent (line discipline) 12 | * ... so what the Parent receive is NOT NECESSARLY something the Child sent 13 | 14 | * in Raw Mode, it's almost working as if the PTY was not here, for the input processing 15 | 16 | * if the Child output a newline, the PTY emit both a carriage return and a newline, in that order (really confusing...) 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Atomic Terminal 4 | 5 | Early alpha. 6 | 7 | 8 | -------------------------------------------------------------------------------- /README_electron-rebuild: -------------------------------------------------------------------------------- 1 | 2 | Do not call ./node_modules/.bin/electron-rebuild without argument, it will fail. 3 | 4 | Pass to it explicit electron's version and module's directory: 5 | 6 | ./node_modules/.bin/electron-rebuild -v 0.26.0 -m node_modules 7 | 8 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | // Load modules 5 | 6 | // Module to control application life. 7 | var app = require( 'app' ) ; 8 | 9 | // Module to create native browser window. 10 | var BrowserWindow = require( 'browser-window' ) ; 11 | 12 | // Get the crash reporter 13 | var crashReporter = require( 'crash-reporter' ) ; 14 | 15 | // Processus communication 16 | var ChildProcess = require( './ChildProcess.js' ) ; 17 | 18 | // Safely set the process' title from the package name 19 | process.title = require( './package.json' ).name ; 20 | 21 | 22 | 23 | // Start the crash reporter 24 | crashReporter.start() ; 25 | 26 | 27 | 28 | // Keep a global reference of the window object, if you don't, the window will 29 | // be closed automatically when the javascript object is GCed. 30 | var mainWindow = null ; 31 | 32 | 33 | 34 | // Quit when all windows are closed. 35 | app.on( 'window-all-closed' , function() { 36 | if ( process.platform !== 'darwin' ) 37 | { 38 | app.quit() ; 39 | } 40 | } ) ; 41 | 42 | 43 | 44 | var argPos , devTools = false , args = process.argv.slice() ; 45 | 46 | // Open dev tools? 47 | if ( ( argPos = args.indexOf( '--dev' ) ) !== -1 ) 48 | { 49 | args.splice( argPos , 1 ) ; 50 | devTools = true ; 51 | } 52 | 53 | 54 | // This method will be called when atom-shell has done everything 55 | // initialization and ready for creating browser windows. 56 | app.on( 'ready' , function() { 57 | 58 | // Create the browser window. 59 | mainWindow = new BrowserWindow( { 60 | width: 830 , 61 | height: 480 62 | } ) ; 63 | 64 | // Open dev tools? 65 | if ( devTools ) { mainWindow.openDevTools() ; } 66 | 67 | mainWindow.command = { path: args[ 2 ] , args: args.slice( 3 ) } ; 68 | mainWindow.childProcess = ChildProcess.create( args[ 2 ] , args.slice( 3 ) ) ; 69 | 70 | // and load the index.html of the app. 71 | mainWindow.loadUrl( 'file://' + __dirname + '/front/terminal.html' ) ; 72 | 73 | // Emitted when the window is closed. 74 | mainWindow.on( 'closed' , function() { 75 | // Dereference the window object, usually you would store windows 76 | // in an array if your app supports multi windows, this is the time 77 | // when you should delete the corresponding element. 78 | mainWindow = null ; 79 | } ) ; 80 | 81 | } ) ; 82 | 83 | 84 | -------------------------------------------------------------------------------- /bin/atomic-terminal: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // It just returns a path 4 | var electronPath = require( 'electron-prebuilt' ) ; 5 | 6 | var childProcess = require( 'child_process' ) ; 7 | 8 | // Adjust the command line arguments: remove the "node " part 9 | var args = process.argv.slice( 2 ) ; 10 | // ... and insert the root path of our application (it's the parent directory) as the first argument 11 | args.unshift( __dirname + '/../' ) ; 12 | 13 | // Run electron 14 | childProcess.spawn( electronPath , args , { stdio: 'inherit' } ) ; 15 | -------------------------------------------------------------------------------- /childProcessTests/echo.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var count = process.argv[ 2 ] || 10 ; 4 | 5 | process.stdout.write( 'Starting echo.js...' ) ; 6 | 7 | try { 8 | process.stdin.setRawMode( true ) ; 9 | } 10 | catch ( error ) { 11 | console.log( 'Not a TTY' ) ; 12 | } 13 | 14 | process.stdin.on( 'data' , function( data ) { 15 | 16 | console.log( 'Count #' , count , ':' , data ) ; 17 | 18 | if ( count <= 0 ) 19 | { 20 | process.stdout.write( 'Exiting...' ) ; 21 | process.exit() ; 22 | } 23 | 24 | process.stdout.write( data ) ; 25 | count -- ; 26 | } ) ; 27 | -------------------------------------------------------------------------------- /childProcessTests/node.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var ChildProcess = require( '../ChildProcess.js' ) ; 4 | 5 | 6 | //var child = ChildProcess.create( process.argv[ 2 ] , process.argv.slice( 3 ) ) ; 7 | var child = ChildProcess.create( process.argv[ 2 ] , process.argv.slice( 3 ) , require( 'child_pty' ).spawn ) ; 8 | //var child = ChildProcess.create( process.argv[ 2 ] , process.argv.slice( 3 ) , require( 'child_process' ).spawn ) ; 9 | 10 | child.run() ; 11 | 12 | child.on( 'close' , function() { 13 | console.log( 'Child closed...' ) ; 14 | process.exit() ; 15 | } ) ; 16 | 17 | child.on( 'output' , function( data ) { 18 | console.log( 'Received: "' + data.toString() + '"' ) ; 19 | } ) ; 20 | 21 | var count = 0 ; 22 | 23 | function send() 24 | { 25 | var string ; 26 | string = String.fromCharCode( 65 + Math.floor( Math.random() * 20 ) ) ; 27 | if ( ! ( count % 5 ) ) { string += '\n' ; } 28 | console.log( 'Sending: "' + string + '"' ) ; 29 | child.input( string ) ; 30 | count ++ ; 31 | setTimeout( send , 100 ) ; 32 | } 33 | 34 | setTimeout( send , 100 ) ; 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /documentation.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Atomic Terminal 4 | 5 | Early alpha. 6 | 7 | 8 | -------------------------------------------------------------------------------- /front/css/common.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | .clear { 5 | clear: both; 6 | } -------------------------------------------------------------------------------- /front/css/debug.css: -------------------------------------------------------------------------------- 1 | [data-debug] { 2 | -webkit-animation-duration: 2000ms; 3 | -webkit-animation-iteration-count: 1; 4 | -webkit-animation-fill-mode: backwards; 5 | -webkit-animation-delay: 500ms; 6 | } 7 | 8 | 9 | [data-debug="cell"] {-webkit-animation-name: cell; } 10 | [data-debug="char"] {-webkit-animation-name: char; } 11 | [data-debug="class"] {-webkit-animation-name: class; } 12 | [data-debug="new"] {-webkit-animation-name: new; } 13 | @-webkit-keyframes cell { 14 | from { background-color: blue; } 15 | to { background-color: rgba(0,0,0,0); } 16 | } 17 | @-webkit-keyframes char { 18 | from { background-color: red; } 19 | to { background-color: rgba(0,0,0,0); } 20 | } 21 | @-webkit-keyframes class { 22 | from { background-color: pink; } 23 | to { background-color: rgba(0,0,0,0); } 24 | } 25 | @-webkit-keyframes new { 26 | from { background-color: green; } 27 | to { background-color: rgba(0,0,0,0); } 28 | } 29 | -------------------------------------------------------------------------------- /front/css/terminal.css: -------------------------------------------------------------------------------- 1 | #contentTable { 2 | table-layout: fixed; 3 | border-collapse: collapse; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | #contentTable div { 9 | margin: 0px; 10 | padding: 0px; 11 | border: none; 12 | } 13 | 14 | #contentTable span { 15 | display: inline-block; 16 | vertical-align: bottom; 17 | overflow: visible; 18 | text-align: center; 19 | } 20 | 21 | .cursor { 22 | animation: blink 1500ms step-start 1000ms infinite; 23 | -webkit-animation: blink 1500ms step-start 1000ms infinite; 24 | } 25 | 26 | 27 | .bold { 28 | font-weight: bold; 29 | } 30 | 31 | .italic { 32 | font-style: italic; 33 | } 34 | 35 | .underline { 36 | text-decoration: underline; 37 | } 38 | 39 | .strike { 40 | text-decoration: line-through; 41 | } 42 | 43 | .blink { 44 | -webkit-animation: blink .75s linear infinite; 45 | animation: blink .75s linear infinite; 46 | } 47 | -------------------------------------------------------------------------------- /front/js/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // Generic options 3 | "maxerr" : 20, 4 | 5 | // Predefined globals (node.js, browser, etc...) 6 | "node" : true, 7 | "browser" : true, 8 | "jquery" : true, 9 | 10 | 11 | // Security 12 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 13 | //"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 14 | "latedef" : false, // true: Require variables/functions to be defined before being used 15 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 16 | "unused" : true, // true: Require all defined variables be used 17 | 18 | // Style 19 | "indent" : 4, // Specify indentation spacing 20 | "camelcase" : true, 21 | // "maxlen" : 120, 22 | "curly" : true, 23 | "newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`. 24 | "noempty" : true, // Prohibit use of empty blocks. 25 | "nonew" : false, // Prohibit use of constructors for side-effects. 26 | "plusplus" : false, // Prohibit use of `++` & `--`. 27 | "sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`. 28 | "smarttabs" : true, // This option suppresses warnings about mixed tabs and spaces when the latter are used for alignmnent only. 29 | "trailing" : true, // Prohibit trailing whitespaces. 30 | "immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 31 | "quotmark" : false, // Quotation mark consistency: 32 | // false : do nothing (default) 33 | // true : ensure whatever is used is consistent 34 | // "single" : require single quotes 35 | // "double" : require double quotes 36 | "maxparams" : 6, // {int} Max number of formal params allowed per function 37 | "maxdepth" : 6, // {int} Max depth of nested blocks (within functions) 38 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 39 | "boss" : false // true: Tolerate assignments where comparisons would be expected 40 | } 41 | -------------------------------------------------------------------------------- /front/js/Terminal.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Cédric Ronvel 3 | 4 | The MIT License (MIT) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | 27 | // Load modules 28 | var remote = require( 'remote' ) ; 29 | var punycode = require( 'punycode' ) ; 30 | var string = require( 'string-kit' ) ; 31 | var tree = require( 'tree-kit' ) ; 32 | 33 | 34 | 35 | function Terminal() { throw new Error( "[Front/Terminal] use Terminal.create() instead..." ) ; } 36 | module.exports = Terminal ; 37 | 38 | 39 | 40 | // Submodule parts 41 | Terminal.csi = require( './csi.js' ) ; 42 | Terminal.osc = require( './osc.js' ) ; 43 | Terminal.dom = require( './dom.js' ) ; 44 | 45 | 46 | 47 | Terminal.create = function create( options ) 48 | { 49 | var terminal = Object.create( Terminal.prototype ) ; 50 | 51 | if ( ! options || typeof options !== 'object' ) { options = {} ; } 52 | 53 | terminal.width = options.width || 80 ; 54 | terminal.height = options.height || 24 ; 55 | 56 | terminal.domStyle = { 57 | terminal: document.getElementById( 'terminalStyle' ) , 58 | palette: document.getElementById( 'paletteStyle' ) , 59 | palette256: document.getElementById( 'palette256Style' ) 60 | } ; 61 | 62 | terminal.cell = { 63 | width: 10 , 64 | height: 19 65 | } ; 66 | 67 | terminal.font = { 68 | family: 'monospace' , 69 | size: 18 70 | } ; 71 | 72 | terminal.cursor = { 73 | x: 1 , 74 | y: 1 , 75 | fgColor: false , 76 | bgColor: false , 77 | bold: false , 78 | dim: false , 79 | italic: false , 80 | underline: false , 81 | blink: false , 82 | inverse: false , 83 | hidden: false , 84 | strike: false , 85 | classAttr: null , 86 | styleAttr: null , 87 | 88 | // Position on the screen, may differ from x,y until .updateCursor() is called 89 | screenX: 1 , 90 | screenY: 1 , 91 | screenInverse: false , 92 | screenHidden: false , 93 | 94 | blinkTimeout: 500 , 95 | steadyTimeout: 1000 , // Time before blinking again 96 | 97 | updateNeeded: false 98 | } ; 99 | 100 | terminal.savedCursorPosition = { x: 1 , y: 1 } ; 101 | 102 | terminal.state = [] ; 103 | 104 | terminal.remoteWin = remote.getCurrentWindow() ; 105 | 106 | terminal.palette = tree.extend( { deep: true } , [] , defaultPalette ) ; 107 | terminal.defaultFgColorIndex = 7 ; 108 | terminal.defaultBgColorIndex = 0 ; 109 | terminal.dimAlpha = 0.5 ; 110 | 111 | terminal.cwd = '/' ; // Temp? 112 | 113 | //console.log( string.inspect( { style: 'color' } , terminal.palette ) ) ; process.exit() ; 114 | terminal.paletteStyle( true , true ) ; 115 | 116 | terminal.updateAttrs() ; 117 | 118 | Terminal.dom.init( terminal ) ; 119 | 120 | return terminal ; 121 | } ; 122 | 123 | 124 | Terminal.prototype.terminalStyle = function terminalStyle() 125 | { 126 | var css = '' ; 127 | 128 | css += 'body {\n' + 129 | '\tfont-family: ' + this.font.family + ', monospace;\n' + 130 | '\tfont-size: ' + this.font.size + 'px;\n' + 131 | '}\n' ; 132 | 133 | css += '#contentTable {\n' + 134 | '\twidth: ' + this.width * this.cell.width + 'px;\n' + 135 | '\theight: ' + this.height * this.cell.height + 'px;\n' + 136 | '}\n' ; 137 | 138 | css += '#contentTable div {\n' + 139 | '\theight: ' + this.cell.height + 'px;\n' + 140 | '}\n' ; 141 | 142 | css += '#contentTable span {\n' + 143 | '\twidth: ' + this.cell.width + 'px;\n' + 144 | '\theight: ' + this.cell.height + 'px;\n' + 145 | '}\n' ; 146 | 147 | this.domStyle.terminal.innerHTML = css ; 148 | } ; 149 | 150 | 151 | 152 | /* 153 | .paletteStyle( lowPalette , highPalette ) 154 | * lowPalette `boolean` if the low palette style should be rebuilt 155 | * highPalette `boolean` if the high palette (256 colors) style should be rebuilt 156 | */ 157 | Terminal.prototype.paletteStyle = function paletteStyle( lowPalette , highPalette ) 158 | { 159 | var self = this , i , css ; 160 | 161 | var setRegister = function( c , rgb ) { 162 | 163 | css += '.fgColor' + c + ' {\n' + 164 | '\tcolor: rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ');\n' + 165 | '}\n' + 166 | '.bgColor' + c + ' {\n' + 167 | '\tbackground-color: rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ');\n' + 168 | '}\n' + 169 | '.dim.fgColor' + c + ' {\n' + 170 | '\tcolor: rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + self.dimAlpha + ');\n' + 171 | '}\n' ; 172 | } ; 173 | 174 | if ( lowPalette ) 175 | { 176 | css = '' ; 177 | for ( i = 0 ; i <= 15 ; i ++ ) { setRegister( i , this.palette[ i ] ) ; } 178 | this.domStyle.palette.innerHTML = css ; 179 | } 180 | 181 | if ( highPalette ) 182 | { 183 | css = '' ; 184 | for ( i = 16 ; i <= 255 ; i ++ ) { setRegister( i , this.palette[ i ] ) ; } 185 | this.domStyle.palette256.innerHTML = css ; 186 | } 187 | } ; 188 | 189 | 190 | 191 | Terminal.prototype.start = function start() 192 | { 193 | // The terminal is ready: run the underlying process! 194 | this.remoteWin.childProcess.run() ; 195 | this.remoteWin.childProcess.on( 'output' , Terminal.prototype.onStdout.bind( this ) ) ; 196 | } ; 197 | 198 | 199 | 200 | /* 201 | function parseNumbers( sequence ) 202 | { 203 | return sequence.split( ';' ).map( function( value ) { return parseInt( value , 10 ) ; } ) ; 204 | } 205 | */ 206 | 207 | 208 | 209 | Terminal.prototype.updateAttrs = function updateAttrs() 210 | { 211 | var attrs = this.attrsFromObject( this.cursor ) ; 212 | 213 | this.cursor.classAttr = attrs.class ; 214 | this.cursor.styleAttr = attrs.style ; 215 | } ; 216 | 217 | 218 | // Extra 'inverse' is used for cursor update, to not have to clone the object... 219 | Terminal.prototype.attrsFromObject2 = function attrsFromObject2() 220 | { 221 | return { 222 | fgColor:this.defaultFgColorIndex, 223 | bgColor:this.defaultBgColorIndex, 224 | class: 'fgColor'+this.defaultFgColorIndex+' bgColor'+ this.defaultBgColorIndex, 225 | style: null 226 | } ; 227 | } ; 228 | 229 | // Extra 'inverse' is used for cursor update, to not have to clone the object... 230 | /* COPY, to remove once new function finalized */ 231 | Terminal.prototype.attrsFromObject = function attrsFromObject( object , inverse ) 232 | { 233 | var fgColor , bgColor , attr = [] , style = [] , tmp ; 234 | // console.log( object ) ; 235 | fgColor = object.fgColor || object.fgColor === 0 ? object.fgColor : this.defaultFgColorIndex ; 236 | bgColor = object.bgColor || object.bgColor === 0 ? object.bgColor : this.defaultBgColorIndex ; 237 | 238 | if ( object.bold ) { attr.push( 'bold' ) ; } 239 | if ( object.dim ) { attr.push( 'dim' ) ; } 240 | if ( object.italic ) { attr.push( 'italic' ) ; } 241 | if ( object.underline ) { attr.push( 'underline' ) ; } 242 | if ( object.blink ) { attr.push( 'blink' ) ; } 243 | if ( object.strike ) { attr.push( 'strike' ) ; } 244 | 245 | if ( ! object.inverse !== ! inverse ) { tmp = bgColor ; bgColor = fgColor ; fgColor = tmp ; } // jshint ignore:line 246 | 247 | if ( object.hidden ) { fgColor = bgColor ; } 248 | 249 | if ( Array.isArray( fgColor ) ) { style.push( 'color: rgb(' + fgColor.join( ',' ) + ');' ) ; } 250 | else { attr.push( 'fgColor' + fgColor ) ; } 251 | 252 | if ( Array.isArray( bgColor ) ) { style.push( 'background-color: rgb(' + bgColor.join( ',' ) + ');' ) ; } 253 | else { attr.push( 'bgColor' + bgColor ) ; } 254 | 255 | return { 256 | fgColor:fgColor, 257 | bgColor:bgColor, 258 | class: attr.join( ' ' ) || null , 259 | style: style.join( ' ' ) || null 260 | } ; 261 | } ; 262 | 263 | 264 | 265 | Terminal.prototype.updateCursor = function updateCursor() 266 | { 267 | this.cursor.screenX = this.cursor.x ; 268 | this.cursor.screenY = this.cursor.y ; 269 | 270 | if ( this.cursor.screenHidden ) { 271 | Terminal.dom.hideCursor() ; 272 | } 273 | 274 | if ( this.cursor.x > this.width ) { return ; } 275 | 276 | Terminal.dom.setCursor( this.cursor.screenX - 1 , this.cursor.screenY - 1 ) ; 277 | } ; 278 | 279 | 280 | Terminal.prototype.printChar = function printChar( char ) 281 | { 282 | if ( this.cursor.x > this.width ) 283 | { 284 | this.cursor.x = 1 ; 285 | this.cursor.y ++ ; 286 | 287 | if ( this.cursor.y > this.height ) 288 | { 289 | //this.cursor.y = this.height ; // now done by .scrollDown() 290 | this.scrollDown() ; 291 | } 292 | } 293 | 294 | var attrs = { 295 | class: this.cursor.classAttr, 296 | style: this.cursor.styleAttr 297 | } ; 298 | 299 | Terminal.dom.setCell( this.cursor.x - 1 , this.cursor.y - 1 , char , attrs ) ; 300 | 301 | this.cursor.x ++ ; 302 | 303 | this.cursor.updateNeeded = true ; 304 | 305 | //console.log( [ this.cursor.x , this.cursor.y ] ) ; 306 | } ; 307 | 308 | 309 | 310 | Terminal.prototype.scrollDown = function scrollDown() 311 | { 312 | Terminal.dom.insertRow() ; 313 | 314 | // Update cursor's coordinate 315 | this.cursor.y -- ; 316 | this.cursor.screenY -- ; 317 | } ; 318 | 319 | 320 | 321 | // Notice: PTY may emit both a carriage return followed by a newline when a single newline is emitted from the real child process 322 | Terminal.prototype.newLine = function newLine( carriageReturn ) 323 | { 324 | if ( carriageReturn ) { this.cursor.x = 1 ; } 325 | 326 | this.cursor.y ++ ; 327 | 328 | if ( this.cursor.y > this.height ) 329 | { 330 | //this.cursor.y = this.height ; // now done by .scrollDown() 331 | this.scrollDown() ; 332 | } 333 | 334 | this.cursor.updateNeeded = true ; 335 | 336 | //console.log( [ this.cursor.x , this.cursor.y ] ) ; 337 | } ; 338 | 339 | 340 | 341 | Terminal.prototype.moveTo = function moveTo( x , y ) 342 | { 343 | //console.log( '< moveTo coordinate: (' + this.cursor.x + ',' + this.cursor.y + ') {' + x + ',' + y + '}' ) ; 344 | 345 | if ( x !== undefined ) 346 | { 347 | this.cursor.x = Math.max( 1 , Math.min( x , this.width + 1 ) ) ; // bound to 1-width range 348 | } 349 | 350 | if ( y !== undefined ) 351 | { 352 | this.cursor.y = Math.max( 1 , Math.min( y , this.height ) ) ; // bound to 1-height range 353 | } 354 | 355 | this.cursor.updateNeeded = true ; 356 | 357 | //console.log( '> moveTo coordinate: (' + this.cursor.x + ',' + this.cursor.y + ')' ) ; 358 | } ; 359 | 360 | 361 | 362 | Terminal.prototype.move = function move( x , y ) 363 | { 364 | //console.log( '< move coordinate: (' + this.cursor.x + ',' + this.cursor.y + ')' ) ; 365 | 366 | if ( x !== undefined ) 367 | { 368 | this.cursor.x = Math.max( 1 , Math.min( this.cursor.x + x , this.width + 1 ) ) ; // bound to 1-width range 369 | } 370 | 371 | if ( y !== undefined ) 372 | { 373 | this.cursor.y = Math.max( 1 , Math.min( this.cursor.y + y , this.height ) ) ; // bound to 1-height range 374 | } 375 | 376 | this.cursor.updateNeeded = true ; 377 | 378 | //console.log( '> move coordinate: (' + this.cursor.x + ',' + this.cursor.y + ')' ) ; 379 | } ; 380 | 381 | 382 | 383 | Terminal.prototype.saveCursorPosition = function saveCursorPosition() 384 | { 385 | this.savedCursorPosition = { x: this.cursor.x , y: this.cursor.y } ; 386 | } ; 387 | 388 | 389 | 390 | Terminal.prototype.restoreCursorPosition = function restoreCursorPosition() 391 | { 392 | this.moveTo( this.savedCursorPosition.x , this.savedCursorPosition.y ) ; 393 | } ; 394 | 395 | 396 | 397 | Terminal.prototype.erase = function erase( type ) 398 | { 399 | var x , y , attrs , 400 | yMin , yMax , xMinInline , xMaxInline , xMin , xMax ; 401 | 402 | switch ( type ) 403 | { 404 | case 'all' : 405 | yMin = 1 ; 406 | yMax = this.height ; 407 | xMinInline = 1 ; 408 | xMaxInline = this.width ; 409 | break ; 410 | 411 | case 'line' : 412 | yMin = this.cursor.y ; 413 | yMax = this.cursor.y ; 414 | xMinInline = 1 ; 415 | xMaxInline = this.width ; 416 | break ; 417 | 418 | case 'above' : 419 | yMin = 1 ; 420 | yMax = this.cursor.y ; 421 | xMinInline = 1 ; 422 | xMaxInline = Math.min( this.cursor.x , this.width ) ; // Erase the cursor's cell too 423 | break ; 424 | 425 | case 'below' : 426 | yMin = this.cursor.y ; 427 | yMax = this.height ; 428 | xMinInline = Math.min( this.cursor.x , this.width ) ; // Erase the cursor's cell too 429 | xMaxInline = this.width ; 430 | break ; 431 | 432 | case 'lineAfter' : 433 | yMin = this.cursor.y ; 434 | yMax = this.cursor.y ; 435 | xMinInline = Math.min( this.cursor.x , this.width ) ; // Erase the cursor's cell too 436 | xMaxInline = this.width ; 437 | break ; 438 | 439 | case 'lineBefore' : 440 | yMin = this.cursor.y ; 441 | yMax = this.cursor.y ; 442 | xMinInline = 1 ; 443 | xMaxInline = Math.min( this.cursor.x , this.width ) ; // Erase the cursor's cell too 444 | break ; 445 | 446 | default : 447 | throw new Error( '.erase(): unknown type "' + type + '"' ) ; 448 | } 449 | 450 | for ( y = yMin ; y <= yMax ; y ++ ) 451 | { 452 | xMin = y === yMin ? xMinInline : 1 ; 453 | xMax = y === yMax ? xMaxInline : this.width ; 454 | 455 | for ( x = xMin ; x <= xMax ; x ++ ) 456 | { 457 | attrs = this.attrsFromObject( { 458 | fgColor: this.cursor.fgColor , 459 | bgColor: this.cursor.bgColor 460 | } ) ; 461 | Terminal.dom.setCell( x - 1 , y - 1 , ' ' , attrs ) ; 462 | } 463 | } 464 | } ; 465 | 466 | 467 | Terminal.prototype.insertLine = function insertLine( n ) 468 | { 469 | var i ; 470 | 471 | if ( n === undefined ) { n = 1 ; } 472 | 473 | for ( i = 0 ; i < n ; i ++ ) 474 | { 475 | Terminal.dom.insertRow( this.cursor.y - 1 ) ; 476 | } 477 | 478 | this.cursor.screenY += n ; 479 | this.cursor.updateNeeded = true ; 480 | } ; 481 | 482 | 483 | 484 | Terminal.prototype.deleteLine = function deleteLine( n ) 485 | { 486 | var i ; 487 | 488 | if ( n === undefined ) { n = 1 ; } 489 | 490 | for ( i = 0 ; i < n ; i ++ ) 491 | { 492 | Terminal.dom.deleteRow( this.cursor.y - 1 ) ; 493 | } 494 | 495 | this.cursor.updateNeeded = true ; 496 | } ; 497 | 498 | 499 | Terminal.prototype.insert = function insert( n ) 500 | { 501 | var i ; 502 | 503 | if ( this.cursor.x > this.width ) { return ; } 504 | 505 | if ( n === undefined ) { n = 1 ; } 506 | 507 | for ( i = 0 ; i < n ; i ++ ) 508 | { 509 | Terminal.dom.insertCell( this.cursor.x - 1 , this.cursor.y - 1 ) ; 510 | } 511 | 512 | this.cursor.screenX += n ; 513 | this.cursor.updateNeeded = true ; 514 | } ; 515 | 516 | 517 | Terminal.prototype.delete = function delete_( n ) 518 | { 519 | var i ; 520 | 521 | if ( this.cursor.x > this.width ) { return ; } 522 | 523 | if ( n === undefined ) { n = 1 ; } 524 | 525 | for ( i = 0 ; i < n ; i ++ ) 526 | { 527 | Terminal.dom.deleteCell( this.cursor.x - 1 , this.cursor.y - 1 ) ; 528 | } 529 | 530 | this.cursor.updateNeeded = true ; 531 | } ; 532 | 533 | 534 | 535 | 536 | /* STDOUT parsing */ 537 | 538 | 539 | 540 | Terminal.prototype.onStdout = function onStdout( chunk ) 541 | { 542 | var buffer , char , codepoint , found , bytes , 543 | index = 0 , length = chunk.length ; 544 | 545 | // Reset cursor update 546 | this.cursor.updateNeeded = false ; 547 | 548 | //if ( ! Buffer.isBuffer( chunk ) ) { throw new Error( 'not a buffer' ) ; } 549 | //console.log( 'Chunk: \n' + string.inspect( { style: 'color' } , chunk ) ) ; 550 | //console.error( 'Chunk:\n' + string.escape.control( chunk.toString() ) ) ; 551 | 552 | // I know that converting from binary string is deprecated, but I don't really have the choice here 553 | if ( typeof chunk === 'string' ) { chunk = new Buffer( chunk , 'binary' ) ; } 554 | 555 | if ( this.onStdoutRemainder ) 556 | { 557 | // If there is a remainder, just unshift it 558 | chunk = Buffer.concat( [ this.onStdoutRemainder , chunk ] ) ; 559 | //console.log( 'Found a remainder, final chunk \n' + string.inspect( { style: 'color' } , chunk ) ) ; 560 | } 561 | 562 | while ( index < length ) 563 | { 564 | found = false ; 565 | bytes = 1 ; 566 | 567 | if ( chunk[ index ] <= 0x1f || chunk[ index ] === 0x7f ) 568 | { 569 | // Those are ASCII control character and DEL key 570 | bytes = this.controlCharacter( chunk , index ) ; 571 | } 572 | else if ( chunk[ index ] >= 0x80 ) 573 | { 574 | // Unicode bytes per char guessing 575 | if ( chunk[ index ] < 0xc0 ) { continue ; } // We are in a middle of an unicode multibyte sequence... Something fails somewhere, we will just continue for now... 576 | else if ( chunk[ index ] < 0xe0 ) { bytes = 2 ; } 577 | else if ( chunk[ index ] < 0xf0 ) { bytes = 3 ; } 578 | else if ( chunk[ index ] < 0xf8 ) { bytes = 4 ; } 579 | else if ( chunk[ index ] < 0xfc ) { bytes = 5 ; } 580 | else { bytes = 6 ; } 581 | 582 | buffer = chunk.slice( index , index + bytes ) ; 583 | char = buffer.toString( 'utf8' ) ; 584 | 585 | if ( bytes > 2 ) { codepoint = punycode.ucs2.decode( char )[ 0 ] ; } 586 | else { codepoint = char.charCodeAt( 0 ) ; } 587 | 588 | //console.log( 'multibyte char "' + char + '"' ) ; 589 | this.printChar( char ) ; 590 | //this.emit( 'key' , char , [ char ] , { isCharacter: true , codepoint: codepoint , code: buffer } ) ; 591 | } 592 | else 593 | { 594 | // Standard ASCII 595 | char = String.fromCharCode( chunk[ index ] ) ; 596 | this.printChar( char ) ; 597 | //this.emit( 'key' , char , [ char ] , { isCharacter: true , codepoint: chunk[ index ] , code: chunk[ index ] } ) ; 598 | } 599 | 600 | if ( bytes === null ) 601 | { 602 | // Special case here: we should accumulate more of the buffer 603 | 604 | this.onStdoutRemainder = chunk.slice( index ) ; 605 | //console.log( 'bytes === null, this.onStdoutRemainder: \n' + string.inspect( { style: 'color' } , this.onStdoutRemainder ) + this.onStdoutRemainder.toString() ) ; 606 | return ; 607 | } 608 | 609 | index += bytes ; 610 | } 611 | 612 | this.onStdoutRemainder = null ; 613 | 614 | if ( this.cursor.updateNeeded ) { this.updateCursor() ; } 615 | } ; 616 | 617 | 618 | 619 | // TODO: switch case -> hashmap 620 | Terminal.prototype.controlCharacter = function controlCharacter( chunk , index ) 621 | { 622 | switch ( chunk[ index ] ) 623 | { 624 | // Backspace, it does *NOT* delete, it's just like the left arrow 625 | case 0x08 : 626 | case 0x7f : 627 | this.move( -1 ) ; 628 | return 1 ; 629 | 630 | // New Line 631 | case 0x0a : 632 | this.newLine() ; 633 | return 1 ; 634 | 635 | // Carriage Return 636 | // PTY may emit both a carriage return followed by a newline when a single newline is emitted from the real child process 637 | case 0x0d : 638 | this.moveTo( 1 ) ; 639 | return 1 ; 640 | 641 | // Escape 642 | case 0x1b : 643 | if ( index + 1 < chunk.length ) { return this.escapeSequence( chunk , index + 1 ) ; } 644 | return 1 ; 645 | 646 | default : 647 | console.error( string.format( 'Not implemented: Control 0x%x' , chunk[ index ] ) ) ; 648 | return 1 ; 649 | } 650 | } ; 651 | 652 | 653 | 654 | // TODO: switch case -> hashmap 655 | Terminal.prototype.escapeSequence = function escapeSequence( chunk , index ) 656 | { 657 | var char = String.fromCharCode( chunk[ index ] ) ; 658 | 659 | switch ( char ) 660 | { 661 | case '[' : 662 | if ( index + 1 < chunk.length ) { return this.csiSequence( chunk , index + 1 ) ; } 663 | return null ; 664 | 665 | case ']' : 666 | if ( index + 1 < chunk.length ) { return this.oscSequence( chunk , index + 1 ) ; } 667 | return null ; 668 | 669 | case '7' : 670 | this.saveCursorPosition() ; 671 | return 2 ; 672 | 673 | case '8' : 674 | this.restoreCursorPosition() ; 675 | return 2 ; 676 | 677 | case 'F' : // move to bottom-left 678 | this.moveTo( 1 , this.height ) ; 679 | return 2 ; 680 | 681 | // Deprecated: no-op, change the character set 682 | case ' ' : 683 | case '#' : 684 | case '%' : 685 | case '(' : 686 | case ')' : 687 | case '*' : 688 | case '+' : 689 | case '-' : 690 | case '/' : 691 | return 3 ; 692 | 693 | default : 694 | // Unknown sequence 695 | // console.error( 'Not implemented: ESC "' + char + '"' ) ; 696 | 697 | /* 698 | this.printChar( '\x1b' ) ; 699 | this.printChar( char ) ; 700 | //*/ 701 | return 2 ; 702 | } 703 | } ; 704 | 705 | 706 | 707 | Terminal.prototype.csiSequence = function csiSequence( chunk , index ) 708 | { 709 | var i , char , sequence = '' ; 710 | 711 | 712 | for ( i = index ; i < chunk.length ; i ++ ) 713 | { 714 | char = String.fromCharCode( chunk[ i ] ) ; 715 | 716 | // Check for sequence terminator 717 | if ( chunk[ i ] >= 0x40 ) 718 | { 719 | if ( Terminal.csi[ char ] ) { Terminal.csi[ char ].call( this , sequence ) ; } 720 | else { console.error( 'Not implemented: CSI "' + char + '" (sequence: "' + sequence + '")' ) ; } 721 | 722 | return sequence.length + 3 ; // ESC + [ + sequence + terminator 723 | } 724 | 725 | sequence += char ; 726 | } 727 | 728 | // We should never reach here, except if the buffer was too short 729 | return null ; 730 | } ; 731 | 732 | 733 | 734 | Terminal.prototype.oscSequence = function oscSequence( chunk , index ) 735 | { 736 | var i , char , sequence = '' , num ; 737 | 738 | 739 | for ( i = index ; i < chunk.length ; i ++ ) 740 | { 741 | char = String.fromCharCode( chunk[ i ] ) ; 742 | 743 | // Check for sequence terminator 744 | // 0x07 = Bell 745 | // 0x1b = Esc 746 | // 0x5c = \ 747 | if ( ( chunk[ i ] === 0x07 ) || ( chunk[ i ] === 0x1b && chunk[ i + 1 ] === 0x5c ) ) 748 | { 749 | index = sequence.indexOf( ';' ) ; 750 | 751 | if ( index > 0 ) 752 | { 753 | num = parseInt( sequence.slice( 0 , index ) , 10 ) ; 754 | 755 | if ( ! isNaN( num ) ) 756 | { 757 | if ( Terminal.osc[ num ] ) { Terminal.osc[ num ].call( this , sequence.slice( index + 1 ) ) ; } 758 | else { console.error( 'Not implemented: OSC "' + num + '" (sequence: ' + sequence.slice( index + 1 ) + '")' ) ; } 759 | } 760 | else 761 | { 762 | console.log( "Trouble: NaN!" ) ; 763 | } 764 | } 765 | 766 | return sequence.length + 2 + ( chunk[ i ] === 0x07 ? 1 : 2 ) ; // ESC + ] + sequence + Bell/ST 767 | } 768 | 769 | sequence += char ; 770 | } 771 | 772 | console.log( "Trouble: " + string.escape.control( chunk.toString() ) ) ; 773 | // We should never reach here, except if the buffer was too short 774 | return null ; 775 | } ; 776 | 777 | 778 | 779 | 780 | 781 | /* Misc */ 782 | 783 | 784 | 785 | // This is used for adjustement of floating point value, before applying Math.floor() 786 | var adjustFloor = 0.0000001 ; 787 | 788 | // Build the default palette 789 | 790 | // This make atomic-terminal and terminal-kit use the same colorScheme 791 | var defaultPalette = require( 'terminal-kit/lib/colorScheme/atomic-terminal.json' ) ; 792 | 793 | ( function buildDefaultPalette() 794 | { 795 | var register , offset , factor , l ; 796 | 797 | for ( register = 16 ; register < 232 ; register ++ ) 798 | { 799 | // RGB 6x6x6 800 | offset = register - 16 ; 801 | factor = 255 / 5 ; 802 | defaultPalette[ register ] = { 803 | r: Math.floor( ( Math.floor( offset / 36 + adjustFloor ) % 6 ) * factor + adjustFloor ) , 804 | g: Math.floor( ( Math.floor( offset / 6 + adjustFloor ) % 6 ) * factor + adjustFloor ) , 805 | b: Math.floor( ( offset % 6 ) * factor + adjustFloor ) , 806 | names: [] 807 | } ; 808 | } 809 | 810 | for ( register = 232 ; register <= 255 ; register ++ ) 811 | { 812 | // Grayscale 0..23 813 | offset = register - 231 ; // not 232, because the first of them is not a #000000 black 814 | factor = 255 / 25 ; // not 23, because the last is not a #ffffff white 815 | l = Math.floor( offset * factor + adjustFloor ) ; 816 | defaultPalette[ register ] = { r: l , g: l , b: l , names: [] } ; 817 | } 818 | } )() ; 819 | -------------------------------------------------------------------------------- /front/js/csi.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Cédric Ronvel 3 | 4 | The MIT License (MIT) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | 27 | var csi = {} ; 28 | module.exports = csi ; 29 | 30 | 31 | 32 | var i ; 33 | 34 | 35 | 36 | function parseNumbers( sequence ) 37 | { 38 | return sequence.split( ';' ).map( function( value ) { 39 | value = parseInt( value , 10 ) ; 40 | if ( isNaN( value ) ) { return undefined ; } 41 | return value ; 42 | } ) ; 43 | } 44 | 45 | 46 | 47 | 48 | 49 | /* Cursor */ 50 | 51 | 52 | 53 | csi.A = function up( sequence ) 54 | { 55 | var params = parseNumbers( sequence ) ; 56 | this.move( undefined , - ( params[ 0 ] === undefined ? 1 : params[ 0 ] ) ) ; 57 | } ; 58 | 59 | csi.B = function down( sequence ) 60 | { 61 | var params = parseNumbers( sequence ) ; 62 | this.move( undefined , ( params[ 0 ] === undefined ? 1 : params[ 0 ] ) ) ; 63 | } ; 64 | 65 | csi.C = function right( sequence ) 66 | { 67 | var params = parseNumbers( sequence ) ; 68 | this.move( ( params[ 0 ] === undefined ? 1 : params[ 0 ] ) ) ; 69 | } ; 70 | 71 | csi.D = function left( sequence ) 72 | { 73 | var params = parseNumbers( sequence ) ; 74 | this.move( - ( params[ 0 ] === undefined ? 1 : params[ 0 ] ) ) ; 75 | } ; 76 | 77 | csi.E = function nextLine( sequence ) 78 | { 79 | var params = parseNumbers( sequence ) ; 80 | this.moveTo( 1 , this.cursor.y + ( params[ 0 ] === undefined ? 1 : params[ 0 ] ) ) ; 81 | } ; 82 | 83 | csi.F = function previousLine( sequence ) 84 | { 85 | var params = parseNumbers( sequence ) ; 86 | this.moveTo( 1 , this.cursor.y - ( params[ 0 ] === undefined ? 1 : params[ 0 ] ) ) ; 87 | } ; 88 | 89 | csi.G = csi['`'] = function column( sequence ) 90 | { 91 | var params = parseNumbers( sequence ) ; 92 | this.moveTo( params[ 0 ] === undefined ? 1 : params[ 0 ] ) ; 93 | } ; 94 | 95 | csi.a = function relativeColumn( sequence ) 96 | { 97 | var params = parseNumbers( sequence ) ; 98 | this.move( params[ 0 ] === undefined ? 1 : params[ 0 ] ) ; 99 | } ; 100 | 101 | csi.H = csi.f = function moveTo( sequence ) 102 | { 103 | if ( sequence === '' ) 104 | { 105 | // Without params, it should go to the top-left corner 106 | this.moveTo( 1 , 1 ) ; 107 | return ; 108 | } 109 | 110 | var params = parseNumbers( sequence ) ; 111 | //console.log( 'csi moveTo - sequence: "' + sequence + '" (' + params[ 1 ] + ',' + params[ 0 ] + ')' ) ; 112 | this.moveTo( params[ 1 ] , params[ 0 ] ) ; 113 | } ; 114 | 115 | //csi.I = function tab( sequence ) 116 | 117 | csi.s = function saveCursor() { this.saveCursorPosition() ; } ; 118 | csi.u = function restoreCursor() { this.restoreCursorPosition() ; } ; 119 | 120 | csi.d = function row( sequence ) 121 | { 122 | var params = parseNumbers( sequence ) ; 123 | this.moveTo( undefined , params[ 0 ] === undefined ? 1 : params[ 0 ] ) ; 124 | } ; 125 | 126 | csi.e = function relativeRow( sequence ) 127 | { 128 | var params = parseNumbers( sequence ) ; 129 | this.move( undefined , params[ 0 ] === undefined ? 1 : params[ 0 ] ) ; 130 | } ; 131 | 132 | 133 | 134 | 135 | 136 | /* Editing */ 137 | 138 | 139 | 140 | csi.J = function eraseDisplay( sequence ) 141 | { 142 | var params = parseNumbers( sequence ) ; 143 | 144 | switch ( params[ 0 ] ) 145 | { 146 | case 1 : 147 | this.erase( 'above' ) ; 148 | break ; 149 | case 2 : 150 | this.erase( 'all' ) ; 151 | break ; 152 | case 3 : 153 | // Not implemented: erase saved lines (xterm specific) 154 | break ; 155 | 156 | //case 0 : 157 | default : 158 | this.erase( 'below' ) ; 159 | break ; 160 | } 161 | } ; 162 | 163 | 164 | 165 | csi.K = function eraseLine( sequence ) 166 | { 167 | var params = parseNumbers( sequence ) ; 168 | 169 | switch ( params[ 0 ] ) 170 | { 171 | case 1 : 172 | this.erase( 'lineBefore' ) ; 173 | break ; 174 | case 2 : 175 | this.erase( 'line' ) ; 176 | break ; 177 | 178 | //case 0 : 179 | default : 180 | this.erase( 'lineAfter' ) ; 181 | break ; 182 | } 183 | } ; 184 | 185 | 186 | 187 | csi['@'] = function insert( sequence ) 188 | { 189 | var params = parseNumbers( sequence ) ; 190 | this.insert( params[ 0 ] === undefined ? 1 : params[ 0 ] ) ; 191 | } ; 192 | 193 | 194 | 195 | csi.P = function delete_( sequence ) 196 | { 197 | var params = parseNumbers( sequence ) ; 198 | this.delete( params[ 0 ] === undefined ? 1 : params[ 0 ] ) ; 199 | } ; 200 | 201 | 202 | 203 | csi.L = function insertLine( sequence ) 204 | { 205 | var params = parseNumbers( sequence ) ; 206 | this.insertLine( params[ 0 ] === undefined ? 1 : params[ 0 ] ) ; 207 | } ; 208 | 209 | 210 | 211 | csi.M = function deleteLine( sequence ) 212 | { 213 | var params = parseNumbers( sequence ) ; 214 | this.deleteLine( params[ 0 ] === undefined ? 1 : params[ 0 ] ) ; 215 | } ; 216 | 217 | 218 | 219 | 220 | 221 | /* Styles and colors */ 222 | 223 | 224 | 225 | csi.m = function characterAttributes( sequence ) 226 | { 227 | var i ; 228 | var params = parseNumbers( sequence ) ; 229 | 230 | for ( i = 0 ; i < params.length ; i ++ ) 231 | { 232 | if ( params[ i ] === undefined ) { continue ; } 233 | 234 | if ( csi.m[ params[ i ] ] ) 235 | { 236 | i += csi.m[ params[ i ] ].apply( this , params.slice( i + 1 ) ) || 0 ; 237 | } 238 | else 239 | { 240 | console.error( 'Not implemented: CSI m (SGR) "' + params[ i ] + '" full sequence: "' + sequence + '"' ) ; 241 | } 242 | } 243 | } ; 244 | 245 | 246 | 247 | csi.m[ 0 ] = function styleReset() 248 | { 249 | this.cursor.fgColor = false ; 250 | this.cursor.bgColor = false ; 251 | this.cursor.bold = false ; 252 | this.cursor.dim = false ; 253 | this.cursor.italic = false ; 254 | this.cursor.underline = false ; 255 | this.cursor.blink = false ; 256 | this.cursor.inverse = false ; 257 | this.cursor.hidden = false ; 258 | this.cursor.strike = false ; 259 | //console.log( 'styleReset!!!' ) ; 260 | this.updateAttrs() ; 261 | } ; 262 | 263 | csi.m[ 1 ] = function bold() 264 | { 265 | this.cursor.bold = true ; 266 | this.updateAttrs() ; 267 | } ; 268 | 269 | csi.m[ 2 ] = function dim() 270 | { 271 | this.cursor.dim = true ; 272 | this.updateAttrs() ; 273 | } ; 274 | 275 | csi.m[ 3 ] = function italic() 276 | { 277 | this.cursor.italic = true ; 278 | this.updateAttrs() ; 279 | } ; 280 | 281 | csi.m[ 4 ] = function underline() 282 | { 283 | this.cursor.underline = true ; 284 | this.updateAttrs() ; 285 | } ; 286 | 287 | csi.m[ 5 ] = function blink() 288 | { 289 | this.cursor.blink = true ; 290 | this.updateAttrs() ; 291 | } ; 292 | 293 | csi.m[ 7 ] = function inverse() 294 | { 295 | this.cursor.inverse = true ; 296 | this.updateAttrs() ; 297 | } ; 298 | 299 | csi.m[ 8 ] = function hidden() 300 | { 301 | this.cursor.hidden = true ; 302 | this.updateAttrs() ; 303 | } ; 304 | 305 | csi.m[ 9 ] = function strike() 306 | { 307 | this.cursor.strike = true ; 308 | this.updateAttrs() ; 309 | } ; 310 | 311 | csi.m[ 22 ] = function noBoldNoDim() 312 | { 313 | this.cursor.bold = false ; 314 | this.cursor.dim = false ; 315 | this.updateAttrs() ; 316 | } ; 317 | 318 | csi.m[ 23 ] = function noItalic() 319 | { 320 | this.cursor.italic = false ; 321 | this.updateAttrs() ; 322 | } ; 323 | 324 | csi.m[ 24 ] = function noUnderline() 325 | { 326 | this.cursor.underline = false ; 327 | this.updateAttrs() ; 328 | } ; 329 | 330 | csi.m[ 25 ] = function noBlink() 331 | { 332 | this.cursor.blink = false ; 333 | this.updateAttrs() ; 334 | } ; 335 | 336 | csi.m[ 27 ] = function noInverse() 337 | { 338 | this.cursor.inverse = false ; 339 | this.updateAttrs() ; 340 | } ; 341 | 342 | csi.m[ 28 ] = function noHidden() 343 | { 344 | this.cursor.hidden = false ; 345 | this.updateAttrs() ; 346 | } ; 347 | 348 | csi.m[ 29 ] = function noStrike() 349 | { 350 | this.cursor.strike = false ; 351 | this.updateAttrs() ; 352 | } ; 353 | 354 | 355 | 356 | function createSetFgColor( c ) 357 | { 358 | return function setFgColor() { 359 | this.cursor.fgColor = c ; 360 | this.updateAttrs() ; 361 | } ; 362 | } 363 | 364 | function createSetBgColor( c ) 365 | { 366 | return function setBgColor() { 367 | this.cursor.bgColor = c ; 368 | this.updateAttrs() ; 369 | } ; 370 | } 371 | 372 | for ( i = 0 ; i < 8 ; i ++ ) 373 | { 374 | csi.m[ 30 + i ] = createSetFgColor( i ) ; 375 | csi.m[ 90 + i ] = createSetFgColor( i + 8 ) ; 376 | csi.m[ 40 + i ] = createSetBgColor( i ) ; 377 | csi.m[ 100 + i ] = createSetBgColor( i + 8 ) ; 378 | } 379 | 380 | csi.m[ 39 ] = createSetFgColor( false ) ; 381 | csi.m[ 49 ] = createSetBgColor( false ) ; 382 | 383 | 384 | 385 | csi.m[ 38 ] = function setHighFgColor() 386 | { 387 | var trueColor = arguments[ 0 ] === 2 ; 388 | 389 | if ( trueColor ) 390 | { 391 | // 24 bits True Colors 392 | this.cursor.fgColor = [ arguments[ 1 ] , arguments[ 2 ] , arguments[ 3 ] ] ; 393 | this.updateAttrs() ; 394 | return 4 ; 395 | } 396 | else 397 | { 398 | // 256 colors 399 | this.cursor.fgColor = arguments[ 1 ] ; 400 | this.updateAttrs() ; 401 | return 2 ; 402 | } 403 | } ; 404 | 405 | 406 | 407 | csi.m[ 48 ] = function setHighBgColor() 408 | { 409 | var trueColor = arguments[ 0 ] === 2 ; 410 | 411 | if ( trueColor ) 412 | { 413 | // 24 bits True Colors 414 | this.cursor.bgColor = [ arguments[ 1 ] , arguments[ 2 ] , arguments[ 3 ] ] ; 415 | this.updateAttrs() ; 416 | return 4 ; 417 | } 418 | else 419 | { 420 | // 256 colors 421 | this.cursor.bgColor = arguments[ 1 ] ; 422 | this.updateAttrs() ; 423 | return 2 ; 424 | } 425 | } ; 426 | 427 | 428 | 429 | -------------------------------------------------------------------------------- /front/js/dom.js: -------------------------------------------------------------------------------- 1 | /* 2 | TODO: 3 | - add a nice font 4 | 5 | - Paste with ctrl-v add a strange char at the end (on visible after backspace) 6 | work great with middle click 7 | 8 | - Set the size of the terminal by dom 9 | 10 | - Optimise Terminal.prototype.attrsFromObject: too many recompile due to type change 11 | 12 | - Set css in this file 13 | */ 14 | 15 | var dom = {} ; 16 | module.exports = dom ; 17 | 18 | // ref to the main Terminal object, set in dom.init 19 | var Terminal ; 20 | var keyboard = require( './keyboard.js' ) ; 21 | 22 | var type = { 23 | parent:'div', 24 | row:'div', 25 | cell:'span' 26 | } ; 27 | 28 | 29 | /* ************** */ 30 | /* Init functions */ 31 | /* ************** */ 32 | 33 | dom.init = function( terminal ) { 34 | Terminal = terminal ; 35 | 36 | terminal.terminalStyle() ; 37 | 38 | dom.createLayout() ; 39 | dom.events() ; 40 | 41 | css.initCursor() ; 42 | } ; 43 | 44 | dom.createLayout = function() { 45 | dom.$main = document.createElement( type.parent ) ; 46 | dom.$main.id = 'contentTable' ; 47 | dom.$main.className = 'fgColor' + Terminal.defaultFgColorIndex + ' bgColor' + Terminal.defaultBgColorIndex ; 48 | dom.$main.appendChild( fragments.full() ) ; 49 | document.body.appendChild( dom.$main ) ; 50 | } ; 51 | 52 | dom.events = function() { 53 | document.addEventListener( 'keydown' , keyboard.onKeyDown.bind( Terminal ) , false ) ; 54 | document.addEventListener( 'keypress' , keyboard.onKeyPress.bind( Terminal ) , false ) ; 55 | document.addEventListener( 'paste' , keyboard.onPaste.bind( Terminal ) , false ) ; 56 | } ; 57 | 58 | /* ************** */ 59 | /* Main functions */ 60 | /* ************** */ 61 | 62 | dom.insertRow = function( y ) { 63 | if ( y === undefined ) { 64 | dom.deleteRow( 0 ) ; 65 | } 66 | else { 67 | dom.$main.removeChild( dom.$main.children[ -1 ] ) ; 68 | dom.$main.insertBefore( fragments.row() , dom.$main.children[ y ] ) ; 69 | 70 | Terminal.state.pop() ; 71 | Terminal.state.splice( y , 0 , dom.rowArray() ) ; 72 | } 73 | } ; 74 | 75 | dom.insertCell = function( x , y ) { 76 | var cell = dom.getCell( x , y ) , 77 | parentCell = cell.parentNode ; 78 | 79 | parentCell.insertBefore( fragments.cell() , cell ) ; 80 | parentCell.removeChild( cell.parentNode.lastChild ) ; 81 | 82 | Terminal.state[ y ].pop() ; 83 | Terminal.state[ y ].splice( x , 0 , dom.cellArray() ) ; 84 | } ; 85 | 86 | 87 | dom.deleteRow = function( y ) { 88 | dom.$main.removeChild( dom.$main.children[ y ] ) ; 89 | dom.$main.appendChild( fragments.row() ) ; 90 | 91 | Terminal.state.splice( y , 1 ) ; 92 | Terminal.state.push( dom.rowArray() ) ; 93 | } ; 94 | 95 | dom.deleteCell = function( x , y ) { 96 | var cell = dom.getCell( x , y ) , 97 | parent = cell.parentNode ; 98 | 99 | parent.removeChild( cell ) ; 100 | parent.appendChild( fragments.cell() ) ; 101 | 102 | Terminal.state[ y ].splice( x , 1 ) ; 103 | Terminal.state[ y ].push( dom.cellArray() ) ; 104 | } ; 105 | 106 | dom.setCell = function( x , y , char , attrs ) { 107 | var state = Terminal.state[ y ][ x ] , 108 | cell = dom.getCell( x , y ) ; 109 | 110 | if ( attrs.class !== state.class ) { 111 | cell.className = attrs.class ; 112 | } 113 | 114 | if ( char ) { 115 | // if ( char !== state.char ) { 116 | cell.textContent = char ; 117 | } 118 | 119 | if ( attrs.style ) { 120 | cell.style = attrs.style ; 121 | } 122 | 123 | if ( debug.state ) { 124 | if ( attrs.class !== state.class || char ) { 125 | debug.cell( cell , 'cell' ) ; 126 | } 127 | 128 | } 129 | 130 | // Update the internal state 131 | Terminal.state[ y ][ x ] = { 132 | char: char , 133 | class: attrs.class , 134 | 135 | fgColor: Terminal.cursor.fgColor , 136 | bgColor: Terminal.cursor.bgColor , 137 | bold: Terminal.cursor.bold , 138 | dim: Terminal.cursor.dim , 139 | italic: Terminal.cursor.italic , 140 | underline: Terminal.cursor.underline , 141 | blink: Terminal.cursor.blink , 142 | inverse: Terminal.cursor.inverse , 143 | hidden: Terminal.cursor.hidden , 144 | strike: Terminal.cursor.strike 145 | } ; 146 | } ; 147 | 148 | dom.getCell = function( x , y ) { 149 | return dom.$main.children[ y ].children[ x ] ; 150 | } ; 151 | 152 | dom.cursor = false ; 153 | dom.setCursor = function( x , y ) { 154 | var cell = dom.getCell( x , y ) , 155 | state = Terminal.state[ y ][ x ] ; 156 | 157 | if ( dom.cursor ) { 158 | dom.cursor.classList.remove('cursor') ; 159 | } 160 | dom.cursor = cell ; 161 | cell.classList.add('cursor') ; 162 | css.setCursor( state.fgColor || Terminal.defaultFgColorIndex , state.bgColor || Terminal.defaultBgColorIndex ) ; 163 | } ; 164 | 165 | dom.hideCursor = function() { 166 | if ( dom.cursor ) { 167 | dom.cursor.classList.remove('cursor') ; 168 | } 169 | } ; 170 | 171 | 172 | /* **************************** */ 173 | /* "Need to be moved" functions */ 174 | /* **************************** */ 175 | 176 | dom.cellArray = function() { 177 | return { 178 | char: ' ' , 179 | fgColor: Terminal.cursor.fgColor , 180 | bgColor: Terminal.cursor.bgColor 181 | } ; 182 | } ; 183 | dom.rowArray = function() { 184 | var arr = [] ; 185 | 186 | for ( var x = 1 ; x <= Terminal.width ; x ++ ) { 187 | arr.push( dom.cellArray() ) ; 188 | } 189 | return arr ; 190 | } ; 191 | 192 | 193 | /* *********** */ 194 | /* CSS related */ 195 | /* *********** */ 196 | 197 | var css = {} ; 198 | 199 | css.cursor = { 200 | stylesheet: {} , 201 | color:'', 202 | backgroundColor:'' 203 | } ; 204 | 205 | css.initCursor = function() { 206 | var styleEl = document.createElement('style') ; 207 | 208 | document.head.appendChild(styleEl); 209 | 210 | css.cursor.stylesheet = styleEl.sheet; 211 | 212 | css.cursor.stylesheet.insertRule( '.cursor { color:#000; }' , 0 ) ; 213 | css.cursor.stylesheet.insertRule( '@-webkit-keyframes blink { 50% { background-color:#000 ; color:rgb(170,170,170) ; } }' , 1 ) ; 214 | } ; 215 | 216 | 217 | css.setCursor = function( color , backgroundColor ) { 218 | if ( color !== css.cursor.color ) { 219 | css.cursor.color = color ; 220 | color = 'rgb(' + Terminal.palette[ color ].r +','+ Terminal.palette[ color ].g +','+ Terminal.palette[ color ].b + ')'; 221 | 222 | css.cursor.stylesheet.cssRules[0].style.backgroundColor = color ; 223 | css.cursor.stylesheet.cssRules[1].cssRules[0].style.color = color ; 224 | } 225 | if ( backgroundColor !== css.cursor.backgroundColor ) { 226 | css.cursor.backgroundColor = backgroundColor ; 227 | backgroundColor = 'rgb(' + Terminal.palette[ backgroundColor ].r +','+ Terminal.palette[ backgroundColor ].g +','+ Terminal.palette[ backgroundColor ].b + ')'; 228 | 229 | css.cursor.stylesheet.cssRules[0].style.color = backgroundColor ; 230 | css.cursor.stylesheet.cssRules[1].cssRules[0].style.backgroundColor = backgroundColor ; 231 | } 232 | } ; 233 | 234 | 235 | 236 | /* **************************** */ 237 | /* Fragments used in the layout */ 238 | /* **************************** */ 239 | 240 | var fragments = { 241 | cachedFull: false , 242 | cachedRow: false , 243 | cachedCell: false , 244 | 245 | reset: function() { 246 | this.cachedFull = false ; 247 | this.cachedRow = false ; 248 | this.cachedCell = false ; 249 | } , 250 | 251 | full: function() { 252 | if ( ! this.cachedFull ) { 253 | this.cachedFull = document.createDocumentFragment() ; 254 | 255 | for ( var y = 1 ; y <= Terminal.height ; y ++ ) { 256 | Terminal.state.push( dom.rowArray() ) ; 257 | this.cachedFull.appendChild( this.row() ) ; 258 | } 259 | } 260 | 261 | return this.cachedFull.cloneNode( true ) ; 262 | } , 263 | 264 | row: function() { 265 | if ( ! this.cachedRow ) { 266 | this.cachedRow = document.createDocumentFragment() ; 267 | var row = document.createElement( type.row ) ; 268 | 269 | for ( var x = 1 ; x <= Terminal.width ; x ++ ) { 270 | row.appendChild( this.cell() ) ; 271 | } 272 | 273 | this.cachedRow.appendChild( row ) ; 274 | } 275 | 276 | var clone = this.cachedRow.cloneNode( true ) ; 277 | 278 | if ( debug.state ) { 279 | clone.setAttribute('data-debug','new') ; 280 | } 281 | return clone ; 282 | } , 283 | 284 | cell: function() { 285 | if ( ! this.cachedCell ) { 286 | this.cachedCell = document.createDocumentFragment() ; 287 | 288 | var cell = document.createElement( type.cell ) ; 289 | this.cachedCell.appendChild( cell ) ; 290 | } 291 | 292 | var clone = this.cachedCell.cloneNode( true ) ; 293 | 294 | if ( debug.state ) { 295 | clone.setAttribute('data-debug','new') ; 296 | } 297 | return clone ; 298 | } 299 | } ; 300 | 301 | 302 | var debug = { 303 | state:false, 304 | cell: function( cell , value ) { 305 | cell.removeAttribute('data-debug' ) ; 306 | 307 | // to trigger new the animation 308 | setTimeout( function() { 309 | cell.setAttribute('data-debug',value ) ; 310 | } , 0 ) ; 311 | } 312 | } ; 313 | -------------------------------------------------------------------------------- /front/js/keyboard.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Cédric Ronvel 3 | 4 | The MIT License (MIT) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | 27 | var keyboard = {} ; 28 | module.exports = keyboard ; 29 | 30 | 31 | 32 | // Should I use keypressed event instead? 33 | 34 | keyboard.onKeyDown = function onKeyDown( event ) 35 | { 36 | //console.log( string.inspect( { style: 'color' , depth: 1 } , event ) ) ; 37 | var keyCode = event.keyCode ; 38 | var key ; 39 | 40 | switch( keyCode ) 41 | { 42 | case 16: 43 | case 17: 44 | case 18: 45 | case 225: 46 | // shift, ctrl, alt, altgr: do nothing when those key are pressed alone 47 | break ; 48 | 49 | // BACKSPACE 50 | case 8: 51 | if ( event.shiftKey ) { key = '\x08' ; } // backspace / shift backspace 52 | else { key = '\x7f' ; } 53 | break ; 54 | 55 | // TAB 56 | case 9: 57 | if ( event.shiftKey ) { key = '\x1b[Z' ; } 58 | else if ( event.altKey ) { key = '\x1b\x09' ; } 59 | else { key = '\x09' ; } 60 | break ; 61 | 62 | // ENTER / RETURN 63 | case 13: 64 | key = '\x0d' ; 65 | break ; 66 | 67 | // ESCAPE 68 | case 27: 69 | key = '\x1b' ; 70 | break ; 71 | 72 | case 32: 73 | if ( event.ctrlKey && event.altKey ) { key = '\x1b\x00' ; } // ESC NUL 74 | else if ( event.ctrlKey ) { key = '\x00' ; } // NUL 75 | else if ( event.altKey ) { key = '\x1b ' ; } // ESC SPACE 76 | break ; 77 | 78 | // PAGE UP 79 | case 33: 80 | key = '\x1b[5~' ; 81 | break ; 82 | 83 | // PAGE DOWN 84 | case 34: 85 | key = '\x1b[6~' ; 86 | break ; 87 | 88 | // END 89 | case 35: 90 | key = '\x1b[4~' ; 91 | break ; 92 | 93 | // HOME 94 | case 36: 95 | key = '\x1b[1~' ; 96 | break ; 97 | 98 | // LEFT 99 | case 37: 100 | if ( event.shiftKey ) { key = '\x1b[1;2D' ; } 101 | else if ( event.altKey ) { key = '\x1b[1;3D' ; } 102 | else if ( event.ctrlKey ) { key = '\x1b[1;5D' ; } 103 | else { key = '\x1b[D' ; } 104 | break ; 105 | 106 | // UP 107 | case 38: 108 | if ( event.shiftKey ) { key = '\x1b[1;2A' ; } 109 | else if ( event.altKey ) { key = '\x1b[1;3A' ; } 110 | else if ( event.ctrlKey ) { key = '\x1b[1;5A' ; } 111 | else { key = '\x1b[A' ; } 112 | break ; 113 | 114 | // RIGHT 115 | case 39: 116 | if ( event.shiftKey ) { key = '\x1b[1;2C' ; } 117 | else if ( event.altKey ) { key = '\x1b[1;3C' ; } 118 | else if ( event.ctrlKey ) { key = '\x1b[1;5C' ; } 119 | else { key = '\x1b[C' ; } 120 | break ; 121 | 122 | // DOWN 123 | case 40: 124 | if ( event.shiftKey ) { key = '\x1b[1;2B' ; } 125 | else if ( event.altKey ) { key = '\x1b[1;3B' ; } 126 | else if ( event.ctrlKey ) { key = '\x1b[1;5B' ; } 127 | else { key = '\x1b[B' ; } 128 | break ; 129 | 130 | // INSERT 131 | case 45: 132 | if ( event.altKey ) { key = '\x1b[2;3~' ; } 133 | else { key = '\x1b[2~' ; } 134 | break ; 135 | 136 | // DELETE 137 | case 46: 138 | if ( event.altKey ) { key = '\x1b[3;3~' ; } 139 | else { key = '\x1b[3~' ; } 140 | break ; 141 | 142 | // F1 143 | case 112: 144 | if ( event.shiftKey && event.ctrlKey ) { key = '\x1bO1;6P' ; } 145 | else if ( event.shiftKey ) { key = '\x1bO1;2P' ; } 146 | else if ( event.ctrlKey ) { key = '\x1bO1;5P' ; } 147 | else { key = '\x1bOP' ; } 148 | break ; 149 | 150 | // F2 151 | case 113: 152 | if ( event.shiftKey && event.ctrlKey ) { key = '\x1bO1;6Q' ; } 153 | else if ( event.shiftKey ) { key = '\x1bO1;2Q' ; } 154 | else if ( event.ctrlKey ) { key = '\x1bO1;5Q' ; } 155 | else { key = '\x1bOQ' ; } 156 | break ; 157 | 158 | // F3 159 | case 114: 160 | if ( event.shiftKey && event.ctrlKey ) { key = '\x1bO1;6R' ; } 161 | else if ( event.shiftKey ) { key = '\x1bO1;2R' ; } 162 | else if ( event.ctrlKey ) { key = '\x1bO1;5R' ; } 163 | else { key = '\x1bOR' ; } 164 | break ; 165 | 166 | // F4 167 | case 115: 168 | if ( event.shiftKey && event.ctrlKey ) { key = '\x1bO1;6S' ; } 169 | else if ( event.shiftKey ) { key = '\x1bO1;2S' ; } 170 | else if ( event.ctrlKey ) { key = '\x1bO1;5S' ; } 171 | else { key = '\x1bOS' ; } 172 | break ; 173 | 174 | // F5 175 | case 116: 176 | if ( event.shiftKey && event.ctrlKey ) { key = '\x1b[15;6~' ; } 177 | else if ( event.shiftKey ) { key = '\x1b[15;2~' ; } 178 | else if ( event.ctrlKey ) { key = '\x1b[15;5~' ; } 179 | else { key = '\x1b[15~' ; } 180 | break ; 181 | 182 | // F6 183 | case 117: 184 | if ( event.shiftKey && event.ctrlKey ) { key = '\x1b[17;6~' ; } 185 | else if ( event.shiftKey ) { key = '\x1b[17;2~' ; } 186 | else if ( event.ctrlKey ) { key = '\x1b[17;5~' ; } 187 | else { key = '\x1b[17~' ; } 188 | break ; 189 | 190 | // F7 191 | case 118: 192 | if ( event.shiftKey && event.ctrlKey ) { key = '\x1b[18;6~' ; } 193 | else if ( event.shiftKey ) { key = '\x1b[18;2~' ; } 194 | else if ( event.ctrlKey ) { key = '\x1b[18;5~' ; } 195 | else { key = '\x1b[18~' ; } 196 | break ; 197 | 198 | // F8 199 | case 119: 200 | if ( event.shiftKey && event.ctrlKey ) { key = '\x1b[19;6~' ; } 201 | else if ( event.shiftKey ) { key = '\x1b[19;2~' ; } 202 | else if ( event.ctrlKey ) { key = '\x1b[19;5~' ; } 203 | else { key = '\x1b[19~' ; } 204 | break ; 205 | 206 | // F9 207 | case 120: 208 | if ( event.shiftKey && event.ctrlKey ) { key = '\x1b[20;6~' ; } 209 | else if ( event.shiftKey ) { key = '\x1b[20;2~' ; } 210 | else if ( event.ctrlKey ) { key = '\x1b[20;5~' ; } 211 | else { key = '\x1b[20~' ; } 212 | break ; 213 | 214 | // F10 215 | case 121: 216 | if ( event.shiftKey && event.ctrlKey ) { key = '\x1b[21;6~' ; } 217 | else if ( event.shiftKey ) { key = '\x1b[21;2~' ; } 218 | else if ( event.ctrlKey ) { key = '\x1b[21;5~' ; } 219 | else { key = '\x1b[21~' ; } 220 | break ; 221 | 222 | // F11 223 | case 122: 224 | if ( event.shiftKey && event.ctrlKey ) { key = '\x1b[23;6~' ; } 225 | else if ( event.shiftKey ) { key = '\x1b[23;2~' ; } 226 | else if ( event.ctrlKey ) { key = '\x1b[23;5~' ; } 227 | else { key = '\x1b[23~' ; } 228 | break ; 229 | 230 | // F12 231 | case 123: 232 | if ( event.shiftKey && event.ctrlKey ) { key = '\x1b[24;6~' ; } 233 | else if ( event.shiftKey ) { key = '\x1b[24;2~' ; } 234 | else if ( event.ctrlKey ) { key = '\x1b[24;5~' ; } 235 | else { key = '\x1b[24~' ; } 236 | break ; 237 | 238 | // Caret, etc 239 | case 229: 240 | if ( event.shiftKey ) { key = '¨' ; } 241 | else { key = '^' ; } 242 | break ; 243 | 244 | default : 245 | 246 | if ( event.ctrlKey ) 247 | { 248 | if ( keyCode >= 65 && keyCode <= 90 ) 249 | { 250 | // CTRL + [A-Z] or CTRL + ALT + [A-Z] 251 | key = ( event.altKey ? '\x1b' : '' ) + String.fromCharCode( keyCode - 64 ) ; 252 | } 253 | } 254 | else if ( event.altKey || event.metaKey ) 255 | { 256 | if ( keyCode >= 65 && keyCode <= 90 ) 257 | { 258 | // ALT + [A-Z] or ALT + SHIFT + [A-Z] 259 | key = '\x1b' + String.fromCharCode( keyCode + ( event.shiftKey ? 0 : 32 ) ) ; 260 | } 261 | } 262 | 263 | /* 264 | console.log( "keydown: " + 265 | ( event.shiftKey ? 'Shift+' : '' ) + 266 | ( event.ctrlKey ? 'Ctrl+' : '' ) + 267 | ( event.altKey ? 'Alt+' : '' ) + 268 | ( event.metaKey ? 'Meta+' : '' ) + 269 | keyChar + 270 | ' [' + keyCode + ']' 271 | ) ; 272 | //*/ 273 | break ; 274 | } 275 | 276 | // We should NOT return false here: it would prevent the 'keypress' event from firing 277 | if ( key === undefined ) { return ; } 278 | 279 | event.preventDefault() ; 280 | //event.stopPropagation() ; 281 | 282 | this.remoteWin.childProcess.input( key ) ; 283 | 284 | // Important 285 | return false ; 286 | } ; 287 | 288 | 289 | 290 | keyboard.onKeyPress = function( event ) 291 | { 292 | var keyCode = event.keyCode ; 293 | var keyChar = String.fromCharCode( keyCode ) ; 294 | //console.log( string.inspect( { style: 'color' , depth: 1 } , event ) ) ; 295 | 296 | // In those case, let onKeyDown handle things 297 | if ( keyCode < 0x20 || keyCode === 0x7f || event.ctrlKey || event.altKey || event.metaKey ) { return false ; } 298 | 299 | // So we have got a regular character, just emit it 300 | 301 | event.preventDefault() ; 302 | //event.stopPropagation() ; 303 | 304 | /* 305 | console.log( "keypress: " + 306 | keyChar + 307 | ' [' + keyCode + '][' + 308 | ( event.shiftKey ? ' Shift ' : '' ) + 309 | ( event.ctrlKey ? ' Ctrl ' : '' ) + 310 | ( event.altKey ? ' Alt ' : '' ) + 311 | ( event.metaKey ? ' Meta ' : '' ) + 312 | ']' 313 | ) ; 314 | //*/ 315 | 316 | this.remoteWin.childProcess.input( keyChar ) ; 317 | //console.log( 'input keyChar: "' + keyChar + '"' ) ; 318 | 319 | // Important 320 | return false; 321 | } ; 322 | 323 | 324 | keyboard.onPaste = function( event ) 325 | { 326 | this.remoteWin.childProcess.input( event.clipboardData.getData('text') ) ; 327 | // return false; 328 | } ; 329 | -------------------------------------------------------------------------------- /front/js/osc.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Cédric Ronvel 3 | 4 | The MIT License (MIT) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | 27 | var osc = {} ; 28 | module.exports = osc ; 29 | 30 | 31 | 32 | 33 | 34 | function parseNumbers( sequence ) 35 | { 36 | return sequence.split( ';' ).map( function( value ) { 37 | value = parseInt( value , 10 ) ; 38 | if ( isNaN( value ) ) { return undefined ; } 39 | return value ; 40 | } ) ; 41 | } 42 | 43 | 44 | 45 | 46 | 47 | /* --- */ 48 | 49 | 50 | 51 | osc[ 7 ] = function setCwd( sequence ) 52 | { 53 | // This is sent (e.g. by bash) to let the terminal knows about the Current Working Directory 54 | this.cwd = sequence ; 55 | } ; 56 | -------------------------------------------------------------------------------- /front/js/terminalMain.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Cédric Ronvel 3 | 4 | The MIT License (MIT) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | 27 | // It appears that the current directory path is the HTML's directory... 28 | // It's only true for files loaded directly via a 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /log/touch: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atomic-terminal", 3 | "version": "0.0.29", 4 | "description": "A terminal powered by Electron", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "dependencies": { 10 | "async-kit": "^0.5.8", 11 | "child_pty": "^1.0.0", 12 | "dom-kit": "0.0.10", 13 | "electron-prebuilt": "^0.26.0", 14 | "electron-rebuild": "^0.1.4", 15 | "string-kit": "^0.1.10", 16 | "terminal-kit": "^0.13.1", 17 | "tree-kit": "^0.5.5" 18 | }, 19 | "devDependencies": { 20 | "expect.js": "^0.3.1", 21 | "jshint": "^2.5.6", 22 | "mocha": "^1.21.4" 23 | }, 24 | "scripts": { 25 | "install": "electron-rebuild", 26 | "test": "mocha" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/cronvel/atomic-terminal.git" 31 | }, 32 | "bin": { 33 | "atomic-terminal": "./bin/atomic-terminal" 34 | }, 35 | "keywords": [ 36 | "terminal", 37 | "electron" 38 | ], 39 | "author": "Cédric Ronvel", 40 | "license": "MIT", 41 | "bugs": { 42 | "url": "https://github.com/cronvel/atomic-terminal/issues" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sample/child_pty-test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | Copyright (c) 2015 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | 28 | 29 | // Modules 30 | //var spawn = require( 'child_process' ).spawn ; 31 | var spawn = require( 'child_pty' ).spawn ; 32 | 33 | 34 | 35 | // Create the object & export it 36 | var processCom = {} ; 37 | 38 | 39 | 40 | processCom.exec = function exec( command , args ) 41 | { 42 | if ( ! args ) { args = [] ; } 43 | 44 | console.log( command ) ; 45 | console.log( args ) ; 46 | 47 | var interface = spawn( command , args , { columns: 80 , rows: 24 } ) ; 48 | 49 | /* 50 | interface.stdout.on( 'data' , function( data ) { 51 | interface.emit( 'input' , data ) ; 52 | } ) ; 53 | 54 | interface.stderr.on( 'data', function( data ) { 55 | interface.emit( 'input' , data ) ; 56 | } ) ; 57 | 58 | interface.on( 'close', function( code ) { 59 | interface.emit( 'close' , code ) ; 60 | } ) ; 61 | */ 62 | 63 | return interface ; 64 | } ; 65 | 66 | 67 | 68 | var child = processCom.exec( process.argv[ 2 ] ) ; 69 | 70 | child.stdout.on( 'data' , function( data ) { 71 | process.stdout.write( data ) ; 72 | } ) ; 73 | 74 | child.on( 'close' , function( data ) { 75 | process.exit() ; 76 | } ) ; 77 | 78 | 79 | -------------------------------------------------------------------------------- /sample/key-test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | The Cedric's Swiss Knife (CSK) - CSK terminal toolbox test suite 4 | 5 | Copyright (c) 2009 - 2014 Cédric Ronvel 6 | 7 | The MIT License (MIT) 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | */ 27 | 28 | /* jshint unused:false */ 29 | 30 | 31 | /* 32 | process.stdin.setRawMode( true ) ; 33 | 34 | process.stdin.on( 'data' , function( data ) { 35 | console.log( 'received data: ' , data ) ; 36 | } ) ; 37 | //*/ 38 | 39 | var term = require( 'terminal-kit' ).terminal ; 40 | 41 | function terminate() 42 | { 43 | term.brightBlack( 'About to exit...\n' ) ; 44 | term.grabInput( false ) ; 45 | term.applicationKeypad( false ) ; 46 | term.beep() ; 47 | term.fullscreen( false ) ; 48 | 49 | // Add a 100ms delay, so the terminal will be ready when the process effectively exit, preventing bad escape sequences drop 50 | setTimeout( function() { process.exit() } , 100 ) ; 51 | } 52 | 53 | 54 | 55 | term.fullscreen() ; 56 | term.bold.cyan( 'Key test, hit anything on the keyboard to see how it is detected...\n' ) ; 57 | term.green( 'Hit CTRL-C to quit.\n\n' ) ; 58 | 59 | // Set Application Keypad mode, but it does not works on every box (sometime numlock should be off for this to work) 60 | 61 | var applicationKeypad = true ; 62 | term.applicationKeypad( applicationKeypad ) ; 63 | 64 | //term.keyboardModifier() ; 65 | 66 | term.grabInput() ; 67 | //term.grabInput( { mouse: 'motion' , focus: true } ) ; 68 | 69 | term.on( 'key' , function( name , matches , data ) { 70 | 71 | console.log( 72 | "Key:" , name , 73 | ", length:" , name.length , 74 | ", all matches:" , matches , 75 | ", is character:" , data.isCharacter , 76 | ", codepoint:" , data.codepoint ? data.codepoint.toString( 16 ) : '' , 77 | ", buffer:" , Buffer.isBuffer( data.code ) ? data.code : data.code.toString( 16 ) 78 | ) ; 79 | 80 | switch ( name ) 81 | { 82 | case 'CTRL_C' : 83 | term.green( 'CTRL-C received...\n' ) ; 84 | terminate() ; 85 | break ; 86 | 87 | case 'CTRL_K' : 88 | applicationKeypad = ! applicationKeypad ; 89 | term.applicationKeypad( applicationKeypad ) ; 90 | term.green( 'CTRL-K received, switching application keypad mode %s...\n' , applicationKeypad ? 'on' : 'off' ) ; 91 | break ; 92 | } 93 | 94 | } ) ; 95 | 96 | -------------------------------------------------------------------------------- /sample/random-char.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 4 | var thingsToWrite = [ 5 | 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 6 | 'n' , 'o' , 'p' , 'q' , 'r' , 's' , 't' , 'u' , 'v' , 'w' , 'x' , 'y' , 'z' , 7 | '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 8 | ' ' , ' ' , ' ' , ' ' , ' ' , ' ' , 9 | '\n' , '\n' , '\n' , 10 | 11 | // bold 12 | '\x1b[1m' , '\x1b[1m' , '\x1b[5m' , '\x1b[5m' , 13 | '\x1b[22m' , '\x1b[22m' , '\x1b[22m' , '\x1b[22m' , 14 | ] ; 15 | 16 | 17 | 18 | function randomChar() 19 | { 20 | process.stdout.write( thingsToWrite[ Math.floor( Math.random() * thingsToWrite.length ) ] ) ; 21 | 22 | setTimeout( randomChar , Math.random() * 500 ) ; 23 | } 24 | 25 | randomChar() ; 26 | 27 | -------------------------------------------------------------------------------- /sample/style-test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | The Cedric's Swiss Knife (CSK) - CSK terminal toolbox test suite 4 | 5 | Copyright (c) 2009 - 2014 Cédric Ronvel 6 | 7 | The MIT License (MIT) 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | */ 27 | 28 | /* jshint unused:false */ 29 | 30 | 31 | 32 | var term = require( 'terminal-kit' ).terminal ; 33 | 34 | 35 | 36 | var i ; 37 | 38 | // Test foreground colors 39 | 40 | term.bold( '=== Foreground colors ===\n\n' ) ; 41 | 42 | term.white( 'white ' ).brightWhite( 'brightWhite' )( '\n' ) ; 43 | term.black( 'black ' ).brightBlack( 'brightBlack' )( '\n' ) ; 44 | term.red( 'red ' ).brightRed( 'brightRed' )( '\n' ) ; 45 | term.yellow( 'yellow ' ).brightYellow( 'brightYellow' )( '\n' ) ; 46 | term.green( 'green ' ).brightGreen( 'brightGreen' )( '\n' ) ; 47 | term.cyan( 'cyan ' ).brightCyan( 'brightCyan' )( '\n' ) ; 48 | term.blue( 'blue ' ).brightBlue( 'brightBlue' )( '\n' ) ; 49 | term.magenta( 'magenta ' ).brightMagenta( 'brightMagenta' )( '\n' ) ; 50 | 51 | // Check the color() function 52 | for ( i = 0 ; i < 16 ; i ++ ) { term.color( i , '*' ) ; } 53 | term( '\n' ) ; 54 | 55 | 56 | 57 | // Test background colors 58 | 59 | term.bold( '\n=== Background colors ===\n\n' ) ; 60 | 61 | term.bgWhite( 'bgWhite ' ).bgBrightWhite( 'bgBrightWhite' )( '\n' ) ; 62 | term.bgBlack( 'bgBlack ' ).bgBrightBlack( 'bgBrightBlack' )( '\n' ) ; 63 | term.bgRed( 'bgRed ' ).bgBrightRed( 'bgBrightRed' )( '\n' ) ; 64 | term.bgYellow( 'bgYellow ' ).bgBrightYellow( 'bgBrightYellow' )( '\n' ) ; 65 | term.bgGreen( 'bgGreen ' ).bgBrightGreen( 'bgBrightGreen' )( '\n' ) ; 66 | term.bgCyan( 'bgCyan ' ).bgBrightCyan( 'bgBrightCyan' )( '\n' ) ; 67 | term.bgBlue( 'bgBlue ' ).bgBrightBlue( 'bgBrightBlue' )( '\n' ) ; 68 | term.bgMagenta( 'bgMagenta ' ).bgBrightMagenta( 'bgBrightMagenta' )( '\n' ) ; 69 | 70 | // Check the bgColor() function 71 | for ( i = 0 ; i < 16 ; i ++ ) { term.bgColor( i , ' ' ) ; } 72 | term( '\n' ) ; 73 | 74 | //process.exit() ; 75 | 76 | 77 | // Test styles 78 | 79 | term.bold( '\n=== Styles ===\n\n' ) ; 80 | 81 | term.bold( 'bold' )( '\n' ) ; 82 | term.dim( 'dim' )( '\n' ) ; 83 | term.italic( 'italic' )( '\n' ) ; 84 | term.underline( 'underline' )( '\n' ) ; 85 | term.blink( 'blink' )( '\n' ) ; 86 | term.inverse( 'inverse' )( '\n' ) ; 87 | term.hidden( 'hidden' )( ' <-- hidden\n' ) ; 88 | term.strike( 'strike' )( '\n' ) ; 89 | 90 | 91 | 92 | // Test mixed styles 93 | 94 | term.bold( '\n=== Mixed styles ===\n\n' ) ; 95 | 96 | term.bold.red( 'bold-red' )( '\n' ) ; 97 | term.dim.red( 'dim-red' )( '\n' ) ; 98 | term.bold.dim.red( 'bold-dim-red' )( '\n' ) ; 99 | term.cyan.bgRed( 'cyan-on-red' )( '\n' ) ; 100 | term.bold.cyan.bgRed( 'bold-cyan-on-red' )( '\n' ) ; 101 | term.bold.italic.underline( 'bold-italic-underline' )( '\n' ) ; 102 | 103 | 104 | 105 | // Reset before exiting... 106 | 107 | term( '\n' ) ; 108 | term.styleReset() ; 109 | term( 'Reset...\n' ) ; 110 | 111 | -------------------------------------------------------------------------------- /test/atomic-terminal-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | Test Terminal 3 | 4 | Copyright (c) 2015 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /* jshint unused:false */ 28 | /* global describe, it, before, after */ 29 | 30 | 31 | 32 | var expect = require( 'expect.js' ) ; 33 | 34 | 35 | 36 | describe( "..." , function() { 37 | 38 | it( "..." , function() { 39 | } ) ; 40 | } ) ; 41 | 42 | -------------------------------------------------------------------------------- /to-support.txt: -------------------------------------------------------------------------------- 1 | 2 | /* Escape sequences */ 3 | 4 | 5 | // Remove colors 6 | var defaultColor = '\x1b[39m' ; // back to the default color, most of time it is the same than .white 7 | var bgDefaultColor = '\x1b[49m' ; // back to the default color, most of time it is the same than .bgBlack 8 | 9 | 10 | 11 | var esc = { 12 | 13 | /* Common sequences */ 14 | 15 | // Reset the terminal 16 | reset: { on: '\x1bc' } , 17 | 18 | /* Cursor sequences */ 19 | 20 | scrollUp: { on: '\x1b[%US' } , 21 | scrollDown: { on: '\x1b[%UT' } , 22 | hideCursor: { on: '\x1b[?25l' , off: '\x1b[?25h' } , 23 | 24 | /* Editing sequences */ 25 | 26 | // This set the alternate screen buffer, do not work on many term, due to this titeInhibit shit... 27 | alternateScreenBuffer: { on: '\x1b[?1049h' , off: '\x1b[?1049l' } , 28 | 29 | /* Misc sequences */ 30 | 31 | bell: { on: '\x07' } , // Emit an audible bell 32 | 33 | 34 | /* Input / Output sequences */ 35 | 36 | // Request terminal ID 37 | // requestTerminalId: { on: '\x1b[>c' } , 38 | 39 | // Terminal will send the cursor coordinate only one time 40 | requestCursorLocation: { on: '\x1b[6n' } , // '\x1b[?6n' is not widely supported, '\x1b[6n' is better 41 | 42 | // Terminal will send the cursor coordinate only one time 43 | requestScreenSize: { on: '\x1b[18t' } , 44 | 45 | // Terminal will send the rgb color for a register 46 | requestColor: { on: '\x1b]4;%u;?\x07' } , 47 | 48 | // Terminal will send event on button pressed with mouse position 49 | mouseButton: { on: '\x1b[?1000h' , off: '\x1b[?1000l' } , 50 | 51 | // Terminal will send position of the column hilighted 52 | mouseHilight: { on: '\x1b[?1001h' , off: '\x1b[?1001l' } , 53 | 54 | // Terminal will send event on button pressed and mouse motion as long as a button is down, with mouse position 55 | mouseDrag: { on: '\x1b[?1002h' , off: '\x1b[?1002l' } , 56 | 57 | // Terminal will send event on button pressed and motion 58 | mouseMotion: { on: '\x1b[?1003h' , off: '\x1b[?1003l' } , 59 | 60 | // Another mouse protocol that extend coordinate mapping (without it, it supports only 223 rows and columns) 61 | mouseSGR: { on: '\x1b[?1006h' , off: '\x1b[?1006l' } , 62 | 63 | // Terminal will send event when it gains and loses focus 64 | focusEvent: { on: '\x1b[?1004h' , off: '\x1b[?1004l' } , 65 | 66 | // Should allow keypad to send different code than 0..9 keys but it does not works on some setup 67 | applicationKeypad: { on: '\x1b[?1h\x1b=' , off: '\x1b[?1l\x1b>' } , 68 | 69 | // When enabled, the terminal will report if modifiers (SHIFT, CTRL, ALT) are on 70 | /* Not widely supported 71 | keyboardModifier: { on: '\x1b[>0;1m' , off: '\x1b[>0;0m' } , 72 | cursorKeyModifier: { on: '\x1b[>1;1m' , off: '\x1b[>1;0m' } , 73 | functionKeyModifier: { on: '\x1b[>2;1m' , off: '\x1b[>2;0m' } , 74 | otherKeyModifier: { on: '\x1b[>3;1m' , off: '\x1b[>3;0m' } , 75 | */ 76 | 77 | // Do not work... use node.js stdout.setRawMode(true) instead 78 | //noecho: { on: '\x1b[12h' } , 79 | 80 | /* OSC - OS Control sequences: may be unavailable on some context */ 81 | 82 | // Set the title of an xterm-compatible window 83 | windowTitle: { on: '\x1b]0;%s\x1b\\' } , 84 | 85 | // Those sequences accept either #%x%x%x or rgb:%d/%d/%d 86 | // Sometime rgb:%d/%d/%d should be encoded into the 0..65535 range, so #%x%x%x is more reliable 87 | setCursorColorRgb: { on: '\x1b]12;#%x%x%x\x07' } , // it want rgb as parameter, like rgb:127/0/32 88 | setDefaultColorRgb: { on: '\x1b]10;#%x%x%x\x07' } , // ->|TODOC|<- not widely supported 89 | setDefaultBgColorRgb: { on: '\x1b]11;#%x%x%x\x07' } , // ->|TODOC|<- not widely supported 90 | setColorLL: { on: '\x1b]4;%u;#%x%x%x\x07' } , 91 | setFont: { on: '\x1b]50;%s\x07' } , // ->|TODOC|<- rarely supported 92 | 93 | } ; 94 | 95 | 96 | 97 | 98 | 99 | /* Inputs management */ 100 | 101 | 102 | 103 | var handler = {} ; 104 | 105 | 106 | 107 | handler.mouseX11Protocol = function mouseX11Protocol( basename , buffer ) 108 | { 109 | var code = buffer[ 0 ] ; 110 | var result = { 111 | data: { 112 | shift: code & 4 ? true : false , 113 | alt: code & 8 ? true : false , 114 | ctrl: code & 16 ? true : false 115 | } 116 | } ; 117 | 118 | if ( code & 32 ) 119 | { 120 | if ( code & 64 ) 121 | { 122 | result.name = basename + ( code & 1 ? '_WHEEL_DOWN' : '_WHEEL_UP' ) ; 123 | } 124 | else 125 | { 126 | // Button event 127 | switch ( code & 3 ) 128 | { 129 | case 0 : result.name = basename + '_LEFT_BUTTON_PRESSED' ; break ; 130 | case 1 : result.name = basename + '_MIDDLE_BUTTON_PRESSED' ; break ; 131 | case 2 : result.name = basename + '_RIGHT_BUTTON_PRESSED' ; break ; 132 | case 3 : result.name = basename + '_BUTTON_RELEASED' ; break ; 133 | } 134 | } 135 | } 136 | else if ( code & 64 ) 137 | { 138 | // Motion event 139 | result.name = basename + '_MOTION' ; 140 | } 141 | 142 | result.eaten = 3 ; 143 | result.data.code = code ; 144 | result.data.x = buffer[ 1 ] - 32 ; 145 | result.data.y = buffer[ 2 ] - 32 ; 146 | 147 | return result ; 148 | } ; 149 | 150 | 151 | 152 | handler.mouseSGRProtocol = function mouseSGRProtocol( basename , buffer ) 153 | { 154 | var code , pressed , matches , result ; 155 | 156 | matches = buffer.toString().match( /^([0-9]*);([0-9]*);([0-9]*)(.)/ ) ; 157 | 158 | code = parseInt( matches[ 1 ] ) ; 159 | pressed = matches[ 4 ] !== 'm' ; 160 | 161 | result = { 162 | data: { 163 | shift: code & 4 ? true : false , 164 | alt: code & 8 ? true : false , 165 | ctrl: code & 16 ? true : false 166 | } 167 | } ; 168 | 169 | result.data.x = parseInt( matches[ 2 ] ) ; 170 | result.data.y = parseInt( matches[ 3 ] ) ; 171 | result.eaten = matches[ 0 ].length ; 172 | 173 | if ( code & 32 ) 174 | { 175 | // Motions event 176 | result.name = basename + '_MOTION' ; 177 | } 178 | else 179 | { 180 | if ( code & 64 ) 181 | { 182 | result.name = basename + ( code & 1 ? '_WHEEL_DOWN' : '_WHEEL_UP' ) ; 183 | } 184 | else 185 | { 186 | // Button event 187 | switch ( code & 3 ) 188 | { 189 | case 0 : 190 | result.name = basename + '_LEFT_BUTTON' ; 191 | //if ( this.state.button.left === pressed ) { result.disable = true ; } 192 | this.state.button.left = pressed ; 193 | break ; 194 | 195 | case 1 : 196 | result.name = basename + '_MIDDLE_BUTTON' ; 197 | //if ( this.state.button.middle === pressed ) { result.disable = true ; } 198 | this.state.button.middle = pressed ; 199 | break ; 200 | 201 | case 2 : 202 | result.name = basename + '_RIGHT_BUTTON' ; 203 | //if ( this.state.button.right === pressed ) { result.disable = true ; } 204 | this.state.button.right = pressed ; 205 | break ; 206 | 207 | case 3 : 208 | result.name = basename + '_OTHER_BUTTON' ; 209 | //if ( this.state.button.other === pressed ) { result.disable = true ; } 210 | this.state.button.other = pressed ; 211 | break ; 212 | } 213 | 214 | result.name += pressed ? '_PRESSED' : '_RELEASED' ; 215 | } 216 | } 217 | 218 | result.data.code = code ; 219 | 220 | return result ; 221 | } ; 222 | 223 | 224 | 225 | handler.cursorLocation = function cursorLocation( basename , paramString ) 226 | { 227 | var params = paramString.split( ';' ) ; 228 | 229 | return { 230 | name: 'CURSOR_LOCATION' , 231 | data: { 232 | x: parseInt( params[ 1 ] ) , 233 | y: parseInt( params[ 0 ] ) 234 | } 235 | } ; 236 | } ; 237 | 238 | 239 | 240 | handler.colorRegister = function colorRegister( basename , paramString ) 241 | { 242 | var matches = paramString.match( /^([0-9]*);rgb:([0-9a-f]{2})[0-9a-f]*\/([0-9a-f]{2})[0-9a-f]*\/([0-9a-f]{2})[0-9a-f]*/ ) ; 243 | 244 | return { 245 | name: 'COLOR_REGISTER' , 246 | data: { 247 | register: parseInt( matches[ 1 ] ) , 248 | r: parseInt( matches[ 2 ] , 16 ) , 249 | g: parseInt( matches[ 3 ] , 16 ) , 250 | b: parseInt( matches[ 4 ] , 16 ) 251 | } 252 | } ; 253 | } ; 254 | 255 | 256 | 257 | handler.screenSize = function screenSize( basename , paramString ) 258 | { 259 | var params = paramString.split( ';' ) , 260 | width = parseInt( params[ 1 ] ) , 261 | height = parseInt( params[ 0 ] ) , 262 | resized = this.root.width !== width || this.root.height !== height ; 263 | 264 | this.root.width = width ; 265 | this.root.height = height ; 266 | 267 | return { 268 | name: 'SCREEN_SIZE' , 269 | data: { 270 | resized: resized , 271 | width: width , 272 | height: height 273 | } 274 | } ; 275 | } ; 276 | 277 | 278 | 279 | 280 | 281 | /* Key Mapping */ 282 | 283 | 284 | 285 | var keymap = { 286 | 287 | // Application Keypad 288 | KP_NUMLOCK: [] , // '\x1bOP' , 289 | KP_DIVIDE: '\x1bOo' , 290 | KP_MULTIPLY: '\x1bOj' , 291 | KP_MINUS: '\x1bOm' , 292 | KP_0: [] , // '\x1b[2~' , 293 | KP_1: [] , // '\x1bOF' , 294 | KP_2: [] , // '\x1b[B' , 295 | KP_3: [] , // '\x1b[6~' , 296 | KP_4: [] , // '\x1b[D' , 297 | KP_5: [ '\x1bOE' , '\x1b[E' ] , 298 | KP_6: [] , // '\x1b[C' , 299 | KP_7: [] , // '\x1bOH' , 300 | KP_8: [] , // '\x1b[A' , 301 | KP_9: [] , // '\x1b[5~' , 302 | KP_PLUS: '\x1bOk' , 303 | KP_DELETE: [] , // '\x1b[3~' , 304 | KP_ENTER: '\x1bOM' , 305 | 306 | CURSOR_LOCATION: { starter: '\x1b[' , ender: 'R' , event: 'terminal' , handler: 'cursorLocation' } , 307 | SCREEN_SIZE: { starter: '\x1b[8;' , ender: 't' , event: 'terminal' , handler: 'screenSize' } , 308 | COLOR_REGISTER: { starter: '\x1b]4;' , ender: '\x07' , event: 'terminal' , handler: 'colorRegister' } , 309 | 310 | FOCUS_IN: { code: '\x1b[I' , event: 'terminal' , data: {} } , 311 | FOCUS_OUT: { code: '\x1b[O' , event: 'terminal' , data: {} } , 312 | 313 | MOUSE: [ 314 | { code: '\x1b[<' , event: 'mouse' , handler: 'mouseSGRProtocol' } , 315 | { code: '\x1b[M' , event: 'mouse' , handler: 'mouseX11Protocol' } 316 | ] 317 | } ; 318 | 319 | 320 | 321 | module.exports = { 322 | esc: esc , 323 | keymap: keymap , 324 | handler: handler , 325 | colorRegister: require( '../colorScheme/xterm.json' ) 326 | } ; 327 | 328 | --------------------------------------------------------------------------------