clipboard_win/
monitor.rs

1//! Clipboard monitoring utility
2
3use error_code::ErrorCode;
4use windows_win::{
5    raw,
6    Window,
7    Messages
8};
9
10use windows_win::sys::{
11    HWND,
12    AddClipboardFormatListener,
13    RemoveClipboardFormatListener,
14    PostMessageW,
15    WM_CLIPBOARDUPDATE,
16};
17
18const CLOSE_PARAM: isize = -1;
19
20///Shutdown channel
21///
22///On drop requests shutdown to gracefully close clipboard listener as soon as possible.
23///
24///This is silently ignored, if there is no thread awaiting
25pub struct Shutdown {
26    window: HWND,
27}
28
29unsafe impl Send for Shutdown {}
30
31impl Drop for Shutdown {
32    #[inline(always)]
33    fn drop(&mut self) {
34        unsafe {
35            PostMessageW(self.window, WM_CLIPBOARDUPDATE, 0, CLOSE_PARAM)
36        };
37    }
38}
39
40///Clipboard listener guard.
41///
42///On drop unsubscribes window from listening on clipboard changes
43struct ClipboardListener(HWND);
44
45impl ClipboardListener {
46    #[inline]
47    ///Subscribes window to clipboard changes.
48    pub fn new(window: &Window) -> Result<Self, ErrorCode> {
49        let window = window.inner();
50        unsafe {
51            if AddClipboardFormatListener(window) != 1 {
52                Err(ErrorCode::last_system())
53            } else {
54                Ok(ClipboardListener(window))
55            }
56        }
57    }
58}
59
60impl Drop for ClipboardListener {
61    #[inline]
62    fn drop(&mut self) {
63        unsafe {
64            RemoveClipboardFormatListener(self.0);
65        }
66    }
67}
68
69///Clipboard monitor
70///
71///This is implemented via dummy message-only window.
72///
73///This approach definitely works for console applications,
74///but it is not tested on windowed application
75///
76///Due to nature of implementation, it is not safe to move it into different thread.
77///
78///If needed, user should create monitor and pass Shutdown handle to the separate thread.
79///
80///Once created, messages will start accumulating immediately
81///
82///Therefore you generally should start listening for messages once you created instance
83///
84///`Monitor` implements `Iterator` by continuously calling `Monitor::recv` and returning the same result.
85///This `Iterator` is never ending, even when you perform shutdown.
86///
87///You should use `Shutdown` to interrupt blocking `Monitor::recv`
88pub struct Monitor {
89    _listener: ClipboardListener,
90    window: Window,
91}
92
93impl Monitor {
94    #[inline(always)]
95    ///Creates new instance
96    pub fn new() -> Result<Self, ErrorCode> {
97        let window = Window::from_builder(raw::window::Builder::new().class_name("STATIC").parent_message())?;
98        let _listener = ClipboardListener::new(&window)?;
99
100        Ok(Self {
101            _listener,
102            window
103        })
104    }
105
106    #[inline(always)]
107    fn iter(&self) -> Messages {
108        let mut msg = Messages::new();
109        msg.window(Some(self.window.inner()))
110           .low(Some(WM_CLIPBOARDUPDATE))
111           .high(Some(WM_CLIPBOARDUPDATE));
112        msg
113    }
114
115    #[inline(always)]
116    ///Creates shutdown channel.
117    pub fn shutdown_channel(&self) -> Shutdown {
118        Shutdown {
119            window: self.window.inner()
120        }
121    }
122
123    ///Waits for new clipboard message, blocking until then.
124    ///
125    ///Returns `Ok(true)` if event received.
126    ///
127    ///If `Shutdown` request detected, then return `Ok(false)`
128    pub fn recv(&mut self) -> Result<bool, ErrorCode> {
129        for msg in self.iter() {
130            let msg = msg?;
131            match msg.id() {
132                WM_CLIPBOARDUPDATE => return Ok(msg.inner().lParam != CLOSE_PARAM),
133                _ => unreachable!(),
134            }
135        }
136
137        unreachable!();
138    }
139
140    ///Attempts to get any clipboard update event
141    ///
142    ///Returns `Ok(true)` if event received,
143    ///otherwise return `Ok(false)` indicating no clipboard event present
144    ///
145    ///If `Shutdown` request detected, it is ignored
146    pub fn try_recv(&mut self) -> Result<bool, ErrorCode> {
147        let mut iter = self.iter();
148        iter.non_blocking();
149        while let Some(msg) = iter.next() {
150            let msg = msg?;
151            match msg.id() {
152                WM_CLIPBOARDUPDATE => {
153                    //Skip shutdown requests
154                    if msg.inner().lParam == CLOSE_PARAM {
155                        continue;
156                    }
157
158                    return Ok(true);
159                }
160                _ => unreachable!(),
161            }
162        }
163
164        Ok(false)
165    }
166}
167
168impl Iterator for Monitor {
169    type Item = Result<bool, ErrorCode>;
170
171    #[inline(always)]
172    fn next(&mut self) -> Option<Self::Item> {
173        Some(self.recv())
174    }
175}