Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Python curses example
import sys,os
import curses
def draw_menu(stdscr):
k = 0
cursor_x = 0
cursor_y = 0
# Clear and refresh the screen for a blank canvas
stdscr.clear()
stdscr.refresh()
# Start colors in curses
curses.start_color()
curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE)
# Loop where k is the last character pressed
while (k != ord('q')):
# Initialization
stdscr.clear()
height, width = stdscr.getmaxyx()
if k == curses.KEY_DOWN:
cursor_y = cursor_y + 1
elif k == curses.KEY_UP:
cursor_y = cursor_y - 1
elif k == curses.KEY_RIGHT:
cursor_x = cursor_x + 1
elif k == curses.KEY_LEFT:
cursor_x = cursor_x - 1
cursor_x = max(0, cursor_x)
cursor_x = min(width-1, cursor_x)
cursor_y = max(0, cursor_y)
cursor_y = min(height-1, cursor_y)
# Declaration of strings
title = "Curses example"[:width-1]
subtitle = "Written by Clay McLeod"[:width-1]
keystr = "Last key pressed: {}".format(k)[:width-1]
statusbarstr = "Press 'q' to exit | STATUS BAR | Pos: {}, {}".format(cursor_x, cursor_y)
if k == 0:
keystr = "No key press detected..."[:width-1]
# Centering calculations
start_x_title = int((width // 2) - (len(title) // 2) - len(title) % 2)
start_x_subtitle = int((width // 2) - (len(subtitle) // 2) - len(subtitle) % 2)
start_x_keystr = int((width // 2) - (len(keystr) // 2) - len(keystr) % 2)
start_y = int((height // 2) - 2)
# Rendering some text
whstr = "Width: {}, Height: {}".format(width, height)
stdscr.addstr(0, 0, whstr, curses.color_pair(1))
# Render status bar
stdscr.attron(curses.color_pair(3))
stdscr.addstr(height-1, 0, statusbarstr)
stdscr.addstr(height-1, len(statusbarstr), " " * (width - len(statusbarstr) - 1))
stdscr.attroff(curses.color_pair(3))
# Turning on attributes for title
stdscr.attron(curses.color_pair(2))
stdscr.attron(curses.A_BOLD)
# Rendering title
stdscr.addstr(start_y, start_x_title, title)
# Turning off attributes for title
stdscr.attroff(curses.color_pair(2))
stdscr.attroff(curses.A_BOLD)
# Print rest of text
stdscr.addstr(start_y + 1, start_x_subtitle, subtitle)
stdscr.addstr(start_y + 3, (width // 2) - 2, '-' * 4)
stdscr.addstr(start_y + 5, start_x_keystr, keystr)
stdscr.move(cursor_y, cursor_x)
# Refresh the screen
stdscr.refresh()
# Wait for next input
k = stdscr.getch()
def main():
curses.wrapper(draw_menu)
if __name__ == "__main__":
main()
@objarni
Copy link

objarni commented Mar 8, 2019

To get less flickering, use stdscr.erase() instead of stdscr.clear(), see this S.O answer.

@josdyr
Copy link

josdyr commented Mar 27, 2019

This is exactly what I need, cheers 👍

@christophergeiger3
Copy link

christophergeiger3 commented Aug 23, 2019

Wow, great example, thank you! 🎉 😄

@claymcleod
Copy link
Author

claymcleod commented Aug 23, 2019

Wow, sorry for not responding all, I actually have never received a notification on this from GitHub until the last comment. You’re all very welcome, glad this code could help you out!

@acrandal
Copy link

acrandal commented Oct 21, 2019

This is an absolutely gorgeous, relatively minimal example for starting with ncurses in Python! Thank you for assembling it.

@agribot2
Copy link

agribot2 commented Nov 4, 2019

Worked first time, out of the box ! Thanks for providing this example.
I'm using an RPI, using the pre-installed Geany debugger. The output was correctly displayed in the Geany terminal window.

@eder78
Copy link

eder78 commented Nov 6, 2019

Thank you, very useful. :)

Copy link

ghost commented Dec 10, 2019

This is an excellent example! Very simple and easy for demonstrating Curses. Thanks!

@klimach
Copy link

klimach commented Dec 31, 2019

thx a lot 👍
Very helpful example

@SenorRodriguez
Copy link

SenorRodriguez commented Feb 15, 2020

Neat and clean. Exactly what i was looking for.

@batera1963
Copy link

batera1963 commented Apr 11, 2020

A really helpful example. Many thanks !

@DuaneNielsen
Copy link

DuaneNielsen commented Apr 17, 2020

Loving this example. My thanks!

@Iyxan23
Copy link

Iyxan23 commented Jun 11, 2020

Nice example, Thank You!

@Gim6626
Copy link

Gim6626 commented Jun 12, 2020

Tried to do same using npyscreen - failed after couple of hours.
Googled "python curses tutorial" and second link is this.
Brilliant!
Thank you very much!

@ThatXliner
Copy link

ThatXliner commented Jul 29, 2020

Instead of doing ord(‘some char’), you can do k = chr(stdscr.getch()) ( sorry I’m on mobile)

@rel1c
Copy link

rel1c commented Oct 8, 2020

This was exactly what I was looking for! Thank you for the great example.

@JuDelCo
Copy link

JuDelCo commented Oct 21, 2020

Perfect, thanks !

@mouchh
Copy link

mouchh commented Nov 5, 2020

Thanks!

Got blocked with a stupid mistake, I named my file curses.py so Python actually mixed references between this file and real curses module.
AttributeError: module 'curses' has no attribute 'wrapper'
Just rename your file with something different! In case it helps anyone.

@RyuuzakiJulio
Copy link

RyuuzakiJulio commented Nov 15, 2020

Can courses receive Japanese text input?

@gitcabezon
Copy link

gitcabezon commented Jan 7, 2021

Very excited to find this! However it suffers the same problem my testing does on the Mac (probably others) -- resizing the terminal to <7 lines or <43 cols causes it to crash.

@brunoGenX
Copy link

brunoGenX commented Jan 21, 2021

Very nice example. thank you very much!

@diVineProportion
Copy link

diVineProportion commented Jan 25, 2021

Can courses receive Japanese text input?

maybe the rich module can help you out

@pjfarleyiii
Copy link

pjfarleyiii commented Feb 23, 2021

I also thank Clay for his very nice code example. I took it on myself to improve it a little by adding code to more cleanly clear out the keycode line(s) and to get all three curses "key read" functions to show the results for getch(), get_wch() and getkey() all for the same single keystroke entered.

Copy pasted below if anyone is interested.

Edit: After seeing unexpected failures of the getkey() code on Linux systems using the ncursesw library (though it never failed on Windows systems using the PDCurses library underneath) , I asked for some help on the bug-ncurses mailing list, and discovered that the unget_wch() function does not know what to do with a non-character (i.e., arrow keys, Home, End, F1, etc.). Instead, if get_wch() returns an integer representing one of those "non-printing" keys, you must use ungetch() to put that key back in order for a subsequent getkey() or get_wch() to be able to retrieve it again.

I have updated the code below to reflect that technique.

Peter

import curses

def draw_menu(stdscr):
    k = 0
    kw = 0
    kk = ""
    height, width = stdscr.getmaxyx()
    min_x_keystr = width - 1
    min_x_wkeystr = width - 1
    min_x_kkeystr = width - 1
    cursor_x = 0
    cursor_y = 0

    # Clear and refresh the screen for a blank canvas
    stdscr.clear()
    stdscr.refresh()

    # Start colors in curses
    curses.start_color()
    curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK)
    curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK)
    curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE)

    # Loop where k is the last character pressed
    while (k != ord('q')):

        # Initialization
        #stdscr.clear()
        height, width = stdscr.getmaxyx()

        if k == curses.KEY_DOWN:
            cursor_y = cursor_y + 1
        elif k == curses.KEY_UP:
            cursor_y = cursor_y - 1
        elif k == curses.KEY_RIGHT:
            cursor_x = cursor_x + 1
        elif k == curses.KEY_LEFT:
            cursor_x = cursor_x - 1

        cursor_x = max(0, cursor_x)
        cursor_x = min(width-1, cursor_x)

        cursor_y = max(0, cursor_y)
        cursor_y = min(height-1, cursor_y)

        # Declaration of strings
        title = "Curses example"[:width-1]
        subtitle = "Written by Clay McLeod"[:width-1]
        statusbarstr = "Press 'q' to exit | STATUS BAR | Pos: {}, {}".format(cursor_x, cursor_y)
        if k == 0:
            keystr = "No key press detected..."[:width-1]
        else:
            keystr = "Last key pressed: {}".format(k)[:width-1]
        if kw == 0:
            wkeystr = "No wide key press detected..."[:width-1]
        else:
            wkeystr = "Last wide key pressed: {}".format(kw)[:width-1]
        if kk == 0:
            kkeystr = "No 'key' press detected..."[:width-1]
        else:
            kkeystr = "Last 'key' pressed: {}".format(kk)[:width-1]

        # Centering calculations
        start_x_title = int((width // 2) - (len(title) // 2) - len(title) % 2)
        start_x_subtitle = int((width // 2) - (len(subtitle) // 2) - len(subtitle) % 2)
        start_x_keystr = int((width // 2) - (len(keystr) // 2) - len(keystr) % 2)
        start_x_wkeystr = int((width // 2) - (len(wkeystr) // 2) - len(wkeystr) % 2)
        start_x_kkeystr = int((width // 2) - (len(kkeystr) // 2) - len(kkeystr) % 2)
        start_y = int((height // 2) - 2)
        min_x_keystr = min(min_x_keystr, start_x_keystr)
        min_x_wkeystr = min(min_x_wkeystr, start_x_wkeystr)
        min_x_kkeystr = min(min_x_kkeystr, start_x_kkeystr)

        # Rendering some text
        whstr = "Width: {}, Height: {}".format(width, height)
        stdscr.addstr(0, 0, whstr, curses.color_pair(1))

        # Render status bar
        stdscr.attron(curses.color_pair(3))
        stdscr.addstr(height-1, 0, statusbarstr)
        stdscr.addstr(height-1, len(statusbarstr), " " * (width - len(statusbarstr) - 1))
        stdscr.attroff(curses.color_pair(3))

        # Turning on attributes for title
        stdscr.attron(curses.color_pair(2))
        stdscr.attron(curses.A_BOLD)

        # Rendering title
        stdscr.addstr(start_y, start_x_title, title)

        # Turning off attributes for title
        stdscr.attroff(curses.color_pair(2))
        stdscr.attroff(curses.A_BOLD)

        # Print rest of text
        stdscr.addstr(start_y + 1, start_x_subtitle, subtitle)
        stdscr.addstr(start_y + 3, (width // 2) - 2, '-' * 4)
        stdscr.addstr(start_y + 5, start_x_keystr, keystr)
        stdscr.addstr(start_y + 7, start_x_wkeystr, wkeystr)
        stdscr.addstr(start_y + 9, start_x_kkeystr, kkeystr)
        stdscr.move(cursor_y, cursor_x)

        # Refresh the screen
        stdscr.refresh()

        # Wait for next input
        k = stdscr.getch()

        # Get wide-char version
        curses.ungetch(k)
        kw = stdscr.get_wch()

        # Get key version
        if isinstance(kw, str):
            curses.unget_wch(kw)
        else:
            curses.ungetch(kw)
        kk = stdscr.getkey()

        # Clear the keystroke text
        stdscr.move(start_y + 5, min_x_keystr)
        stdscr.clrtoeol()
        stdscr.move(start_y + 7, min_x_wkeystr)
        stdscr.clrtoeol()
        stdscr.move(start_y + 9, min_x_kkeystr)
        stdscr.clrtoeol()
        stdscr.refresh()

def main():
    curses.wrapper(draw_menu)

if __name__ == "__main__":
    main()

@MegDuck
Copy link

MegDuck commented Apr 20, 2021

To get less flickering, use stdscr.erase() instead of stdscr.clear(), see this S.O answer.

Thank you!

@HyoMiYing
Copy link

HyoMiYing commented May 20, 2021

Thank you for the code. When ran in the terminal, it does good to represent some of the major functionalities of the curses library 👍

@cole-wilson
Copy link

cole-wilson commented May 21, 2021

Thanks!

@arnu515
Copy link

arnu515 commented Jul 11, 2021

Thank you!

@BansheePrime
Copy link

BansheePrime commented Jul 29, 2021

Thank you for great example.

@Shasless
Copy link

Shasless commented Jul 31, 2021

thanks

@lucarhee
Copy link

lucarhee commented Oct 26, 2021

thank you.

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