0

I'm trying to develop a multithreaded program with a TUI interface . Basically I have a main Loop deciding what to do and some task (like the TUI or reading data from a queue and processing it ) are running in separate thread . My TUI is using curses and is a thread derived class that look like this (i removed non essential code for clarity) :

import threading
from time import sleep
import curses
import logging
from curses.textpad import Textbox, rectangle
from datetime import datetime
import re
from curses import panel
import os
import sys


class GenericTUI(threading.Thread):

    def __init__(self, logger=logging.getLogger()):
        threading.Thread.__init__(self,name="genericTUI" + str(os.getpid()), daemon=True)
        self.keyPressedList = list()
        self.alive = True
        self._myStdscr = None
        self.title = ""
        self.logger = logger
        self.lock = threading.Lock()


    def run(self):
            curses.wrapper(self.main)
            curses.nocbreak()
            curses.echo()
            curses.noraw()
            sys.exit(0)


    def main(self,stdscr):
        self._myStdscr = stdscr
        self._myStdscr.nodelay(True)
        self._myStdscr.keypad(True)
        self._myStdscr.box()
        while self.alive:
            sleep(0.4)
            try : 
                key =  self._myStdscr.getkey()
                if re.match('[A-Z_\+\-\*/]', key):
                    self.keyPressedList.append(key)

            except Exception as e:
               ## ignoring no key presssed 
               pass

            try :
                with self.lock :
                    self._myStdscr.clear()
                    self._myStdscr.addstr(1, 2, str(datetime.now())+" "+ sys.argv[0] +" "+self.title )
                    ### printing other things 
                    self._myStdscr.refresh()
            except Exception as e:
                self.logger.error(e, exc_info=True)
                continue

        self._myStdscr.clear()
        self._myStdscr.keypad(0)


    def getKeyPressed(self):
        if self.keyPressedList :
            return self.keyPressedList.pop()
        else :
            return None

    def stop(self):
        self.alive = False


    def updateTitle(self,title):
        with self.lock : self.title = title


if __name__ == "__main__":
    ## the main is used for some test when the lib is called directly
    testGUI = GenericTUI()
    alive = True
    testGUI.logger.addHandler(logging.StreamHandler())
    testGUI.logger.setLevel(logging.DEBUG)
    testGUI.start()
    while alive :
        testGUI.updateTitle('title %s'%str(datetime.now() ))
        k = testGUI.getKeyPressed()
        if k is not None:
            if k=='Q' :
                alive = False
            else :
                testGUI.addMessage('unknown key %s'%k , maj=True)
        sleep(0.1)

the main loop of my program instantiate and start a genericTUI object and get keypressed from it or set value to display.

But when i quit the program , my terminal is in a funny state even if I used the curses wrapper function or try to reset manually using curses.nocbreak() and others.

I can't figure what I did wrong ? Am i mistaken using curses inside a thread ??

Chmick Chmick
  • 43
  • 1
  • 6

2 Answers2

0

I found the answer but puting it in the comment section makes it hard to read . So I also put it here : the curses wapper does not like the thread in daemon mode :

so the following code works fine and restore the terminal in a correct state :


class GenericTUI(threading.Thread):

    def __init__(self, logger=logging.getLogger()):
        threading.Thread.__init__(self,name="genericTUI" + str(os.getpid()), daemon=False)
        self.keyPressedList = list()

and in the stop function adding a curses.endwin() helps :

    def stop(self):
        curses.endwin()
        self.alive = False

hope it helps other

Chmick Chmick
  • 43
  • 1
  • 6
  • What is calling self.stop()? – Wes Modes Nov 23 '21 at 01:09
  • @WesModes Not sure to understand your question . Here is an example how i use it : ``` testGUI = GenericTUI() alive = True testGUI.start() while alive: try : k = testGUI.getKeyPressed() if k is not None: if k== 'Q': testGUI.stop() alive = False ``` sorry after multiples edit i can't get it to format my code right – Chmick Chmick Nov 23 '21 at 17:32
  • I think you'll have to add that to your answer to communicate how to use it. Formatting not allowed in comments which are meant to be brief. – Wes Modes Dec 03 '21 at 19:06
0

@WesModes I use the stop to have a clean way to stop the TUI. The endwin is cleaning the screen . example :

testGUI=GenericTUI()
alive = True
testGUI.start() 
while alive: 
try : 
   k = testGUI.getKeyPressed() 
   if k is not None: 
     if k== 'Q': 
     testGUI.stop() 
     alive = False 
Chmick Chmick
  • 43
  • 1
  • 6