Regularly, I work on machines connected to TVs which respond to interactions with remote control devices. Yesterday, while doing some actions at the device command line, I realized that I don't need the remote control. All I need to control applications is in the shell. This is an Android based machine, so it is possible to move the menu selector to the left by executing:
$ input keyevent 21
I made a conf file with some keys I needed:
$ cat androidKeys.sh
KEY_LEFT=21
KEY_RIGHT=22
KEY_UP=19
KEY_DOWN=20
KEY_BACK=4
KEY_ENTER=66
KEY_MENU=3
KEY_VOLUME_UP=24
KEY_VOLUME_DOWN=25
KEY_VOLUME_MUTE=164
And created a script to easily send commands:
$ cat send.sh
#!/usr/bin/env bash
source ./androidKeys.sh
HOST_IP=$1
EVENT_KEY=$2
KEY_CODE=$(eval "echo \$${EVENT_KEY}")
ssh $HOST_IP "input keyevent ${KEY_CODE}"
Noticed the
eval "echo \$${EVENT_KEY}"? It lets you obtain a variable's value by using the content of another variable to indicate the name of the first variable. So, the $EVENT_KEY may contain 'KEY_LEFT', which when passed to eval is evaluated as
"echo $KEY_LEFT" , giving us the value of $KEY_LEFT.
Then I did:
$ ./send.sh 10.0.0.123 KEY_LEFT
And the cursor moved to the left.
This is better, I thought, but it could be even better. I could be using the directional keys on my keyboard.
But it is not a simple task, the following has to possible:
1) obtain the key pressed
2) convert into a numerical representation
3) translate that representation into our KEY_* codes
4) invoke send.sh, passing the host IP and the key code
For the first task, an
stty/
dd combo comes to rescue. We use stty in raw mode, and capture three bytes of input with dd (bs=3). This will allow us to capture single and multi byte keys.
Then we parse this string (
ord function below), by iterating over it and converting each character to a number by
printfing it as integer.
Finally, we pass the numerical sequence returned by
ord to another
charCodeTokey function which translates to our known KEY_* codes.
$ cat auto.sh
#!/usr/bin/env bash
HOST_IP=$1
function charCodeToKey ()
{
CHAR_CODE=($@)
CHAR_STRING="${CHAR_CODE[@]}"
[ "$CHAR_STRING" = "27 91 68" ] && echo 'KEY_LEFT'
[ "$CHAR_STRING" = "27 91 67" ] && echo 'KEY_RIGHT'
[ "$CHAR_STRING" = "27 91 65" ] && echo 'KEY_UP'
[ "$CHAR_STRING" = "27 91 66" ] && echo 'KEY_DOWN'
[ "$CHAR_STRING" = "127" ] && echo 'KEY_BACK'
[ "$CHAR_STRING" = "13" ] && echo 'KEY_ENTER'
[ "$CHAR_STRING" = "27 79 72" ] && echo 'KEY_MENU'
[ "$CHAR_STRING" = "43" ] && echo 'KEY_VOLUME_UP'
[ "$CHAR_STRING" = "45" ] && echo 'KEY_VOLUME_DOWN'
}
function ord()
{
str="$@"
strLen=${#str}
for i in $(seq 0 $(($strLen - 1)))
do
c=${str:$i:1}
printf '%d ' "'$c"
done
}
while :
do
old_tty_settings=$(stty -g)
stty raw -echo
DATA=$(dd bs=3 count=1 2>/dev/null)
stty "$old_tty_settings"
CHARS=$(ord $DATA)
if [ "${CHARS}" = "3 " ]
then
exit
fi
KEY_EVENT=$(charCodeToKey "$CHARS")
if [ "$KEY_EVENT" != "" ]
then
echo $KEY_EVENT
./send.sh $HOST_IP $KEY_EVENT
else
echo "Unsupported key"
fi
done
One caveat of using stty's this way is that after starting the infinite while loop, it is impossible to stop. This is because stty captures the ^C command, and translates it too. So we need to check for it (the
if [ "${CHARS}" = "3 " ].. exit above), and exit the script. Also, we must save the terminal settings (
old_tty_settings=$(stty -g) ) and put it back (
stty "$old_tty_settings" ), otherwise the terminal will be unusable after we exit the script.
So now I can control all my applications on the TV through my shell, no more remotes :)
Well, maybe not all applications yet. These scripts will need more key codes... and mouse support.