Instantly share code, notes, and snippets.
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save Ferdi265/e8844e11d502faa4cc2d to your computer and use it in GitHub Desktop.
AndroidTerminal shell menu script
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
#!/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