I really recommend you look into using panels. Anytime you will have widgets that could possibly overlap, it makes life alot easier. This is a simple example that should get you started. (Neither curses.beep() or curses.flash() seem to work on my terminal, but that is beside the point)

#!/usr/bin/env python

import curses
from curses import panel


class Menu(object):
    def __init__(self, items, stdscreen):
        self.window = stdscreen.subwin(0, 0)
        self.window.keypad(1)
        self.panel = panel.new_panel(self.window)
        self.panel.hide()
        panel.update_panels()

        self.position = 0
        self.items = items
        self.items.append(("exit", "exit"))

    def navigate(self, n):
        self.position += n
        if self.position < 0:
            self.position = 0
        elif self.position >= len(self.items):
            self.position = len(self.items) - 1

    def display(self):
        self.panel.top()
        self.panel.show()
        self.window.clear()

        while True:
            self.window.refresh()
            curses.doupdate()
            for index, item in enumerate(self.items):
                if index == self.position:
                    mode = curses.A_REVERSE
                else:
                    mode = curses.A_NORMAL

                msg = "%d. %s" % (index, item[0])
                self.window.addstr(1 + index, 1, msg, mode)

            key = self.window.getch()

            if key in [curses.KEY_ENTER, ord("\n")]:
                if self.position == len(self.items) - 1:
                    break
                else:
                    self.items[self.position][1]()

            elif key == curses.KEY_UP:
                self.navigate(-1)

            elif key == curses.KEY_DOWN:
                self.navigate(1)

        self.window.clear()
        self.panel.hide()
        panel.update_panels()
        curses.doupdate()


class MyApp(object):
    def __init__(self, stdscreen):
        self.screen = stdscreen
        curses.curs_set(0)

        submenu_items = [("beep", curses.beep), ("flash", curses.flash)]
        submenu = Menu(submenu_items, self.screen)

        main_menu_items = [
            ("beep", curses.beep),
            ("flash", curses.flash),
            ("submenu", submenu.display),
        ]
        main_menu = Menu(main_menu_items, self.screen)
        main_menu.display()


if __name__ == "__main__":
    curses.wrapper(MyApp)

Some things to note when looking over your code.

Using curses.wrapper(callable) to launch your application is cleaner than doing your own try/except with cleanup.

Your class calls initscr twice which will probably generate two screens (havent tested if it returns the same screen if its setup), and then when you have multiple menus there is no proper handling of (what should be) different windows/screens. I think its clearer and better bookkeeping to pass the menu the screen to use and let the menu make a subwindow to display in as in my example.

Naming a list 'list' isn't a great idea, because it shadows the list() function.

If you want to launch another terminal app like 'top', it is probably better to let python exit curses cleanly first then launch in order to prevent any futzing with terminal settings.

Answer from kalhartt on Stack Overflow
🌐
PyPI
pypi.org › project › curses-menu
curses-menu
JavaScript is disabled in your browser. Please enable JavaScript to proceed · A required part of this site couldn’t load. This may be due to a browser extension, network issues, or browser settings. Please check your connection, disable any ad blockers, or try using a different browser
Top answer
1 of 1
58

I really recommend you look into using panels. Anytime you will have widgets that could possibly overlap, it makes life alot easier. This is a simple example that should get you started. (Neither curses.beep() or curses.flash() seem to work on my terminal, but that is beside the point)

#!/usr/bin/env python

import curses
from curses import panel


class Menu(object):
    def __init__(self, items, stdscreen):
        self.window = stdscreen.subwin(0, 0)
        self.window.keypad(1)
        self.panel = panel.new_panel(self.window)
        self.panel.hide()
        panel.update_panels()

        self.position = 0
        self.items = items
        self.items.append(("exit", "exit"))

    def navigate(self, n):
        self.position += n
        if self.position < 0:
            self.position = 0
        elif self.position >= len(self.items):
            self.position = len(self.items) - 1

    def display(self):
        self.panel.top()
        self.panel.show()
        self.window.clear()

        while True:
            self.window.refresh()
            curses.doupdate()
            for index, item in enumerate(self.items):
                if index == self.position:
                    mode = curses.A_REVERSE
                else:
                    mode = curses.A_NORMAL

                msg = "%d. %s" % (index, item[0])
                self.window.addstr(1 + index, 1, msg, mode)

            key = self.window.getch()

            if key in [curses.KEY_ENTER, ord("\n")]:
                if self.position == len(self.items) - 1:
                    break
                else:
                    self.items[self.position][1]()

            elif key == curses.KEY_UP:
                self.navigate(-1)

            elif key == curses.KEY_DOWN:
                self.navigate(1)

        self.window.clear()
        self.panel.hide()
        panel.update_panels()
        curses.doupdate()


