Gentoo Forums
Gentoo Forums
Gentoo Forums
Quick Search: in
Python: curses and refresh on resize inside a thread
View unanswered posts
View posts from last 24 hours

 
Reply to topic    Gentoo Forums Forum Index Portage & Programming
View previous topic :: View next topic  
Author Message
musv
Advocate
Advocate


Joined: 01 Dec 2002
Posts: 3337
Location: de

PostPosted: Wed Jul 19, 2017 3:08 pm    Post subject: Python: curses and refresh on resize inside a thread Reply with quote

Hi again,

due to the competent help, I'm back again with a Python problem.

I'm playing around with pycurses to develop a graphical application, which runs in a terminal. The application is multithreaded. And so I put the ncurses routine also in a separate thread. But on resizing the terminal I get a lot of garbage instead a clean refresh.

Code:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import curses
import time
import threading

def update_time(screen,te):
    while 1:
        maxy,maxx = screen.getmaxyx()
        foo = "x: {} - y: {}".format(maxx,maxy)
        if te.is_set():
            curses.resizeterm(maxy,maxx)
            screen.clear()
        screen.hline(3,0,curses.ACS_HLINE,maxx)
        screen.move(4,0)
        screen.clrtoeol()
        screen.addstr(4,2, time.strftime("%a, %d %b %Y %H:%M:%S"))
        screen.addstr(4, maxx-len(foo)-2,"{}".format(foo))
        screen.hline(5,0,curses.ACS_HLINE,maxx)
        screen.move(6,0)
        screen.clrtoeol()
        screen.addstr(6,0,"{}".format("Resize" if te.is_set() else ""))
        screen.refresh()
        te.wait(timeout=1)
#        time.sleep(1)
        te.clear()

screen = curses.initscr()
curses.noecho()
curses.curs_set(0)
screen.keypad(True)

screen.addstr("This is a Sample Curses Script\n\n")
screen.refresh()

te = threading.Event()
clock = threading.Thread(target=update_time, args=(screen,te,))
clock.daemon = True
clock.start()
while 1:
    event = screen.getch()
    if event == ord("q"):
        break
    if event == curses.KEY_RESIZE:
        te.set()

curses.nocbreak()
screen.keypad(False)
curses.echo()
curses.endwin()


Expected behaviour: A resize of the terminal window repaints the display

With time.sleep(1) ist works mostly. But If I use thread_event.wait(timeout=1) I get garbage, when I resize the terminal window a few times.

What am I doing wrong? And how can I get it stable?
Back to top
View user's profile Send private message
wjb
l33t
l33t


Joined: 10 Jul 2005
Posts: 608
Location: Fife, Scotland

PostPosted: Wed Jul 19, 2017 10:04 pm    Post subject: Reply with quote

Is curses threadsafe? - it's acting not.

The thread logic as it is, is not good. It clears the event at the end of the loop, then tests it at the top of the loop - i.e. straight after its been cleared. If you want to know whether it timed out or was signalled you need to test the return value if the wait.

I wouldn't assume its going to be happy about the screen variable being used by the two threads. Possibly use a mutex around rhw blocks of references to screen to stop simultaneous access. Or move all the GUI code into the main thread and do non-GUI work in the other thread(s).
Back to top
View user's profile Send private message
Hu
Moderator
Moderator


Joined: 06 Mar 2007
Posts: 21633

PostPosted: Thu Jul 20, 2017 1:10 am    Post subject: Re: Python: curses and refresh on resize inside a thread Reply with quote

As wjb says, that event management looks questionable. You should clear the event immediately after discovering it set, then do the required work. That produces two possible orderings:
    1. Thread update_time detects the event is set.
    2. Thread update_time clears the event.
    3. Thread main sets the event again.

    1. Thread update_time detects the event is set.
    2. Thread main sets the event again.
    3. Thread update_time clears the event. The second set is lost, but the redraw is already queued.

With your current implementation, you can get a bad ordering:
  1. Thread update_time detects the event is set.
  2. Thread update_time reads the window dimensions and redraws accordingly.
  3. Thread update_time sleeps.
  4. The user resizes the window. Thread main sets the event.
  5. Thread update_time wakes and clears the event.
  6. The loop starts again, and the event is clear, even though the user resized the window after the last redraw, so another redraw is needed.
As part of that change, you need to avoid rereading the event state. You should create a local variable initialized according to whether the event was set, then use that variable for later tests (such as your adding of the "Resize" string).

If you need more help, please describe exactly what goes wrong. What is the nature of the garbage?
Back to top
View user's profile Send private message
musv
Advocate
Advocate


Joined: 01 Dec 2002
Posts: 3337
Location: de

PostPosted: Thu Jul 20, 2017 12:32 pm    Post subject: Reply with quote

Thanks, got it:

Code:
def update_time(screen,te):
    while 1:
        maxy,maxx = screen.getmaxyx()
        foo = "x: {} - y: {}".format(maxx,maxy)
        if te.is_set():
            te.clear()
            curses.resizeterm(maxy,maxx)
            screen.clear()
        screen.hline(3,0,curses.ACS_HLINE,maxx)
        screen.move(4,0)
        screen.clrtoeol()
        screen.addstr(4,2, time.strftime("%a, %d %b %Y %H:%M:%S"))
        screen.addstr(4, maxx-len(foo)-2,"{}".format(foo))
        screen.hline(5,0,curses.ACS_HLINE,maxx)
        screen.move(6,0)
        screen.clrtoeol()
        screen.addstr(6,0,"{}".format("Resize" if te.is_set() else ""))
        screen.refresh()
        te.wait(timeout=10)


works perfectly. I moved the event clearing into the if-clause.

wjb wrote:
I wouldn't assume its going to be happy about the screen variable being used by the two threads.

Yes, I'll change that too.

Thanks a lot for your ideas.
Back to top
View user's profile Send private message
Hu
Moderator
Moderator


Joined: 06 Mar 2007
Posts: 21633

PostPosted: Fri Jul 21, 2017 1:18 am    Post subject: Reply with quote

I suggested the local variable specifically to prevent the bug you show introduced in the latest version. ;) You left unchanged the ternary expression that chooses either "Resize" or "", but now that the event is cleared inside the conditional block, you will only ever get the string "Resize" if you hit a very tight race where the window is resized a second time after the clear, but before the screen.addstr call.
Back to top
View user's profile Send private message
Display posts from previous:   
Reply to topic    Gentoo Forums Forum Index Portage & Programming All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum