Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sending WM_COPYDATA with Python 3

I'm trying to write a python script that will interface with my copy of stickies. I'm having trouble with how Python interacts with the WM_COPYDATA struct, and unfortunately I haven't been able to find many examples online.

Using the code:

import struct
import win32con
import win32gui

import struct, array
int_buffer = array.array("L", [0])
char_buffer = array.array('b', 'do new sticky')
int_buffer_address = int_buffer.buffer_info()[0]
char_buffer_address, char_buffer_size = char_buffer.buffer_info

copy_struct = struct.pack("pLp",
int_buffer_address,
char_buffer_size, char_buffer_address)

hwnd = win32gui.FindWindow("ZhornSoftwareStickiesMain", None)

win32gui.SendMessage(w, WM_COPYDATA, hwnd, copy_struct)

I get the following error:

C:\Users\%userprofile%\Desktop>python sender.py
Traceback (most recent call last):
  File "sender.py", line 7, in <module>
    char_buffer = array.array('b', 'do new sticky')
TypeError: an integer is required

I can't seem to figure out why I'm getting such an error. Any ideas?

Edit: Some partially working code

import struct
import win32con
import win32gui
import struct, array


int_buffer = array.array("L", [0])
char_buffer = array.array('b', b"do manage open")
int_buffer_address = int_buffer.buffer_info()[0]

# Add () to buffer_info to call it.
char_buffer_address, char_buffer_size = char_buffer.buffer_info()

# Need P  type for the addresses.
copy_struct = struct.pack("PLP",int_buffer_address,char_buffer_size, char_buffer_address)

hwnd = win32gui.FindWindow(None, "ZhornSoftwareStickiesMain")
win32gui.SendMessage(hwnd, win32con.WM_COPYDATA, None, copy_struct)
like image 225
Jacobm001 Avatar asked Feb 15 '23 17:02

Jacobm001


2 Answers

In Python 3, strings are Unicode. Try using a byte string:

>>> import array
>>> array.array('b',b'do new sticky')
array('b', [100, 111, 32, 110, 101, 119, 32, 115, 116, 105, 99, 107, 121])

There are a few other changes as well:

import struct, array
int_buffer = array.array("L", [0])
char_buffer = array.array('b', b'do new sticky')
int_buffer_address = int_buffer.buffer_info()[0]

# Add () to buffer_info to call it.
char_buffer_address, char_buffer_size = char_buffer.buffer_info()

# Need P  type for the addresses.
copy_struct = struct.pack("PLP",int_buffer_address,char_buffer_size, char_buffer_address)

I didn't install the software, but I think the next lines should be:

hwnd = win32gui.FindWindow(None, "ZhornSoftwareStickiesMain")
win32gui.SendMessage(hwnd, win32con.WM_COPYDATA, None, copy_struct)

Edit

I installed the software. Initially I couldn't get any commands to work, but the trick that wasn't clear from the API documentation is that each string must start with api:

import struct
import win32con
import win32gui
import array

char_buffer = array.array('B', b'api do new sticky hello, world')
char_buffer_address, char_buffer_size = char_buffer.buffer_info()
copy_struct = struct.pack("PLP", 12345, char_buffer_size, char_buffer_address)
hwnd = win32gui.FindWindow(None, "ZhornSoftwareStickiesMain")
win32gui.SendMessage(hwnd, win32con.WM_COPYDATA, None, copy_struct)

Stickies sample

like image 183
Mark Tolonen Avatar answered Feb 26 '23 17:02

Mark Tolonen


This is much easier to do with ctypes. This code has been tested with a simple Delphi application that receives and displays the string that is sent via WM_COPYDATA.

import win32con
import ctypes
import ctypes.wintypes

FindWindow = ctypes.windll.user32.FindWindowW
SendMessage = ctypes.windll.user32.SendMessageW

class COPYDATASTRUCT(ctypes.Structure):
    _fields_ = [
        ('dwData', ctypes.wintypes.LPARAM),
        ('cbData', ctypes.wintypes.DWORD),
        ('lpData', ctypes.c_wchar_p) 
        #formally lpData is c_void_p, but we do it this way for convenience
    ]

hwnd = FindWindow('TheNameOfMyWindowClass', None)
cds = COPYDATASTRUCT()
cds.dwData = 0
str = 'boo'
cds.cbData = ctypes.sizeof(ctypes.create_unicode_buffer(str))
cds.lpData = ctypes.c_wchar_p(str)

SendMessage(hwnd, win32con.WM_COPYDATA, 0, ctypes.byref(cds))

This assumes that the recipient expects a UTF-16 encoded payload. If the recipient is expecting ANSI encoded payload then you need to use this variant.

import win32con
import ctypes
import ctypes.wintypes

FindWindow = ctypes.windll.user32.FindWindowW
SendMessage = ctypes.windll.user32.SendMessageW

class COPYDATASTRUCT(ctypes.Structure):
    _fields_ = [
        ('dwData', ctypes.wintypes.LPARAM),
        ('cbData', ctypes.wintypes.DWORD),
        ('lpData', ctypes.c_char_p)
        #formally lpData is c_void_p, but we do it this way for convenience
]

hwnd = FindWindow('TheNameOfMyWindowClass', None)
cds = COPYDATASTRUCT()
cds.dwData = 0
str = b'boo'
cds.cbData = ctypes.sizeof(ctypes.create_string_buffer(str))
cds.lpData = ctypes.c_char_p(str)

SendMessage(hwnd, win32con.WM_COPYDATA, 0, ctypes.byref(cds))

Now, looking at the Stickies link that you posted, you need to use the ANSI variant. If you want to receive notification, you'll need to pass a window handle in you SendMessage call instead of 0 as above.

like image 43
David Heffernan Avatar answered Feb 26 '23 17:02

David Heffernan