diff --git a/src/common.rs b/src/common.rs index 221a883..adda67c 100644 --- a/src/common.rs +++ b/src/common.rs @@ -176,7 +176,7 @@ impl Drop for ScopeGuard { pub(crate) mod private { // This is currently unused on macOS, so silence the warning which appears // since there's no extension traits making use of this trait sealing structure. - #[cfg_attr(target_vendor = "apple", allow(unreachable_pub))] + #[cfg_attr(target_vendor = "apple", allow(unreachable_pub, dead_code))] pub trait Sealed {} impl Sealed for crate::Get<'_> {} diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index d8207e3..7c5c657 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -7,6 +7,10 @@ use log::{trace, warn}; use crate::ImageData; use crate::{common::private, Error}; +// Magic strings used in `Set::exclude_from_history()` on linux +const KDE_EXCLUSION_MIME: &str = "x-kde-passwordManagerHint"; +const KDE_EXCLUSION_HINT: &[u8] = b"secret"; + mod x11; #[cfg(feature = "wayland-data-control")] @@ -158,19 +162,29 @@ pub(crate) struct Set<'clipboard> { clipboard: &'clipboard mut Clipboard, wait: WaitConfig, selection: LinuxClipboardKind, + exclude_from_history: bool, } impl<'clipboard> Set<'clipboard> { pub(crate) fn new(clipboard: &'clipboard mut Clipboard) -> Self { - Self { clipboard, wait: WaitConfig::default(), selection: LinuxClipboardKind::Clipboard } + Self { + clipboard, + wait: WaitConfig::default(), + selection: LinuxClipboardKind::Clipboard, + exclude_from_history: false, + } } pub(crate) fn text(self, text: Cow<'_, str>) -> Result<(), Error> { match self.clipboard { - Clipboard::X11(clipboard) => clipboard.set_text(text, self.selection, self.wait), + Clipboard::X11(clipboard) => { + clipboard.set_text(text, self.selection, self.wait, self.exclude_from_history) + } #[cfg(feature = "wayland-data-control")] - Clipboard::WlDataControl(clipboard) => clipboard.set_text(text, self.selection, self.wait), + Clipboard::WlDataControl(clipboard) => { + clipboard.set_text(text, self.selection, self.wait, self.exclude_from_history) + } } } @@ -254,6 +268,13 @@ pub trait SetExtLinux: private::Sealed { /// # } /// ``` fn clipboard(self, selection: LinuxClipboardKind) -> Self; + + /// Excludes the data which will be set on the clipboard from being added to + /// the desktop clipboard managers' histories by adding the MIME-Type `x-kde-passwordMangagerHint` + /// to the clipboard's selection data. + /// + /// This is the most widely adopted convention on Linux. + fn exclude_from_history(self) -> Self; } impl SetExtLinux for crate::Set<'_> { @@ -271,6 +292,11 @@ impl SetExtLinux for crate::Set<'_> { self.platform.wait = WaitConfig::Until(deadline); self } + + fn exclude_from_history(mut self) -> Self { + self.platform.exclude_from_history = true; + self + } } pub(crate) struct Clear<'clipboard> { diff --git a/src/platform/linux/wayland.rs b/src/platform/linux/wayland.rs index b3916a6..c3ca8fa 100644 --- a/src/platform/linux/wayland.rs +++ b/src/platform/linux/wayland.rs @@ -10,6 +10,7 @@ use wl_clipboard_rs::{ #[cfg(feature = "image-data")] use super::encode_as_png; use super::{into_unknown, LinuxClipboardKind, WaitConfig}; +use super::{KDE_EXCLUSION_HINT, KDE_EXCLUSION_MIME}; use crate::common::Error; #[cfg(feature = "image-data")] use crate::common::ImageData; @@ -79,12 +80,24 @@ impl Clipboard { text: Cow<'_, str>, selection: LinuxClipboardKind, wait: WaitConfig, + exclude_from_history: bool, ) -> Result<(), Error> { let mut opts = Options::new(); opts.foreground(matches!(wait, WaitConfig::Forever)); opts.clipboard(selection.try_into()?); let source = Source::Bytes(text.into_owned().into_bytes().into_boxed_slice()); - opts.copy(source, MimeType::Text).map_err(|e| match e { + if exclude_from_history { + opts.copy_multi(vec![ + MimeSource { source, mime_type: MimeType::Text }, + MimeSource { + source: Source::Bytes(Box::from(KDE_EXCLUSION_HINT)), + mime_type: MimeType::Specific(String::from(KDE_EXCLUSION_MIME)), + }, + ]) + } else { + opts.copy(source, MimeType::Text) + } + .map_err(|e| match e { CopyError::PrimarySelectionUnsupported => Error::ClipboardNotSupported, other => into_unknown(other), })?; diff --git a/src/platform/linux/x11.rs b/src/platform/linux/x11.rs index 0dd7c50..cc58669 100644 --- a/src/platform/linux/x11.rs +++ b/src/platform/linux/x11.rs @@ -23,7 +23,6 @@ use std::{ thread::JoinHandle, thread_local, time::{Duration, Instant}, - usize, }; use log::{error, trace, warn}; @@ -46,6 +45,7 @@ use x11rb::{ #[cfg(feature = "image-data")] use super::encode_as_png; use super::{into_unknown, LinuxClipboardKind, WaitConfig}; +use super::{KDE_EXCLUSION_HINT, KDE_EXCLUSION_MIME}; #[cfg(feature = "image-data")] use crate::ImageData; use crate::{common::ScopeGuard, Error}; @@ -80,6 +80,7 @@ x11rb::atom_manager! { HTML: b"text/html", PNG_MIME: b"image/png", + X_KDE_PASSWORDMANAGERHINT: KDE_EXCLUSION_MIME.as_bytes(), // This is just some random name for the property on our window, into which // the clipboard owner writes the data we requested. @@ -878,11 +879,19 @@ impl Clipboard { message: Cow<'_, str>, selection: LinuxClipboardKind, wait: WaitConfig, + exclude_from_history: bool, ) -> Result<()> { - let data = vec![ClipboardData { + let mut data = vec![]; + data.push(ClipboardData { bytes: message.into_owned().into_bytes(), format: self.inner.atoms.UTF8_STRING, - }]; + }); + if exclude_from_history { + data.push(ClipboardData { + bytes: KDE_EXCLUSION_HINT.to_vec(), + format: self.inner.atoms.X_KDE_PASSWORDMANAGERHINT, + }); + } self.inner.write(data, selection, wait) }