Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Receiving WM_COPYDATA in Python

I am trying to read from Python the WM_COPYDATA message some applications (I'm trying with Spotify) send to WindowsLiveMessenger to update the "What I'm listening to..." phrase.

From what I have been able to find, WM_COPYDATA messages come in a COPYDATASTRUCT with the following structure:

  • dwData in our case 0x547 so that it access the listening now feature
  • cbData with the length of the string received
  • lpData with a pointer to the string itself, may include Unicode characters

The string should have the following format: \0Music\0status\0format\0song\0artist\0album\0 as stated by ListeningNowTracker

What we receive in a WM_COPYDATA event is a pointer for lParam that contains the COPYDATASTRUCT.

I started tinkering with pywin32 functions and I remembered that they do not accept Unicode characters from past experience, then I switched to ctypes. Despite this being an almost new world in Python for me, I tried with POINTER() and all I got was unknown objects for me or access violations.

I think that the code should create a COPYDATASTRUCT:

class CopyDataStruct(Structure):
    _fields_ = [('dwData', c_int),
                ('cbData', c_int),
                ('lpData', c_void_p)]

Then make the lParam be a pointer to that structure, get the string pointer from lpData and finally get the string with ctypes.string_at(lpData,cbData).

Any tips?

UPDATE 1

The WM_COPYDATA event is received by a hidden window built with win32gui just for this purpose. The copydata event is connected to a function called OnCopyData and this is its header:
def OnCopyData(self, hwnd, msg, wparam, lparam):
The values the function delivers are correct as compared with the ones from the Spy++ messages log.

UPDATE 2

This should be close to what I want, but gives a NULL pointer error.

class CopyDataStruct(ctypes.Structure):
    _fields_ = [('dwData', c_int),
                ('cbData', c_int),
                ('lpData', c_wchar_p)]

PCOPYDATASTRUCT = ctypes.POINTER(CopyDataStruct)
pCDS = ctypes.cast(lparam,  PCOPYDATASTRUCT)
print ctypes.wstring_at(pCDS.contents.lpData)
like image 819
Chiva Avatar asked Mar 09 '11 17:03

Chiva


1 Answers

I wrote the following trivial win32gui app:

import win32con, win32api, win32gui, ctypes, ctypes.wintypes

class COPYDATASTRUCT(ctypes.Structure):
    _fields_ = [
        ('dwData', ctypes.wintypes.LPARAM),
        ('cbData', ctypes.wintypes.DWORD),
        ('lpData', ctypes.c_void_p)
    ]
PCOPYDATASTRUCT = ctypes.POINTER(COPYDATASTRUCT)

class Listener:

    def __init__(self):
        message_map = {
            win32con.WM_COPYDATA: self.OnCopyData
        }
        wc = win32gui.WNDCLASS()
        wc.lpfnWndProc = message_map
        wc.lpszClassName = 'MyWindowClass'
        hinst = wc.hInstance = win32api.GetModuleHandle(None)
        classAtom = win32gui.RegisterClass(wc)
        self.hwnd = win32gui.CreateWindow (
            classAtom,
            "win32gui test",
            0,
            0, 
            0,
            win32con.CW_USEDEFAULT, 
            win32con.CW_USEDEFAULT,
            0, 
            0,
            hinst, 
            None
        )
        print self.hwnd

    def OnCopyData(self, hwnd, msg, wparam, lparam):
        print hwnd
        print msg
        print wparam
        print lparam
        pCDS = ctypes.cast(lparam, PCOPYDATASTRUCT)
        print pCDS.contents.dwData
        print pCDS.contents.cbData
        print ctypes.wstring_at(pCDS.contents.lpData)
        return 1

l = Listener()
win32gui.PumpMessages()

I then sent the window a WM_COPYDATA message from another app (written in Delphi):

Text := 'greetings!';
CopyData.cbData := (Length(Text)+1)*StringElementSize(Text);
CopyData.lpData := PWideChar(Text);
SendMessage(hwnd, WM_COPYDATA, Handle, NativeInt(@CopyData));

The output was:

461584
461584
74
658190
2620592
42
22
greetings!

So it seems that it works trivially, pretty much as you coded it.

The only thing that I can think of is that the text in Spotify's COPYDATASTRUCT is not null-terminated. You should be able to check that quite easily by reading out the data. Make use of the cbData member.

like image 107
David Heffernan Avatar answered Sep 28 '22 07:09

David Heffernan