modified vt100 to capture keys correctly
/ test (push) Successful in 8s Details

This commit is contained in:
Leon van Kammen 2024-10-22 06:59:53 +00:00
parent 10a3ff6688
commit ce44449fc4
2 changed files with 159 additions and 164 deletions

View File

@ -35,24 +35,27 @@ if( typeof AFRAME != 'undefined '){
schema: { schema: {
iso: { type:"string", "default":"https://forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" }, iso: { type:"string", "default":"https://forgejo.isvery.ninja/assets/xrsh-buildroot/main/xrsh.iso" },
overlayfs: { type:"string"}, overlayfs: { type:"string"},
cols: { type: 'number',"default": 80 }, width: { type: 'number',"default": -1 },
rows: { type: 'number',"default": 20 }, height: { type: 'number',"default": -1 },
lineHeight: { type: 'number',"default": 18 },
padding: { type: 'number',"default": 18 }, padding: { type: 'number',"default": 18 },
minimized: { type: 'boolean',"default":false}, minimized: { type: 'boolean',"default":false},
maximized: { type: 'boolean',"default":false}, maximized: { type: 'boolean',"default":true},
muteUntilPrompt:{ type: 'boolean',"default":true}, // mute stdout until a prompt is detected in ISO muteUntilPrompt:{ type: 'boolean',"default":true}, // mute stdout until a prompt is detected in ISO
HUD: { type: 'boolean',"default":false}, // link to camera movement HUD: { type: 'boolean',"default":false}, // link to camera movement
transparent: { type:'boolean', "default":false }, // need good gpu transparent: { type:'boolean', "default":false }, // need good gpu
xterm: { type: 'boolean', "default":false }, // use xterm.js? (=slower)
memory: { type: 'number', "default":64 }, // VM memory (in MB) memory: { type: 'number', "default":64 }, // VM memory (in MB)
bufferLatency: { type: 'number', "default":300 }, // in ms: bufferlatency from webworker to xterm (batch-update every char to texture) bufferLatency: { type: 'number', "default":30 }, // in ms: bufferlatency from webworker to xterm (batch-update every char to texture)
canvasLatency: { type: 'number', "default":500 }, // in ms: time between canvas re-draws
renderer: { type: 'string', "default":"canvas" },// 'dom' or 'canvas' (=faster) for immersive mode
debug: { type: 'boolean', "default":false } debug: { type: 'boolean', "default":false }
}, },
init: function(){ init: function(){
this.el.object3D.visible = false this.el.object3D.visible = false
if( this.data.width == -1 ) this.data.width = document.body.offsetWidth
if( this.data.height == -1 ) this.data.height = document.body.offsetHeight
this.data.width -= this.data.padding*2
this.data.height -= this.data.padding*2
this.initHud() this.initHud()
fetch(this.data.iso,{method: 'HEAD'}) fetch(this.data.iso,{method: 'HEAD'})
.then( (res) => { .then( (res) => {
@ -70,6 +73,7 @@ if( typeof AFRAME != 'undefined '){
com: "com/dom.js", com: "com/dom.js",
window: "com/window.js", window: "com/window.js",
v86: "com/isoterminal/libv86.js", v86: "com/isoterminal/libv86.js",
vt100: "com/isoterminal/VT100.js",
// allow xrsh to selfcontain scene + itself // allow xrsh to selfcontain scene + itself
xhook: "https://jpillora.com/xhook/dist/xhook.min.js", xhook: "https://jpillora.com/xhook/dist/xhook.min.js",
selfcontain: "com/selfcontainer.js", selfcontain: "com/selfcontainer.js",
@ -85,8 +89,9 @@ if( typeof AFRAME != 'undefined '){
scale: 1.0, scale: 1.0,
events: ['click','keydown'], events: ['click','keydown'],
html: (me) => `<div class="isoterminal"> html: (me) => `<div class="isoterminal">
<input type="text" style="opacity:0; position:absolute; width:200px; height:200px;"/> <div id="vt100" tabindex="0">
<pre></pre> <pre></pre>
</div>
</div>`, </div>`,
css: (me) => `.isoterminal{ css: (me) => `.isoterminal{
@ -98,7 +103,10 @@ if( typeof AFRAME != 'undefined '){
.isoterminal div{ .isoterminal div{
display:block; display:block;
position:relative; position:relative;
line-height:18px; line-height: ${me.com.data.lineHeight}px;
}
#vt100 {
outline: none !important;
} }
@font-face { @font-face {
font-family: 'Cousine'; font-family: 'Cousine';
@ -115,6 +123,15 @@ if( typeof AFRAME != 'undefined '){
.isoterminal style{ display:none } .isoterminal style{ display:none }
blink{
border:none;
padding:none;
}
span blink:last-of-type{
border-right: 8px solid #F07;
padding-right: 3px;
}
#overlay .winbox:has(> .isoterminal){ #overlay .winbox:has(> .isoterminal){
background:transparent; background:transparent;
box-shadow:none; box-shadow:none;
@ -132,28 +149,11 @@ if( typeof AFRAME != 'undefined '){
.XR .isoterminal{ .XR .isoterminal{
background: #000; background: #000;
} }
.isoterminal *, .isoterminal *{
.isoterminal .xterm-dom-renderer-owner-1 .xterm-rows { font-size: 14px;
background:transparent !important; font-family: "Cousine",Liberation Mono,DejaVu Sans Mono,Courier New,monospace;
font-size: 14px; font-weight:500 !important;
font-family: "Cousine",Liberation Mono,DejaVu Sans Mono,Courier New,monospace; text-shadow: 0px 0px 10px #F075;
font-weight:500 !important;
text-shadow: 0px 0px 10px #F075;
}
.isoterminal .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-block {
background-color:#a5F !important;
}
.isoterminal .xterm-rows div{
height:8px;
height:18px;
}
.isoterminal .xterm-rows span{
width:8px;
}
.isoterminal .xterm-helpers {
position:absolute;
opacity:0;
top: -2000px;
} }
@keyframes fade { @keyframes fade {
@ -168,7 +168,6 @@ if( typeof AFRAME != 'undefined '){
to { opacity: 1.0; } to { opacity: 1.0; }
} }
.isoterminal .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-block,
.blink{ .blink{
animation:fade 1000ms infinite; animation:fade 1000ms infinite;
-webkit-animation:fade 1000ms infinite; -webkit-animation:fade 1000ms infinite;
@ -179,21 +178,15 @@ if( typeof AFRAME != 'undefined '){
initTerminal: async function(singleton){ initTerminal: async function(singleton){
if( this.data.xterm ){ // why not latest xterm or v3.12 with builtin-canvas support?
// why 3.12? // first versions used 1.5.4, a typescript rewrite which:
// first versions used 1.5.4, a typescript rewrite which: // * acts weird with oculus browser keyboard (does not repaint properly after typing)
// * acts weird with oculus browser keyboard (does not repaint properly after typing) // * does not use canvas anymore [which would be ideal for THREE.js texture]
// * does not use canvas anymore [which would be ideal for THREE.js texture] // * does not allow switching between dom/canvas
// * does not allow switching between dom/canvas // * only allows a standalone WebGL addon (conflicts with THREE)
// * only allows a standalone WebGL addon (conflicts with THREE) // * heavily dependent on requestAnimationFrame (conflicts with THREE)
// * heavily dependent on requestAnimationFrame (conflicts with THREE) // * typescript-rewrite results in ~300k lib (instead of 96k)
// * typescript-rewrite results in ~300k lib (instead of 96k) // * v3.12 had slightly better performance but still very heavy
this.requires.xtermcss = "//unpkg.com/xterm@3.12.0/dist/xterm.css",
this.requires.xtermjs = "//unpkg.com/xterm@3.12.0/dist/xterm.js",
this.requires.xtermcss = "com/xterm.js"
}else{
this.requires.vt100 = "com/isoterminal/VT100.js"
}
await AFRAME.utils.require(this.requires) await AFRAME.utils.require(this.requires)
await AFRAME.utils.require({ // ISOTerminal plugins await AFRAME.utils.require({ // ISOTerminal plugins
@ -225,18 +218,15 @@ if( typeof AFRAME != 'undefined '){
this.term = new ISOTerminal(instance,this.data) this.term = new ISOTerminal(instance,this.data)
instance.addEventListener('DOMready', () => { instance.addEventListener('DOMready', () => {
if( this.data.renderer == 'dom' || !this.data.xterm ){ instance.setAttribute("html-as-texture-in-xr", `domid: #${this.el.dom.id}; faceuser: true`)
instance.setAttribute("html-as-texture-in-xr", `domid: #${this.el.dom.id}; faceuser: true`) setTimeout( () => this.setupVT100(instance),100)
}
if( !this.data.xterm ) this.setupVT100(instance)
//instance.winbox.resize(720,380) //instance.winbox.resize(720,380)
let size = `width: ${Math.floor(this.data.cols*8.65)}; height: ${Math.floor(this.data.rows*21.1)}` let size = `width: ${this.data.width}; height: ${this.data.height}`
instance.setAttribute("window", `title: xrsh.iso; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}; ${size}; min: ${this.data.minimized}; max: ${this.data.maximized}`) instance.setAttribute("window", `title: xrsh.iso; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}; ${size}; min: ${this.data.minimized}; max: ${this.data.maximized}`)
}) })
instance.addEventListener('window.oncreate', (e) => { instance.addEventListener('window.oncreate', (e) => {
instance.dom.classList.add('blink') instance.dom.classList.add('blink')
if( this.data.xterm ) this.setupXterm(instance)
// run iso // run iso
let opts = {dom:instance.dom} let opts = {dom:instance.dom}
@ -246,15 +236,6 @@ if( typeof AFRAME != 'undefined '){
instance.setAttribute("dom", "") instance.setAttribute("dom", "")
this.term.addEventListener('postReady', (e)=>{
// bugfix: send window dimensions to xterm (xterm.js does that from dom-sizechange to xterm via escape codes)
let wb = instance.winbox
if( this.data.maximized ){
wb.restore()
wb.maximize()
}else wb.resize()
})
this.term.addEventListener('ready', (e) => { this.term.addEventListener('ready', (e) => {
instance.dom.classList.remove('blink') instance.dom.classList.remove('blink')
this.term.emit('status',"running") this.term.emit('status',"running")
@ -278,9 +259,6 @@ if( typeof AFRAME != 'undefined '){
instance.addEventListener('window.onmaximize', resize ) instance.addEventListener('window.onmaximize', resize )
const focus = (showdom) => (e) => { const focus = (showdom) => (e) => {
if( this.el.components.xterm ){
this.el.components.xterm.term.focus()
}
if( this.el.components.window && this.data.renderer == 'canvas'){ if( this.el.components.window && this.data.renderer == 'canvas'){
this.el.components.window.show( showdom ) this.el.components.window.show( showdom )
} }
@ -311,14 +289,22 @@ if( typeof AFRAME != 'undefined '){
console.test.run() console.test.run()
}, },
setupXterm: function(){
instance.setAttribute("xterm",`cols: ${this.data.cols}; rows: ${this.data.rows}; canvasLatency: ${this.data.canvasLatency}; XRrenderer: ${this.data.renderer}`)
instance.addEventListener("xterm-input", (e) => this.term.send(e.detail,0) )
},
setupVT100: function(instance){ setupVT100: function(instance){
this.vt100 = new VT100(100,50, this.el.dom, 100 ) const el = this.el.dom.querySelector('#vt100')
this.vt100.curs_set(1,true) this.vt100 = new VT100(
Math.floor(this.data.width/this.data.lineHeight),
Math.floor(this.data.height*0.8/this.data.lineHeight),
el,
100
)
this.vt100.curs_set( 1, true)
el.focus()
this.vt100.getch( (ch,t) => {
console.log(ch)
this.term.send( ch )
this.vt100.curs_set( 0, true)
})
this.el.addEventListener('serial-output-byte', (e) => { this.el.addEventListener('serial-output-byte', (e) => {
const byte = e.detail const byte = e.detail
var chr = String.fromCharCode(byte); var chr = String.fromCharCode(byte);
@ -327,6 +313,14 @@ if( typeof AFRAME != 'undefined '){
this.el.addEventListener('serial-output-string', (e) => { this.el.addEventListener('serial-output-string', (e) => {
this.vt100.write(e.detail) this.vt100.write(e.detail)
}) })
//this.el.dom.querySelector('input').addEventListener('keyup', (e) => {
// VT100.handle_onkeypress_( {charCode : e.charCode || e.keyCode, keyCode: e.keyCode}, (chars) => {
// debugger
// chars.map( (c) => this.term.send(str) )
// })
//})
}, },
events:{ events:{

View File

@ -86,7 +86,7 @@ function VT100(wd, ht, el_or_id, max_scroll_lines, fg, bg)
fg = VT100.COLOR_WHITE; fg = VT100.COLOR_WHITE;
} }
if (typeof(bg) == 'undefined') { if (typeof(bg) == 'undefined') {
bg = VT100.COLOR_BLACK; bg = VT100.COLOR_TRANSPARENT //COLOR_BLACK;
} }
var r; var r;
@ -116,13 +116,14 @@ function VT100(wd, ht, el_or_id, max_scroll_lines, fg, bg)
this.key_buf_ = []; this.key_buf_ = [];
this.echo_ = false; this.echo_ = false;
this.esc_state_ = 0; this.esc_state_ = 0;
this.log_level_ = VT100.WARN; this.log_level_ = VT100.DEBUG //WARN;
this.clear_all(); this.clear_all();
this.refresh(); this.refresh();
} }
// public constants -- colours and colour pairs // public constants -- colours and colour pairs
VT100.COLOR_TRANSPARENT = -1;
VT100.COLOR_BLACK = 0; VT100.COLOR_BLACK = 0;
VT100.COLOR_BLUE = 1; VT100.COLOR_BLUE = 1;
VT100.COLOR_GREEN = 2; VT100.COLOR_GREEN = 2;
@ -164,96 +165,98 @@ VT100.the_vt_ = undefined;
// class methods // class methods
// this is actually an event handler // this is actually an event handler
VT100.handle_onkeypress_ = function VT100_handle_onkeypress(event) VT100.handle_onkeypress_ = function VT100_handle_onkeypress(event,cb)
{ {
//dump("event target: " + event.target.id + "\n"); //dump("event target: " + event.target.id + "\n");
//dump("event originalTarget: " + event.originalTarget.id + "\n"); //dump("event originalTarget: " + event.originalTarget.id + "\n");
var vt = VT100.the_vt_, ch; var vt = VT100.the_vt_, ch;
if (vt === undefined) if (vt === undefined)
return true; return true;
if (VT100.browser_ie_ || VT100.browser_opera_) { //if ( event.keyCode != undefined || !event.charCode){
ch = event.keyCode; // ch = event.keyCode;
if (ch == 13) // if (ch == 13)
ch = 10; // ch = 10;
else if (ch > 255 || (ch < 32 && ch != 8)) // else if (ch > 255 || (ch < 32 && ch != 8))
return true; // return true;
ch = String.fromCharCode(ch); // ch = String.fromCharCode(ch);
} else { //} else {
ch = event.charCode; ch = event.charCode;
//dump("ch: " + ch + "\n"); //dump("ch: " + ch + "\n");
//dump("ctrl?: " + event.ctrlKey + "\n"); //dump("ctrl?: " + event.ctrlKey + "\n");
vt.debug("onkeypress:: keyCode: " + event.keyCode + ", ch: " + event.charCode); vt.debug("onkeypress:: keyCode: " + event.keyCode + ", ch: " + event.charCode);
if (ch) { if (ch) {
if (ch > 255) if (ch > 255)
return true; return true;
if (event.ctrlKey && event.shiftKey) { if (event.ctrlKey && event.shiftKey) {
// Don't send the copy/paste commands. // Don't send the copy/paste commands.
var charStr = String.fromCharCode(ch); var charStr = String.fromCharCode(ch);
if (charStr == 'C' || charStr == 'V') { if (charStr == 'C' || charStr == 'V') {
return false; return false;
} }
} }
if (event.ctrlKey) { if (event.ctrlKey) {
ch = String.fromCharCode(ch - 96); ch = String.fromCharCode(ch - 96);
} else { } else {
ch = String.fromCharCode(ch); ch = String.fromCharCode(ch);
if (ch == '\r') if (ch == '\r')
ch = '\n'; ch = '\n';
} }
} else { } else {
switch (event.keyCode) { switch (event.key) {
case event.DOM_VK_BACK_SPACE: case "Backspace":
ch = '\b'; ch = '\b';
break; break;
case event.DOM_VK_TAB: case "Tab":
ch = '\t'; ch = '\t';
break; break;
case event.DOM_VK_RETURN: case event.DOM_VK_RETURN:
case event.DOM_VK_ENTER: case event.DOM_VK_ENTER:
ch = '\r'; ch = '\r';
break; break;
case event.DOM_VK_UP: case event.DOM_VK_UP:
if (this.cursor_key_mode_ == VT100.CK_CURSOR) if (this.cursor_key_mode_ == VT100.CK_CURSOR)
ch = '\x1b[A'; ch = '\x1b[A';
else else
ch = '\x1bOA'; ch = '\x1bOA';
break; break;
case event.DOM_VK_DOWN: case event.DOM_VK_DOWN:
if (this.cursor_key_mode_ == VT100.CK_CURSOR) if (this.cursor_key_mode_ == VT100.CK_CURSOR)
ch = '\x1b[B'; ch = '\x1b[B';
else else
ch = '\x1bOB'; ch = '\x1bOB';
break; break;
case event.DOM_VK_RIGHT: case event.DOM_VK_RIGHT:
if (this.cursor_key_mode_ == VT100.CK_CURSOR) if (this.cursor_key_mode_ == VT100.CK_CURSOR)
ch = '\x1b[C'; ch = '\x1b[C';
else else
ch = '\x1bOC'; ch = '\x1bOC';
break; break;
case event.DOM_VK_LEFT: case event.DOM_VK_LEFT:
if (this.cursor_key_mode_ == VT100.CK_CURSOR) if (this.cursor_key_mode_ == VT100.CK_CURSOR)
ch = '\x1b[D'; ch = '\x1b[D';
else else
ch = '\x1bOD'; ch = '\x1bOD';
break; break;
case event.DOM_VK_DELETE: case event.DOM_VK_DELETE:
ch = '\x1b[3~'; ch = '\x1b[3~';
break; break;
case event.DOM_VK_HOME: case event.DOM_VK_HOME:
ch = '\x1b[H'; ch = '\x1b[H';
break; break;
case event.DOM_VK_ESCAPE: case event.DOM_VK_ESCAPE:
ch = '\x1b'; ch = '\x1b';
break; break;
default: default:
return true; return true;
} }
} }
// Stop the event from doing anything else. // Stop the event from doing anything else.
event.preventDefault(); event.preventDefault();
}
vt.key_buf_.push(ch); vt.key_buf_.push(ch);
setTimeout(VT100.go_getch_, 0); if( cb ){
cb(vt.key_buf_)
vt.key_buf_ = []
}else setTimeout(VT100.go_getch_, 0);
return false; return false;
} }
@ -345,7 +348,7 @@ VT100.prototype.html_colours_ = function VT100_html_colours_(attr)
f: '#' + (fg & 4 ? co1 : co0) + f: '#' + (fg & 4 ? co1 : co0) +
(fg & 2 ? co1 : co0) + (fg & 2 ? co1 : co0) +
(fg & 1 ? co1 : co0), (fg & 1 ? co1 : co0),
b: '#' + (bg & 4 ? co1 : co0) + b: attr.bg == VT100.COLOR_TRANSPARENT ? 'transparent' : '#' + (bg & 4 ? co1 : co0) +
(bg & 2 ? co1 : co0) + (bg & 2 ? co1 : co0) +
(bg & 1 ? co1 : co0) (bg & 1 ? co1 : co0)
}; };
@ -545,12 +548,10 @@ VT100.prototype.curs_set = function(vis, grab, eventist)
this.grab_events_ = true; this.grab_events_ = true;
VT100.the_vt_ = this; VT100.the_vt_ = this;
eventist.addEventListener("keypress", VT100.handle_onkeypress_, false); eventist.addEventListener("keypress", VT100.handle_onkeypress_, false);
if (VT100.browser_ie_) eventist.addEventListener("keydown", VT100.handle_onkeypress_, false);
document.onkeydown = VT100.handle_onkeydown_;
} else { } else {
eventist.removeEventListener("keypress", VT100.handle_onkeypress_, false); eventist.removeEventListener("keypress", VT100.handle_onkeypress_, false);
if (VT100.browser_ie_) eventist.removeEventListener("keydown", VT100.handle_onkeypress_, false);
document.onkeydown = VT100.handle_onkeydown_;
this.grab_events_ = false; this.grab_events_ = false;
VT100.the_vt_ = undefined; VT100.the_vt_ = undefined;
} }
@ -831,7 +832,7 @@ VT100.prototype.write = function VT100_write(stuff)
} else { } else {
code = this.escape(ch); code = this.escape(ch);
} }
this.debug(" write:: ch: " + ch.charCodeAt(0) + ", '" + code + "'"); this.debug(" write:: ch: " + ch.charCodeAt(0) + ", '" + code + "'");
codes += code; codes += code;
} }