Skip to content

Instantly share code, notes, and snippets.

@alganet
Last active July 16, 2024 18:07
Show Gist options
  • Save alganet/63f1dbc97b8fd35f7bb14ec30f79d2a5 to your computer and use it in GitHub Desktop.
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)
#!/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