Skip to main content

clipboard_watcher/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use futures::{
4  Stream,
5  channel::mpsc::{self, Receiver, Sender},
6};
7use log::{debug, error, info, trace, warn};
8use std::{
9  collections::HashMap,
10  fmt::Display,
11  path::PathBuf,
12  pin::Pin,
13  sync::{
14    Arc, Mutex,
15    atomic::{AtomicBool, AtomicUsize, Ordering},
16    mpsc::sync_channel,
17  },
18  task::{Context, Poll},
19  thread::JoinHandle,
20  time::Duration,
21};
22
23mod body;
24pub use body::*;
25
26mod body_senders;
27use body_senders::*;
28
29mod error;
30pub use error::*;
31
32mod event_listener;
33pub use event_listener::*;
34
35mod logging;
36use logging::*;
37
38mod stream;
39pub use stream::*;
40
41mod formats;
42pub use formats::*;
43
44#[cfg(target_os = "linux")]
45mod linux {
46  pub(crate) mod driver;
47  pub(crate) mod observer;
48}
49#[cfg(target_os = "macos")]
50mod macos {
51  pub(crate) mod driver;
52  pub(crate) mod observer;
53}
54#[cfg(windows)]
55mod win {
56  mod driver;
57  mod observer;
58}
59
60pub(crate) trait Observer {
61  fn observe(&mut self, body_senders: Arc<BodySenders>);
62}
63
64/// The struct that is responsible for starting and stopping the Observer.
65#[derive(Debug)]
66pub(crate) struct Driver {
67  /// This is cloned and passed to the Observer threads to give them the interruption signal
68  pub(crate) stop: Arc<AtomicBool>,
69
70  /// This is the handle of the spawned Observer thread.
71  pub(crate) handle: Option<JoinHandle<()>>,
72}
73
74/// The context for the clipboard content
75#[derive(Clone, Copy)]
76pub struct ClipboardContext<'a> {
77  formats: &'a Formats,
78  #[cfg(target_os = "linux")]
79  x11: &'a linux::observer::X11Context,
80  #[cfg(target_os = "macos")]
81  pasteboard: &'a objc2::rc::Retained<objc2_app_kit::NSPasteboard>,
82}
83
84impl ClipboardContext<'_> {
85  /// Returns the list of [`Format`]s currently available on the clipboard.
86  #[must_use]
87  #[inline]
88  pub const fn formats(&self) -> &Formats {
89    self.formats
90  }
91
92  /// Checks if a particular format is currently present in the clipboard.
93  #[must_use]
94  #[inline]
95  pub fn has_format(&self, name: &str) -> bool {
96    self.formats.iter().any(|d| d.name.as_ref() == name)
97  }
98
99  /// Attempts to extract a particular [`Format`] from the list of available formats.
100  #[must_use]
101  #[inline]
102  pub fn get_format(&self, name: &str) -> Option<&Format> {
103    self.formats.iter().find(|d| d.name.as_ref() == name)
104  }
105
106  /// Attempts to read the content of a particular format as a 32 bit integer.
107  #[must_use]
108  #[inline]
109  pub fn get_format_as_u32(&self, name: &str) -> Option<u32> {
110    self
111      .get_format_data(name)
112      .and_then(|bytes| Some(u32::from_ne_bytes(bytes.try_into().ok()?)))
113  }
114
115  /// Attempts to read the raw data for a particular format.
116  #[must_use]
117  #[inline]
118  pub fn get_format_data(&self, name: &str) -> Option<Vec<u8>> {
119    self
120      .formats
121      .iter()
122      .find(|d| d.name.as_ref() == name)
123      .and_then(|f| self.get_data(f))
124  }
125}
126
127/// Receives the [`ClipboardContext`] and returns a boolean that indicates whether the content should
128/// be processed or not.
129///
130/// Can be useful to read particular formats like `ExcludeClipboardContentFromMonitorProcessing` that are
131/// placed in the clipboard by other applications.
132pub trait Gatekeeper: Send + Sync + 'static {
133  fn check(&self, ctx: ClipboardContext) -> bool;
134}
135
136impl<F> Gatekeeper for F
137where
138  F: Fn(ClipboardContext) -> bool + Send + Sync + 'static,
139{
140  #[inline]
141  fn check(&self, ctx: ClipboardContext) -> bool {
142    (self)(ctx)
143  }
144}
145
146#[derive(Default)]
147pub struct DefaultGatekeeper;
148
149impl Gatekeeper for DefaultGatekeeper {
150  #[inline]
151  fn check(&self, _: ClipboardContext) -> bool {
152    true
153  }
154}