Displaying wxFrame puts display back to sleep

Hi,

I’m new to the group and have a perplexing problem.

Platform: Windows 7 x64 running 32-bit Python 2.7 and wxPython 3.0.2.0

In a nutshell:

  1. Display a wxFrame

  2. Post a message to put the display to sleep

  3. Wake up the display manually

  4. When I display another wxFrame, it causes the display to go back to sleep :frowning:

Details:

I am running wxApp in a separate thread using the advice on the Wiki regarding locks, etc.

The full application is a systray app, so there is NO MAIN WINDOW. I am using wxPython just to display dialog boxes and messages.

I am signalling into the thread using a Queue to both display the frame and put the display to sleep

The attached example is gutted. To keep it small, I am displaying a splash message that closes with a timer, but displaying other dialogs and frames cause the same behavior.

It prints to the console when the code that puts the display to sleep is executed to ensure that it is not the culprit in causing the second undesired sleep.

This ONLY happens when:

A frame is displayed before sleep and then on the first frame displayed after the sleep. Displaying another frame after the one that causes the ‘resleep’ does not cause it again.

If you run the example, it will display a message and then put the display to sleep.

Make sure that you move your mouse or touch the keyboard promptly when the display goes to sleep or lengthen the amount of sleep in the main loop.

Then it will display another message. If the problem is reproducible, your display will go back to sleep.

To keep the main from terminating, it goes into a perpetual loop at the end that you will need to manually break.

I’ve been able to reproduce on VM’s running Win7 32bit and Win8.1 32bit and another real Win7 64.

Any help or insight would be greatly appreciated.

Ken

import wx
import threading
import Queue
import win32api, win32con

