1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
//! Clipboard monitoring utility

use error_code::ErrorCode;
use windows_win::{
    raw,
    Window,
    Messages
};

use windows_win::sys::{
    HWND,
    AddClipboardFormatListener,
    RemoveClipboardFormatListener,
    PostMessageW,
    WM_CLIPBOARDUPDATE,
};

const CLOSE_PARAM: isize = -1;

///Shutdown channel
///
///On drop requests shutdown to gracefully close clipboard listener as soon as possible.
///
///This is silently ignored, if there is no thread awaiting
pub struct Shutdown {
    window: HWND,
}

unsafe impl Send for Shutdown {}

impl Drop for Shutdown {
    #[inline(always)]
    fn drop(&mut self) {
        unsafe {
            PostMessageW(self.window, WM_CLIPBOARDUPDATE, 0, CLOSE_PARAM)
        };
    }
}

///Clipboard listener guard.
///
///On drop unsubscribes window from listening on clipboard changes
struct ClipboardListener(HWND);

impl ClipboardListener {
    #[inline]
    ///Subscribes window to clipboard changes.
    pub fn new(window: &Window) -> Result<Self, ErrorCode> {
        let window = window.inner();
        unsafe {
            if AddClipboardFormatListener(window) != 1 {
                Err(ErrorCode::last_system())
            } else {
                Ok(ClipboardListener(window))
            }
        }
    }
}

impl Drop for ClipboardListener {
    #[inline]
    fn drop(&mut self) {
        unsafe {
            RemoveClipboardFormatListener(self.0);
        }
    }
}

///Clipboard monitor
///
///This is implemented via dummy message-only window.
///
///This approach definitely works for console applications,
///but it is not tested on windowed application
///
///Due to nature of implementation, it is not safe to move it into different thread.
///
///If needed, user should create monitor and pass Shutdown handle to the separate thread.
///
///Once created, messages will start accumulating immediately
///
///Therefore you generally should start listening for messages once you created instance
///
///`Monitor` implements `Iterator` by continuously calling `Monitor::recv` and returning the same result.
///This `Iterator` is never ending, even when you perform shutdown.
///
///You should use `Shutdown` to interrupt blocking `Monitor::recv`
pub struct Monitor {
    _listener: ClipboardListener,
    window: Window,
}

impl Monitor {
    #[inline(always)]
    ///Creates new instance
    pub fn new() -> Result<Self, ErrorCode> {
        let window = Window::from_builder(raw::window::Builder::new().class_name("STATIC").parent_message())?;
        let _listener = ClipboardListener::new(&window)?;

        Ok(Self {
            _listener,
            window
        })
    }

    #[inline(always)]
    fn iter(&self) -> Messages {
        let mut msg = Messages::new();
        msg.window(Some(self.window.inner()))
           .low(Some(WM_CLIPBOARDUPDATE))
           .high(Some(WM_CLIPBOARDUPDATE));
        msg
    }

    #[inline(always)]
    ///Creates shutdown channel.
    pub fn shutdown_channel(&self) -> Shutdown {
        Shutdown {
            window: self.window.inner()
        }
    }

    ///Waits for new clipboard message, blocking until then.
    ///
    ///Returns `Ok(true)` if event received.
    ///
    ///If `Shutdown` request detected, then return `Ok(false)`
    pub fn recv(&mut self) -> Result<bool, ErrorCode> {
        for msg in self.iter() {
            let msg = msg?;
            match msg.id() {
                WM_CLIPBOARDUPDATE => return Ok(msg.inner().lParam != CLOSE_PARAM),
                _ => unreachable!(),
            }
        }

        unreachable!();
    }

    ///Attempts to get any clipboard update event
    ///
    ///Returns `Ok(true)` if event received,
    ///otherwise return `Ok(false)` indicating no clipboard event present
    ///
    ///If `Shutdown` request detected, it is ignored
    pub fn try_recv(&mut self) -> Result<bool, ErrorCode> {
        let mut iter = self.iter();
        iter.non_blocking();
        while let Some(msg) = iter.next() {
            let msg = msg?;
            match msg.id() {
                WM_CLIPBOARDUPDATE => {
                    //Skip shutdown requests
                    if msg.inner().lParam == CLOSE_PARAM {
                        continue;
                    }

                    return Ok(true);
                }
                _ => unreachable!(),
            }
        }

        Ok(false)
    }
}

impl Iterator for Monitor {
    type Item = Result<bool, ErrorCode>;

    #[inline(always)]
    fn next(&mut self) -> Option<Self::Item> {
        Some(self.recv())
    }
}