Skip to main content

win_context_menu/
com.rs

1//! COM initialization guard and PIDL memory management.
2//!
3//! Windows Shell APIs require COM to be initialized on the calling thread in
4//! single-threaded apartment (STA) mode. The types in this module provide RAII
5//! wrappers so that COM lifetime and PIDL memory are automatically managed.
6
7use crate::error::{Error, Result};
8use windows::Win32::System::Com::CoTaskMemFree;
9use windows::Win32::System::Ole::{OleFlushClipboard, OleInitialize, OleUninitialize};
10use windows::Win32::UI::Shell::Common::ITEMIDLIST;
11
12/// RAII guard for COM initialization. Calls `CoUninitialize` on drop.
13///
14/// # Threading
15///
16/// This guard initializes COM in **single-threaded apartment (STA)** mode,
17/// which is required by the Windows Shell context menu interfaces. All context
18/// menu operations must happen on the same thread that created the `ComGuard`.
19///
20/// `ComGuard` is intentionally **not `Send` or `Sync`** — moving it to another
21/// thread would violate STA rules.
22///
23/// # Example
24///
25/// ```no_run
26/// let _com = win_context_menu::init_com()?;
27/// // COM is active for the lifetime of `_com`
28/// # Ok::<(), win_context_menu::Error>(())
29/// ```
30pub struct ComGuard {
31    // prevent Send + Sync by holding a non-Send type
32    _not_send: std::marker::PhantomData<*mut ()>,
33}
34
35impl ComGuard {
36    /// Initialize COM in single-threaded apartment mode.
37    ///
38    /// Returns [`Error::ComInit`] if COM has already been initialized with an
39    /// incompatible threading model on this thread.
40    pub fn new() -> Result<Self> {
41        // SAFETY: `OleInitialize` initializes COM in STA mode **and** the OLE
42        // clipboard / drag-drop subsystem. We need the OLE clipboard so that
43        // shell copy/paste (which uses `OleSetClipboard` internally) works
44        // correctly and `OleFlushClipboard` can persist data after process exit.
45        // Calling once per thread is safe; redundant calls return S_FALSE and
46        // are balanced by `OleUninitialize` in `Drop`.
47        unsafe {
48            OleInitialize(None).map_err(Error::ComInit)?;
49        }
50        Ok(Self {
51            _not_send: std::marker::PhantomData,
52        })
53    }
54}
55
56impl Drop for ComGuard {
57    fn drop(&mut self) {
58        // SAFETY: `OleFlushClipboard` converts any OLE clipboard data object
59        // into a static snapshot so that clipboard content (e.g. file copy)
60        // survives after this process exits. Safe to call even when no OLE
61        // clipboard data is present — it simply returns S_OK.
62        unsafe {
63            let _ = OleFlushClipboard();
64        }
65        // SAFETY: Balanced with the `OleInitialize` call in `new()`.
66        // Must run on the same thread that called `OleInitialize`.
67        unsafe {
68            OleUninitialize();
69        }
70    }
71}
72
73/// RAII wrapper for a PIDL allocated with `CoTaskMemAlloc`.
74///
75/// Automatically calls `CoTaskMemFree` on drop.
76pub(crate) struct Pidl {
77    ptr: *mut ITEMIDLIST,
78}
79
80impl Pidl {
81    /// Take ownership of a raw PIDL pointer.
82    ///
83    /// # Safety
84    ///
85    /// - `ptr` must have been allocated via `CoTaskMemAlloc` (e.g. returned by
86    ///   `SHParseDisplayName`), or be null.
87    /// - The caller must not free `ptr` after calling this function; `Pidl`
88    ///   takes ownership and will free it on drop.
89    pub unsafe fn from_raw(ptr: *mut ITEMIDLIST) -> Self {
90        Self { ptr }
91    }
92
93    /// Get the raw pointer without transferring ownership.
94    pub fn as_ptr(&self) -> *const ITEMIDLIST {
95        self.ptr as *const ITEMIDLIST
96    }
97}
98
99impl Drop for Pidl {
100    fn drop(&mut self) {
101        if !self.ptr.is_null() {
102            // SAFETY: `self.ptr` was allocated by `CoTaskMemAlloc` (guaranteed
103            // by the safety contract of `from_raw`). Freeing it once here is
104            // correct; the null-check prevents double-free.
105            unsafe {
106                CoTaskMemFree(Some(self.ptr as *const _));
107            }
108        }
109    }
110}