MM window-restore failure with external script

This forum is for questions / discussions regarding development of addons / tweaks for MediaMonkey for Windows 4.

Moderators: Gurus, Addon Administrators

mcow
Posts: 834
Joined: Sun Sep 21, 2008 9:35 pm
Location: Cupertino, California

MM window-restore failure with external script

Post by mcow »

I've long been plagued by this problem, but I've finally figured out a simple way to reproduce.

When I'm running MM with a Python script that hooks in to the events of the SDBApplication object, and then I minimize MM, it does not respond to the clicks on the Windows taskbar. I can force the window to appear by double-clicking, twice, on the tray icon.

I'm seeing this on MMW 1627, altho it's been happening for a long time. Either of the scripts I posted here will reproduce the problem. I'm running in 64-bit Windows 7. I am using the Pulse skin.

Steps to reproduce:
0) On the test system, have an installed version of (32-bit) Python and the pywin32 extension that matches it. I'm using python 2.7.
1) Run MM 4.1.xx; just have the window open, no other action needed.
2) In a command-line window, run the script.
3) Minimize MM window.
4) Try to restore window by clicking MM button in taskbar - program window does not restore, no matter how many clicks or double-clicks are made on the button. Each attempt yields a system beep.
5) Double-click MM tray icon - taskbar button disappears, but tray icon persists.
6) Double-click MM tray icon again - MM window appears, along with taskbar button.
(Script will automatically stop when MM exits.)

Subsequent minimizations of the same session don't result in the same symptom.

Minimizing before running the script does result in the same symptom.

EDIT:
Grayed-out text: that is not true (or, is no longer true, with 4.1.0.1656 and for some time): subsequent minimizations do result in the same symptom. Occasionally, the first or later minimizations will restore after one or two single-clicks on the taskbar button, but usually not.

Also, I have determined that the problem only exists when the script instantiates an event object. A regular COM interface object (Dispatch(), instead of DispatchWithEvents()) does not cause the problem.

And, the problem occurs when running unskinned as well.
Last edited by mcow on Sat Aug 31, 2013 2:28 pm, edited 2 times in total.
mcow
Posts: 834
Joined: Sun Sep 21, 2008 9:35 pm
Location: Cupertino, California

Re: MM window-restore failure with external script

Post by mcow »

Here's a script that easily reproduces the problem.

EDIT: Added parentheses to the print statements so the script will run under either Python2 or Python3.

Code: Select all

# monitor player events from MediaMonkey.
# note: once started, script does not exit until MM is shut down.
import win32com.client
import pythoncom
import time

quit = False

class MMEventHandlers():

    def __init__(self):
        pass

    def OnShutdown(self):
        global quit
        print('>>> SHUTDOWN >>>')
        quit = True

    def OnIdle(self):
        # if OnIdle method is not defined, DebugView shows repeated messages:
        #    Failed calling of COM event: Member not found
        pass


def monitor():
    # running the script will start MM if it's not already running
    SDB = win32com.client.DispatchWithEvents('SongsDB.SDBApplication', MMEventHandlers)
    print('** monitor started')
    while not quit:
        pythoncom.PumpWaitingMessages()
        time.sleep(0.2)

    print('** monitor stopped')

if __name__ == '__main__':
    monitor()
Last edited by mcow on Fri Jun 21, 2019 4:21 pm, edited 1 time in total.
mcow
Posts: 834
Joined: Sun Sep 21, 2008 9:35 pm
Location: Cupertino, California

Re: MM window-restore failure with external script

Post by mcow »

I did open a ticket for this: LNF-102973

I don't suppose this problem is going to get any attention for the 4.1 release. I wish it would. I have an extension for MMW I'd like to publish, but this bug makes my extension annoying to run.

I think there is something fundamentally wrong here in the event system. I suspect that if I could turn off getting OnIdle events, the problem would not occur. I don't use that event; I added a handler for it just so I wouldn't keep seeing the invalid log message about "no such handler" or whatever it was.

Since the external program needs to pump messages anyway, it doesn't make sense to send OnIdle -- the program can do its own idle processing in the message loop.
mcow
Posts: 834
Joined: Sun Sep 21, 2008 9:35 pm
Location: Cupertino, California

Re: MM window-restore failure with external script

Post by mcow »

I would like to revisit this issue. I've been working lately to create a script that runs under Node.js, similar to what I did a few years ago in Python. This project has required that I write my own C++ add-on to Node in order to access the COM library, but it does work. And I find that the same issue—MM window will not restore while an evented object is open in an external process—occurs when I connect to the dispatch object with an

Code: Select all

ISDBApplicationEvents
object.

I am going to edit the script included above so that it will work under Python3 as well as Python2. Also note that PyWin32 installed via pip: it doesn't require a separate download, so it's much easier to set up. Just install Python and then

Code: Select all

%PYTHON_HOME%\Scripts\pip.exe install pywin32
.

I think the problem here is fundamental and I don't expect it be solved for MM4, but I still would like that the COM event handling for MM5 (which, I think, should offer the same functionality). However, I can't test the MM5 behavior under Python, because the Python typelib parser crashes on the MM5 registry entries. I'll open another report in the MM5 forum about that.
mcow
Posts: 834
Joined: Sun Sep 21, 2008 9:35 pm
Location: Cupertino, California

Re: MM window-restore failure with external script

Post by mcow »

Checking in here again to report that I've found a solution to this very long-standing problem. In the script above, and in all the Python scripts that I've posted which intercept MediaMonkey's COM events, I have a loop that, basically, executes

Code: Select all

pythoncom.PumpWaitingMessages()
and then sleeps 200 msec. I never liked the 200 msec sleep, but I didn't know a better way. Today I happened to see a bit of documentation on the web, which led me to an example script, which led me to the solution: use the Windows API MsgWaitForMultipleObject() to wait until messages are available before looping around to call PumpWaitingMessages() again.

Code: Select all

# monitor player events from MediaMonkey.  print event flag and player state info for each
# note: once started, script does not exit until MM is shut down.
import win32api
import win32com.client
import win32event
import pythoncom
import time

quit = False

class MMEventHandlers():

    def __init__(self):
        pass

    def OnShutdown(self):
        global quit
        print('>>> SHUTDOWN >>>')
        if quit is not None:
            win32event.SetEvent(quit)


def monitor():
    # running the script will start MM if it's not already running
    SDB = win32com.client.DispatchWithEvents('SongsDB.SDBApplication', MMEventHandlers)
 
    global quit
    quit = win32event.CreateEvent(None, False, False, "QUIT");
    # running the script will start MM if it's not already running
    SDB = win32com.client.DispatchWithEvents('SongsDB.SDBApplication', MMEventHandlers)
 
    print("** monitor started")
    while True:
        # required by this script because no other message loop running
        # if the app has its message loop (i.e., has a Windows UI), then
        # the events will arrive with no additional handling
        pythoncom.PumpWaitingMessages()
        rc = win32event.MsgWaitForMultipleObjects((quit,), False, win32event.INFINITE, win32event.QS_ALLINPUT)
        if rc == win32event.WAIT_OBJECT_0:      # our quit event was signaled
            break
        elif rc == win32event.WAIT_FAILED:      # (hope this works! (DWORD)0xFFFFFFFF)
            print("unexpected loop termination")
            break
        # otherwise, a message is now available to pump, loop around
 
    win32api.CloseHandle(quit)
    quit = None
   
if __name__ == '__main__':
    monitor()
This probably helps nobody but me, but after years of complaining, I couldn't just keep it to myself.
Post Reply