class SplashMessage ( wx.Frame ):

    def __init__( self, parent, msg, showtime):
        wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size( 381,116 ), style = wx.STAY_ON_TOP|wx.DOUBLE_BORDER )
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.close, self.timer)
        self.SetSizeHintsSz( wx.DefaultSize, wx.DefaultSize )
        bSizer1 = wx.BoxSizer( wx.VERTICAL )
        bSizer1.SetMinSize( wx.Size( 1,3 ) )
        self.m_staticText1 = wx.StaticText( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
        self.m_staticText1.Wrap( -1 )
        bSizer1.Add( self.m_staticText1, 0, wx.ALL|wx.EXPAND, 5 )
        self.m_staticText4 = wx.StaticText( self, wx.ID_ANY, ' %s ' % msg, wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE )
        self.m_staticText4.Wrap( -1 )
        self.m_staticText4.SetFont( wx.Font( wx.NORMAL_FONT.GetPointSize(), 70, 90, 92, False, wx.EmptyString ) )
        bSizer1.Add( self.m_staticText4, 0, wx.ALL|wx.EXPAND, 5 )
        self.m_staticText3 = wx.StaticText( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
        self.m_staticText3.Wrap( -1 )
        bSizer1.Add( self.m_staticText3, 0, wx.ALL|wx.EXPAND, 5 )
        self.SetSizer( bSizer1 )
        bSizer1.Fit(bSizer1.GetContainingWindow())
        self.Layout()
        self.Centre( wx.BOTH )
        self.showtime = showtime
        self.timer.Start(showtime)

    def close(self, event):
        self.Close()
        self.Destroy()

class wxAppThread(threading.Thread):

    def __init__(self):
        super(wxAppThread, self).__init__()
        self.messageq = Queue.Queue()
        self.resultq = Queue.Queue()
        self.abortrequest = threading.Event()
        self.app = None
        self.lock = threading.Lock()
        self.lock.acquire()

    def run(self):
        self.app = wx.App(0, useBestVisual=True)
        self.lock.release()
        while not self.abortrequest.is_set():
            try:
                message = self.messageq.get(True, 0.05)
                if message[0] == 1:
                    pass
                elif message[0] == 2:
                    self.lock.acquire()
                    splash = SplashMessage(None, message[1], message[2])
                    splash.Show()
                    self.lock.release()
                    self.app.MainLoop()
                elif message[0] == 3:
                    pass
                elif message[0] == 4:
                    self.lock.acquire()
                    print 'Sleeping Display'
                    x = win32api.PostMessage(win32con.HWND_BROADCAST, win32con.WM_SYSCOMMAND, win32con.SC_MONITORPOWER, 2)
                    self.lock.release()
            except Queue.Empty:
                continue
        wx.App_CleanUp()

    def abort(self):
        self.abortrequest.set()
        super(wxAppThread, self).join()

    def showSplashMessage(self, msg, showtime=1000):
        self.messageq.put([2, msg, showtime])

    def sleepDisplay(self):
        self.messageq.put([4,])

if __name__ == '__main__':
    import time
    app = wxAppThread()
    app.start()
    app.showSplashMessage('Test Starting')
    time.sleep(2)
    app.sleepDisplay()
    time.sleep(10)
    app.showSplashMessage('Testing Resleep', 1000)
    while True:
        time.sleep(.25)
    app.abort()
    pass

sleepproblemdemo.py (3.41 KB)

What I think is happening is that wx.App must receive the posted message and puts it in it’s own internal queue mechanism and then when the frame is shown, it somehow rebroadcasts the message by dispatching the message again.
The reason I think that it is at the app level is that even if I show a different form after the monitor power broadcast, the undesired monitor sleep occurs. If it not the app class that is responsible, I think that the app may provide a message queue to the new frame at the time it is shown. I also know from stepping through with a debugger that the sleep occurs during the Show() method, not during instantiation. Lastly, if I setup a callback that is registered for power messages, I can see that an additional broadcast message occurs when the Show() method is called.

Furthermore, I can prevent the problem by having a frame open during the power broadcast. I attached another demo with two tests - the original and then one where a frame is shown at the time of the power broadcast message.

If anyone can confirm this, I’ll go ahead and submit a bug ticket.

Thanks again.

sleepproblemdemo2.py (3.78 KB)

Ken Vives wrote:

What I think is happening is that wx.App must receive the posted
message and puts it in it's own internal queue mechanism and then when
the frame is shown, it somehow rebroadcasts the message by dispatching
the message again.

You do have an unusual architecture here, in that you are running
MainLoop in a thread that is not the main thread for the process, and
you are running MainLoop multiple times in a single thread. When
SplashWindow closes, the app sees that the final window has closed, and
sends a "quit" message, which causes MainLoop to exit. At that point,
wx thinks the app is finished.

However, that shouldn't cause this kind of problem. In fact, NO
component should ever be rebroadcasting these messages. It will be
interesting to chase this down.

The reason I think that it is at the app level is that even if I show
a different form after the monitor power broadcast, the undesired
monitor sleep occurs. If it not the app class that is responsible, I
think that the app may provide a message queue to the new frame at the
time it is shown.

Message queues are not associated with windows. Message queues are
associated with threads. Each thread gets one. The thing that's weird
in your case is that the thread's message queue should be closed down
with a WM_QUIT when the window closes. It is the WM_QUIT that causes
MainLoop to return.

I also know from stepping through with a debugger that the sleep
occurs during the Show() method, not during instantiation. Lastly, if
I setup a callback that is registered for power messages, I can see
that an additional broadcast message occurs when the Show() method is
called.

That does seem like a bug.

···

--
Tim Roberts, timr@probo.com
Providenza & Boekelheide, Inc.

Thanks for looking into this. I am not experienced enough to look at the wx source code, but in the hope that it helps, I’ve included a version that also prints the power broadcast messages to the console so that anyone can easily see what is occurring.

Ken

sleepproblemdemo3.py (9.01 KB)

I think I’ve figured this out at the bird’s eye level.

I seem to be able to prevent this from happening by calling SendMessageTimeout with a short timeout period (10 ms) and the flag SMTO_ABORTIFHUNG instead of PostMessage.

So what I think must be happening is that the first time around, the broadcast message ends up in the message queue for the thread associated with the window if PostMessage is used and it is only processed and redispatched the second time that the form is shown.

And when SendMessageTimeout is used, it sees that the thread is not responding and the message does not end up in the message queue for the thread.

Whether this is the expected and desired behavior or not is not clear to me. I would appreciate any opinions as to whether I should submit this as a potential bug or not worry about it since a workaround has been found.