Search code examples
pythonwinapipysidepython-c-api

How to deal with PyCapsule type inside Python


I'm trying to pass the object from

QtGui.QWidget.effectiveWinId() 

to

win32gui.SetWindowLong()

effectiveWinId() is returning:

<capsule object NULL at 0x027C9BF0>
<class 'PyCapsule'>

and SetWindowLong() expects a PyHANDLE (doc says it "should" accept an integer also)

TypeError: The object is not a PyHANDLE object

So my question is how do I grab the value out of a PyCapsule object and or check if its NULL? It seems PyCapsule is all internal API to the C code.

Also I found this bug that does something similar to what I want with Python's 2.X PyCObject which doesn't exist in Python 3.x here: http://srinikom.github.io/pyside-bz-archive/show_bug.cgi?id=523#c18


Solution

  • Okay I managed to figure it out:

    # ...
    capsule = self.effectiveWinId()
    ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
    ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object, ctypes.c_char_p]
    handle = ctypes.pythonapi.PyCapsule_GetPointer(capsule, None)
    win32gui.SetWindowLong(handle, win32con.GWL_WNDPROC, self.new_window_procedure)
    # ...
    

    Here is a python class to deal with Overriding the win32 window procedure:

    import win32con
    import win32gui
    import win32api
    import ctypes
    import pywintypes
    
    def convert_capsule_to_int(capsule):
        ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
        ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object, ctypes.c_char_p]
        return ctypes.pythonapi.PyCapsule_GetPointer(capsule, None)
    
    class WindowProcedure(object):
        self.handle_WM_DESTROY = False
        def __init__(self, handle):
            if isinstance(handle, int) or isinstance(handle, type(pywintypes.HANDLE())):
                self.handle = handle
            else:
                self.handle = convert_capsule_to_int(handle)
            self.old_proc = win32gui.GetWindowLong(self.handle, win32con.GWL_WNDPROC)
            if not self.old_proc:
                raise RuntimeError("Failed to set/get window procedure!") 
            if not win32gui.SetWindowLong(self.handle, win32con.GWL_WNDPROC, self.new_window_procedure):
                raise RuntimeError("Failed to set/get window procedure!")
    
        def handle_old_procedure(self, hwnd, msg, wparam, lparam):
            # For some reason the executable would hang after a QtGui.QWidget exit
            # so I'm forcing it here if self.handle_WM_DESTROY is true
            if msg == win32con.WM_DESTROY and self.handle_WM_DESTROY:
                win32gui.DestroyWindow(hwnd)
                return 0
            return win32gui.CallWindowProc(self.old_proc, hwnd, msg, wparam, lparam)