Last active
July 16, 2024 18:07
-
-
Save alganet/63f1dbc97b8fd35f7bb14ec30f79d2a5 to your computer and use it in GitHub Desktop.
Fast and portable mouse/keyboard terminal capture (zsh,ksh,mksh,bash on Windows+WSL, Linux or Mac OS)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# Quick run: bash -c "$(curl -L https://git.io/fjToH)" | |
# CTRL+W to exit | |
set -euf | |
unsetopt FLOW_CONTROL GLOB NO_MATCH NO_SH_WORD_SPLIT NO_PROMPT_SUBST 2>/dev/null || : | |
write () | |
{ | |
printf "${1:-}" | |
} | |
if test -n "${KSH_VERSION:-}" | |
then | |
if test -z "${KSH_VERSION##*Version AJM*}" | |
then | |
alias local=typeset | |
fi | |
if test -z "${KSH_VERSION##*MIRBSD*}" | |
then | |
write () | |
{ | |
echo -ne "${1:-}" | |
} | |
fi | |
fi | |
hers_initialize () | |
{ | |
if { echo 1 | read -s -k1 2>/dev/null ;} | |
then | |
hers_get_event_byte () | |
{ | |
IFS= read -r -k1 -u0 ${@:-} _hers_byte 2>/dev/null || return 1 | |
} | |
elif { echo 1 | read -r -t'0.1' -n 1 2>/dev/null ;} | |
then | |
hers_get_event_byte () | |
{ | |
IFS= read -r -n 1 ${@:-} _hers_byte 2>/dev/null || return 1 | |
} | |
else | |
echo 'The invoked shell does not support interactive features' 1>&2 | |
return 1 | |
fi | |
local LC_ALL='C' | |
local LC_CTYPE='C' | |
local PS4='\r ${LINENO:-} ' | |
local _hers_tabsize='4' | |
local _hers_line='' | |
local _hers_column='' | |
local _hers_code='' | |
local _held='' | |
local _size='15' | |
local _scroll='1' | |
local _scrolled='0' | |
local _hers_termsize='' | |
local _hers_lines='' | |
local _hers_columns='' | |
local _hers_x=0 | |
local _hers_y=0 | |
trap "echo ALRM" ALRM 2>/dev/null || : | |
trap "echo TTOU" TTOU 2>/dev/null || : | |
trap "echo TTIN" TTIN 2>/dev/null || : | |
trap "hers_setsize" WINCH 2>/dev/null || : | |
trap "hers_setsize" INT 2>/dev/null || : | |
local _hers_previous_stty="$(stty -g)" | |
stty raw -echo -ctlecho -isig -icanon -ixon -ixoff -tostop -ocrnl intr undef discard undef time 0 2>/dev/null | |
local _hers_stty="$(stty -g)" | |
write '\0337' | |
write '\033[?2004h\033[2K' | |
write '\033[22;0;0t' | |
write '\033[?1h' | |
write '\033[?20l' | |
write '\033[?25l' | |
hers_setsize () | |
{ | |
_hers_termsize="$(stty size)" | |
_hers_lines="${_hers_termsize% *}" | |
_hers_columns="${_hers_termsize#* }" | |
_size="${_size:-$_hers_lines}" | |
local _hers_newx=0 | |
local _hers_newy=0 | |
write '\033[6n\r' | |
IFS='[;' read -r -d R _ _hers_newx _hers_newy 2>/dev/null || : | |
_hers_y=$_hers_newy | |
if test $_hers_newy -gt $_hers_x | |
then | |
_hers_x=$_hers_newx | |
else | |
_hers_x=$_hers_newx | |
if test "$_scrolled" -gt "${_size}" && ! test "${_hers_x}" -eq '0' | |
then | |
if test "${_hers_x}" -gt $((_hers_lines - _scroll)) | |
then | |
write '\n\r' | |
write '\033['$((_hers_x - _size))';'$((_hers_lines - 1))'sr' | |
write '\033['$((_hers_lines + 1))'H' | |
elif test "$_size" -gt "$_hers_x" | |
then | |
write '\n\r' | |
write '\033['$((_size - _hers_x))';'$((_hers_lines - 1))'sr' | |
write '\033['$((_hers_lines + 1))'H' | |
fi | |
fi | |
fi | |
} | |
hers_setsize | |
write '\033[?1002h' | |
write '\033[?1003h' | |
write '\033[?1004h' | |
write '\033[777h' | |
write '\033[?1006h' | |
write '\033[3g' | |
local tcont=0 | |
while test $tcont -lt ${_hers_columns} | |
do | |
write '\033['$_hers_tabsize'C\033H' | |
tcont=$((tcont + _hers_tabsize)) | |
done | |
write '\r' | |
hers_escape_loop || : | |
write '\033[;r' | |
write '\033[?1006l' | |
write '\033[?1004l' | |
write '\033[777' | |
write '\033[?1003l' | |
write '\033[?1002l' | |
write '\033[?2004l' | |
write '\033[?1l' | |
write '\033>' | |
write '\0338' | |
write '\033['$((_hers_x + _scrolled -1))'B' | |
write '\033[?20h' | |
write '\033[23;0;0t' | |
write '\033[?25h' | |
stty "${_hers_previous_stty:-}" | |
stty sane echo icanon | |
# Prevent escapes from reaching stdin | |
while read -r -t'0.1' 2>/dev/null | |
do | |
: | |
done | |
write '\r\033[2K' | |
} | |
hers_event () | |
{ | |
set -- ${1} | |
local class=$1 | |
local code=${2:- } | |
_parameters="${_parameters:-}" | |
_mouse="${_mouse:-}" | |
if test ${class:-} = 'M' | |
then | |
case ${2:-} in | |
0 ) { test -n "$_held" && _mouse="drop" && _held="${_held%%ldrop}" ;} || _mouse='lclik';; | |
1 ) { test -n "$_held" && _mouse="drop" && _held="${_held%%mdrop}" ;} || _mouse='mclik';; | |
2 ) { test -n "$_held" && _mouse="drop" && _held="${_held%%rdrop}" ;} || _mouse='rclik';; | |
3 ) test -n "$_held" && _mouse="${_held}" && _held='';; | |
8 ) { test -n "$_held" && _mouse="alt-drop" && _held="${_held%%ldrop}" ;} || _mouse='alt-lclik';; | |
9 ) { test -n "$_held" && _mouse="alt-drop" && _held="${_held%%mdrop}" ;} || _mouse='alt-mclik';; | |
1 ) { test -n "$_held" && _mouse="alt-drop" && _held="${_held%%rdrop}" ;} || _mouse='alt-rclik';; | |
16|96 ) { test -n "$_held" && _mouse="ctrl-drop" && _held="${_held%%ldrop}" ;} || _mouse='ctrl-lclik';; | |
17|97 ) { test -n "$_held" && _mouse="ctrl-drop" && _held="${_held%%mdrop}" ;} || _mouse='ctrl-mclik';; | |
18|98 ) { test -n "$_held" && _mouse="ctrl-drop" && _held="${_held%%rdrop}" ;} || _mouse='ctrl-rclik';; | |
10 ) test -n "$_held" && _mouse="alt-${_held}" && _held='';; | |
11 ) test -n "$_held" && _mouse="alt-${_held}" && _held='';; | |
32 ) _mouse='lhold' && _held='ldrop';; | |
33 ) _mouse='mhold' && _held='mdrop';; | |
34 ) _mouse='rhold' && _held='rdrop';; | |
35 ) _mouse='move' && _held='';; | |
40 ) _mouse='alt-lhold' && _held='ldrop';; | |
41 ) _mouse='alt-mhold' && _held='mdrop';; | |
42 ) _mouse='alt-rhold' && _held='rdrop';; | |
43 ) _mouse='alt-move' && _held='';; | |
48 ) _mouse='ctrl-lhold' && _held='ldrop';; | |
49 ) _mouse='ctrl-mhold' && _held='mdrop';; | |
50 ) _mouse='ctrl-rhold' && _held='rdrop';; | |
51 ) _mouse='ctrl-move' && _held='';; | |
64 ) _mouse='whup';; | |
65 ) _mouse='wdown';; | |
80 ) _mouse='ctrl-whup';; | |
81 ) _mouse='ctrl-wdown';; | |
72 ) _mouse='alt-whup';; | |
73 ) _mouse='alt-wdown';; | |
* ) _mouse="${2:-}" && _held='';; | |
esac | |
_hers_column=$3 | |
_hers_line=$4 | |
_parameters="$_mouse [x$4 y$3] " | |
elif test $class = 'E' | |
then | |
_dbg="${*:-27}" | |
shift | |
_parameters='' | |
case "${*:-27}" in | |
'27' ) _parameters='esc';; | |
'31' ) _parameters='ctrl-q';; | |
'127' ) _parameters='ctrl-h';; | |
'91 90' ) _parameters='shif-tab';; | |
'79 65' ) _parameters='up';; | |
'79 66' ) _parameters='down';; | |
'79 67' ) _parameters='right';; | |
'79 68' ) _parameters='left';; | |
'79 80' ) _parameters='f1';; | |
'79 81' ) _parameters='f2';; | |
'79 82' ) _parameters='f3';; | |
'79 83' ) _parameters='f4';; | |
'91 49 53 126' ) _parameters='f5' ;; | |
'91 49 55 126' ) _parameters='f6' ;; | |
'91 49 56 126' ) _parameters='f7' ;; | |
'91 49 57 126' ) _parameters='f8' ;; | |
'91 50 48 126' ) _parameters='f9' ;; | |
'91 50 49 126' ) _parameters='f10' ;; | |
'91 50 51 126' ) _parameters='f11' ;; | |
'91 50 52 126' ) _parameters='f12' ;; | |
'79 72' | '91 49 126' ) _parameters='home';; | |
'79 70' | '91 52 126' ) _parameters='end';; | |
'91 50 126' ) _parameters='ins';; | |
'91 51 126' ) _parameters='del';; | |
'91 53 126' ) _parameters='pgup';; | |
'91 54 126' ) _parameters='pgdn';; | |
esac | |
hers_5code_modifiers "${*:-}" '49' 'up' '65' | |
hers_5code_modifiers "${*:-}" '49' 'down' '66' | |
hers_5code_modifiers "${*:-}" '49' 'right' '67' | |
hers_5code_modifiers "${*:-}" '49' 'left' '68' | |
hers_5code_modifiers "${*:-}" '49' 'f1' '80' | |
hers_5code_modifiers "${*:-}" '49' 'f2' '81' | |
hers_5code_modifiers "${*:-}" '49' 'f3' '82' | |
hers_5code_modifiers "${*:-}" '49' 'f4' '83' | |
hers_6code_modifiers "${*:-}" '49' 'f5' '53' | |
hers_6code_modifiers "${*:-}" '49' 'f6' '55' | |
hers_6code_modifiers "${*:-}" '49' 'f7' '56' | |
hers_6code_modifiers "${*:-}" '49' 'f8' '57' | |
hers_6code_modifiers "${*:-}" '50' 'f9' '48' | |
hers_6code_modifiers "${*:-}" '50' 'f10' '49' | |
hers_6code_modifiers "${*:-}" '50' 'f11' '51' | |
hers_6code_modifiers "${*:-}" '50' 'f12' '52' | |
hers_5code_modifiers "${*:-}" '49' 'home' '72' | |
hers_5code_modifiers "${*:-}" '49' 'end' '70' | |
hers_5code_modifiers "${*:-}" '50' 'del' '126' | |
hers_5code_modifiers "${*:-}" '51' 'del' '126' | |
hers_5code_modifiers "${*:-}" '53' 'pgup' '126' | |
hers_5code_modifiers "${*:-}" '54' 'pgdn' '126' | |
if test -z "${_parameters}" | |
then | |
if test "$#" -eq 1 && test $1 -lt 27 | |
then | |
_parameters="ctrl-$(printf "\\$(printf '%03o' $(($1 + 96)))")" | |
else | |
_parameters="MISS ${*:-27}" | |
fi | |
fi | |
elif test $class = 'C' | |
then | |
_dbg="${2:-}" | |
if test "${2:-}" != '\' | |
then | |
_parameters="${2:+char }${2:-Space}" | |
else | |
_parameters="char \\\\" | |
fi | |
fi | |
if test ${_full:-no} = 'yes' | |
then | |
write '\n\r\033['"$((_hers_x + _scrolled))"'H[=3]: '"${_parameters}\\033"'[K\033[1A\r'"$(date "+%T.%6N") [${_dbg:-}]\\033"'[K\033[1B' 2>/dev/null & | |
return | |
fi | |
if test "$_scrolled" -lt "${_hers_lines}" && test "$_scrolled" -lt "${_size}" | |
then | |
_scrolled=$((${_scrolled:-0} + $_scroll)) | |
write '\n\r[=1]: '"${_parameters}"'\033[1A\r'"$(date "+%T.%6N") [${_dbg:-}]\\033"'[K\033[1B' 2>/dev/null & | |
return | |
elif test ${_full:-no} = 'no' && test "$_scrolled" -eq $((_size)) | |
then | |
wait | |
if test "${_hers_x}" -gt $((_hers_lines - _hers_x)) | |
then | |
_full=yes | |
if test "$_size" -gt "$_hers_x" | |
then | |
write '\033['$((_size - _hers_x - _scrolled))';'$((_hers_x + _scrolled))'r' | |
write '\033['$((_hers_x + _scrolled))'H' | |
else | |
write '\n\r' | |
write '\033['$((_hers_lines - _scrolled - 1))';'$((_hers_lines - 1))'r' | |
write '\033['$((_hers_lines - _scroll + 1))'H' | |
fi | |
else | |
write '\033['$((_hers_x))';'$((_hers_x + _scrolled))'r' | |
write '\033['$((_hers_x + _scrolled))'H' | |
fi | |
write '\n\r[=2]: '"${_parameters}"'\033[1A\r'"$(date "+%T.%6N") [${_dbg:-}]\\033"'[K\033[1B' 2>/dev/null & | |
_full=yes | |
return | |
fi | |
} | |
hers_6code_modifiers () | |
{ | |
case "${1}" in | |
"91 ${2} ${4} 59 50 126" ) _parameters="shif-${3}";; | |
"91 ${2} ${4} 59 51 126" ) _parameters="alt-${3}";; | |
"91 ${2} ${4} 59 52 126" ) _parameters="alt-shif-${3}";; | |
"91 ${2} ${4} 59 53 126" ) _parameters="ctrl-${3}";; | |
"91 ${2} ${4} 59 54 126" ) _parameters="ctrl-shif-${3}";; | |
"91 ${2} ${4} 59 55 126" ) _parameters="ctrl-alt-${3}";; | |
"91 ${2} ${4} 59 56 126" ) _parameters="ctrl-shif-alt-${3}";; | |
esac | |
} | |
hers_5code_modifiers () | |
{ | |
case "${1}" in | |
"91 ${2} 59 50 ${4}" ) _parameters="shif-${3}";; | |
"91 ${2} 59 51 ${4}" ) _parameters="alt-${3}";; | |
"91 ${2} 59 52 ${4}" ) _parameters="alt-shif-${3}";; | |
"91 ${2} 59 53 ${4}" ) _parameters="ctrl-${3}";; | |
"91 ${2} 59 54 ${4}" ) _parameters="ctrl-shif-${3}";; | |
"91 ${2} 59 55 ${4}" ) _parameters="ctrl-alt-${3}";; | |
"91 ${2} 59 56 ${4}" ) _parameters="ctrl-shif-alt-${3}";; | |
esac | |
} | |
hers_escape_loop () | |
{ | |
local _hers_quitchar="$(printf "\\$(printf '%03d' 27)")" # W | |
local _hers_escchar="$(printf "\\$(printf '%03d' 33)")" | |
hers_dump_code () | |
{ | |
if test $hers_code -gt 31 && test $hers_code -lt 127 | |
then | |
hers_event "C $_hers_byte" | |
return | |
elif test $hers_code -eq 226 | |
then | |
local hers_previous="${_hers_byte}" | |
hers_get_event_byte | |
local hers_previous="${hers_previous}${_hers_byte}" | |
hers_get_event_byte | |
hers_event "C ${hers_previous}${_hers_byte}" | |
return | |
elif test $hers_code -eq 194 || test $hers_code -eq 195 | |
then | |
local hers_previous="${_hers_byte}" | |
hers_get_event_byte | |
hers_event "C ${hers_previous}${_hers_byte}" | |
return | |
else | |
hers_event "E ${hers_code}" | |
fi | |
} | |
while | |
hers_get_event_byte -t'0.02' || | |
hers_get_event_byte -t'0.04' || | |
hers_get_event_byte -t'0.06' || | |
hers_get_event_byte -t'0.12' || | |
hers_get_event_byte -t'0.24' || | |
hers_get_event_byte -t'0.26' || | |
_noinput=yes | |
do | |
if test "${_noinput:-no}" = "yes" | |
then | |
hers_event "F" | |
_noinput=no | |
continue | |
elif test "$_hers_byte" = "$_hers_quitchar" | |
then | |
return | |
elif test "$_hers_byte" = "$_hers_escchar" | |
then | |
local hers_escape='' | |
while hers_get_event_byte -t'0.02' | |
do | |
if test "$hers_escape" = '91' && | |
test "${_hers_byte}" = '<' | |
then | |
local hers_escape='M ' | |
while hers_get_event_byte | |
do | |
if test "${_hers_byte}" = ';' | |
then | |
local hers_escape="${hers_escape} " | |
continue | |
elif | |
test "${_hers_byte}" != 'M' && | |
test "${_hers_byte}" != 'm' | |
then | |
local hers_escape="${hers_escape}${_hers_byte}" | |
continue | |
fi | |
break | |
done | |
hers_event "${hers_escape}" | |
continue 2 | |
elif | |
test "$hers_escape" = '' && | |
test "$_hers_byte" = "[" | |
then | |
local hers_escape="91" | |
continue | |
elif test "$_hers_byte" = "$_hers_quitchar" | |
then | |
return | |
elif | |
test "$_hers_byte" = "$_hers_escchar" | |
then | |
test "$hers_escape" = '' || hers_event "E ${hers_escape}" | |
local hers_escape='' | |
continue | |
fi | |
local hers_code="$(printf '%d' "'${_hers_byte}" 2>/dev/null)" | |
if test "$hers_code" -lt 32 | |
then | |
hers_event "E ${hers_escape}" | |
local hers_escape='' | |
hers_dump_code | |
continue 2 | |
fi | |
local hers_escape="${hers_escape} ${hers_code}" | |
done | |
hers_event "E ${hers_escape}" | |
continue | |
fi | |
local hers_code="$(printf '%d' "'${_hers_byte}" 2>/dev/null)" | |
hers_dump_code | |
done | |
} | |
hers_initialize "${@:-}" && exit |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment