Skip to content

Instantly share code, notes, and snippets.

@abishur
Last active July 28, 2024 19:57
Show Gist options
  • Save abishur/2482046 to your computer and use it in GitHub Desktop.
Save abishur/2482046 to your computer and use it in GitHub Desktop.
A simple menu system using python for the Terminal (Framebufer)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Topmenu and the submenus are based of the example found at this location http://blog.skeltonnetworks.com/2010/03/python-curses-custom-menu/
# The rest of the work was done by Matthew Bennett and he requests you keep these two mentions when you reuse the code :-)
# Basic code refactoring by Andrew Scheller
from time import sleep
import curses, os #curses is the interface for capturing key presses on the menu, os launches the files
screen = curses.initscr() #initializes a new window for capturing key presses
curses.noecho() # Disables automatic echoing of key presses (prevents program from input each key twice)
curses.cbreak() # Disables line buffering (runs each key as it is pressed rather than waiting for the return key to pressed)
curses.start_color() # Lets you use colors when highlighting selected menu option
screen.keypad(1) # Capture input from keypad
# Change this to use different colors when highlighting
curses.init_pair(1,curses.COLOR_BLACK, curses.COLOR_WHITE) # Sets up color pair #1, it does black text with white background
h = curses.color_pair(1) #h is the coloring for a highlighted menu option
n = curses.A_NORMAL #n is the coloring for a non highlighted menu option
MENU = "menu"
COMMAND = "command"
EXITMENU = "exitmenu"
menu_data = {
'title': "Program Launcher", 'type': MENU, 'subtitle': "Please select an option...",
'options':[
{ 'title': "XBMC", 'type': COMMAND, 'command': 'xbmc' },
{ 'title': "Emulation Station - Hit F4 to return to menu, Esc to exit game", 'type': COMMAND, 'command': 'emulationstation' },
{ 'title': "Ur-Quan Masters", 'type': COMMAND, 'command': 'uqm' },
{ 'title': "Dosbox Games", 'type': MENU, 'subtitle': "Please select an option...",
'options': [
{ 'title': "Midnight Rescue", 'type': COMMAND, 'command': 'dosbox /media/samba/Apps/dosbox/doswin/games/SSR/SSR.EXE -exit' },
{ 'title': "Outnumbered", 'type': COMMAND, 'command': 'dosbox /media/samba/Apps/dosbox/doswin/games/SSO/SSO.EXE -exit' },
{ 'title': "Treasure Mountain", 'type': COMMAND, 'command': 'dosbox /media/samba/Apps/dosbox/doswin/games/SST/SST.EXE -exit' },
]
},
{ 'title': "Pianobar", 'type': COMMAND, 'command': 'clear && pianobar' },
{ 'title': "Windows 3.1", 'type': COMMAND, 'command': 'dosbox /media/samba/Apps/dosbox/doswin/WINDOWS/WIN.COM -conf /home/pi/scripts/dosbox2.conf -exit' },
{ 'title': "Reboot", 'type': MENU, 'subtitle': "Select Yes to Reboot",
'options': [
{'title': "NO", 'type': EXITMENU, },
{'title': "", 'type': COMMAND, 'command': '' },
{'title': "", 'type': COMMAND, 'command': '' },
{'title': "", 'type': COMMAND, 'command': '' },
{'title': "YES", 'type': COMMAND, 'command': 'sudo shutdown -r -time now' },
{'title': "", 'type': COMMAND, 'command': '' },
{'title': "", 'type': COMMAND, 'command': '' },
{'title': "", 'type': COMMAND, 'command': '' },
]
},
]
}
# This function displays the appropriate menu and returns the option selected
def runmenu(menu, parent):
# work out what text to display as the last menu option
if parent is None:
lastoption = "Exit"
else:
lastoption = "Return to %s menu" % parent['title']
optioncount = len(menu['options']) # how many options in this menu
pos=0 #pos is the zero-based index of the hightlighted menu option. Every time runmenu is called, position returns to 0, when runmenu ends the position is returned and tells the program what opt$
oldpos=None # used to prevent the screen being redrawn every time
x = None #control for while loop, let's you scroll through options until return key is pressed then returns pos to program
# Loop until return key is pressed
while x !=ord('\n'):
if pos != oldpos:
oldpos = pos
screen.border(0)
screen.addstr(2,2, menu['title'], curses.A_STANDOUT) # Title for this menu
screen.addstr(4,2, menu['subtitle'], curses.A_BOLD) #Subtitle for this menu
# Display all the menu items, showing the 'pos' item highlighted
for index in range(optioncount):
textstyle = n
if pos==index:
textstyle = h
screen.addstr(5+index,4, "%d - %s" % (index+1, menu['options'][index]['title']), textstyle)
# Now display Exit/Return at bottom of menu
textstyle = n
if pos==optioncount:
textstyle = h
screen.addstr(5+optioncount,4, "%d - %s" % (optioncount+1, lastoption), textstyle)
screen.refresh()
# finished updating screen
x = screen.getch() # Gets user input
# What is user input?
if x >= ord('1') and x <= ord(str(optioncount+1)):
pos = x - ord('0') - 1 # convert keypress back to a number, then subtract 1 to get index
elif x == 258: # down arrow
if pos < optioncount:
pos += 1
else: pos = 0
elif x == 259: # up arrow
if pos > 0:
pos += -1
else: pos = optioncount
# return index of the selected item
return pos
# This function calls showmenu and then acts on the selected item
def processmenu(menu, parent=None):
optioncount = len(menu['options'])
exitmenu = False
while not exitmenu: #Loop until the user exits the menu
getin = runmenu(menu, parent)
if getin == optioncount:
exitmenu = True
elif menu['options'][getin]['type'] == COMMAND:
curses.def_prog_mode() # save curent curses environment
os.system('reset')
if menu['options'][getin]['title'] == 'Pianobar':
os.system('amixer cset numid=3 1') # Sets audio output on the pi to 3.5mm headphone jack
screen.clear() #clears previous screen
os.system(menu['options'][getin]['command']) # run the command
screen.clear() #clears previous screen on key press and updates display based on pos
curses.reset_prog_mode() # reset to 'current' curses environment
curses.curs_set(1) # reset doesn't do this right
curses.curs_set(0)
os.system('amixer cset numid=3 2') # Sets audio output on the pi back to HDMI
elif menu['options'][getin]['type'] == MENU:
screen.clear() #clears previous screen on key press and updates display based on pos
processmenu(menu['options'][getin], menu) # display the submenu
screen.clear() #clears previous screen on key press and updates display based on pos
elif menu['options'][getin]['type'] == EXITMENU:
exitmenu = True
# Main program
processmenu(menu_data)
curses.endwin() #VITAL! This closes out the menu system and returns you to the bash prompt.
os.system('clear')
@SngLol
Copy link

SngLol commented Mar 16, 2017

I have a similar but simpler cross-platform menu creator class! And it has a unique interactive design!
It lets you go though the alternatives with an arrow using the arrow keys to move up and down!
If you're interested you can check it out here:
https://github.com/SngLol/PythonMenuSetup

@jamieduk
Copy link

jamieduk commented Jan 31, 2023

what version of python is #!/usr/bin/env python exaclty? im getting error

Traceback (most recent call last):
File "/home/jay/Documents/Scripts/Menus/Python/new_menu.py", line 31, in
import commands
ModuleNotFoundError: No module named 'commands'

works fine on last mint but mint 21 nope!!!

dw i fixed it with this command

pip install virtualenv

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment