Skip to main content

eulumdat_windows_preview/
lib.rs

1//! Windows Preview Handler for EULUMDAT (LDT) and IES photometric files
2//!
3//! This crate implements a Windows Shell Preview Handler that displays
4//! polar diagrams for LDT/IES files in the File Explorer preview pane.
5//!
6//! ## Supported Architectures
7//!
8//! - x64 (x86_64-pc-windows-msvc)
9//! - ARM64 (aarch64-pc-windows-msvc)
10//!
11//! ## Installation
12//!
13//! ```powershell
14//! # Build for your architecture
15//! cargo build --release -p eulumdat-preview --target x86_64-pc-windows-msvc   # x64
16//! cargo build --release -p eulumdat-preview --target aarch64-pc-windows-msvc  # ARM64
17//!
18//! # Register (run as Administrator)
19//! regsvr32 target\x86_64-pc-windows-msvc\release\eulumdat_preview.dll
20//! # or
21//! regsvr32 target\aarch64-pc-windows-msvc\release\eulumdat_preview.dll
22//!
23//! # Unregister
24//! regsvr32 /u target\<arch>\release\eulumdat_preview.dll
25//! ```
26
27#![cfg(windows)]
28
29mod handler;
30mod registry;
31mod render;
32
33use std::ffi::c_void;
34use windows::core::{implement, Error as WinError, IUnknown, Interface, Result as WinResult, GUID};
35use windows::Win32::Foundation::{
36    BOOL, CLASS_E_CLASSNOTAVAILABLE, E_POINTER, E_UNEXPECTED, HINSTANCE, S_OK,
37};
38
39const DLL_PROCESS_ATTACH: u32 = 1;
40const DLL_PROCESS_DETACH: u32 = 0;
41
42/// Debug logging to file
43fn debug_log(msg: &str) {
44    use std::io::Write;
45    if let Ok(mut file) = std::fs::OpenOptions::new()
46        .create(true)
47        .append(true)
48        .open("C:\\eulumdat_preview_debug.log")
49    {
50        let timestamp = std::time::SystemTime::now()
51            .duration_since(std::time::UNIX_EPOCH)
52            .map(|d| d.as_secs())
53            .unwrap_or(0);
54        let _ = writeln!(file, "[{}] {}", timestamp, msg);
55    }
56}
57
58/// DLL entry point - logs when DLL is loaded/unloaded
59#[no_mangle]
60pub unsafe extern "system" fn DllMain(
61    _hinst: HINSTANCE,
62    reason: u32,
63    _reserved: *mut c_void,
64) -> BOOL {
65    match reason {
66        DLL_PROCESS_ATTACH => {
67            debug_log("DllMain: DLL_PROCESS_ATTACH");
68        }
69        DLL_PROCESS_DETACH => {
70            debug_log("DllMain: DLL_PROCESS_DETACH");
71        }
72        _ => {}
73    }
74    BOOL::from(true)
75}
76
77use windows::Win32::System::Com::{IClassFactory, IClassFactory_Impl};
78
79use handler::EulumdatPreviewHandler;
80
81/// CLSID for the Eulumdat Preview Handler
82/// {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}
83pub const CLSID_EULUMDAT_PREVIEW: GUID = GUID::from_values(
84    0xA1B2C3D4,
85    0xE5F6,
86    0x7890,
87    [0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x90],
88);
89
90/// Class factory for creating preview handler instances
91#[implement(IClassFactory)]
92pub struct EulumdatClassFactory;
93
94impl IClassFactory_Impl for EulumdatClassFactory_Impl {
95    fn CreateInstance(
96        &self,
97        punkouter: Option<&IUnknown>,
98        riid: *const GUID,
99        ppvobject: *mut *mut c_void,
100    ) -> WinResult<()> {
101        debug_log("CreateInstance called");
102
103        // COM aggregation not supported
104        if punkouter.is_some() {
105            debug_log("CreateInstance: Aggregation not supported");
106            return Err(WinError::from(
107                windows::Win32::Foundation::CLASS_E_NOAGGREGATION,
108            ));
109        }
110
111        unsafe {
112            if ppvobject.is_null() {
113                debug_log("CreateInstance: Null ppvobject");
114                return Err(WinError::from(E_POINTER));
115            }
116            *ppvobject = std::ptr::null_mut();
117
118            debug_log("CreateInstance: Creating handler...");
119            let handler: IUnknown = EulumdatPreviewHandler::new().into();
120            let result = handler.query(&*riid, ppvobject);
121            debug_log(&format!("CreateInstance: query result = {:?}", result));
122            result.ok()
123        }
124    }
125
126    fn LockServer(&self, _flock: windows::Win32::Foundation::BOOL) -> WinResult<()> {
127        Ok(())
128    }
129}
130
131/// DLL entry point for COM class factory
132///
133/// # Safety
134/// This function is called by COM to get class factories
135#[no_mangle]
136pub unsafe extern "system" fn DllGetClassObject(
137    rclsid: *const GUID,
138    riid: *const GUID,
139    ppv: *mut *mut c_void,
140) -> windows::core::HRESULT {
141    debug_log("DllGetClassObject called");
142
143    if ppv.is_null() {
144        debug_log("DllGetClassObject: Null ppv");
145        return E_POINTER;
146    }
147    *ppv = std::ptr::null_mut();
148
149    if *rclsid != CLSID_EULUMDAT_PREVIEW {
150        debug_log("DllGetClassObject: Wrong CLSID");
151        return CLASS_E_CLASSNOTAVAILABLE;
152    }
153
154    debug_log("DllGetClassObject: Creating factory...");
155    let factory: IClassFactory = EulumdatClassFactory.into();
156    let result = factory.query(&*riid, ppv);
157    debug_log(&format!("DllGetClassObject: result = {:?}", result));
158    result
159}
160
161/// Check if DLL can be unloaded
162///
163/// # Safety
164/// Called by COM to check if DLL can be freed
165#[no_mangle]
166pub unsafe extern "system" fn DllCanUnloadNow() -> windows::core::HRESULT {
167    // For simplicity, always say we can be unloaded
168    S_OK
169}
170
171/// Register the preview handler
172///
173/// # Safety
174/// Called by regsvr32 to register the DLL
175#[no_mangle]
176pub unsafe extern "system" fn DllRegisterServer() -> windows::core::HRESULT {
177    match registry::register_preview_handler() {
178        Ok(()) => S_OK,
179        Err(e) => {
180            // Log error to a file for debugging
181            let _ = std::fs::write(
182                "C:\\eulumdat_preview_register_error.txt",
183                format!("Registration failed: {:?}", e),
184            );
185            E_UNEXPECTED
186        }
187    }
188}
189
190/// Unregister the preview handler
191///
192/// # Safety
193/// Called by regsvr32 /u to unregister the DLL
194#[no_mangle]
195pub unsafe extern "system" fn DllUnregisterServer() -> windows::core::HRESULT {
196    match registry::unregister_preview_handler() {
197        Ok(()) => S_OK,
198        Err(_) => E_UNEXPECTED,
199    }
200}