Skip to content

Instantly share code, notes, and snippets.

@aont
Last active November 13, 2025 12:56
Show Gist options
  • Select an option

  • Save aont/d8743a11cd1b6b96cca2eb03825e0a4e to your computer and use it in GitHub Desktop.

Select an option

Save aont/d8743a11cd1b6b96cca2eb03825e0a4e to your computer and use it in GitHub Desktop.

A Simple Wallpaper Slideshow Tool for Windows

This article introduces a small command-line tool that sets a wallpaper slideshow on Windows. It is written in C++ and uses the Windows Desktop Wallpaper API. The tool allows you to choose multiple image files and apply them as a slideshow on your desktop.


What This Tool Does

The tool changes your desktop background to a slideshow of images.

  • You run the program from the command line.
  • You give it several image file paths as arguments.
  • It passes these images to Windows as a slideshow.
  • If everything goes well, your wallpaper will switch between these images automatically (according to the system’s slideshow settings in Windows).

How to Run the Program

The main function is wmain, which is the Unicode version of main:

int wmain(int argc, wchar_t* argv[])
  • argc is the number of command-line arguments.
  • argv is the list of arguments (as wide strings).

If no image paths are given (argc <= 1), the program prints a usage message:

Usage: slideshow.exe <image1> <image2> ...

If there are arguments, it treats each one from argv[1] onward as an image path and stores them in a std::vector<std::wstring> called images. Then it calls:

SetWallpaperSlideshowFromImages(images);

This function does the real work.


Normalizing Paths

Windows file paths usually use backslashes (\) instead of slashes (/). To avoid problems, the code converts all / characters to \ using a helper function:

std::wstring NormalizePathSeparators(const std::wstring& path)

This makes the paths more consistent and safer to pass to Windows APIs.


Creating a Shell Item Array

Windows APIs often use IShellItemArray to represent a list of files or folders. The function

HRESULT CreateShellItemArrayFromPaths(
    const std::vector<std::wstring>& paths,
    IShellItemArray** ppArray)

does the following:

  1. For each path string:

    • It converts it to a PIDL (an internal item ID list) using SHParseDisplayName.
    • If conversion fails, it prints a warning message.
  2. If no paths are valid, it prints an error and returns failure.

  3. If there are valid PIDLs, it calls SHCreateShellItemArrayFromIDLists to create an IShellItemArray from them.

  4. It frees each PIDL with CoTaskMemFree.

  5. On success, it stores the IShellItemArray pointer in ppArray.

This IShellItemArray will later be passed to the desktop wallpaper API.


Initializing COM and Using IDesktopWallpaper

Windows COM (Component Object Model) must be initialized before using many system APIs. In SetWallpaperSlideshowFromImages, the program calls:

HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
  • If this succeeds, COM is ready.
  • If the mode was already set (RPC_E_CHANGED_MODE), the code continues anyway.
  • If another error occurs, the function prints an error message and returns.

Next, it creates the IShellItemArray from the image paths:

IShellItemArray* pArray = nullptr;
hr = CreateShellItemArrayFromPaths(imagePaths, &pArray);

If this fails, it cleans up COM and exits.

Then it creates the IDesktopWallpaper object:

IDesktopWallpaper* pDesktopWallpaper = nullptr;
hr = CoCreateInstance(
    CLSID_DesktopWallpaper,
    nullptr,
    CLSCTX_ALL,
    IID_IDesktopWallpaper,
    reinterpret_cast<void**>(&pDesktopWallpaper)
);
  • CLSID_DesktopWallpaper identifies the Desktop Wallpaper service.
  • IID_IDesktopWallpaper is the interface ID.

If this fails, it prints an error, releases the shell item array, and uninitializes COM.


Applying the Slideshow

Once everything is ready, the core call is:

hr = pDesktopWallpaper->SetSlideshow(pArray);

This tells Windows:

“Use this list of images as the desktop wallpaper slideshow.”

If the call succeeds, the slideshow is applied. If it fails, an error message is shown.

Finally, the code:

  • Releases pDesktopWallpaper and pArray.
  • Calls CoUninitialize() if COM was initialized in this function.

Error Handling and Messages

The program uses std::wcerr and std::wcout to print messages:

  • Warnings if a path cannot be parsed.
  • Errors when COM initialization, item array creation, or IDesktopWallpaper creation fails.
  • A success message:
std::wcout << L"Slideshow set successfully.\n";

These messages help you understand what went wrong if the slideshow does not apply.


Summary

