Skip to content Skip to sidebar Skip to footer

Getting Rid Of Control Character With Ncurses And Threaded Class

I'm trying to develop some kind of terminal user interface in python3 with threading and ncurse and some weird characters appear. Based on the answer in this post: Threading with P

Solution 1:

I answer my own question, but I think it will be hepfull for others : The trick is in the ncurses wrapper which have is own thread . So If i want the lock to work I have to put it in the main loop . Below is the code modified :

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

class GenericTUI(threading.Thread):

    def __init__(self,textmode=False, messageBoxSize=10, logger=logging.getLogger()):
        threading.Thread.__init__(self)
        self.keyPressedList = list()
        self.alive = True
        self.myStdscr = None
        self.title = ""
        self.messageList = list()
        self.messageBoxSize = messageBoxSize
        self.subTitle = ""
        self.priceInfo = ""
        self.progInfo = ""
        self.textMode = textmode
        self.logger = logger
        self.lock = threading.Lock()
        self.refreshFlag = True


    def run(self):
        if self.textMode :
            with open('/tmp/genericTUI.command','w+') as f:
                # command file in text mode
                pass
            while self.alive :
                print("Program :"+ self.title)
                print("sub"+self.subTitle)
                print("Prices : "+self.priceInfo)
                print("ProgInfos :"+self.progInfo)
                for m in self.messageList :
                    print(m)
                with open('/tmp/genericTUI.command','r+') as f:
                    c = f.read(1)
                    if  c:
                        self.keyPressedList.append(c)
                        f.truncate(0)
                sleep(5)
        else :        
            curses.wrapper(self.main)


    def main(self,stdscr):   
        # color definition
        if curses.has_colors():
            curses.start_color()
            curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE)
            curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_RED)
            curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_YELLOW)
            curses.init_pair(4, curses.COLOR_BLACK, curses.COLOR_BLUE)
            curses.init_pair(5, curses.COLOR_BLACK, curses.COLOR_GREEN)

        ## NE SURTOUT PAS METTRE keypad(false) GENERE DES CARACTERES AU HASARD .. DANGEREUX
        self.myStdscr = stdscr
        self.myStdscr.nodelay(True)
        self.myStdscr.keypad(True)
        self.myStdscr.box()

        counter = 0
        while self.alive:
            try : 
                key =  self.myStdscr.getkey()
                #workaround to avoid multi thread escape character
                if re.match('[A-Z_\+\-\*/]', key) :
                    self.keyPressedList.append(key)
            except Exception as e:
                ## le nodelay rend l interface reactive mais ,le getkey genere un noinput error
                ## si pas de touche pressée d ou le pass 
                pass
            '''    
            screen update in the mail loop
            to be able to thread lock :
            https://stackoverflow.com/questions/46773577/threading-with-python-curses-giving-me-weird-characters
            '''
            # determine la taille de  l ecran
            max_y, max_x = self.myStdscr.getmaxyx() 
            # reecriture
            if self.refreshFlag:
                try :
                    with self.lock :
                        self.myStdscr.clear()
                        for x in range(max_x):
                            self.myStdscr.addch(0,x,curses.ACS_HLINE)
                        self.myStdscr.addstr(1, 2, "Program :"+ self.title, curses.color_pair(1) )
                        self.myStdscr.addstr(2, 2,  self.subTitle)
                        for x in range(max_x):
                            self.myStdscr.addch(3,x,curses.ACS_HLINE)
                        self.myStdscr.addstr(4, 2, "Prices : "+self.priceInfo, curses.color_pair(2))
                        for x in range(max_x):
                            self.myStdscr.addch(5,x,curses.ACS_HLINE)
                        self.myStdscr.addstr(6, 2, "ProgInfos :"+self.progInfo, curses.color_pair(5))
                        for x in range(max_x):
                            self.myStdscr.addch(7,x,curses.ACS_HLINE)
                        indent =0 
                        for m in self.messageList :
                            self.myStdscr.addstr(8+indent, 3,m)
                            indent+=1
                        for y in range(max_y):
                            self.myStdscr.addch(y,0,curses.ACS_VLINE)

                        self.myStdscr.refresh()
                except Exception as e:
                    self.logger.error(repr(e))
                    self.myStdscr.clear()
                finally: 
                    self.refreshFlag = False

            sleep(0.1)
            counter +=1

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

    def stop(self):
        self.alive = False

    def updatePriceInfo(self,priceDict,maj=False):
        result = " ".join(str(key) +":"+ str(value)+"|" for key, value in priceDict.items())
        self.priceInfo = result
        self.refreshFlag = maj


    def updateTitle(self,title, maj=False):
        self.title = str(title)
        self.refreshFlag = maj


    def updateSubTitle(self,subtitleDict, maj=False):
        result = " ".join(str(key) +":"+ str(value)+"|" for key, value in subtitleDict.items())
        self.subTitle = str(result)
        self.refreshFlag = maj

    def updateProgInfo(self,messDict, maj=False):
        result = " ".join(str(key) +":"+ str(value)+"|" for key, value in messDict.items())
        self.progInfo = result
        self.refreshFlag = maj

    def addMessage(self,mess, maj=False):
        self.messageList.append(repr(mess))
        if len(self.messageList) > self.messageBoxSize : self.messageList.pop(0)
        self.refreshFlag = maj

    def getValue(self, mess="Enter Value: (hit Ctrl-G to send)"):
        with self.lock :
            self.myStdscr.addstr(0, 0, mess)

            editwin = curses.newwin(1,7, 2,1)
            rectangle(self.myStdscr, 1,0, 1+1+1, 1+7+1)
            box = Textbox(editwin)
            box.stripspaces = True
            self.myStdscr.refresh()
            # Let the user edit until Ctrl-G is struck.
            box.edit()

        # Get resulting contents
        return(box.gather().strip())

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('time %s'%str(datetime.now() ))
        k = testGUI.getKeyPressed()
        if k is not None:
            if k=='Q' :
                alive = False
            elif k=='M' :
                mess = testGUI.getValue()
                testGUI.addMessage(mess,maj=True)
            else :
                testGUI.addMessage('unknown key %s'%k , maj=True)
        sleep(0.1)
    testGUI.stop()
    ```
you may also notice there is another lock in the getvalue function to avoid the main thread to erase the textbox .

Hope this help others.


Post a Comment for "Getting Rid Of Control Character With Ncurses And Threaded Class"