diff --git a/comtypes/shelllink.py b/comtypes/shelllink.py index 022604d8..901e81a5 100644 --- a/comtypes/shelllink.py +++ b/comtypes/shelllink.py @@ -1,5 +1,6 @@ from ctypes import ( POINTER, + Structure, byref, c_char_p, c_int, @@ -8,12 +9,21 @@ create_string_buffer, create_unicode_buffer, ) -from ctypes.wintypes import DWORD, MAX_PATH, WIN32_FIND_DATAA, WIN32_FIND_DATAW +from ctypes.wintypes import ( + BYTE, + DWORD, + MAX_PATH, + USHORT, + WIN32_FIND_DATAA, + WIN32_FIND_DATAW, +) from typing import TYPE_CHECKING, Literal from comtypes import COMMETHOD, GUID, HRESULT, CoClass, IUnknown if TYPE_CHECKING: + from ctypes import _Pointer + from comtypes import hints # type: ignore @@ -42,8 +52,15 @@ HOTKEYF_EXT = 0x08 HOTKEYF_SHIFT = 0x01 -# fake these... -ITEMIDLIST = c_int + +class SHITEMID(Structure): + _fields_ = [("cb", USHORT), ("abID", BYTE * 1)] + + +class ITEMIDLIST(Structure): + _fields_ = [("mkid", SHITEMID)] + + LPITEMIDLIST = LPCITEMIDLIST = POINTER(ITEMIDLIST) @@ -134,8 +151,8 @@ class IShellLinkA(IUnknown): if TYPE_CHECKING: - def GetIDList(self) -> hints.Incomplete: ... - def SetIDList(self, pidl: hints.Incomplete) -> hints.Incomplete: ... + def GetIDList(self) -> _Pointer[ITEMIDLIST]: ... + def SetIDList(self, pidl: _Pointer[ITEMIDLIST]) -> hints.Hresult: ... def SetDescription(self, pszName: bytes) -> hints.Incomplete: ... def SetWorkingDirectory(self, pszDir: bytes) -> hints.Hresult: ... def SetArguments(self, pszArgs: bytes) -> hints.Hresult: ... @@ -269,8 +286,8 @@ class IShellLinkW(IUnknown): if TYPE_CHECKING: - def GetIDList(self) -> hints.Incomplete: ... - def SetIDList(self, pidl: hints.Incomplete) -> hints.Incomplete: ... + def GetIDList(self) -> _Pointer[ITEMIDLIST]: ... + def SetIDList(self, pidl: _Pointer[ITEMIDLIST]) -> hints.Hresult: ... def SetDescription(self, pszName: str) -> hints.Incomplete: ... def SetWorkingDirectory(self, pszDir: str) -> hints.Hresult: ... def SetArguments(self, pszArgs: str) -> hints.Hresult: ... diff --git a/comtypes/test/test_shelllink.py b/comtypes/test/test_shelllink.py index 112801a5..2726e808 100644 --- a/comtypes/test/test_shelllink.py +++ b/comtypes/test/test_shelllink.py @@ -1,13 +1,25 @@ +import struct import tempfile import unittest as ut +from ctypes import WinDLL, addressof, cast, create_string_buffer, string_at +from ctypes.wintypes import BOOL from pathlib import Path import comtypes.hresult from comtypes import GUID, CoCreateInstance, shelllink +from comtypes.malloc import CoGetMalloc, _CoTaskMemFree from comtypes.persist import IPersistFile +from comtypes.shelllink import LPITEMIDLIST as PIDLIST_ABSOLUTE CLSID_ShellLink = GUID("{00021401-0000-0000-C000-000000000046}") +_shell32 = WinDLL("shell32") + +# https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-ilisequal +_ILIsEqual = _shell32.ILIsEqual +_ILIsEqual.argtypes = [PIDLIST_ABSOLUTE, PIDLIST_ABSOLUTE] +_ILIsEqual.restype = BOOL + class Test_IShellLinkA(ut.TestCase): def setUp(self): @@ -66,6 +78,39 @@ def test_set_and_get_icon_location(self): self.assertEqual(icon_path, str(self.src_file).encode("utf-8")) self.assertEqual(index, 1) + def test_set_and_get_idlist(self): + # Create a manual PIDL for testing. + # In reality, the `abID` portion contains Shell namespace identifiers. + # (e.g. file system item IDs, special folder tokens, virtual folder + # GUIDs, etc.) + # These IDs are referenced/used by Shell folders to identify and locate + # specific items in the namespace. + data = b"\xde\xad\xbe\xef" # dummy test data (meaningless in real use). + cb = len(data) + 2 + # ITEMIDLIST format: + # - little-endian ('<') + # - cb as 16-bit unsigned integer ('H') + # - data bytes of length ('{len(data)}s') + # - terminator as 16-bit unsigned integer ('H') + raw_pidl = struct.pack(f"