Skip to content

Instantly share code, notes, and snippets.

@Ferdi265
Last active August 29, 2015 14:19
Show Gist options
  • Save Ferdi265/e8844e11d502faa4cc2d to your computer and use it in GitHub Desktop.
Save Ferdi265/e8844e11d502faa4cc2d to your computer and use it in GitHub Desktop.
AndroidTerminal shell menu script
#!/system/xbin/bash
# AndroidTerminal shell menu script
#
# Displays a fullscreen menu in the terminal that adapts to terminal size and supports pagination.
# The menu can be navigated with the <w> and <s> keys (to reflect the wasd movement style), or with <u> and <d> (up and down).
# By pressing <return>, the menu item selectection can be confirmed, and the corresponding script will be executed.
#
# Usage: bash menu.sh <menu folder>
#
# <menu folder>: relative or absolute path to folder containing menu and entry files
# example folder hierarchy:
# menu folder:
# - ./menu
# - ./01.entry
# - ./02.entry
# - ./99.entry
#
# Menu and entry file format:
# multiple lines of <key>: <value>
# example file contents:
# ./menu contents:
# title: Example Menu
# termtitle: Example
# ./01.entry contents:
# title: Exit
# termtitle: Exit
# command: echo exiting...; exit
#
# Legal keys for menu and entry files:
# ./menu
# title: name listed at top of menu
# termtitle: name set to the terminal title while in menu
# ./*.entry
# title: name listed in menu, can contain terminal escape sequences
# the content of this line is passed to printf
# termtitle: name set to the terminal title during execution of entry
# command: bash command to execute when selection confirmed
# the value of this key passed to the builtin "eval"
# checkcolor: escape sequence to determine the color and style of the "x" mark when menu item is selected
menu="$1"
gopos() {
# move the terminal cursor to row $1 and column $2
printf "\033[${1};${2}H"
}
get() {
# return the value of key $2 (unquoted RegExp) in file $1
grep "^$2: " "$1" | sed "s/^[^:]*: //"
}
getmenu() {
# return the value of key $1 in the menu file associated with this menu
get "$menu/menu" $1
}
getentry() {
# return the value of key $2 in the $1-th entry file
# behaviour for $1 > $entries is undefined
get ${entry[$1]} $2
}
default() {
# return $1 if it is non-empty, else $2
if [ "$1" == "" ]; then
printf -- "$2"
else
printf -- "$1"
fi
}
init() {
# initialize menu state
# check if menu exists
if [ ! -f "$menu/menu" ]; then
echo "Couldn't find menu"
exit 1
fi
# get list of entries, allow list of 0 entries (inescapable menu if ^C is not used)
# it is advised to create an "exit" menu entry to make gracefully exiting the menu possible
shopt -s nullglob
entry=($menu/*.entry)
shopt -u nullglob
# set number of entries to $entries to avoid ugly array-length syntax
entries=${#entry[@]}
# select the first entry
selected=0
}
reflow() {
# check size of terminal and recalculate pagination if needed
# get terminal size
size=($(stty size 2>/dev/null))
# AndroidTerm hack: in early terminal initialization, "stty size" returns errors
if [ -z ${size[0]} ]; then
# wait until terminal initialized correctly
usleep 100000
# re-call self
reflow
# if previous terminal height is not equal to current terminal height
elif [ "${size[0]}" != "$lines" ]; then
# update terminal height variable
lines=${size[0]}
# the first and last line per page are left free for pagination and terminal title
# including 1 empty line below the title and above the pagination
# total: height - 4 lines
entries_per_page=$(($lines - 4))
# pages is ceil(entries / entries_per_page)
# bash doesn't have ceil()
# so pages is floor(entries / entries_per_page) (+ 1 if entries % entries_per_page is not 0)
pages=$(($entries / $entries_per_page))
rest=$(($entries % $entries_per_page))
if [ $rest -ne 0 ]; then
pages=$(($pages + 1))
fi
# call redraw because pagination has changed
redraw
fi
}
page_of() {
# calculate page of the $1-th entry
cur=$(($1 / $entries_per_page))
printf -- "$cur"
}
redraw() {
# redraw the menu
# this includes the menu title, termtitle, titles of entries, and unfilled "[ ]" for the selection mark
cur_page=$(page_of $selected)
# calculate number of entries on this page
on_page=(${entry[@]:$(($entries_per_page * $cur_page)):$entries_per_page})
# output termtitle and menu title
out="\033]2;$(default "$(getmenu termtitle)" "Menu") \007$(default "$(getmenu title)" "Menu:")"
# iterate over entries on this page
for i in $(seq 0 $((${#on_page[@]} - 1))); do
# output unfilled "[ ]" and entry title
out+="$(gopos $(($i + 3)) 0)[ ] $(getentry $(($cur_page * $entries_per_page + $i)) title)"
done
# output pagination
out+="$(gopos $(($lines)) 0)Page $(($cur_page + 1)) of $pages"
# move cursor to not obscure pagination (to second-to-last line)
out+="$(gopos $(($lines - 1)) 0)"
# clear the screen
clear
# draw the screen
printf -- "$out"
# draw selection mark
check
}
check() {
# draw selection mark
out=""
cur_page=$(page_of $selected)
# calculate number of entries on this page
on_page=(${entry[@]:$(($entries_per_page * $cur_page)):$entries_per_page})
# iterate over entries on this page
for i in $(seq 0 $((${#on_page[@]} - 1))); do
# output space (to overwrite the previous mark) if the item is not selected
# else output the checkcolor for this entry and the selction mark "x"
mark=" "
if [ $(($entries_per_page * $cur_page + $i)) -eq $selected ]; then
mark="$(default $(getentry $(($cur_page * $entries_per_page + $i)) checkcolor) "\e[93m")x\e[39m"
fi
out+="$(gopos $(($i + 3)) 2)$mark"
done
# move cursor to not obscure pagination (to second-to-last line)
out+="$(gopos $(($lines - 1)) 0)"
# draw selection mark, and clear old mark
printf -- "$out"
}
prompt() {
# check for a key press within the next second
if read -sN1 -t1 key; then
# call act if a keypress happened
act "$key"
fi
}
act() {
# check input from prompt
cur_page=$(page_of $selected)
case "$1" in
s|d) # if <s> or <d> (down)
# if not last entry
if [ $selected -lt $(($entries - 1)) ]; then
# select next entry
selected=$(($selected + 1))
fi
;;
w|u) # if <w> or <u> (up)
# if not first entry
if [ $selected -gt 0 ]; then
# select previous entry
selected=$(($selected - 1))
fi
;;
'') # if <return> (confirm)
# print termtitle of entry
printf "\033]2;$(default "$(getentry $selected termtitle)" "Command") \007"
# clear the screen
clear
# run command
eval "$(getentry $selected command)"
# redraw the menu after command
redraw
;;
esac
# if cursor went off the page
if [ $cur_page -ne $(page_of $selected) ]; then
# redraw menu, which switches to the next/previous page to display the correct menu entries
# redraw calls check, to redraw the selection mark
redraw
else
# redraw selection mark
check
fi
}
# initialize the menu
init
while true; do
# check if terminal size changed
reflow
# check for user input
prompt
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment