//! Windows Explorer thumbnail handler for DWG files. //! //! Implements `IThumbnailProvider` (+ `IInitializeWithFile`) as a COM in-proc //! server. Explorer instantiates it for `.dwg` files, hands it the path, then //! calls `GetThumbnail`, which extracts the DWG's embedded preview via the //! shared [`dwg_thumbnailer`] core and returns it as an HBITMAP. //! //! ## Build & register (on Windows, elevated) //! ```text //! cargo build -p dwg-thumbnailer-win --release //! regsvr32 dwg_thumbnailer_win.dll :: register //! regsvr32 /u dwg_thumbnailer_win.dll :: unregister //! ``` //! Then restart Explorer (or run `ie4uinit.exe -show`) to refresh thumbnails. //! //! NOTE: this module is `cfg(windows)`-only and was authored on a Linux host, //! so it has NOT been compiled or tested. Build and test it on Windows; minor //! fixups may be needed for your exact `windows` crate version. #![cfg(windows)] use std::cell::RefCell; use std::ffi::c_void; use windows::core::{implement, IUnknown, Interface, GUID, HRESULT, PCWSTR}; use windows::Win32::Foundation::{ CLASS_E_CLASSNOTAVAILABLE, CLASS_E_NOAGGREGATION, E_FAIL, E_INVALIDARG, HMODULE, S_OK, WIN32_ERROR, }; use windows::Win32::Graphics::Gdi::{ CreateDIBSection, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS, HBITMAP, HDC, }; use windows::Win32::System::Com::{IClassFactory, IClassFactory_Impl}; use windows::Win32::System::LibraryLoader::GetModuleFileNameW; use windows::Win32::System::Registry::{ RegCloseKey, RegCreateKeyExW, RegDeleteTreeW, RegSetValueExW, HKEY, HKEY_CLASSES_ROOT, KEY_WRITE, REG_OPTION_NON_VOLATILE, REG_SZ, }; use windows::Win32::System::SystemServices::{DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH}; use windows::Win32::UI::Shell::PropertiesSystem::{IInitializeWithFile, IInitializeWithFile_Impl}; use windows::Win32::UI::Shell::{ IThumbnailProvider, IThumbnailProvider_Impl, SHChangeNotify, SHCNE_ASSOCCHANGED, SHCNF_IDLIST, WTS_ALPHATYPE, WTSAT_ARGB, }; /// CLSID of this thumbnail provider (stable; used in the registry keys). /// {8F2A9C41-3B6E-4E2D-9C7A-1E0B5D6F42AA} const CLSID_DWG_THUMB: GUID = GUID::from_u128(0x8F2A9C41_3B6E_4E2D_9C7A_1E0B5D6F42AA); /// The interface id Explorer looks up under `.dwg\ShellEx`. const IID_ITHUMBNAILPROVIDER: &str = "{e357fccd-a995-4576-b01f-234630154e96}"; /// Our own module handle, captured in `DllMain` — needed to write the DLL path /// into `InprocServer32` during registration. static mut SELF_HMODULE: HMODULE = HMODULE(std::ptr::null_mut()); #[no_mangle] extern "system" fn DllMain(hinst: HMODULE, reason: u32, _reserved: *mut c_void) -> bool { if reason == DLL_PROCESS_ATTACH { unsafe { SELF_HMODULE = hinst }; } else if reason == DLL_PROCESS_DETACH { } true } // ── The provider COM object ────────────────────────────────────────────────── #[implement(IThumbnailProvider, IInitializeWithFile)] #[derive(Default)] struct DwgThumbProvider { path: RefCell>, } impl IInitializeWithFile_Impl for DwgThumbProvider_Impl { fn Initialize(&self, pszfilepath: &PCWSTR, _grfmode: u32) -> windows::core::Result<()> { let path = unsafe { pszfilepath.to_string() }.map_err(|_| windows::core::Error::from(E_INVALIDARG))?; *self.path.borrow_mut() = Some(path); Ok(()) } } impl IThumbnailProvider_Impl for DwgThumbProvider_Impl { fn GetThumbnail( &self, cx: u32, phbmp: *mut HBITMAP, pdwalpha: *mut WTS_ALPHATYPE, ) -> windows::core::Result<()> { let path = self.path.borrow().clone().ok_or(windows::core::Error::from(E_FAIL))?; let mut img = dwg_thumbnailer::extract(std::path::Path::new(&path), cx.max(1)) .ok_or(windows::core::Error::from(E_FAIL))?; dwg_thumbnailer::badge_dwg(&mut img); // full-width "DWG" band let hbmp = unsafe { rgba_to_hbitmap(&img)? }; unsafe { *phbmp = hbmp; *pdwalpha = WTSAT_ARGB; } Ok(()) } } /// Build a 32-bit top-down BGRA `HBITMAP` from an RGBA image. unsafe fn rgba_to_hbitmap(img: &dwg_thumbnailer::RgbaImage) -> windows::core::Result { let (w, h) = (img.width() as i32, img.height() as i32); let bi = BITMAPINFO { bmiHeader: BITMAPINFOHEADER { biSize: std::mem::size_of::() as u32, biWidth: w, biHeight: -h, // negative → top-down rows biPlanes: 1, biBitCount: 32, biCompression: BI_RGB.0, ..Default::default() }, ..Default::default() }; let mut bits: *mut c_void = std::ptr::null_mut(); let hbmp = CreateDIBSection(HDC::default(), &bi, DIB_RGB_COLORS, &mut bits, None, 0)?; if bits.is_null() { return Err(E_FAIL.into()); } let dst = std::slice::from_raw_parts_mut(bits as *mut u8, (w * h * 4) as usize); for (i, px) in img.pixels().enumerate() { let [r, g, b, a] = px.0; dst[i * 4] = b; dst[i * 4 + 1] = g; dst[i * 4 + 2] = r; dst[i * 4 + 3] = a; } Ok(hbmp) } // ── Class factory ──────────────────────────────────────────────────────────── #[implement(IClassFactory)] struct Factory; impl IClassFactory_Impl for Factory_Impl { fn CreateInstance( &self, punkouter: Option<&IUnknown>, riid: *const GUID, ppvobject: *mut *mut c_void, ) -> windows::core::Result<()> { if punkouter.is_some() { return Err(CLASS_E_NOAGGREGATION.into()); } let provider: IUnknown = DwgThumbProvider::default().into(); unsafe { provider.query(&*riid, ppvobject).ok() } } fn LockServer(&self, _flock: windows::Win32::Foundation::BOOL) -> windows::core::Result<()> { Ok(()) } } // ── DLL exports ────────────────────────────────────────────────────────────── #[no_mangle] extern "system" fn DllGetClassObject( rclsid: *const GUID, riid: *const GUID, ppv: *mut *mut c_void, ) -> HRESULT { unsafe { if *rclsid != CLSID_DWG_THUMB { return CLASS_E_CLASSNOTAVAILABLE; } let factory: IClassFactory = Factory.into(); factory.query(&*riid, ppv) } } #[no_mangle] extern "system" fn DllCanUnloadNow() -> HRESULT { // Simplification: report unloadable only when COM has released everything. // A conservative always-`S_FALSE` keeps the DLL resident (safe, if leakier). windows::Win32::Foundation::S_FALSE } #[no_mangle] extern "system" fn DllRegisterServer() -> HRESULT { match register(true) { Ok(()) => S_OK, Err(e) => e.code(), } } #[no_mangle] extern "system" fn DllUnregisterServer() -> HRESULT { match register(false) { Ok(()) => S_OK, Err(e) => e.code(), } } // ── Registration (HKCR) ────────────────────────────────────────────────────── fn module_path() -> windows::core::Result { let mut buf = [0u16; 1024]; let len = unsafe { GetModuleFileNameW(SELF_HMODULE, &mut buf) }; if len == 0 { return Err(E_FAIL.into()); } Ok(String::from_utf16_lossy(&buf[..len as usize])) } fn register(install: bool) -> windows::core::Result<()> { let clsid = format!("{{{:?}}}", CLSID_DWG_THUMB); // "{8F2A9C41-...}" let clsid_key = format!("CLSID\\{clsid}"); let inproc_key = format!("{clsid_key}\\InprocServer32"); let dwg_shellex = format!(".dwg\\ShellEx\\{IID_ITHUMBNAILPROVIDER}"); if install { let dll = module_path()?; set_value(&clsid_key, None, "OpenCADStudio DWG Thumbnail Provider")?; set_value(&inproc_key, None, &dll)?; set_value(&inproc_key, Some("ThreadingModel"), "Apartment")?; set_value(&dwg_shellex, None, &clsid)?; } else { let _ = delete_tree(&clsid_key); let _ = delete_tree(&dwg_shellex); } unsafe { SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, None, None) }; Ok(()) } fn wide(s: &str) -> Vec { s.encode_utf16().chain(std::iter::once(0)).collect() } fn set_value(sub: &str, name: Option<&str>, value: &str) -> windows::core::Result<()> { let sub_w = wide(sub); let mut hkey = HKEY::default(); let rc = unsafe { RegCreateKeyExW( HKEY_CLASSES_ROOT, PCWSTR(sub_w.as_ptr()), 0, PCWSTR::null(), REG_OPTION_NON_VOLATILE, KEY_WRITE, None, &mut hkey, None, ) }; if rc != WIN32_ERROR(0) { return Err(E_FAIL.into()); } let val_w = wide(value); let bytes = unsafe { std::slice::from_raw_parts(val_w.as_ptr() as *const u8, val_w.len() * 2) }; let name_w = name.map(wide); let rc = unsafe { RegSetValueExW( hkey, name_w.as_ref().map_or(PCWSTR::null(), |n| PCWSTR(n.as_ptr())), 0, REG_SZ, Some(bytes), ) }; unsafe { let _ = RegCloseKey(hkey); } if rc != WIN32_ERROR(0) { return Err(E_FAIL.into()); } Ok(()) } fn delete_tree(sub: &str) -> windows::core::Result<()> { let sub_w = wide(sub); let rc = unsafe { RegDeleteTreeW(HKEY_CLASSES_ROOT, PCWSTR(sub_w.as_ptr())) }; if rc != WIN32_ERROR(0) { return Err(E_FAIL.into()); } Ok(()) }