{
+ await invoke('plugin:clipboard-manager|write_secret', { text })
+}
+
+export {
+ writeText,
+ readText,
+ writeHtml,
+ clear,
+ readImage,
+ writeImage,
+ writeSecret
+}
diff --git a/plugins/clipboard-manager/ios/Sources/ClipboardPlugin.swift b/plugins/clipboard-manager/ios/Sources/ClipboardPlugin.swift
index 11398ef6e..f27744bd5 100644
--- a/plugins/clipboard-manager/ios/Sources/ClipboardPlugin.swift
+++ b/plugins/clipboard-manager/ios/Sources/ClipboardPlugin.swift
@@ -11,6 +11,10 @@ enum WriteOptions: Codable {
case plainText(text: String)
}
+struct WriteSecretArgs: Decodable {
+ let text: String
+}
+
enum ReadClipData: Codable {
case plainText(text: String)
}
@@ -30,6 +34,15 @@ class ClipboardPlugin: Plugin {
}
+ @objc public func writeSecret(_ invoke: Invoke) throws {
+ let args = try invoke.parseArgs(WriteSecretArgs.self)
+ UIPasteboard.general.setItems(
+ [["public.utf8-plain-text": args.text]],
+ options: [.localOnly: true]
+ )
+ invoke.resolve()
+ }
+
@objc public func readText(_ invoke: Invoke) throws {
let clipboard = UIPasteboard.general
if let text = clipboard.string {
diff --git a/plugins/clipboard-manager/permissions/autogenerated/commands/write_secret.toml b/plugins/clipboard-manager/permissions/autogenerated/commands/write_secret.toml
new file mode 100644
index 000000000..6c28172a0
--- /dev/null
+++ b/plugins/clipboard-manager/permissions/autogenerated/commands/write_secret.toml
@@ -0,0 +1,13 @@
+# Automatically generated - DO NOT EDIT!
+
+"$schema" = "../../schemas/schema.json"
+
+[[permission]]
+identifier = "allow-write-secret"
+description = "Enables the write_secret command without any pre-configured scope."
+commands.allow = ["write_secret"]
+
+[[permission]]
+identifier = "deny-write-secret"
+description = "Denies the write_secret command without any pre-configured scope."
+commands.deny = ["write_secret"]
diff --git a/plugins/clipboard-manager/permissions/autogenerated/reference.md b/plugins/clipboard-manager/permissions/autogenerated/reference.md
index 98a7fa961..d4c2430ff 100644
--- a/plugins/clipboard-manager/permissions/autogenerated/reference.md
+++ b/plugins/clipboard-manager/permissions/autogenerated/reference.md
@@ -148,6 +148,32 @@ Denies the write_image command without any pre-configured scope.
|
+`clipboard-manager:allow-write-secret`
+
+ |
+
+
+Enables the write_secret command without any pre-configured scope.
+
+ |
+
+
+
+|
+
+`clipboard-manager:deny-write-secret`
+
+ |
+
+
+Denies the write_secret command without any pre-configured scope.
+
+ |
+
+
+
+|
+
`clipboard-manager:allow-write-text`
|
diff --git a/plugins/clipboard-manager/permissions/schemas/schema.json b/plugins/clipboard-manager/permissions/schemas/schema.json
index 891c6f0d8..ee2447033 100644
--- a/plugins/clipboard-manager/permissions/schemas/schema.json
+++ b/plugins/clipboard-manager/permissions/schemas/schema.json
@@ -354,6 +354,18 @@
"const": "deny-write-image",
"markdownDescription": "Denies the write_image command without any pre-configured scope."
},
+ {
+ "description": "Enables the write_secret command without any pre-configured scope.",
+ "type": "string",
+ "const": "allow-write-secret",
+ "markdownDescription": "Enables the write_secret command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the write_secret command without any pre-configured scope.",
+ "type": "string",
+ "const": "deny-write-secret",
+ "markdownDescription": "Denies the write_secret command without any pre-configured scope."
+ },
{
"description": "Enables the write_text command without any pre-configured scope.",
"type": "string",
diff --git a/plugins/clipboard-manager/src/commands.rs b/plugins/clipboard-manager/src/commands.rs
index a903e9844..5272972d6 100644
--- a/plugins/clipboard-manager/src/commands.rs
+++ b/plugins/clipboard-manager/src/commands.rs
@@ -78,3 +78,12 @@ pub(crate) async fn clear(
) -> Result<()> {
clipboard.clear()
}
+
+#[command]
+pub(crate) async fn write_secret(
+ _app: AppHandle,
+ clipboard: State<'_, Clipboard>,
+ text: &str,
+) -> Result<()> {
+ clipboard.write_secret(text)
+}
diff --git a/plugins/clipboard-manager/src/desktop.rs b/plugins/clipboard-manager/src/desktop.rs
index 6cb1ca9b7..e0c4a020a 100644
--- a/plugins/clipboard-manager/src/desktop.rs
+++ b/plugins/clipboard-manager/src/desktop.rs
@@ -28,6 +28,16 @@ pub struct Clipboard {
}
impl Clipboard {
+ fn with_clipboard(
+ &self,
+ f: impl FnOnce(&mut arboard::Clipboard) -> Result,
+ ) -> crate::Result {
+ match &self.clipboard {
+ Ok(clipboard) => f(clipboard.lock().unwrap().as_mut().unwrap()).map_err(Into::into),
+ Err(e) => Err(crate::Error::Clipboard(e.to_string())),
+ }
+ }
+
/// Writes plain text to the system clipboard.
///
/// # Errors
@@ -35,16 +45,7 @@ impl Clipboard {
/// Returns [`crate::Error::Clipboard`] if the clipboard could not be initialized or the
/// underlying [`arboard`] operation fails.
pub fn write_text<'a, T: Into>>(&self, text: T) -> crate::Result<()> {
- match &self.clipboard {
- Ok(clipboard) => clipboard
- .lock()
- .unwrap()
- .as_mut()
- .unwrap()
- .set_text(text)
- .map_err(Into::into),
- Err(e) => Err(crate::Error::Clipboard(e.to_string())),
- }
+ self.with_clipboard(|c| c.set_text(text))
}
/// Writes an image to the system clipboard as RGBA data.
@@ -54,31 +55,18 @@ impl Clipboard {
/// Returns [`crate::Error::Clipboard`] if the clipboard could not be initialized or the
/// underlying [`arboard`] operation fails.
pub fn write_image(&self, image: &Image<'_>) -> crate::Result<()> {
- match &self.clipboard {
- Ok(clipboard) => clipboard
- .lock()
- .unwrap()
- .as_mut()
- .unwrap()
- .set_image(ImageData {
- bytes: Cow::Borrowed(image.rgba()),
- width: image.width() as usize,
- height: image.height() as usize,
- })
- .map_err(Into::into),
- Err(e) => Err(crate::Error::Clipboard(e.to_string())),
- }
+ self.with_clipboard(|c| {
+ c.set_image(ImageData {
+ bytes: Cow::Borrowed(image.rgba()),
+ width: image.width() as usize,
+ height: image.height() as usize,
+ })
+ })
}
/// Warning: This method should not be used on the main thread! Otherwise the underlying libraries may deadlock on Linux, freezing the whole app, when trying to copy data copied from this app, for example if the user copies text from the WebView.
pub fn read_text(&self) -> crate::Result {
- match &self.clipboard {
- Ok(clipboard) => {
- let text = clipboard.lock().unwrap().as_mut().unwrap().get_text()?;
- Ok(text)
- }
- Err(e) => Err(crate::Error::Clipboard(e.to_string())),
- }
+ self.with_clipboard(|c| c.get_text())
}
/// Writes HTML to the system clipboard, with an optional plain text fallback for targets
@@ -93,16 +81,7 @@ impl Clipboard {
html: T,
alt_text: Option,
) -> crate::Result<()> {
- match &self.clipboard {
- Ok(clipboard) => clipboard
- .lock()
- .unwrap()
- .as_mut()
- .unwrap()
- .set_html(html, alt_text)
- .map_err(Into::into),
- Err(e) => Err(crate::Error::Clipboard(e.to_string())),
- }
+ self.with_clipboard(|c| c.set_html(html, alt_text))
}
/// Clears the system clipboard.
@@ -112,32 +91,17 @@ impl Clipboard {
/// Returns [`crate::Error::Clipboard`] if the clipboard could not be initialized or the
/// underlying [`arboard`] operation fails.
pub fn clear(&self) -> crate::Result<()> {
- match &self.clipboard {
- Ok(clipboard) => clipboard
- .lock()
- .unwrap()
- .as_mut()
- .unwrap()
- .clear()
- .map_err(Into::into),
- Err(e) => Err(crate::Error::Clipboard(e.to_string())),
- }
+ self.with_clipboard(|c| c.clear())
}
/// Warning: This method should not be used on the main thread! Otherwise the underlying libraries may deadlock on Linux, freezing the whole app, when trying to copy data copied from this app, for example if the user copies text from the WebView.
pub fn read_image(&self) -> crate::Result> {
- match &self.clipboard {
- Ok(clipboard) => {
- let image = clipboard.lock().unwrap().as_mut().unwrap().get_image()?;
- let image = Image::new_owned(
- image.bytes.to_vec(),
- image.width as u32,
- image.height as u32,
- );
- Ok(image)
- }
- Err(e) => Err(crate::Error::Clipboard(e.to_string())),
- }
+ let image = self.with_clipboard(|c| c.get_image())?;
+ Ok(Image::new_owned(
+ image.bytes.to_vec(),
+ image.width as u32,
+ image.height as u32,
+ ))
}
pub(crate) fn cleanup(&self) {
@@ -145,4 +109,9 @@ impl Clipboard {
clipboard.lock().unwrap().take();
}
}
+
+ /// This is the same as write_text but it will set hints using `arboard`'s `SetExt*` extensions so clipboard managers exclude the text from history.
+ pub fn write_secret<'a, T: Into>>(&self, text: T) -> crate::Result<()> {
+ self.with_clipboard(|c| crate::secret::exclude_secret(c.set()).text(text))
+ }
}
diff --git a/plugins/clipboard-manager/src/lib.rs b/plugins/clipboard-manager/src/lib.rs
index 024f6f593..b1e3714cc 100644
--- a/plugins/clipboard-manager/src/lib.rs
+++ b/plugins/clipboard-manager/src/lib.rs
@@ -22,6 +22,9 @@ mod mobile;
mod commands;
mod error;
+#[cfg(desktop)]
+mod secret;
+
pub use error::{Error, Result};
#[cfg(desktop)]
@@ -50,7 +53,8 @@ pub fn init() -> TauriPlugin {
commands::read_image,
commands::write_image,
commands::write_html,
- commands::clear
+ commands::clear,
+ commands::write_secret,
])
.setup(|app, api| {
#[cfg(mobile)]
diff --git a/plugins/clipboard-manager/src/mobile.rs b/plugins/clipboard-manager/src/mobile.rs
index bb71b6ce7..4f66961ce 100644
--- a/plugins/clipboard-manager/src/mobile.rs
+++ b/plugins/clipboard-manager/src/mobile.rs
@@ -123,6 +123,22 @@ impl Clipboard {
))
}
+ /// Writes plain text to the system clipboard, marked as sensitive.
+ ///
+ /// On Android the clip is flagged with `ClipDescription.EXTRA_IS_SENSITIVE`, which hides it
+ /// from the clipboard preview. On iOS the text is written as local only, so it is not
+ /// synced to other devices through Universal Clipboard.
+ ///
+ /// # Errors
+ ///
+ /// Returns [`crate::Error::PluginInvoke`] if the underlying mobile plugin call fails.
+ pub fn write_secret<'a, T: Into>>(&self, text: T) -> crate::Result<()> {
+ let text = text.into().to_string();
+ self.0
+ .run_mobile_plugin("writeSecret", WriteSecretArgs { text })
+ .map_err(Into::into)
+ }
+
/// Clears the system clipboard.
///
/// On Android this only works on SDK 28 and above; on older versions the clipboard is
@@ -142,6 +158,11 @@ enum ClipKind {
PlainText { label: Option, text: String },
}
+#[derive(Debug, Serialize)]
+struct WriteSecretArgs {
+ text: String,
+}
+
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum ClipboardContents {
diff --git a/plugins/clipboard-manager/src/secret.rs b/plugins/clipboard-manager/src/secret.rs
new file mode 100644
index 000000000..0e4777db9
--- /dev/null
+++ b/plugins/clipboard-manager/src/secret.rs
@@ -0,0 +1,23 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+#[cfg(windows)]
+use arboard::SetExtWindows;
+
+#[cfg(target_os = "macos")]
+use arboard::SetExtApple;
+
+#[cfg(not(any(windows, target_os = "macos")))]
+use arboard::SetExtLinux;
+
+/// Hints clipboard managers to exclude the data from history.
+///
+/// On Windows, it calls [`arboard::SetExtWindows::exclude_from_monitoring`], which also covers history and cloud sync.
+/// On macOS and Linux, it calls `exclude_from_history`.
+pub(crate) fn exclude_secret(set: arboard::Set<'_>) -> arboard::Set<'_> {
+ #[cfg(windows)]
+ return set.exclude_from_monitoring();
+ #[cfg(not(windows))]
+ return set.exclude_from_history();
+}