From 69a1760a113039b7224a3d880a69cb08ffe82e37 Mon Sep 17 00:00:00 2001 From: Leon van Kammen Date: Thu, 29 Aug 2024 15:18:14 +0000 Subject: [PATCH] improved javascript bridge --- com/isoterminal.js | 58 +++++++++++++++++++++++++++--- com/isoterminal/mnt/alert | 6 +++- com/isoterminal/mnt/hook | 6 +--- com/isoterminal/mnt/js | 15 ++++---- com/isoterminal/mnt/jsh | 15 +++++--- com/isoterminal/mnt/profile | 54 +++++----------------------- com/isoterminal/mnt/profile.xrsh | 47 ++++++++++++++++++++++++ com/isoterminal/mnt/v86pipe | 30 ++++++++++++++++ com/isoterminal/mnt/xrsh | 62 ++++++++++++++++++++++++++++++++ 9 files changed, 226 insertions(+), 67 deletions(-) create mode 100644 com/isoterminal/mnt/profile.xrsh create mode 100755 com/isoterminal/mnt/v86pipe create mode 100755 com/isoterminal/mnt/xrsh diff --git a/com/isoterminal.js b/com/isoterminal.js index 0f310a0..b6bd3f0 100644 --- a/com/isoterminal.js +++ b/com/isoterminal.js @@ -150,7 +150,6 @@ AFRAME.registerComponent('isoterminal', { 'still faster than Windows update', 'loading a microlinux', 'figuring out meaning of life', - 'asking LLM why men have nipples', 'Aligning your chakras now', 'Breathing in good vibes', 'Finding inner peace soon', @@ -181,11 +180,41 @@ AFRAME.registerComponent('isoterminal', { "com/isoterminal/mnt/prompt", "com/isoterminal/mnt/alert", "com/isoterminal/mnt/hook", + "com/isoterminal/mnt/xrsh", "com/isoterminal/mnt/profile", + "com/isoterminal/mnt/profile.xrsh", "com/isoterminal/mnt/profile.js", "com/isoterminal/mnt/motd", + "com/isoterminal/mnt/v86pipe" ] + const redirectConsole = (handler) => { + const log = console.log; + const dir = console.dir; + const err = console.error; + const warn = console.warn; + console.log = (...args)=>{ + const textArg = args[0]; + handler(textArg+'\n'); + log.apply(log, args); + }; + console.error = (...args)=>{ + const textArg = args[0].message?args[0].message:args[0]; + handler( textArg+'\n', '\x1b[31merror\x1b[0m'); + err.apply(log, args); + }; + console.dir = (...args)=>{ + const textArg = args[0].message?args[0].message:args[0]; + handler( JSON.stringify(textArg,null,2)+'\n'); + dir.apply(log, args); + }; + console.warn = (...args)=>{ + const textArg = args[0].message?args[0].message:args[0]; + handler(textArg+'\n','\x1b[38;5;208mwarn\x1b[0m'); + err.apply(log, args); + }; + } + emulator.bus.register("emulator-started", async () => { emulator.serial_adapter.term.element.querySelector('.xterm-viewport').style.background = 'transparent' emulator.serial_adapter.term.clear() @@ -197,6 +226,17 @@ AFRAME.registerComponent('isoterminal', { cat > /dev/null `)) + redirectConsole( (str,prefix) => { + if( emulator.log_to_tty ){ + prefix = prefix ? prefix+' ' : ' ' + str.trim().split("\n").map( (line) => { + emulator.serial_adapter.term.write( '\r\x1b[38;5;165m/dev/browser: \x1b[0m'+prefix+line+'\n' ) + }) + emulator.serial_adapter.term.write( '\r' ) + } + emulator.create_file( "console", this.toUint8Array( str ) ) + }) + let p = files.map( (f) => fetch(f) ) Promise.all(p) .then( (files) => { @@ -267,15 +307,23 @@ AFRAME.registerComponent('isoterminal', { const buf = await emulator.read_file("dev/browser/js") const script = decoder.decode(buf) try{ - let res = (new Function(`return ${script}`))() + let res = (new Function(`${script}`))() if( res && typeof res != 'string' ) res = JSON.stringify(res,null,2) - emulator.create_file( "dev/browser/js", this.toUint8Array( res || '' ) ) }catch(e){ - console.dir(e) - emulator.create_file("dev/browser/js", this.toUint8Array( `[e] `+String(e.stack) ) ) + console.error(e) } } }) + + // enable/disable logging file (echo 1 > mnt/console.tty) + emulator.add_listener("9p-write-end", async (opts) => { + const decoder = new TextDecoder('utf-8'); + if ( opts[0] == 'console.tty' ){ + const buf = await emulator.read_file("console.tty") + const val = decoder.decode(buf) + emulator.log_to_tty = ( String(val).trim() == '1') + } + }) }); diff --git a/com/isoterminal/mnt/alert b/com/isoterminal/mnt/alert index b071a04..ed9e5bd 100755 --- a/com/isoterminal/mnt/alert +++ b/com/isoterminal/mnt/alert @@ -1,2 +1,6 @@ #!/bin/sh -echo "$(printf "\033[0m")[i] $1 $(printf "\033[0m")" +title=$1 +shift +msg="$*" +echo "$title $(printf "\033[0m")$msg" +hook alert $title "$msg" diff --git a/com/isoterminal/mnt/hook b/com/isoterminal/mnt/hook index f984f11..67c0467 100755 --- a/com/isoterminal/mnt/hook +++ b/com/isoterminal/mnt/hook @@ -1,15 +1,11 @@ #!/bin/sh test -z $1 && { echo "usage: hook [args]"; exit 0; } - cmd=$1 shift test -d ~/hook.d/$cmd && { - chmod +x ~/hook.d/$cmd/* find ~/hook.d/$cmd/ -type f -executable | while read hook; do - $hook "$@" || true + { $hook "$@" || true; } | awk '{ gsub(/\/root\/\//,"",$1); $1 = sprintf("%-40s", $1)} 1' done } - -#test $BROWSER = 1 && { diff --git a/com/isoterminal/mnt/js b/com/isoterminal/mnt/js index 338e6fc..ba14975 100755 --- a/com/isoterminal/mnt/js +++ b/com/isoterminal/mnt/js @@ -1,16 +1,19 @@ #!/bin/sh -test -z $1 && { echo "Usage: js 'somefunction(1)'"; exit 0; } +test -z "$1" && { echo "Usage: js 'somefunction(1)'"; exit 0; } +test -n "$BROWSER" || { alert warning "/dev/browser not active (are you running outside of v86?)"; } javascript="$*" # if we are run as shebang, use the file as input case "$1" in - */*) javascript="$(cat $1 | tail +2)" + */*) javascript="args = String('$*').split(' '); $(cat $1 | tail +2)" ;; esac +echo -n "$javascript" > /dev/browser/js -flock /dev/browser/js -c "echo '$javascript' > /dev/browser/js; sleep 0.5; cat /dev/browser/js" - -# we use flock, an awesome way to make processes read/write the same file -# while preventing 1001 concurrency issues +# should we use flock, an awesome way to make processes read/write the same file +# while preventing 1001 concurrency issues? +# attempt: +# +# flock /dev/browser/js -c "echo \"$javascript\" > /dev/browser/js" diff --git a/com/isoterminal/mnt/jsh b/com/isoterminal/mnt/jsh index 69237e1..7f2427b 100755 --- a/com/isoterminal/mnt/jsh +++ b/com/isoterminal/mnt/jsh @@ -20,18 +20,23 @@ to_js(){ } # run argument as js -test -z $1 || { +test -z "$1" || { func=$(to_js "$@") func=${func/,)/)} js "$func" + hook "$@" + exit 0 } # otherwise start repl +echo "jsh> type 'exit' or CTRL-C to quit" +echo "jsh> HINT: to run alert('foo') outside this REPL, run 'jsh alert foo'" +echo "jsh>" while true; do - echo -n "$(printf "\033[0m")jsh> $(printf "\033[0m")" - read input - test $input = exit && exit - js "$input" + echo -n -e "\r$(printf "\033[0m")jsh> $(printf "\033[0m")" + read line + test "$line" = exit && exit + js "$line" done diff --git a/com/isoterminal/mnt/profile b/com/isoterminal/mnt/profile index acc6bf5..0adf165 100644 --- a/com/isoterminal/mnt/profile +++ b/com/isoterminal/mnt/profile @@ -1,50 +1,15 @@ -install_xrsh(){ - - setup_binaries(){ - for bin in /mnt/prompt /mnt/alert /mnt/confirm /mnt/hook /mnt/js*; do - chmod +x $bin - ln -s $bin /bin/. - done - } - - setup_browser_dev(){ - mkdir -p /mnt/dev/browser - touch /mnt/dev/browser/js - touch /mnt/dev/browser/html - touch /mnt/dev/browser/console - ln -s /mnt/dev/browser /dev/browser - test -f /etc/profile && rm /etc/profile - ln -s /mnt/profile /etc/profile - } - - setup_hook_dirs(){ # see /mnt/hook for usage - mkdir -p ~/hook.d/alert - mkdir -p ~/hook.d/confirm - mkdir -p ~/hook.d/prompt - echo -e "#!/bin/sh\necho yo" > ~/hook.d/alert/yo - echo -e "#!/bin/js\nalert('yo')" > ~/hook.d/alert/yo.js - echo -e "#!/usr/bin/lua\nalert('yo')" > ~/hook.d/alert/yo.lua - } - - setup_binaries - setup_browser_dev - setup_hook_dirs -} - -test -d /dev/browser || install_xrsh - -test -f /mnt/V86 && { - mount -a - udhcpc 1>>/var/log/network.log 2>>/var/log/network.log & - echo 0 > /proc/sys/kernel/printk -} +# install xrsh env +source /mnt/profile.xrsh ## forward not-found commands to javascript (via jsh) command_not_found_handle(){ - echo "$1 not found" - alert "did you mean $1(...) (javascript?)" - alert "TIP: run 'jsh $1 hello' to run $1('hello')" - alert " or simply 'jsh' for a js console" + echo "$1 not found, did you mean $1(...) (javascript?)" + alert '[XRSH TIPS]' + alert 'js console: ' "type 'jsh'" + alert 'js shellfunction:' "type 'alias $1=\"jsh $1\"' to run '$1 yo' as $1('yo')" + alert 'js logging: ' "type 'echo 0 > /dev/browser/console.tty' to disable" + alert 'js capture log: ' "type 'tail -f /dev/browser/console'" + alert 'jsh<->sh hooks: ' "type 'chmod +x ~/hook.d/alert/* && alert helloworld'" } # source javascript functions @@ -55,4 +20,3 @@ resize cat /mnt/motd export PATH=$PATH:/mnt export PS1="\nxrsh # \033[0m" -export BROWSER=0 # running inside v86 (wasm) will set this to 1 diff --git a/com/isoterminal/mnt/profile.xrsh b/com/isoterminal/mnt/profile.xrsh new file mode 100644 index 0000000..683d9c5 --- /dev/null +++ b/com/isoterminal/mnt/profile.xrsh @@ -0,0 +1,47 @@ +#!/bin/sh + +test -d /dev/browser || { + + setup_binaries(){ + for bin in /mnt/prompt /mnt/alert /mnt/confirm /mnt/hook /mnt/js* /mnt/v86pipe /mnt/xrsh; do + chmod +x $bin + ln -s $bin /bin/. + done + } + + setup_browser_dev(){ + mkdir -p /mnt/dev/browser + touch /mnt/dev/browser/js + touch /mnt/dev/browser/html + touch /mnt/console.tty + ln -s /mnt/dev/browser /dev/browser + # setup console goodies + ln -s /mnt/console.tty /dev/browser/console.tty # emulator.write_file() only writes to /mnt/. :( + echo 1 > /dev/browser/console.tty # should be in /proc, but v86 gives 'no such file or dir' when creating it there + v86pipe /mnt/console /dev/browser/console & + + test -f /etc/profile && rm /etc/profile + ln -s /mnt/profile /etc/profile + } + + setup_hook_dirs(){ # see /mnt/hook for usage + mkdir -p ~/hook.d/alert + mkdir -p ~/hook.d/confirm + mkdir -p ~/hook.d/prompt + echo -e "#!/bin/sh\necho hook.d/alert/yo: yo \$*" > ~/hook.d/alert/yo + echo -e "#!/bin/js\nalert(\"hook.d/alert/yo.js \"+args.slice(1).join(' '))" > ~/hook.d/alert/yo.js + echo -e "#!/usr/bin/lua\nprint(\"hook.d/alert/yo.lua: yo \" .. arg[1])" > ~/hook.d/alert/yo.lua + } + + setup_network(){ + test -n "$BROWSER" || return 0 + mount -a + udhcpc 1>>/var/log/network.log 2>>/var/log/network.log & + echo 0 > /proc/sys/kernel/printk + } + + setup_binaries + setup_browser_dev + setup_hook_dirs +} + diff --git a/com/isoterminal/mnt/v86pipe b/com/isoterminal/mnt/v86pipe new file mode 100755 index 0000000..c3983d2 --- /dev/null +++ b/com/isoterminal/mnt/v86pipe @@ -0,0 +1,30 @@ +#!/bin/sh +# +# this daemon allows 'tail -f' on v86 files (which don't persist inode when updated +# via javascript) +# more info see: https://github.com/copy/v86/issues/1140 +# +# Hopefully as V86 (or my understanding of it) matures, this will be no longer needed + +test -z $2 && { echo "usage: v86pipe "; exit 0; } + +# Start reading from the last line in the log file +last_size=0 +LOG_FILE=$1 +LOG_PIPE=$2 + +test -f $LOG_FILE || touch $LOG_FILE +test -p $LOG_PIPE || mkfifo $LOG_PIPE + +while true; do + # Get the current size of the file using wc -c (count bytes) + current_size=$(wc -c < $LOG_FILE) + test $current_size = $last_size || { + cat $LOG_FILE > $LOG_PIPE + truncate -s 0 $LOG_FILE + } + last_size=$current_size + + # Sleep for a moment to avoid excessive CPU usage + sleep 0.2 +done diff --git a/com/isoterminal/mnt/xrsh b/com/isoterminal/mnt/xrsh new file mode 100755 index 0000000..f3867e8 --- /dev/null +++ b/com/isoterminal/mnt/xrsh @@ -0,0 +1,62 @@ +#!/bin/sh +# +# a minimalistic terminal muxer + +# Save the original stdout and stderr file descriptors for later restoration +exec 3>&1 4>&2 + +# Function to check if a session is already running on the given VT +is_session_running() { + vt_number=$1 + + # Check if any process is running on /dev/tty + fuser /dev/tty"$vt_number" >/dev/null 2>&1 + return $? +} + +# Function to mute the output of a session +mute_session() { + vt_number=$1 + if is_session_running "$vt_number"; then + # Redirect stdout and stderr of the session to /dev/null + exec > /dev/null 2>&1 + fi +} + +# Function to unmute the current session (restore stdout and stderr) +unmute_session() { + exec 1>&3 2>&4 # Restore stdout and stderr from file descriptors 3 and 4 +} + +# Function to start a new session if not already running +start_or_switch_session() { + vt_number=$1 + + # Mute all other sessions except the one we're switching to + for vt in $(seq 1 12); do # Assuming you have up to 12 VTs, adjust as needed + if [ "$vt" != "$vt_number" ]; then + mute_session "$vt" + fi + done + + if is_session_running "$vt_number"; then + echo "Switching to existing session on VT$vt_number" + unmute_session # Unmute the session we're switching to + chvt "$vt_number" + else + echo "Starting a new session on VT$vt_number" + openvt -c "$vt_number" -- /bin/sh & + sleep 1 # Give the session a moment to start + unmute_session # Unmute the new session + chvt "$vt_number" + fi +} + +# Ensure a session number is provided +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Start or switch to the session +start_or_switch_session $1