|
# slideshow.py |
|
import sys |
|
import ctypes |
|
from ctypes import wintypes |
|
|
|
# --- GUID definitions --------------------------------------------------------- |
|
|
|
class GUID(ctypes.Structure): |
|
_fields_ = [ |
|
("Data1", wintypes.DWORD), |
|
("Data2", wintypes.WORD), |
|
("Data3", wintypes.WORD), |
|
("Data4", ctypes.c_ubyte * 8), |
|
] |
|
|
|
def __init__(self, d1, d2, d3, d4_0, d4_1, d4_2, d4_3, d4_4, d4_5, d4_6, d4_7): |
|
super().__init__( |
|
d1, |
|
d2, |
|
d3, |
|
(ctypes.c_ubyte * 8)( |
|
d4_0, d4_1, d4_2, d4_3, d4_4, d4_5, d4_6, d4_7 |
|
) |
|
) |
|
|
|
# Use IID / CLSID from the original C++ code |
|
IID_IDesktopWallpaper = GUID( |
|
0xB92B56A9, 0x8B55, 0x4E14, |
|
0x9A, 0x89, 0x01, 0x99, 0xBB, 0xB6, 0xF9, 0x3B |
|
) |
|
|
|
CLSID_DesktopWallpaper = GUID( |
|
0xC2CF3110, 0x460E, 0x4FC1, |
|
0xB9, 0xD0, 0x8A, 0x1C, 0x0C, 0x9C, 0xC4, 0xBD |
|
) |
|
|
|
# Common COM IID_IUnknown |
|
IID_IUnknown = GUID( |
|
0x00000000, 0x0000, 0x0000, |
|
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 |
|
) |
|
|
|
# --- Windows API / COM initialization ---------------------------------------- |
|
|
|
ole32 = ctypes.OleDLL("ole32") |
|
shell32 = ctypes.OleDLL("shell32") |
|
|
|
# HRESULT CoInitializeEx(LPVOID pvReserved, DWORD dwCoInit); |
|
COINIT_APARTMENTTHREADED = 0x2 |
|
RPC_E_CHANGED_MODE = 0x80010106 |
|
|
|
ole32.CoInitializeEx.restype = ctypes.c_long # HRESULT |
|
ole32.CoInitializeEx.argtypes = [ctypes.c_void_p, ctypes.c_ulong] |
|
|
|
ole32.CoUninitialize.restype = None |
|
ole32.CoUninitialize.argtypes = [] |
|
|
|
# HRESULT CoCreateInstance(REFCLSID, LPUNKNOWN, DWORD, REFIID, LPVOID*); |
|
CLSCTX_ALL = 0x17 |
|
|
|
ole32.CoCreateInstance.restype = ctypes.c_long |
|
ole32.CoCreateInstance.argtypes = [ |
|
ctypes.POINTER(GUID), # rclsid |
|
ctypes.c_void_p, # pUnkOuter |
|
ctypes.c_ulong, # dwClsContext |
|
ctypes.POINTER(GUID), # riid |
|
ctypes.POINTER(ctypes.c_void_p), # ppv |
|
] |
|
|
|
# HRESULT SHParseDisplayName( |
|
# PCWSTR pszName, IBindCtx *pbc, PIDLIST_ABSOLUTE *ppidl, |
|
# SFGAOF sfgaoIn, SFGAOF *psfgaoOut); |
|
SHParseDisplayName = shell32.SHParseDisplayName |
|
SHParseDisplayName.restype = ctypes.c_long # HRESULT |
|
SHParseDisplayName.argtypes = [ |
|
wintypes.LPCWSTR, # pszName |
|
ctypes.c_void_p, # pbc |
|
ctypes.POINTER(ctypes.c_void_p), # ppidl (PIDLIST_ABSOLUTE*) |
|
ctypes.c_ulong, # sfgaoIn (SFGAOF) |
|
ctypes.POINTER(ctypes.c_ulong) # psfgaoOut (SFGAOF*) |
|
] |
|
|
|
# HRESULT SHCreateShellItemArrayFromIDLists( |
|
# UINT cidl, PCIDLIST_ABSOLUTE *rgpidl, IShellItemArray **ppv); |
|
SHCreateShellItemArrayFromIDLists = shell32.SHCreateShellItemArrayFromIDLists |
|
SHCreateShellItemArrayFromIDLists.restype = ctypes.c_long # HRESULT |
|
SHCreateShellItemArrayFromIDLists.argtypes = [ |
|
wintypes.UINT, # cidl |
|
ctypes.POINTER(ctypes.c_void_p), # rgpidl |
|
ctypes.POINTER(ctypes.c_void_p) # ppv (IShellItemArray**) |
|
] |
|
|
|
# CoTaskMemFree |
|
ole32.CoTaskMemFree.restype = None |
|
ole32.CoTaskMemFree.argtypes = [ctypes.c_void_p] |
|
|
|
# --- Minimal IDesktopWallpaper interface definition (only SetSlideshow) ---- |
|
|
|
class IDesktopWallpaperVtbl(ctypes.Structure): |
|
# The vtable must list all IUnknown methods + all methods in IDesktopWallpaper. |
|
# |
|
# IDesktopWallpaper methods in order (from MSDN): |
|
# 0: QueryInterface |
|
# 1: AddRef |
|
# 2: Release |
|
# 3: SetWallpaper |
|
# 4: GetWallpaper |
|
# 5: GetMonitorDevicePathAt |
|
# 6: GetMonitorDevicePathCount |
|
# 7: GetMonitorRECT |
|
# 8: SetBackgroundColor |
|
# 9: GetBackgroundColor |
|
# 10: SetPosition |
|
# 11: GetPosition |
|
# 12: SetSlideshow |
|
# 13: GetSlideshow |
|
# 14: SetSlideshowOptions |
|
# 15: GetSlideshowOptions |
|
# 16: AdvanceSlideshow |
|
# 17: GetStatus |
|
# 18: Enable |
|
# |
|
# We only need SetSlideshow, but the method order cannot change, |
|
# so dummy entries must be included. |
|
# |
|
_fields_ = [ |
|
# IUnknown |
|
("QueryInterface", ctypes.WINFUNCTYPE( |
|
ctypes.c_long, # HRESULT |
|
ctypes.c_void_p, ctypes.POINTER(GUID), ctypes.POINTER(ctypes.c_void_p) |
|
)), |
|
("AddRef", ctypes.WINFUNCTYPE(ctypes.c_ulong, ctypes.c_void_p)), |
|
("Release", ctypes.WINFUNCTYPE(ctypes.c_ulong, ctypes.c_void_p)), |
|
|
|
# IDesktopWallpaper methods (with dummies) |
|
("SetWallpaper", ctypes.WINFUNCTYPE( |
|
ctypes.c_long, ctypes.c_void_p, |
|
wintypes.LPCWSTR, wintypes.LPCWSTR |
|
)), |
|
("GetWallpaper", ctypes.WINFUNCTYPE( |
|
ctypes.c_long, ctypes.c_void_p, |
|
wintypes.LPCWSTR, ctypes.POINTER(wintypes.LPWSTR) |
|
)), |
|
("GetMonitorDevicePathAt", ctypes.WINFUNCTYPE( |
|
ctypes.c_long, ctypes.c_void_p, |
|
wintypes.UINT, ctypes.POINTER(wintypes.LPWSTR) |
|
)), |
|
("GetMonitorDevicePathCount", ctypes.WINFUNCTYPE( |
|
ctypes.c_long, ctypes.c_void_p, |
|
ctypes.POINTER(wintypes.UINT) |
|
)), |
|
("GetMonitorRECT", ctypes.WINFUNCTYPE( |
|
ctypes.c_long, ctypes.c_void_p, |
|
wintypes.LPCWSTR, ctypes.POINTER(wintypes.RECT) |
|
)), |
|
("SetBackgroundColor", ctypes.WINFUNCTYPE( |
|
ctypes.c_long, ctypes.c_void_p, |
|
wintypes.DWORD |
|
)), |
|
("GetBackgroundColor", ctypes.WINFUNCTYPE( |
|
ctypes.c_long, ctypes.c_void_p, |
|
ctypes.POINTER(wintypes.DWORD) |
|
)), |
|
("SetPosition", ctypes.WINFUNCTYPE( |
|
ctypes.c_long, ctypes.c_void_p, |
|
ctypes.c_int # DESKTOP_WALLPAPER_POSITION |
|
)), |
|
("GetPosition", ctypes.WINFUNCTYPE( |
|
ctypes.c_long, ctypes.c_void_p, |
|
ctypes.POINTER(ctypes.c_int) |
|
)), |
|
|
|
# The method we want: HRESULT SetSlideshow(IShellItemArray *items); |
|
("SetSlideshow", ctypes.WINFUNCTYPE( |
|
ctypes.c_long, ctypes.c_void_p, |
|
ctypes.c_void_p # IShellItemArray* |
|
)), |
|
|
|
# Remaining unused methods—dummy definitions |
|
("GetSlideshow", ctypes.WINFUNCTYPE( |
|
ctypes.c_long, ctypes.c_void_p, |
|
ctypes.POINTER(ctypes.c_void_p) |
|
)), |
|
("SetSlideshowOptions", ctypes.WINFUNCTYPE( |
|
ctypes.c_long, ctypes.c_void_p, |
|
ctypes.c_int, # DESKTOP_SLIDESHOW_OPTIONS |
|
ctypes.c_uint # UINT dwSlideshowTick |
|
)), |
|
("GetSlideshowOptions", ctypes.WINFUNCTYPE( |
|
ctypes.c_long, ctypes.c_void_p, |
|
ctypes.POINTER(ctypes.c_int), |
|
ctypes.POINTER(ctypes.c_uint) |
|
)), |
|
("AdvanceSlideshow", ctypes.WINFUNCTYPE( |
|
ctypes.c_long, ctypes.c_void_p, |
|
wintypes.LPCWSTR, ctypes.c_int # DESKTOP_SLIDESHOW_DIRECTION |
|
)), |
|
("GetStatus", ctypes.WINFUNCTYPE( |
|
ctypes.c_long, ctypes.c_void_p, |
|
ctypes.POINTER(ctypes.c_int) # DESKTOP_SLIDESHOW_STATE |
|
)), |
|
("Enable", ctypes.WINFUNCTYPE( |
|
ctypes.c_long, ctypes.c_void_p, |
|
wintypes.BOOL |
|
)), |
|
] |
|
|
|
|
|
class IDesktopWallpaper(ctypes.Structure): |
|
_fields_ = [("lpVtbl", ctypes.POINTER(IDesktopWallpaperVtbl))] |
|
|
|
|
|
# --- Utilities ---------------------------------------------------- |
|
|
|
def hr_failed(hr): |
|
return hr < 0 # HRESULT values < 0 mean failure |
|
|
|
|
|
def create_shell_item_array_from_paths(paths): |
|
""" |
|
Equivalent to the C++ CreateShellItemArrayFromPaths: |
|
vector<wstring> -> IShellItemArray* |
|
""" |
|
if not paths: |
|
print("[ERROR] No paths given.", file=sys.stderr) |
|
return None |
|
|
|
pidls = [] |
|
for path in paths: |
|
# Python str is Unicode, so it can be passed directly as LPCWSTR |
|
ppidl = ctypes.c_void_p() |
|
sfgao_out = ctypes.c_ulong(0) |
|
|
|
hr = SHParseDisplayName( |
|
path, |
|
None, |
|
ctypes.byref(ppidl), |
|
0, |
|
ctypes.byref(sfgao_out) |
|
) |
|
|
|
if not hr_failed(hr) and ppidl.value: |
|
pidls.append(ppidl.value) |
|
else: |
|
print(f"[WARN] SHParseDisplayName failed for: {path} hr=0x{hr:08X}", |
|
file=sys.stderr) |
|
|
|
if not pidls: |
|
print("[ERROR] No valid paths.", file=sys.stderr) |
|
return None |
|
|
|
# Build PIDL array |
|
cidl = len(pidls) |
|
pidl_array_type = ctypes.c_void_p * cidl |
|
pidl_array = pidl_array_type(*pidls) |
|
|
|
p_array = ctypes.c_void_p() |
|
hr = SHCreateShellItemArrayFromIDLists( |
|
cidl, |
|
pidl_array, |
|
ctypes.byref(p_array) |
|
) |
|
|
|
# Free each PIDL |
|
for p in pidls: |
|
ole32.CoTaskMemFree(p) |
|
|
|
if hr_failed(hr): |
|
print(f"[ERROR] SHCreateShellItemArrayFromIDLists failed. hr=0x{hr:08X}", |
|
file=sys.stderr) |
|
return None |
|
|
|
return p_array.value # void* as IShellItemArray* |
|
|
|
|
|
def set_wallpaper_slideshow_from_images(image_paths): |
|
""" |
|
Equivalent to the C++ SetWallpaperSlideshowFromImages. |
|
""" |
|
hr = ole32.CoInitializeEx(None, COINIT_APARTMENTTHREADED) |
|
need_uninit = False |
|
|
|
if not hr_failed(hr): |
|
# S_OK or S_FALSE |
|
need_uninit = True |
|
elif hr == RPC_E_CHANGED_MODE: |
|
# Already initialized in another mode; continue anyway. |
|
pass |
|
else: |
|
print(f"[ERROR] CoInitializeEx failed. hr=0x{hr:08X}", file=sys.stderr) |
|
return hr |
|
|
|
shell_item_array = create_shell_item_array_from_paths(image_paths) |
|
if not shell_item_array: |
|
if need_uninit: |
|
ole32.CoUninitialize() |
|
return -1 |
|
|
|
# Create IDesktopWallpaper instance |
|
p_desktop_wallpaper = ctypes.c_void_p() |
|
hr = ole32.CoCreateInstance( |
|
ctypes.byref(CLSID_DesktopWallpaper), |
|
None, |
|
CLSCTX_ALL, |
|
ctypes.byref(IID_IDesktopWallpaper), |
|
ctypes.byref(p_desktop_wallpaper) |
|
) |
|
|
|
if hr_failed(hr): |
|
print("[ERROR] CoCreateInstance(CLSID_DesktopWallpaper) failed. " |
|
f"hr=0x{hr:08X}", file=sys.stderr) |
|
if need_uninit: |
|
ole32.CoUninitialize() |
|
return hr |
|
|
|
# Treat COM pointer as IDesktopWallpaper structure |
|
dw = ctypes.cast(p_desktop_wallpaper, ctypes.POINTER(IDesktopWallpaper)) |
|
|
|
# Equivalent to: dw->lpVtbl->SetSlideshow(dw, shell_item_array) |
|
fn_set_slideshow = dw.contents.lpVtbl.contents.SetSlideshow |
|
hr = fn_set_slideshow(dw, shell_item_array) |
|
|
|
if hr_failed(hr): |
|
print("[ERROR] IDesktopWallpaper::SetSlideshow failed. " |
|
f"hr=0x{hr:08X}", file=sys.stderr) |
|
|
|
# Call Release |
|
fn_release = dw.contents.lpVtbl.contents.Release |
|
fn_release(dw) |
|
|
|
if need_uninit: |
|
ole32.CoUninitialize() |
|
|
|
return hr |
|
|
|
|
|
def main(argv=None): |
|
if argv is None: |
|
argv = sys.argv |
|
|
|
if len(argv) <= 1: |
|
print("Usage: python slideshow.py <image1> <image2> ...", file=sys.stderr) |
|
return 1 |
|
|
|
images = [os.path.abspath(image_path) for image_path in argv[1:]] |
|
|
|
hr = set_wallpaper_slideshow_from_images(images) |
|
if hr_failed(hr): |
|
print(f"SetWallpaperSlideshowFromImages failed. hr=0x{hr:08X}", |
|
file=sys.stderr) |
|
return 1 |
|
|
|
print("Slideshow set successfully.") |
|
return 0 |
|
|
|
|
|
if __name__ == "__main__": |
|
raise SystemExit(main()) |