class MyApp(object):
    def __init__(self, stdscreen):
        self.screen = stdscreen
        curses.curs_set(0)

        submenu_items = [("beep", curses.beep), ("flash", curses.flash)]
        submenu = Menu(submenu_items, self.screen)

        main_menu_items = [
            ("beep", curses.beep),
            ("flash", curses.flash),
            ("submenu", submenu.display),
        ]
        main_menu = Menu(main_menu_items, self.screen)
        main_menu.display()


if __name__ == "__main__":
    curses.wrapper(MyApp)

Some things to note when looking over your code.

Using curses.wrapper(callable) to launch your application is cleaner than doing your own try/except with cleanup.

Your class calls initscr twice which will probably generate two screens (havent tested if it returns the same screen if its setup), and then when you have multiple menus there is no proper handling of (what should be) different windows/screens. I think its clearer and better bookkeeping to pass the menu the screen to use and let the menu make a subwindow to display in as in my example.

Naming a list 'list' isn't a great idea, because it shadows the list() function.

If you want to launch another terminal app like 'top', it is probably better to let python exit curses cleanly first then launch in order to prevent any futzing with terminal settings.

Discussions

Simple, menu-based curses GUI library
Hey, r/Python ! Please check out my project, and feel free to post any feedback you might have. It's my first attempt at making anything I actually think other people might use, so I'm sure there are improvements to be made. The title pretty much says it all, it's a library for making menu-based GUIs on the console using curses. It's designed to be quick and easy to use, as well as decently extensible. I also used developing it as a way to learn about a bunch of tools for testing, documentation, packaging, etc. More on reddit.com
🌐 r/Python
9
118
February 14, 2016
Good python library for a text based interface?
Python has a built in module for this called curses . blessed (get it?) is another library built on top of curses, and provides a much more convenient interface. I'd recommend that. There's an okay discussion on another reddit thread https://www.reddit.com/r/Python/comments/1132ct/im_trying_to_make_a_fancy_cli_interface_in_python/ More on reddit.com
🌐 r/Python
19
31
December 4, 2016
As a lover of TUI interfaces, I made a library for creating them in python, and then used it to write a TUI application for managing git repositories!
The library uses curses as a back end, but only uses ascii characters for rendering the entire interface, no other tricks. The TUI library is here: https://github.com/jwlodek/py_cui and the git management project in the video is here: https://github.com/jwlodek/pyautogit For the video I am using a Debian 10 machine running xfce4 + xfce4-terminal. Hope you guys find this as interesting as I did to make it! Sidenote: I'm aware the library is called py_cui, though it probably should be py_tui - the reason for the chosen name being that the idea from the project came from a similar project in go called gocui. Edit: Thank you so much for gold! More on reddit.com
🌐 r/linux
84
1587
February 24, 2020
I created a python module for command line checkboxes, better yes/no or number input.

It is on GitHub and pypi. You can install it with pip3 install cutie. The source code for the example and documentation is at both those locations.

EDIT: I just noticed, that the gif does not show everything clearly. There is a Yes/No question at the beginning and the quest is secure text input. I updated the gif on the GitHub, but I cannot change the post. It also shows the sentence at the end for longer.

