How can I simultaneously read a string as input in one window while updating another window? This is for using curses in Python.
This would be useful e.g. for having a program that displays some output which can happen at any time, even when the user is typing. The idea being that the user can continue to type without having the currently entered, semi-complete string get truncated or cut in the middle due to sudden output from the program.
I've tried to use and modify the code from this question: Python/curses user input while updating screen
Since the code already exists in the other question I'm not posting it again here.
However, this code only reads a single character.
I can't just call getstr, as this will block and stop updating the other window until the user has entered a full string.
It might seem obvious how to solve it: Use threads. However, this is already warned against in the aforementioned question---curses doesn't play well with threads in Python, it seems.
Another "obvious" way to solve it would be to implement your own buffer, read one character at a time, implement basic editing, and keep using select to this in a non-blocking manner.
I am hoping that there is some way to read a string in a non-blocking manner while providing basic line editing (so I don't need to implement it myself!) using curses, as I can imagine this is a rather typical use case.
Here is an attempt using threading, modified from the aforementioned example code. The problem with this code is that it garbles the display. The display remains garbled until the window is resized, then it appears fine.
The code reads user input in one window (one thread), grabs a mutex, gives the string to some shared string, the other thread grabs the mutex, and displays it.
What is wrong with this code? What causes the garbled output? As soon as I remove the other thread manipulating curses (removing the getstr call), it stops being garbled.
#!/usr/bin/python
# -*- coding: iso-8859-1 -*-
import curses, curses.panel
import random
import time
import sys
import select
import threading
gui = None
class ui:
def __init__(self):
self.output_mutex = threading.Lock()
self.output_str = ""
self.stdscr = curses.initscr()
# curses.noecho()
curses.echo()
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, "Input: ")
# user_input = self.win3.getstr(8, 1, 20)
# self.win3.addstr(2, 1, "Output: %s" % user_input)
# self.pan1.hide()
def refresh(self):
curses.panel.update_panels()
self.win3.refresh()
self.win2.refresh()
self.win1.refresh()
def quit_ui(self):
curses.nocbreak()
self.stdscr.keypad(0)
curses.curs_set(1)
curses.echo()
curses.endwin()
print "UI quitted"
exit(0)
def worker_output(ui):
count = 0
running = 1
while True:
ui.win2.addstr(3, 1, str(count)+": "+str(int(round(random.random()*999))))
ui.win2.addstr(4, 1, str(running))
ui.output_mutex.acquire()
ui.win2.addstr(5, 1, ui.output_str)
ui.output_mutex.release()
ui.refresh()
time.sleep(0.1)
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):
threads = []
t = threading.Thread(target=worker_output, args=(self.ui,))
threads.append(t)
t.start()
user_input = ""
while True:
self.ui.win3.addstr(1, 1, "Input: ")
user_input = self.ui.win3.getstr(1, 8, 20)
self.ui.win3.addstr(2, 1, "Output: %s" % user_input)
# self.ui.refresh()
# self.ui.win3.clear()
self.ui.output_mutex.acquire()
self.ui.output_str = user_input
self.ui.output_mutex.release()
time.sleep(.2)
if __name__ == "__main__":
f = feeder()
f.run()