This tool is a compact example of how to:

  • Work with Unicode command-line arguments on Windows.
  • Normalize file paths.
  • Convert file paths into shell items.
  • Use COM and the IDesktopWallpaper interface.
  • Programmatically set a wallpaper slideshow from the command line.

It is especially useful for users who want to script or automate wallpaper changes, or for developers who want a reference implementation of the Windows Desktop Wallpaper API in C++.

// slideshow.cpp
// g++ -municode main.cpp -lole32 -lshell32
#include <windows.h>
#include <shlobj.h>
#include <shobjidl.h> // IDesktopWallpaper, IShellItemArray
#include <vector>
#include <string>
#include <iostream>
// Libraries to link with: ole32.lib, shell32.lib
// Enable this only if the GUIDs are missing in an old SDK, etc. (uncomment if needed)
const IID IID_IDesktopWallpaper =
{ 0xB92B56A9, 0x8B55, 0x4E14, { 0x9A, 0x89, 0x01, 0x99, 0xBB, 0xB6, 0xF9, 0x3B } };
const CLSID CLSID_DesktopWallpaper =
{ 0xC2CF3110, 0x460E, 0x4FC1, { 0xB9, 0xD0, 0x8A, 0x1C, 0x0C, 0x9C, 0xC4, 0xBD } };
// Convert '/' to '\'
std::wstring NormalizePathSeparators(const std::wstring& path)
{
std::wstring result = path;
for (auto& ch : result)
{
if (ch == L'/')
ch = L'\\';
}
return result;
}
HRESULT CreateShellItemArrayFromPaths(
const std::vector<std::wstring>& paths,
IShellItemArray** ppArray)
{
if (!ppArray) return E_POINTER;
*ppArray = nullptr;
std::vector<PIDLIST_ABSOLUTE> pidls;
pidls.reserve(paths.size());
HRESULT hr = S_OK;
for (const auto& rawPath : paths)
{
std::wstring path = NormalizePathSeparators(rawPath);
PIDLIST_ABSOLUTE pidl = nullptr;
SFGAOF sfgao = 0;
// Relative paths are also OK (interpreted relative to the current directory)
hr = SHParseDisplayName(
path.c_str(),
nullptr,
&pidl,
0,
&sfgao
);
if (SUCCEEDED(hr) && pidl)
{
pidls.push_back(pidl);
}
else
{
std::wcerr << L"[WARN] SHParseDisplayName failed for: "
<< path << L" hr=0x" << std::hex << hr << std::endl;
}
}
if (pidls.empty())
{
std::wcerr << L"[ERROR] No valid paths." << std::endl;
return E_FAIL;
}
IShellItemArray* pArray = nullptr;
hr = SHCreateShellItemArrayFromIDLists(
static_cast<UINT>(pidls.size()),
const_cast<PCIDLIST_ABSOLUTE*>(pidls.data()),
&pArray
);
// Clean up PIDLs
for (auto pidl : pidls)
{
CoTaskMemFree(pidl);
}
if (FAILED(hr))
{
std::wcerr << L"[ERROR] SHCreateShellItemArrayFromIDLists failed. hr=0x"
<< std::hex << hr << std::endl;
return hr;
}
*ppArray = pArray;
return S_OK;
}
HRESULT SetWallpaperSlideshowFromImages(const std::vector<std::wstring>& imagePaths)
{
HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
bool needUninit = false;
if (SUCCEEDED(hr))
{
needUninit = true;
}
else if (hr == RPC_E_CHANGED_MODE)
{
// If COM is already initialized, just continue
// (depending on your app design, you may treat this as an error instead)
}
else
{
std::wcerr << L"[ERROR] CoInitializeEx failed. hr=0x"
<< std::hex << hr << std::endl;
return hr;
}
IShellItemArray* pArray = nullptr;
hr = CreateShellItemArrayFromPaths(imagePaths, &pArray);
if (FAILED(hr))
{
if (needUninit) CoUninitialize();
return hr;
}
IDesktopWallpaper* pDesktopWallpaper = nullptr;
hr = CoCreateInstance(
CLSID_DesktopWallpaper,
nullptr,
CLSCTX_ALL,
IID_IDesktopWallpaper,
reinterpret_cast<void**>(&pDesktopWallpaper)
);
if (FAILED(hr))
{
std::wcerr << L"[ERROR] CoCreateInstance(CLSID_DesktopWallpaper) failed. hr=0x"
<< std::hex << hr << std::endl;
pArray->Release();
if (needUninit) CoUninitialize();
return hr;
}
// Equivalent to Python's dw.SetSlideshow(psia_p)
hr = pDesktopWallpaper->SetSlideshow(pArray);
if (FAILED(hr))
{
std::wcerr << L"[ERROR] IDesktopWallpaper::SetSlideshow failed. hr=0x"
<< std::hex << hr << std::endl;
}
pDesktopWallpaper->Release();
pArray->Release();
if (needUninit) CoUninitialize();
return hr;
}
// Assuming a Unicode build: compiled with /DUNICODE /D_UNICODE
int wmain(int argc, wchar_t* argv[])
{
if (argc <= 1)
{
std::wcerr << L"Usage: slideshow.exe <image1> <image2> ...\n";
return 1;
}
std::vector<std::wstring> images;
images.reserve(argc - 1);
// Treat everything from argv[1] onward as image paths
for (int i = 1; i < argc; ++i)
{
images.emplace_back(argv[i]);
}
HRESULT hr = SetWallpaperSlideshowFromImages(images);
if (FAILED(hr))
{
std::wcerr << L"SetWallpaperSlideshowFromImages failed. hr=0x"
<< std::hex << hr << std::endl;
return 1;
}
std::wcout << L"Slideshow set successfully.\n";
return 0;
}
# slideshow_comtypes.py
import os
import sys
import ctypes
from ctypes import wintypes
import comtypes
from comtypes import GUID, HRESULT, POINTER
from comtypes import COMMETHOD
from comtypes.client import CreateObject
# --- Load Windows API -------------------------------------------------------
ole32 = ctypes.OleDLL("ole32")
shell32 = ctypes.OleDLL("shell32")
# 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]
# --- IDesktopWallpaper / IShellItemArray definitions -------------------------
# Minimal definition for IShellItemArray (no methods needed; only the pointer type is required)
class IShellItemArray(comtypes.IUnknown):
_iid_ = GUID("{B63EA76D-1F85-456F-A19C-48159EFA858B}")
_methods_ = [] # Empty because it's only passed to SetSlideshow
# Enums are treated as int
DESKTOP_WALLPAPER_POSITION = ctypes.c_int
DESKTOP_SLIDESHOW_OPTIONS = ctypes.c_int
DESKTOP_SLIDESHOW_DIRECTION = ctypes.c_int
DESKTOP_SLIDESHOW_STATE = ctypes.c_int
class IDesktopWallpaper(comtypes.IUnknown):
_iid_ = GUID("{B92B56A9-8B55-4E14-9A89-0199BBB6F93B}")
_methods_ = [
# IUnknown methods are automatically added by comtypes
# HRESULT SetWallpaper([in] LPCWSTR monitorID, [in] LPCWSTR wallpaper);
COMMETHOD(
[],
HRESULT,
"SetWallpaper",
(["in"], wintypes.LPCWSTR, "monitorID"),
(["in"], wintypes.LPCWSTR, "wallpaper"),
),
# HRESULT GetWallpaper([in] LPCWSTR monitorID, [out] LPWSTR *wallpaper);
COMMETHOD(
[],
HRESULT,
"GetWallpaper",
(["in"], wintypes.LPCWSTR, "monitorID"),
(["out"], POINTER(wintypes.LPWSTR), "wallpaper"),
),
# HRESULT GetMonitorDevicePathAt([in] UINT monitorIndex, [out] LPWSTR *monitorID);
COMMETHOD(
[],
HRESULT,
"GetMonitorDevicePathAt",
(["in"], wintypes.UINT, "monitorIndex"),
(["out"], POINTER(wintypes.LPWSTR), "monitorID"),
),
# HRESULT GetMonitorDevicePathCount([out] UINT *count);
COMMETHOD(
[],
HRESULT,
"GetMonitorDevicePathCount",
(["out"], POINTER(wintypes.UINT), "count"),
),
# HRESULT GetMonitorRECT([in] LPCWSTR monitorID, [out] RECT *displayRect);
COMMETHOD(
[],
HRESULT,
"GetMonitorRECT",
(["in"], wintypes.LPCWSTR, "monitorID"),
(["out"], POINTER(wintypes.RECT), "displayRect"),
),
# HRESULT SetBackgroundColor([in] COLORREF color);
COMMETHOD(
[],
HRESULT,
"SetBackgroundColor",
(["in"], wintypes.DWORD, "color"),
),
# HRESULT GetBackgroundColor([out] COLORREF *color);
COMMETHOD(
[],
HRESULT,
"GetBackgroundColor",
(["out"], POINTER(wintypes.DWORD), "color"),
),
# HRESULT SetPosition([in] DESKTOP_WALLPAPER_POSITION position);
COMMETHOD(
[],
HRESULT,
"SetPosition",
(["in"], DESKTOP_WALLPAPER_POSITION, "position"),
),
# HRESULT GetPosition([out] DESKTOP_WALLPAPER_POSITION *position);
COMMETHOD(
[],
HRESULT,
"GetPosition",
(["out"], POINTER(DESKTOP_WALLPAPER_POSITION), "position"),
),
# HRESULT SetSlideshow([in] IShellItemArray *items);
COMMETHOD(
[],
HRESULT,
"SetSlideshow",
(["in"], POINTER(IShellItemArray), "items"),
),
# HRESULT GetSlideshow([out] IShellItemArray **items);
COMMETHOD(
[],
HRESULT,
"GetSlideshow",
(["out"], POINTER(POINTER(IShellItemArray)), "items"),
),
# HRESULT SetSlideshowOptions([in] DESKTOP_SLIDESHOW_OPTIONS options, [in] UINT slideshowTick);
COMMETHOD(
[],
HRESULT,
"SetSlideshowOptions",
(["in"], DESKTOP_SLIDESHOW_OPTIONS, "options"),
(["in"], wintypes.UINT, "slideshowTick"),
),
# HRESULT GetSlideshowOptions([out] DESKTOP_SLIDESHOW_OPTIONS *options, [out] UINT *slideshowTick);
COMMETHOD(
[],
HRESULT,
"GetSlideshowOptions",
(["out"], POINTER(DESKTOP_SLIDESHOW_OPTIONS), "options"),
(["out"], POINTER(wintypes.UINT), "slideshowTick"),
),
# HRESULT AdvanceSlideshow([in] LPCWSTR monitorID, [in] DESKTOP_SLIDESHOW_DIRECTION direction);
COMMETHOD(
[],
HRESULT,
"AdvanceSlideshow",
(["in"], wintypes.LPCWSTR, "monitorID"),
(["in"], DESKTOP_SLIDESHOW_DIRECTION, "direction"),
),
# HRESULT GetStatus([out] DESKTOP_SLIDESHOW_STATE *state);
COMMETHOD(
[],
HRESULT,
"GetStatus",
(["out"], POINTER(DESKTOP_SLIDESHOW_STATE), "state"),
),
# HRESULT Enable([in] BOOL enable);
COMMETHOD(
[],
HRESULT,
"Enable",
(["in"], wintypes.BOOL, "enable"),
),
]
# CLSID_DesktopWallpaper
CLSID_DesktopWallpaper = GUID("{C2CF3110-460E-4FC1-B9D0-8A1C0C9CC4BD}")
# --- Utilities -------------------------------------------------------------
def hr_failed(hr):
# Simple check: treat negative HRESULT values as failures
return hr < 0
def create_shell_item_array_from_paths(paths):
"""
Equivalent to the C++ version of CreateShellItemArrayFromPaths:
[str] -> IShellItemArray*
"""
if not paths:
print("[ERROR] No paths given.", file=sys.stderr)
return None
pidls = []
for path in paths:
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
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)
)
# Clean up PIDLs
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
# void* → IShellItemArray*
return ctypes.cast(p_array, POINTER(IShellItemArray))
def set_wallpaper_slideshow_from_images(image_paths):
"""
Equivalent to the C++ function SetWallpaperSlideshowFromImages.
Uses comtypes to create IDesktopWallpaper and call SetSlideshow.
"""
comtypes.CoInitialize() # CoInitialize does nothing if already initialized
try:
shell_item_array = create_shell_item_array_from_paths(image_paths)
if not shell_item_array:
return -1
# Create DesktopWallpaper COM object via CreateObject using CLSID
desktop_wallpaper = CreateObject(
CLSID_DesktopWallpaper,
interface=IDesktopWallpaper
)
hr = desktop_wallpaper.SetSlideshow(shell_item_array)
if hr_failed(hr):
print(
f"[ERROR] IDesktopWallpaper::SetSlideshow failed. hr=0x{hr:08X}",
file=sys.stderr
)
return hr
finally:
# Uninitialize COM if not needed further
comtypes.CoUninitialize()
def main(argv=None):
if argv is None:
argv = sys.argv
if len(argv) <= 1:
print("Usage: python slideshow_comtypes.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())
# 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())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment