Last active
March 1, 2025 00:12
-
-
Save davesteele/8838f03e0594ef11c89f77a7bca91206 to your computer and use it in GitHub Desktop.
A Python asyncio curses template
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.
Learn more about bidirectional Unicode characters
# | |
# This file supports http://davesteele.github.io/development/2021/08/29/python-asyncio-curses/ | |
# | |
import asyncio | |
from abc import ABC, abstractmethod | |
from curses import ERR, KEY_RESIZE, curs_set, wrapper | |
import _curses | |
class Display(ABC): | |
def __init__(self, stdscr: "_curses._CursesWindow"): | |
self.stdscr = stdscr | |
self.done: bool = False | |
@abstractmethod | |
def make_display(self) -> None: | |
pass | |
@abstractmethod | |
def handle_char(self, char: int) -> None: | |
pass | |
def set_exit(self) -> None: | |
self.done = True | |
async def run(self) -> None: | |
curs_set(0) | |
self.stdscr.nodelay(True) | |
self.make_display() | |
while not self.done: | |
char = self.stdscr.getch() | |
if char == ERR: | |
await asyncio.sleep(0.1) | |
elif char == KEY_RESIZE: | |
self.make_display() | |
else: | |
self.handle_char(char) | |
class MyDisplay(Display): | |
def make_display(self) -> None: | |
msg1 = "Resize at will" | |
msg2 = "Press 'q' to exit" | |
maxy, maxx = self.stdscr.getmaxyx() | |
self.stdscr.erase() | |
self.stdscr.box() | |
self.stdscr.addstr( | |
int(maxy / 2) - 1, int((maxx - len(msg1)) / 2), msg1 | |
) | |
self.stdscr.addstr( | |
int(maxy / 2) + 1, int((maxx - len(msg2)) / 2), msg2 | |
) | |
self.stdscr.refresh() | |
def handle_char(self, char: int) -> None: | |
if chr(char) == "q": | |
self.set_exit() | |
async def display_main(stdscr): | |
display = MyDisplay(stdscr) | |
await display.run() | |
def main(stdscr) -> None: | |
return asyncio.run(display_main(stdscr)) | |
if __name__ == "__main__": | |
wrapper(main) |
This was very helpful....thanks!
Note also that the asyncio.sleep(..)
call is vital here too: without that, basically 100% of the time is spent in curses and the reactor / proactor won't have any chance to "do network stuff"
"without that"
👍 thanks (edited)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The nodelay() polling of getch() is necessary. Otherwise, e.g. KEY_RESIZE is never returned from the blocking executor call.