More on reddit.com
🌐 r/Python
55
824
November 14, 2018
🌐
Python documentation
docs.python.org › 3 › howto › curses.html
Curses Programming with Python — Python 3.14.4 documentation
Because the curses API is so large, some functions aren’t supported in the Python interface. Often this isn’t because they’re difficult to implement, but because no one has needed them yet. Also, Python doesn’t yet support the menu library associated with ncurses.
🌐
Readthedocs
curses-menu.readthedocs.io › en › latest › usage.html
Usage — curses-menu documentation - Read the Docs
from cursesmenu import SelectionMenu a_list=["red", "blue", "green"] menu = CursesMenu.make_selection_menu(a_list,"Select an option") menu.show() menu.join() selection = menu.selected_option
🌐
GitHub
github.com › pmbarrett314 › curses-menu
GitHub - pmbarrett314/curses-menu: A simple console menu system in python using the curses library · GitHub
Windows users can visit http://www.lfd.uci.edu/~gohlke/pythonlibs/#curses and get a third-party build for your platform and Python version. ... It’s designed to be pretty simple to use. Here’s an example · menu = CursesMenu("Root Menu", "Root Menu Subtitle") item1 = MenuItem("Basic item that does nothing", menu) function_item = FunctionItem("FunctionItem, get input", input, ["Enter an input: "]) print(__file__) command_item = CommandItem( "CommandItem that opens another menu", f"python {__file__}", ) submenu = CursesMenu.make_selection_menu([f"item{x}" for x in range(1, 20)]) submenu_item
Starred by 477 users
Forked by 49 users
Languages   Python
🌐
Gnosis
gnosis.cx › publish › programming › charming_python_6.html
CHARMING PYTHON #6 -- Curses programming in Python: Tips for Beginners --
The application curses_txt2html is structured in terms of a familiar topbar menu with drop-downs and nested submenus. All of the menuing functions were done "from scratch" on top of curses. As a result, these menus lack some of the features of more sophisticated curses wrapper programs, but the basic functionality can be implemented in a moderate number of lines using only curses.
Find elsewhere
🌐
Adamlamers
adamlamers.com › post › FTPD9KNRA8CT
Easy ncurses Menu with Python - Adam's Blog
March 17, 2016 - menu = {'title' : 'Curses Menu', 'type' : 'menu', 'subtitle' : 'A Curses menu in Python'} option_1 = {'title' : 'Hello World', 'type' : 'command', 'command' : 'echo Hello World!'} menu['options'] = [option_1] m = CursesMenu(menu) selected_action = m.display() if selected_action['type'] != 'exitmenu': os.system(selected_action['command']) You could also put a custom type in the 'type' of a menuitem, and call a Python function instead of a call out to the OS.
🌐
Read the Docs
app.readthedocs.org › projects › curses-menu
curses-menu - Read the Docs Community
August 1, 2022 - A simple Python menu-based GUI system on the terminal using curses.
🌐
Readthedocs
curses-menu.readthedocs.io › _ › downloads › en › latest › pdf pdf
curses-menu Paul Barrett Dec 29, 2022
December 29, 2022 - Put the console back in curses mode and resume the menu. ... Return the console to its original state and pause the menu. ... A menu item that executes a Python function with arguments.
🌐
Reddit
reddit.com › r/python › simple, menu-based curses gui library
r/Python on Reddit: Simple, menu-based curses GUI library
February 14, 2016 - It's my first attempt at making anything I actually think other people might use, so I'm sure there are improvements to be made. The title pretty much says it all, it's a library for making menu-based GUIs on the console using curses.
🌐
PyPI
pypi.org › project › CursesMenu
CursesMenu · PyPI
November 28, 2019 - CursesMenu is a GTK inspired widget engine using curses for use with command line interfaces. Also inspired by the Ubuntu Live Server installation menus.
      » pip install CursesMenu
    
Published   Nov 28, 2019
Version   0.0.1a0
🌐
YouTube
youtube.com › the python oracle
How to create a menu and submenus in Python curses? - YouTube
Become part of the top 3% of the developers by applying to Toptal https://topt.al/25cXVn--Music by Eric Matyashttps://www.soundimage.orgTrack title: Luau--Ch...
Published   November 22, 2022
Views   583
🌐
Python
docs.python.org › 3 › library › curses.html
curses — Terminal handling for character-cell displays
Tutorial material on using curses with Python, by Andrew Kuchling and Eric Raymond.
🌐
GitHub
gist.github.com › lurch › 2494781
A simple menu system using python for the Terminal (Framebufer) · GitHub
A simple menu system using python for the Terminal (Framebufer) Raw · menu_launcher.py · This file contains hidden or 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.
🌐
PyPI
pypi.org › project › curses-menu › 0.5.0
curses-menu 0.5.0
JavaScript is disabled in your browser · Please enable JavaScript to proceed · A required part of this site couldn’t load. This may be due to a browser extension, network issues, or browser settings. Please check your connection, disable any ad blockers, or try using a different browser
🌐
DevDungeon
devdungeon.com › content › curses-programming-python
Curses Programming in Python | DevDungeon
June 10, 2019 - If you want to check out a simple finished project that uses Python curses, check out the issh DevDungeon project which creates a menu for choosing SSH connections.
🌐
Medium
medium.com › vera-worri › curses-for-building-ui-in-python-93c4c62bd711
Curses for Building UI in Python. My quest to build a terminal based ui… | by Vera Worri | Vera Worri | Medium
January 11, 2017 - The latter helped me start and create a menu that will give options to: ... from unicurses import * from cursesmenu import CursesMenu, SelectionMenu from cursesmenu.items import FunctionItem,SubmenuItem, CommandItem from Eavesdrop import eavesdrop menu = CursesMenu('Eavedrop Options',"When You Have Finished Run Eavesdrop") command_item = CommandItem('Run Console Commands : ', 'Do Console Work Here: ') function_item = FunctionItem("Run Eavesdrop", eavesdrop()) submenu = CursesMenu("This is the submenu") submenu_item = SubmenuItem("Show a submenu", submenu, menu=menu) menu.append_item(command_item) menu.append_item(submenu_item) menu.append_item(function_item) menu.start() menu.join() menu.show()