arenasys
arenasys

Reputation: 11

Setting window properties via SHGetPropertyStoreForWindow using only ctypes

Python developers need to set certain window properties for their applications to work correctly with the Windows taskbar (correct icon and grouping, and the ability to pin). In my case a PyQt5 application.

Its possible to use pywin32 to access the property store like so:

from win32com.propsys import propsys, pscon
import pythoncom
def setWindowProperties(hwnd, app_id, display_name, relaunch_path):
    propStore = propsys.SHGetPropertyStoreForWindow(hwnd, propsys.IID_IPropertyStore)
    propStore.SetValue(pscon.PKEY_AppUserModel_ID, propsys.PROPVARIANTType(app_id, pythoncom.VT_ILLEGAL))
    propStore.SetValue(pscon.PKEY_AppUserModel_RelaunchDisplayNameResource, propsys.PROPVARIANTType(display_name, pythoncom.VT_ILLEGAL))
    propStore.SetValue(pscon.PKEY_AppUserModel_RelaunchCommand, propsys.PROPVARIANTType(relaunch_path, pythoncom.VT_ILLEGAL))
    propStore.Commit()

hwnd = ... # Window handle (for Qt see QWindow::winId() -> int(engine.rootObjects()[0].winId()))
app_id = "CompanyName.ProductName.Version"
display_name = "ProductName"
relaunch_path = "C:\\path\\to\\exe"

setWindowProperties(hwnd, app_id, display_name, relaunch_path)

This theoretically can be done with only ctypes to avoid a dependency on pywin32, but information about implementing this is scarce.

Upvotes: 1

Views: 206

Answers (1)

arenasys
arenasys

Reputation: 11

import ctypes
from ctypes import wintypes

GUID = ctypes.c_ubyte * 16

class PROPERTYKEY(ctypes.Structure):
    _fields_ = [("fmtid", GUID),
                ("pid", wintypes.DWORD)]

class PROPVARIANT(ctypes.Structure):
    _fields_ = [("vt", wintypes.USHORT),
                ("wReserved1", wintypes.USHORT),
                ("wReserved2", wintypes.USHORT),
                ("wReserved3", wintypes.USHORT),
                ("pszVal", wintypes.LPWSTR)]

class IPropertyStoreVtbl(ctypes.Structure):
    _fields_ = [
        ('QueryInterface', ctypes.CFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p, ctypes.POINTER(GUID), ctypes.POINTER(ctypes.c_void_p))),
        ('AddRef', ctypes.CFUNCTYPE(ctypes.c_ulong, ctypes.c_void_p)),
        ('Release', ctypes.CFUNCTYPE(ctypes.c_ulong, ctypes.c_void_p)),
        ('GetCount', ctypes.CFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p, ctypes.POINTER(ctypes.c_ulong))),
        ('GetAt', ctypes.CFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p, ctypes.c_ulong, ctypes.POINTER(PROPERTYKEY))),
        ('GetValue', ctypes.CFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p, ctypes.POINTER(PROPERTYKEY), ctypes.POINTER(PROPVARIANT))),
        ('SetValue', ctypes.CFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p, ctypes.POINTER(PROPERTYKEY), ctypes.POINTER(PROPVARIANT))),
        ('Commit', ctypes.CFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p))
    ]

class IPropertyStore(ctypes.Structure):
    _fields_ = [('lpVtbl', ctypes.POINTER(IPropertyStoreVtbl))]

# {886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99}
IID_IPropertyStore = (GUID)(*bytearray.fromhex("eb8e6d88f28c46448d02cdba1dbdcf99"))
# {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}
PKEY_AppUserModel = (GUID)(*bytearray.fromhex("55284c9f799f394ba8d0e1d42de1d5f3"))

ctypes.windll.ole32.CoInitialize.restype = ctypes.HRESULT
ctypes.windll.ole32.CoInitialize.argtypes = [ctypes.c_void_p]
ctypes.windll.ole32.CoUninitialize.restype = None
ctypes.windll.ole32.CoUninitialize.argtypes = None
ctypes.windll.shell32.SHGetPropertyStoreForWindow.restype = ctypes.HRESULT
ctypes.windll.shell32.SHGetPropertyStoreForWindow.argtypes = [wintypes.HWND, ctypes.POINTER(GUID), ctypes.POINTER(ctypes.POINTER(IPropertyStore))]

def setWindowProperties(hwnd, app_id, display_name, relaunch_path):
    ctypes.windll.ole32.CoInitialize(None)

    prop_store = ctypes.POINTER(IPropertyStore)()
    result = ctypes.windll.shell32.SHGetPropertyStoreForWindow(int(hwnd), IID_IPropertyStore, ctypes.pointer(prop_store))
    if result != 0:
        return False
    functions = prop_store.contents.lpVtbl.contents
    
    success = False
    # PID of PKEY_AppUserModel_ID is 5, etc
    values = (5, app_id), (4, display_name), (2, relaunch_path)
    for pid, value in values:
        prop_key = PROPERTYKEY()
        prop_key.fmtid = PKEY_AppUserModel
        prop_key.pid = pid

        prop_variant = PROPVARIANT()
        prop_variant.vt = 31 # VT_LPWSTR
        prop_variant.pszVal = value

        result = functions.SetValue(prop_store, prop_key, prop_variant)
        if result != 0:
            break
    else:
        success = True
    
    if success:
        functions.Commit(prop_store)
        
    functions.Release(prop_store)
    ctypes.windll.ole32.CoUninitialize()
    return success

hwnd = ...
app_id = "CompanyName.ProductName.Version"
display_name = "ProductName"
relaunch_path = "C:\\path\\to\\exe"

setWindowProperties(hwnd, app_id, display_name, relaunch_path)

Upvotes: 0

Related Questions