I have a reworked python curses code with two 'threads' basically. They are not real threads - one main subwidow processing function, and the second, a different subwindow processing function, executing on the timer. And I ran into an interesting effect:
What can be causing this effect? Is there any way to avoid this effect other than checking the return string?
#!/usr/bin/env python
# Simple code to show timer updates
import curses
import os, signal, sys, time, traceback
import math
UPDATE_INTERVAL = 2
test_bed_windows = []
global_count = 0
def signal_handler(signum, frame):
global test_bed_windows
global global_count
if (signum == signal.SIGALRM):
# Update all the test bed windows
# restart the timer.
signal.alarm(UPDATE_INTERVAL)
global_count += 1
for tb_window in test_bed_windows:
tb_window.addstr(1, 1, "Upd: {0}.{1}".format(global_count, test_bed_windows.index(tb_window)))
tb_window.refresh()
else:
print("unexpected signal: {0}".format(signam))
pass
def main(stdscr):
# window setup
screen_y, screen_x = stdscr.getmaxyx()
stdscr.box()
# print version
version_str = " Timer Demo v:0 "
stdscr.addstr(0, screen_x - len(version_str) - 1, version_str)
stdscr.refresh()
window = stdscr.subwin(screen_y-2,screen_x-2,1,1)
for i in range(3):
subwin = window.derwin(3,12,1,2 + (15*i))
test_bed_windows.append(subwin)
subwin.box()
subwin.refresh()
signal.signal(signal.SIGALRM, signal_handler)
signal.alarm(UPDATE_INTERVAL)
# Output the prompt and wait for the input:
window.addstr(12, 1, "Enter Q/q to exit\n")
window.refresh()
the_prompt = "Enter here> "
while True:
window.addstr(the_prompt)
window.refresh()
curses.echo()
selection = window.getstr()
curses.noecho()
if selection == '':
continue
elif selection.upper() == 'Q':
break
else:
window.addstr("Entered: {0}".format(selection))
window.refresh()
if __name__ == '__main__':
curses.wrapper(main)
I suspect it's not the writing to the subwindow that's causing the getstr() to return an empty string, but rather the alarm signal itself. (Writing to a window from within an signal handler may not be well defined either, but that's a separate issue.)
The C library Curses, which Python's curses module is often built on top of, will return from most of its blocking input calls when any signal (other than a few it handles internally) comes in. In C there's a defined API for the situation (the function returns -1 and sets errno to EINTER).
The Python module says it will raise an exception if a curses function returns with an error. I'm not sure why it is not doing so in this case.
Edit: A possible solution is to use a more programmer-friendly console UI library than curses. Urwid appears (in my brief browsing of the manual) to support event-driven UI updates (including updates on a timer) while simultaneously handling keyboard input. It may be easier to learn that than to deal with the sketchy and poorly documented interactions between signals and curses.
Edit: from the man page for the getch():
The behavior of getch and friends in the presence of handled signals is unspecified in the SVr4 and XSI Curses documentation. Under historical curses implementations, it varied depending on whether the operating system's implementation of handled signal receipt interrupts a read(2) call in progress or not, and also (in some implementations) depending on whether an input timeout or non-blocking mode has been set.
Programmers concerned about portability should be prepared for either of two cases: (a) signal receipt does not interrupt getch; (b) signal receipt interrupts getch and causes it to return ERR with errno set to EINTR. Under the ncurses implementation, handled signals never interrupt getch.
I have tried using getch instead of getstr and it does return -1 on a signal. That (negative return value) would solve this problem if it was implemented by getstr. So now the option are (1) writing your own getstr with error handling or (2) using Urwid. Could this be a bug for Python library?