clipboard_win_html/
lib.rs

1//! A library to set HTML to the clipboard.
2//!
3//! ## Example
4//! ```rust
5//! use clipboard_win_html::set_clipboard_html;
6//!
7//! set_clipboard_html("<h1>Hello, World!</h1>".to_string()).unwrap();
8//! ```
9//! ## Platform
10//! - Windows
11//!
12//! ## Windows References
13//! - [HTML Clipboard Format](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format)
14//! - [Clipboard Functions](https://docs.microsoft.com/en-us/windows/win32/dataxchg/clipboard-functions)
15//! - [RegisterClipboardFormatW function](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerclipboardformatw)
16//! - [GlobalAlloc function](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globalalloc)
17//! - [GlobalLock function](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globallock)
18//! - [GlobalUnlock function](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globalunlock)
19//! - [SetClipboardData function](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclipboarddata)
20//! - [EmptyClipboard function](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-emptyclipboard)
21//! - [CloseClipboard function](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-closeclipboard)
22//! - [OpenClipboard function](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-openclipboard)
23//! - [Clipboard Formats](https://docs.microsoft.com/en-us/windows/win32/dataxchg/clipboard-formats)
24//! - [Clipboard Data Formats](https://docs.microsoft.com/en-us/windows/win32/dataxchg/clipboard-data-formats)
25//! - [Clipboard](https://docs.microsoft.com/en-us/windows/win32/dataxchg/clipboard)
26use std::fmt;
27use std::ffi::CString;
28use windows::Win32::{
29    Foundation::{HANDLE, HGLOBAL},
30    System::Memory::{GlobalAlloc, GlobalLock, GlobalUnlock, GMEM_MOVEABLE},
31};
32
33/// Any errors that might occur during the process of setting HTML to the clipboard.
34#[derive(Debug)]
35pub enum Error {
36    HtmlTemplateCreationError,
37    OpenClipboardError,
38    EmptyClipboardError,
39    SetClipboardError,
40    CloseClipboardError,
41    MemoryAllocationError,
42}
43
44impl fmt::Display for Error {
45    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46        match self {
47            Error::HtmlTemplateCreationError => write!(f, "Failed to create HTML template."),
48            Error::OpenClipboardError => write!(f, "Failed to open clipboard."),
49            Error::EmptyClipboardError => write!(f, "Failed to empty clipboard."),
50            Error::SetClipboardError => write!(f, "Failed to set clipboard."),
51            Error::CloseClipboardError => write!(f, "Failed to close clipboard."),
52            Error::MemoryAllocationError => write!(f, "Failed to allocate memory."),
53        }
54    }
55}
56
57/// Set HTML to the clipboard on Windows.
58///
59/// https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
60///
61/// Uses 50 characters for the offsets.
62pub fn set_clipboard_html(html: String) -> Result<(), Error> {
63    // Create the HTML document to set to clipboard
64    let fragment = html;
65
66    let start_html = 391;
67    let start_fragment = 454;
68    let start_selection = Some(start_fragment);
69
70    let end_fragment = start_fragment + fragment.len() - 1;
71    let end_selection = Some(end_fragment);
72    // 37 is the `<!-- EndFragment -->\n...`
73    let end_html = end_fragment + 37;
74
75    let mut document = String::new();
76
77    // Description
78
79    // Version
80    document.push_str(format!("Version:{}\n", "0.9").as_str());
81
82    // StartHTML
83    document.push_str(format!("StartHTML:{:0>50}\n", start_html).as_str());
84    // EndHTML
85    document.push_str(format!("EndHTML:{:0>50}\n", end_html).as_str());
86
87    // StartFragment
88    document.push_str(format!("StartFragment:{:0>50}\n", start_fragment).as_str());
89    // EndFragment
90    document.push_str(format!("EndFragment:{:0>50}\n", end_fragment).as_str());
91
92    if let (Some(start_selection), Some(end_selection)) = (start_selection, end_selection) {
93        // StartSelection
94        document.push_str(format!("StartSelection:{:0>50}\n", start_selection).as_str());
95        // EndSelection
96        document.push_str(format!("EndSelection:{:0>50}\n", end_selection).as_str());
97    }
98
99    // Context
100
101    document.push_str(
102        r#"<!DOCTYPE>
103<HTML>
104<HEAD>
105</HEAD>
106<BODY>
107<!-- StartFragment -->
108"#,
109    );
110    document.push_str(&fragment);
111    document.push_str(
112        r#"
113<!-- EndFragment -->
114</BODY>
115</HTML>"#,
116    );
117
118    let cstring = CString::new(document)
119        .map_err(|_| Error::HtmlTemplateCreationError)?
120        .into_bytes_with_nul();
121
122    // Register Format
123    #[allow(non_snake_case)]
124    let CF_HTML;
125    unsafe {
126        // [Where they tell us the official name](https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format)
127        //
128        // The official name of the clipboard (the string used by RegisterClipboardFormat) is HTML Format.
129        let format_name: Vec<u16> = "HTML Format\0".encode_utf16().collect();
130        let pcwstr = windows::core::PCWSTR(format_name.as_ptr());
131        // RegisterClipboardFormatW: <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerclipboardformatw>
132        let uint = windows::Win32::System::DataExchange::RegisterClipboardFormatW(pcwstr);
133        CF_HTML = uint;
134    }
135
136
137    // 1. Open Clipboard
138    // 2. Empty Clipboard
139    // 3. Set Clipboard
140    // 4. Close Clipboard
141
142    // 1. Open Clipboard
143    unsafe {
144        windows::Win32::System::DataExchange::OpenClipboard(None)
145            .map_err(|_| Error::OpenClipboardError)?;
146    }
147
148    // 2. Empty Clipboard
149    unsafe {
150        windows::Win32::System::DataExchange::EmptyClipboard()
151        .map_err(|_| Error::EmptyClipboardError)?;
152    }
153
154    // 3. Set Clipboard
155    unsafe {
156        let mem_alloc: HGLOBAL =
157            GlobalAlloc(GMEM_MOVEABLE, cstring.len() * std::mem::size_of::<u16>())
158            .map_err(|_| Error::MemoryAllocationError)?;
159        let mem_lock = GlobalLock(mem_alloc);
160        std::ptr::copy_nonoverlapping(cstring.as_ptr(), mem_lock as *mut u8, cstring.len());
161        let _ = GlobalUnlock(mem_alloc);
162        let handle = HANDLE(mem_alloc.0 as isize);
163
164        if windows::Win32::System::DataExchange::SetClipboardData(CF_HTML, handle).is_err() {
165            panic!("Failed to set clipboard.");
166        }
167    }
168
169    // 4. Close Clipboard
170    unsafe {
171        windows::Win32::System::DataExchange::CloseClipboard()
172        .map_err(|_| Error::CloseClipboardError)?;
173    }
174
175    Ok(())
176}