Python / curses user input on screen refresh
I am currently coding an application UI with python / curses and I was wondering if it would be possible to ask the user to press keys (cbreak mode) to hide or show some panels or windows while the UI is constantly updating.
I read the official python docs on curses and made several attempts, but even using cbreak mode and non-blocking input mode (nodelay) I could not get it to work properly (I was able to get the user but at the cost of blocking the UI which I don't need) ...
So my question is simple, is this possible? And if so, how?
I may have read the docs wrong, but I haven't found alternative docs or examples on this.
I was thinking about making the application multithreaded, but I haven't seen how it can help me in this case.
Thank you for your help, advice, or a pointer to a detailed document.
EDIT:
Finally, I got the following multi-threaded code, but it is not satisfactory. The UI loads as it should, but after the update, the display is screwed out.
I also don't understand why curses.panel.hidden () returns False while the panel in question is hidden. It seems that updating the window associated with the panel displays the panel or something. I am truly lost at this moment!
import threading
import curses, curses.panel
import random
import time
gui = None
class ui:
def __init__(self):
self.feeder = feeder(self)
self.stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
curses.curs_set(0)
self.stdscr.keypad(1)
self.win1 = curses.newwin(10, 50, 0, 0)
self.win1.border(0)
self.pan1 = curses.panel.new_panel(self.win1)
self.win2 = curses.newwin(10, 50, 0, 0)
self.win2.border(0)
self.pan2 = curses.panel.new_panel(self.win2)
self.win3 = curses.newwin(10, 50, 12, 0)
self.win3.border(0)
self.pan3 = curses.panel.new_panel(self.win3)
self.win1.addstr(1, 1, "Window 1")
self.win2.addstr(1, 1, "Window 2")
self.win3.addstr(1, 1, "Press 's' to switch windows or 'q' to quit.")
self.pan1.hide()
self.win1.refresh()
curses.panel.update_panels()
self.win2.refresh()
self.feeder.start()
def ask(self):
while True:
self.win3.addstr(5,1, "Hidden = win1: "+str(self.pan1.hidden())+\
"win2:"+str(self.pan2.hidden()), 0)
self.win3.refresh()
k = self.win3.getkey()
if k == 's':
if self.pan1.hidden():
self.pan2.hide()
self.pan1.show()
self.win1.refresh()
self.win3.addstr(2, 1, "Pan1 restored")
else:
self.pan1.hide()
self.pan2.show()
self.win2.refresh()
self.win3.addstr(2, 1, "Pan2 restored")
self.win3.addstr(5,1, "Hidden = win1: "+\
str(self.pan1.hidden())+\
" win2:"+str(self.pan2.hidden()), 0)
elif k == 'q':
break
self.quit_ui()
def quit_ui(self):
self.feeder.stop()
curses.nocbreak()
self.stdscr.keypad(0)
curses.curs_set(1)
curses.echo()
curses.endwin()
exit(0)
def display_data(self, window, data):
window.addstr(3, 1, data, 0)
class feeder(threading.Thread):
# Fake U.I feeder
def __init__(self, ui):
super(feeder, self).__init__()
self.running = False
self.ui = ui
self.count = 0
def stop(self):
self.running = False
def run(self):
self.running = True
self.feed()
def feed(self):
while self.running:
self.ui.win1.addstr(3, 1, str(self.count)+\
": "+str(int(round(random.random()*9999))))
self.ui.win1.addstr(4, 1, str(self.running))
self.ui.win2.addstr(3, 1, str(self.count)+\
": "+str(int(round(random.random()*9999))))
self.ui.win2.addstr(4, 1, str(self.running))
time.sleep(0.5)
self.count += 1
if __name__ == "__main__":
gui = ui()
gui.ask()
source to share
I finally managed to get it working by reading one byte from sys.stdin in a double while loop and then avoiding using another stream. The following code may not work on MS Windows and since I am not a professional developer it may not be optimized or throw fatal bugs, but this is just a rough draft to make me understand how it works (although comments are welcome). Special thanks to Pavel Griffiths who guided me to sys.stdin.
#!/usr/bin/python
# -*- coding: iso-8859-1 -*-
import curses, curses.panel
import random
import time
import sys
import select
gui = None
class ui:
def __init__(self):
self.stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
curses.curs_set(0)
self.stdscr.keypad(1)
self.win1 = curses.newwin(10, 50, 0, 0)
self.win1.border(0)
self.pan1 = curses.panel.new_panel(self.win1)
self.win2 = curses.newwin(10, 50, 0, 0)
self.win2.border(0)
self.pan2 = curses.panel.new_panel(self.win2)
self.win3 = curses.newwin(10, 50, 12, 0)
self.win3.border(0)
self.pan3 = curses.panel.new_panel(self.win3)
self.win1.addstr(1, 1, "Window 1")
self.win2.addstr(1, 1, "Window 2")
self.win3.addstr(1, 1, "Press 's' to switch windows or 'q' to quit.")
self.pan1.hide()
def refresh(self):
curses.panel.update_panels()
self.win2.refresh()
self.win1.refresh()
def switch_pan(self):
if self.pan1.hidden():
self.pan2.bottom()
self.pan2.hide()
self.pan1.top()
self.pan1.show()
else:
self.pan1.bottom()
self.pan1.hide()
self.pan2.top()
self.pan2.show()
self.refresh()
def quit_ui(self):
curses.nocbreak()
self.stdscr.keypad(0)
curses.curs_set(1)
curses.echo()
curses.endwin()
print "UI quitted"
exit(0)
class feeder:
# Fake U.I feeder
def __init__(self):
self.running = False
self.ui = ui()
self.count = 0
def stop(self):
self.running = False
def run(self):
self.running = True
self.feed()
def feed(self):
while self.running :
while sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
line = sys.stdin.read(1)
if line.strip() == "q":
self.stop()
self.ui.quit_ui()
break
elif line.strip() == "s":
self.ui.switch_pan()
self.ui.win1.addstr(3, 1, str(self.count)+\
": "+str(int(round(random.random()*999))))
self.ui.win1.addstr(4, 1, str(self.running))
self.ui.win2.addstr(3, 1, str(self.count)+\
": "+str(int(round(random.random()*999))))
self.ui.win2.addstr(4, 1, str(self.running))
self.ui.refresh()
time.sleep(0.1)
self.count += 1
if __name__ == "__main__":
f = feeder()
f.run()
source to share