From 182aed6724a9a514799cf58fe2e4110c4a25d1dd Mon Sep 17 00:00:00 2001 From: cijiugechu Date: Thu, 21 Dec 2023 18:46:40 +0800 Subject: [PATCH 01/17] feat(clipboard): support `read_image` & `write_image` --- .changes/clipboard-image.md | 6 + Cargo.lock | 129 +++++++++++++++++++- plugins/clipboard-manager/Cargo.toml | 1 + plugins/clipboard-manager/guest-js/index.ts | 52 +++++++- plugins/clipboard-manager/src/api-iife.js | 2 +- plugins/clipboard-manager/src/commands.rs | 12 +- plugins/clipboard-manager/src/desktop.rs | 67 +++++++++- plugins/clipboard-manager/src/error.rs | 2 + plugins/clipboard-manager/src/lib.rs | 6 +- plugins/clipboard-manager/src/models.rs | 2 + 10 files changed, 267 insertions(+), 12 deletions(-) create mode 100644 .changes/clipboard-image.md diff --git a/.changes/clipboard-image.md b/.changes/clipboard-image.md new file mode 100644 index 0000000000..75da088bf7 --- /dev/null +++ b/.changes/clipboard-image.md @@ -0,0 +1,6 @@ +--- +"clipboard": "minor" +"clipboard-js": "minor" +--- + +Add support for `read_image` and `write_image` to the clipboard plugin. \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 1eb5ca1261..9c33e77468 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -230,7 +230,7 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "api" -version = "2.0.0-alpha.9" +version = "2.0.0-alpha.10" dependencies = [ "log", "serde", @@ -632,6 +632,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -1231,6 +1237,30 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset 0.9.0", + "scopeguard", +] + [[package]] name = "crossbeam-queue" version = "0.3.8" @@ -1250,6 +1280,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -1695,6 +1731,22 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "exr" +version = "1.71.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -2178,6 +2230,16 @@ dependencies = [ "polyval 0.6.1", ] +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.28.0" @@ -2464,6 +2526,15 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -2735,9 +2806,13 @@ dependencies = [ "bytemuck", "byteorder", "color_quant", + "exr", + "gif", + "jpeg-decoder", "num-rational", "num-traits", "png", + "qoi", "tiff", ] @@ -3011,6 +3086,9 @@ name = "jpeg-decoder" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +dependencies = [ + "rayon", +] [[package]] name = "js-sys" @@ -3097,6 +3175,12 @@ dependencies = [ "spin 0.5.2", ] +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libappindicator" version = "0.9.0" @@ -4341,6 +4425,15 @@ dependencies = [ "psl-types", ] +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -4508,6 +4601,26 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "read-progress-stream" version = "1.0.0" @@ -5955,6 +6068,7 @@ name = "tauri-plugin-clipboard-manager" version = "2.0.0-alpha.6" dependencies = [ "arboard", + "image", "log", "serde", "serde_json", @@ -5965,7 +6079,7 @@ dependencies = [ [[package]] name = "tauri-plugin-deep-link" -version = "2.0.0-alpha.4" +version = "2.0.0-alpha.5" dependencies = [ "log", "serde", @@ -6022,7 +6136,7 @@ dependencies = [ [[package]] name = "tauri-plugin-http" -version = "2.0.0-alpha.7" +version = "2.0.0-alpha.8" dependencies = [ "data-url", "glob", @@ -8048,6 +8162,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + [[package]] name = "zvariant" version = "3.15.0" diff --git a/plugins/clipboard-manager/Cargo.toml b/plugins/clipboard-manager/Cargo.toml index b5e9df8da9..46afe650a6 100644 --- a/plugins/clipboard-manager/Cargo.toml +++ b/plugins/clipboard-manager/Cargo.toml @@ -25,3 +25,4 @@ thiserror = { workspace = true } [target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] arboard = "3" +image = "0.24" diff --git a/plugins/clipboard-manager/guest-js/index.ts b/plugins/clipboard-manager/guest-js/index.ts index 8338ac1047..c6194e2299 100644 --- a/plugins/clipboard-manager/guest-js/index.ts +++ b/plugins/clipboard-manager/guest-js/index.ts @@ -12,6 +12,8 @@ import { invoke } from "@tauri-apps/api/core"; type ClipResponse = Record<"plainText", { text: string }>; +type ClipImageResponse = Record<"image", { buffer: number[] }>; + /** * Writes plain text to the clipboard. * @example @@ -49,8 +51,54 @@ async function writeText( * @since 2.0.0 */ async function readText(): Promise { - const kind: ClipResponse = await invoke("plugin:clipboard|read"); + const kind: ClipResponse = await invoke("plugin:clipboard|read_text"); return kind.plainText.text; } -export { writeText, readText }; +/** + * Gets the clipboard content as Uint8Array image. + * @example + * ```typescript + * import { readImage } from '@tauri-apps/plugin-clipboard-manager'; + * + * const clipboardImage = await readImage(); + * const blob = new Blob([clipboardImage.buffer], { type: 'image' }) + * const url = URL.createObjectURL(blob) + * ``` + * @since 2.0.0 + */ +async function readImage(): Promise { + const kind: ClipImageResponse = await invoke("plugin:clipboard|read_image"); + return Uint8Array.from(kind.image.buffer); +} + +/** + * Writes image buffer to the clipboard. + * @example + * ```typescript + * import { writeImage } from '@tauri-apps/plugin-clipboard-manager'; + * const buffer = [ + * // A red pixel + * 255, 0, 0, 255, + * + * // A green pixel + * 0, 255, 0, 255, + * ]; + * await writeImage(buffer); + * ``` + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.0.0 + */ +async function writeImage(buffer: Uint8Array | Array): Promise { + return invoke("plugin:clipboard|write", { + data: { + image: { + buffer: Array.isArray(buffer) ? buffer : Array.from(buffer), + }, + }, + }); +} + +export { writeText, readText, readImage, writeImage }; diff --git a/plugins/clipboard-manager/src/api-iife.js b/plugins/clipboard-manager/src/api-iife.js index 9e494285af..a7e6ba4afc 100644 --- a/plugins/clipboard-manager/src/api-iife.js +++ b/plugins/clipboard-manager/src/api-iife.js @@ -1 +1 @@ -if("__TAURI__"in window){var __TAURI_PLUGIN_CLIPBOARDMANAGER__=function(e){"use strict";async function n(e,n={},r){return window.__TAURI_INTERNALS__.invoke(e,n,r)}return"function"==typeof SuppressedError&&SuppressedError,e.readText=async function(){return(await n("plugin:clipboard|read")).plainText.text},e.writeText=async function(e,r){return n("plugin:clipboard|write",{data:{plainText:{label:r?.label,text:e}}})},e}({});Object.defineProperty(window.__TAURI__,"clipboardManager",{value:__TAURI_PLUGIN_CLIPBOARDMANAGER__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_CLIPBOARDMANAGER__=function(r){"use strict";async function e(r,e={},n){return window.__TAURI_INTERNALS__.invoke(r,e,n)}return"function"==typeof SuppressedError&&SuppressedError,r.readImage=async function(){const r=await e("plugin:clipboard|read_image");return Uint8Array.from(r.image.buffer)},r.readText=async function(){return(await e("plugin:clipboard|read_text")).plainText.text},r.writeImage=async function(r){return e("plugin:clipboard|write",{data:{image:{buffer:Array.isArray(r)?r:Array.from(r)}}})},r.writeText=async function(r,n){return e("plugin:clipboard|write",{data:{plainText:{label:n?.label,text:r}}})},r}({});Object.defineProperty(window.__TAURI__,"clipboardManager",{value:__TAURI_PLUGIN_CLIPBOARDMANAGER__})} diff --git a/plugins/clipboard-manager/src/commands.rs b/plugins/clipboard-manager/src/commands.rs index eec868a84f..ec8e03d65d 100644 --- a/plugins/clipboard-manager/src/commands.rs +++ b/plugins/clipboard-manager/src/commands.rs @@ -16,9 +16,17 @@ pub(crate) async fn write( } #[command] -pub(crate) async fn read( +pub(crate) async fn read_text( _app: AppHandle, clipboard: State<'_, Clipboard>, ) -> Result { - clipboard.read() + clipboard.read_text() +} + +#[command] +pub(crate) async fn read_image( + _app: AppHandle, + clipboard: State<'_, Clipboard>, +) -> Result { + clipboard.read_image() } diff --git a/plugins/clipboard-manager/src/desktop.rs b/plugins/clipboard-manager/src/desktop.rs index 555321dc18..7f8cacc024 100644 --- a/plugins/clipboard-manager/src/desktop.rs +++ b/plugins/clipboard-manager/src/desktop.rs @@ -2,12 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use arboard::ImageData; +use image::{GenericImageView, ImageEncoder}; use serde::de::DeserializeOwned; use tauri::{plugin::PluginApi, AppHandle, Runtime}; use crate::models::*; -use std::sync::Mutex; +use std::io::{BufWriter, Cursor}; +use std::{borrow::Cow, sync::Mutex}; pub fn init( app: &AppHandle, @@ -28,14 +31,34 @@ pub struct Clipboard { impl Clipboard { pub fn write(&self, kind: ClipKind) -> crate::Result<()> { - let ClipKind::PlainText { text, .. } = kind; + match kind { + ClipKind::PlainText { text, .. } => self.write_text(text), + ClipKind::Image { buffer } => self.write_image(buffer), + } + } + + fn write_text(&self, text: String) -> crate::Result<()> { match &self.clipboard { Ok(clipboard) => clipboard.lock().unwrap().set_text(text).map_err(Into::into), Err(e) => Err(crate::Error::Clipboard(e.to_string())), } } - pub fn read(&self) -> crate::Result { + fn write_image(&self, buffer: Vec) -> crate::Result<()> { + match &self.clipboard { + Ok(clipboard) => { + let image = buffer_to_image_data(&buffer)?; + clipboard + .lock() + .unwrap() + .set_image(image) + .map_err(Into::into) + } + Err(e) => Err(crate::Error::Clipboard(e.to_string())), + } + } + + pub fn read_text(&self) -> crate::Result { match &self.clipboard { Ok(clipboard) => { let text = clipboard.lock().unwrap().get_text()?; @@ -44,4 +67,42 @@ impl Clipboard { Err(e) => Err(crate::Error::Clipboard(e.to_string())), } } + + pub fn read_image(&self) -> crate::Result { + match &self.clipboard { + Ok(clipboard) => { + let image = clipboard.lock().unwrap().get_image()?; + let buffer = image_data_to_buffer(&image)?; + Ok(ClipboardContents::Image { buffer }) + } + Err(e) => Err(crate::Error::Clipboard(e.to_string())), + } + } +} + +fn buffer_to_image_data(buffer: &[u8]) -> crate::Result { + let loaded = image::load_from_memory(buffer)?; + + let pixels = loaded + .pixels() + .flat_map(|(_, _, pixel)| pixel.0) + .collect::>(); + + Ok(ImageData { + width: loaded.width() as usize, + height: loaded.height() as usize, + bytes: Cow::Owned(pixels), + }) +} + +// copied from https://github.com/CrossCopy/tauri-plugin-clipboard/blob/main/src/util.rs +fn image_data_to_buffer(img: &ImageData) -> crate::Result> { + let mut buffer: Vec = Vec::new(); + image::codecs::png::PngEncoder::new(BufWriter::new(Cursor::new(&mut buffer))).write_image( + &img.bytes, + img.width as u32, + img.height as u32, + image::ColorType::Rgba8, + )?; + Ok(buffer) } diff --git a/plugins/clipboard-manager/src/error.rs b/plugins/clipboard-manager/src/error.rs index bf71802fec..402951590d 100644 --- a/plugins/clipboard-manager/src/error.rs +++ b/plugins/clipboard-manager/src/error.rs @@ -14,6 +14,8 @@ pub enum Error { #[cfg(desktop)] #[error("{0}")] Clipboard(String), + #[error("invalid image: {0}")] + Image(#[from] image::ImageError), } impl Serialize for Error { diff --git a/plugins/clipboard-manager/src/lib.rs b/plugins/clipboard-manager/src/lib.rs index d4ccdb0ab2..f6149d0686 100644 --- a/plugins/clipboard-manager/src/lib.rs +++ b/plugins/clipboard-manager/src/lib.rs @@ -49,7 +49,11 @@ impl> crate::ClipboardExt for T { pub fn init() -> TauriPlugin { Builder::new("clipboard") .js_init_script(include_str!("api-iife.js").to_string()) - .invoke_handler(tauri::generate_handler![commands::write, commands::read]) + .invoke_handler(tauri::generate_handler![ + commands::write, + commands::read_text, + commands::read_image, + ]) .setup(|app, api| { #[cfg(mobile)] let clipboard = mobile::init(app, api)?; diff --git a/plugins/clipboard-manager/src/models.rs b/plugins/clipboard-manager/src/models.rs index a223a679da..10c0ba41fb 100644 --- a/plugins/clipboard-manager/src/models.rs +++ b/plugins/clipboard-manager/src/models.rs @@ -8,10 +8,12 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all = "camelCase")] pub enum ClipKind { PlainText { label: Option, text: String }, + Image { buffer: Vec }, } #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum ClipboardContents { PlainText { text: String }, + Image { buffer: Vec }, } From 076677b68ec7fd9c9962e5d773835c7ea2c9a510 Mon Sep 17 00:00:00 2001 From: cijiugechu Date: Thu, 21 Dec 2023 18:55:59 +0800 Subject: [PATCH 02/17] fix plugin name --- .changes/{clipboard-image.md => clipboard-manager-image.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename .changes/{clipboard-image.md => clipboard-manager-image.md} (56%) diff --git a/.changes/clipboard-image.md b/.changes/clipboard-manager-image.md similarity index 56% rename from .changes/clipboard-image.md rename to .changes/clipboard-manager-image.md index 75da088bf7..37699467e9 100644 --- a/.changes/clipboard-image.md +++ b/.changes/clipboard-manager-image.md @@ -1,6 +1,6 @@ --- -"clipboard": "minor" -"clipboard-js": "minor" +"clipboard-manager": "minor" +"clipboard-manager-js": "minor" --- Add support for `read_image` and `write_image` to the clipboard plugin. \ No newline at end of file From dbfafad3fa0101ec81f6c0938c6e3ca099b06834 Mon Sep 17 00:00:00 2001 From: cijiugechu Date: Thu, 21 Dec 2023 19:33:06 +0800 Subject: [PATCH 03/17] platform specific bahavior --- plugins/clipboard-manager/src/commands.rs | 2 +- plugins/clipboard-manager/src/desktop.rs | 2 +- plugins/clipboard-manager/src/error.rs | 1 + plugins/clipboard-manager/src/lib.rs | 1 + plugins/clipboard-manager/src/mobile.rs | 4 ++++ 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/clipboard-manager/src/commands.rs b/plugins/clipboard-manager/src/commands.rs index ec8e03d65d..f75af735f4 100644 --- a/plugins/clipboard-manager/src/commands.rs +++ b/plugins/clipboard-manager/src/commands.rs @@ -20,7 +20,7 @@ pub(crate) async fn read_text( _app: AppHandle, clipboard: State<'_, Clipboard>, ) -> Result { - clipboard.read_text() + clipboard.read() } #[command] diff --git a/plugins/clipboard-manager/src/desktop.rs b/plugins/clipboard-manager/src/desktop.rs index 7f8cacc024..ec418bd971 100644 --- a/plugins/clipboard-manager/src/desktop.rs +++ b/plugins/clipboard-manager/src/desktop.rs @@ -58,7 +58,7 @@ impl Clipboard { } } - pub fn read_text(&self) -> crate::Result { + pub fn read(&self) -> crate::Result { match &self.clipboard { Ok(clipboard) => { let text = clipboard.lock().unwrap().get_text()?; diff --git a/plugins/clipboard-manager/src/error.rs b/plugins/clipboard-manager/src/error.rs index 402951590d..9cf4996525 100644 --- a/plugins/clipboard-manager/src/error.rs +++ b/plugins/clipboard-manager/src/error.rs @@ -14,6 +14,7 @@ pub enum Error { #[cfg(desktop)] #[error("{0}")] Clipboard(String), + #[cfg(desktop)] #[error("invalid image: {0}")] Image(#[from] image::ImageError), } diff --git a/plugins/clipboard-manager/src/lib.rs b/plugins/clipboard-manager/src/lib.rs index f6149d0686..b53c0bd0c7 100644 --- a/plugins/clipboard-manager/src/lib.rs +++ b/plugins/clipboard-manager/src/lib.rs @@ -52,6 +52,7 @@ pub fn init() -> TauriPlugin { .invoke_handler(tauri::generate_handler![ commands::write, commands::read_text, + #[cfg(desktop)] commands::read_image, ]) .setup(|app, api| { diff --git a/plugins/clipboard-manager/src/mobile.rs b/plugins/clipboard-manager/src/mobile.rs index 0796471126..0833824bcf 100644 --- a/plugins/clipboard-manager/src/mobile.rs +++ b/plugins/clipboard-manager/src/mobile.rs @@ -39,4 +39,8 @@ impl Clipboard { pub fn read(&self) -> crate::Result { self.0.run_mobile_plugin("read", ()).map_err(Into::into) } + + pub fn read_image(&self) -> crate::Result { + panic!("unsupported on this platform") + } } From 68176ed7a23aaa2e3b0bc9078bb3da2de8185662 Mon Sep 17 00:00:00 2001 From: cijiugechu Date: Tue, 26 Dec 2023 18:20:47 +0800 Subject: [PATCH 04/17] remove unnecessary BufWriter --- plugins/clipboard-manager/src/desktop.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/clipboard-manager/src/desktop.rs b/plugins/clipboard-manager/src/desktop.rs index ec418bd971..83c992fbfa 100644 --- a/plugins/clipboard-manager/src/desktop.rs +++ b/plugins/clipboard-manager/src/desktop.rs @@ -9,7 +9,6 @@ use tauri::{plugin::PluginApi, AppHandle, Runtime}; use crate::models::*; -use std::io::{BufWriter, Cursor}; use std::{borrow::Cow, sync::Mutex}; pub fn init( @@ -98,7 +97,7 @@ fn buffer_to_image_data(buffer: &[u8]) -> crate::Result { // copied from https://github.com/CrossCopy/tauri-plugin-clipboard/blob/main/src/util.rs fn image_data_to_buffer(img: &ImageData) -> crate::Result> { let mut buffer: Vec = Vec::new(); - image::codecs::png::PngEncoder::new(BufWriter::new(Cursor::new(&mut buffer))).write_image( + image::codecs::png::PngEncoder::new(&mut buffer).write_image( &img.bytes, img.width as u32, img.height as u32, From 1ef615004029db3c24fd2f99ff2c738aabd46525 Mon Sep 17 00:00:00 2001 From: cijiugechu Date: Thu, 28 Dec 2023 19:58:00 +0800 Subject: [PATCH 05/17] improvement --- .../android/src/main/java/ClipboardPlugin.kt | 4 +- plugins/clipboard-manager/guest-js/index.ts | 4 +- .../ios/Sources/ClipboardPlugin.swift | 4 +- plugins/clipboard-manager/src/api-iife.js | 2 +- plugins/clipboard-manager/src/commands.rs | 15 +++++-- plugins/clipboard-manager/src/desktop.rs | 43 +++++++++---------- plugins/clipboard-manager/src/lib.rs | 4 +- plugins/clipboard-manager/src/mobile.rs | 14 ++++-- 8 files changed, 54 insertions(+), 36 deletions(-) diff --git a/plugins/clipboard-manager/android/src/main/java/ClipboardPlugin.kt b/plugins/clipboard-manager/android/src/main/java/ClipboardPlugin.kt index 432e337e8c..ea8d8163ea 100644 --- a/plugins/clipboard-manager/android/src/main/java/ClipboardPlugin.kt +++ b/plugins/clipboard-manager/android/src/main/java/ClipboardPlugin.kt @@ -87,7 +87,7 @@ class ClipboardPlugin(private val activity: Activity) : Plugin(activity) { @Command @Suppress("MoveVariableDeclarationIntoWhen") - fun write(invoke: Invoke) { + fun writeText(invoke: Invoke) { val args = invoke.parseArgs(WriteOptions::class.java) val clipData = when (args) { @@ -106,7 +106,7 @@ class ClipboardPlugin(private val activity: Activity) : Plugin(activity) { } @Command - fun read(invoke: Invoke) { + fun readText(invoke: Invoke) { val data = if (manager.hasPrimaryClip()) { if (manager.primaryClipDescription?.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN) == true) { val item: ClipData.Item = manager.primaryClip!!.getItemAt(0) diff --git a/plugins/clipboard-manager/guest-js/index.ts b/plugins/clipboard-manager/guest-js/index.ts index c6194e2299..3b76234a87 100644 --- a/plugins/clipboard-manager/guest-js/index.ts +++ b/plugins/clipboard-manager/guest-js/index.ts @@ -31,7 +31,7 @@ async function writeText( text: string, opts?: { label?: string }, ): Promise { - return invoke("plugin:clipboard|write", { + return invoke("plugin:clipboard|write_text", { data: { plainText: { label: opts?.label, @@ -92,7 +92,7 @@ async function readImage(): Promise { * @since 2.0.0 */ async function writeImage(buffer: Uint8Array | Array): Promise { - return invoke("plugin:clipboard|write", { + return invoke("plugin:clipboard|write_image", { data: { image: { buffer: Array.isArray(buffer) ? buffer : Array.from(buffer), diff --git a/plugins/clipboard-manager/ios/Sources/ClipboardPlugin.swift b/plugins/clipboard-manager/ios/Sources/ClipboardPlugin.swift index 30729d5309..c85abf7cd3 100644 --- a/plugins/clipboard-manager/ios/Sources/ClipboardPlugin.swift +++ b/plugins/clipboard-manager/ios/Sources/ClipboardPlugin.swift @@ -16,7 +16,7 @@ enum ReadClipData: Codable { } class ClipboardPlugin: Plugin { - @objc public func write(_ invoke: Invoke) throws { + @objc public func writeText(_ invoke: Invoke) throws { let options = try invoke.parseArgs(WriteOptions.self) let clipboard = UIPasteboard.general switch options { @@ -30,7 +30,7 @@ class ClipboardPlugin: Plugin { } - @objc public func read(_ invoke: Invoke) throws { + @objc public func readText(_ invoke: Invoke) throws { let clipboard = UIPasteboard.general if let text = clipboard.string { invoke.resolve(ReadClipData.plainText(text: text)) diff --git a/plugins/clipboard-manager/src/api-iife.js b/plugins/clipboard-manager/src/api-iife.js index a7e6ba4afc..51c9c5640f 100644 --- a/plugins/clipboard-manager/src/api-iife.js +++ b/plugins/clipboard-manager/src/api-iife.js @@ -1 +1 @@ -if("__TAURI__"in window){var __TAURI_PLUGIN_CLIPBOARDMANAGER__=function(r){"use strict";async function e(r,e={},n){return window.__TAURI_INTERNALS__.invoke(r,e,n)}return"function"==typeof SuppressedError&&SuppressedError,r.readImage=async function(){const r=await e("plugin:clipboard|read_image");return Uint8Array.from(r.image.buffer)},r.readText=async function(){return(await e("plugin:clipboard|read_text")).plainText.text},r.writeImage=async function(r){return e("plugin:clipboard|write",{data:{image:{buffer:Array.isArray(r)?r:Array.from(r)}}})},r.writeText=async function(r,n){return e("plugin:clipboard|write",{data:{plainText:{label:n?.label,text:r}}})},r}({});Object.defineProperty(window.__TAURI__,"clipboardManager",{value:__TAURI_PLUGIN_CLIPBOARDMANAGER__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_CLIPBOARDMANAGER__=function(r){"use strict";async function e(r,e={},a){return window.__TAURI_INTERNALS__.invoke(r,e,a)}return"function"==typeof SuppressedError&&SuppressedError,r.readImage=async function(){const r=await e("plugin:clipboard|read_image");return Uint8Array.from(r.image.buffer)},r.readText=async function(){return(await e("plugin:clipboard|read_text")).plainText.text},r.writeImage=async function(r){return e("plugin:clipboard|write_image",{data:{image:{buffer:Array.isArray(r)?r:Array.from(r)}}})},r.writeText=async function(r,a){return e("plugin:clipboard|write_text",{data:{plainText:{label:a?.label,text:r}}})},r}({});Object.defineProperty(window.__TAURI__,"clipboardManager",{value:__TAURI_PLUGIN_CLIPBOARDMANAGER__})} diff --git a/plugins/clipboard-manager/src/commands.rs b/plugins/clipboard-manager/src/commands.rs index f75af735f4..104bc15466 100644 --- a/plugins/clipboard-manager/src/commands.rs +++ b/plugins/clipboard-manager/src/commands.rs @@ -7,12 +7,21 @@ use tauri::{command, AppHandle, Runtime, State}; use crate::{ClipKind, Clipboard, ClipboardContents, Result}; #[command] -pub(crate) async fn write( +pub(crate) async fn write_text( _app: AppHandle, clipboard: State<'_, Clipboard>, data: ClipKind, ) -> Result<()> { - clipboard.write(data) + clipboard.write_text(data) +} + +#[command] +pub(crate) async fn write_image( + _app: AppHandle, + clipboard: State<'_, Clipboard>, + data: ClipKind, +) -> Result<()> { + clipboard.write_image(data) } #[command] @@ -20,7 +29,7 @@ pub(crate) async fn read_text( _app: AppHandle, clipboard: State<'_, Clipboard>, ) -> Result { - clipboard.read() + clipboard.read_text() } #[command] diff --git a/plugins/clipboard-manager/src/desktop.rs b/plugins/clipboard-manager/src/desktop.rs index 83c992fbfa..bc55ddada8 100644 --- a/plugins/clipboard-manager/src/desktop.rs +++ b/plugins/clipboard-manager/src/desktop.rs @@ -29,35 +29,34 @@ pub struct Clipboard { } impl Clipboard { - pub fn write(&self, kind: ClipKind) -> crate::Result<()> { + pub fn write_text(&self, kind: ClipKind) -> crate::Result<()> { match kind { - ClipKind::PlainText { text, .. } => self.write_text(text), - ClipKind::Image { buffer } => self.write_image(buffer), + ClipKind::PlainText { text, .. } => match &self.clipboard { + Ok(clipboard) => clipboard.lock().unwrap().set_text(text).map_err(Into::into), + Err(e) => Err(crate::Error::Clipboard(e.to_string())), + }, + _ => Err(crate::Error::Clipboard("Invalid clip kind".to_string())), } } - fn write_text(&self, text: String) -> crate::Result<()> { - match &self.clipboard { - Ok(clipboard) => clipboard.lock().unwrap().set_text(text).map_err(Into::into), - Err(e) => Err(crate::Error::Clipboard(e.to_string())), - } - } - - fn write_image(&self, buffer: Vec) -> crate::Result<()> { - match &self.clipboard { - Ok(clipboard) => { - let image = buffer_to_image_data(&buffer)?; - clipboard - .lock() - .unwrap() - .set_image(image) - .map_err(Into::into) - } - Err(e) => Err(crate::Error::Clipboard(e.to_string())), + pub fn write_image(&self, kind: ClipKind) -> crate::Result<()> { + match kind { + ClipKind::Image { buffer, .. } => match &self.clipboard { + Ok(clipboard) => { + let image = buffer_to_image_data(&buffer)?; + clipboard + .lock() + .unwrap() + .set_image(image) + .map_err(Into::into) + } + Err(e) => Err(crate::Error::Clipboard(e.to_string())), + }, + _ => Err(crate::Error::Clipboard("Invalid clip kind".to_string())), } } - pub fn read(&self) -> crate::Result { + pub fn read_text(&self) -> crate::Result { match &self.clipboard { Ok(clipboard) => { let text = clipboard.lock().unwrap().get_text()?; diff --git a/plugins/clipboard-manager/src/lib.rs b/plugins/clipboard-manager/src/lib.rs index b53c0bd0c7..b253cb2377 100644 --- a/plugins/clipboard-manager/src/lib.rs +++ b/plugins/clipboard-manager/src/lib.rs @@ -50,10 +50,12 @@ pub fn init() -> TauriPlugin { Builder::new("clipboard") .js_init_script(include_str!("api-iife.js").to_string()) .invoke_handler(tauri::generate_handler![ - commands::write, + commands::write_text, commands::read_text, #[cfg(desktop)] commands::read_image, + #[cfg(desktop)] + commands::write_image ]) .setup(|app, api| { #[cfg(mobile)] diff --git a/plugins/clipboard-manager/src/mobile.rs b/plugins/clipboard-manager/src/mobile.rs index 0833824bcf..40e2861566 100644 --- a/plugins/clipboard-manager/src/mobile.rs +++ b/plugins/clipboard-manager/src/mobile.rs @@ -32,15 +32,23 @@ pub fn init( pub struct Clipboard(PluginHandle); impl Clipboard { - pub fn write(&self, kind: ClipKind) -> crate::Result<()> { + pub fn write_text(&self, kind: ClipKind) -> crate::Result<()> { self.0.run_mobile_plugin("write", kind).map_err(Into::into) } - pub fn read(&self) -> crate::Result { + pub fn write_image(&self, kind: ClipKind) -> crate::Result<()> { + Err(crate::Error::Clipboard( + "Unsupported on this platform".to_string(), + )) + } + + pub fn read_text(&self) -> crate::Result { self.0.run_mobile_plugin("read", ()).map_err(Into::into) } pub fn read_image(&self) -> crate::Result { - panic!("unsupported on this platform") + Err(crate::Error::Clipboard( + "Unsupported on this platform".to_string(), + )) } } From 73bcfa6f597d99a4fb7dbd1f3199ee5bcc291931 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 7 Mar 2024 14:20:25 -0300 Subject: [PATCH 06/17] update example --- plugins/clipboard-manager/guest-js/index.ts | 4 ++-- plugins/clipboard-manager/src/api-iife.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/clipboard-manager/guest-js/index.ts b/plugins/clipboard-manager/guest-js/index.ts index 03a3bcc281..e65ca7ef6e 100644 --- a/plugins/clipboard-manager/guest-js/index.ts +++ b/plugins/clipboard-manager/guest-js/index.ts @@ -68,7 +68,7 @@ async function readText(): Promise { * @since 2.0.0 */ async function readImage(): Promise { - const kind: ClipImageResponse = await invoke("plugin:clipboard|read_image"); + const kind: ClipImageResponse = await invoke("plugin:clipboard-manager|read_image"); return Uint8Array.from(kind.image.buffer); } @@ -91,7 +91,7 @@ async function readImage(): Promise { * @since 2.0.0 */ async function writeImage(buffer: Uint8Array | Array): Promise { - return invoke("plugin:clipboard|write_image", { + return invoke("plugin:clipboard-manager|write_image", { data: { image: { buffer: Array.isArray(buffer) ? buffer : Array.from(buffer), diff --git a/plugins/clipboard-manager/src/api-iife.js b/plugins/clipboard-manager/src/api-iife.js index a0c7f854a4..015867fcc3 100644 --- a/plugins/clipboard-manager/src/api-iife.js +++ b/plugins/clipboard-manager/src/api-iife.js @@ -1 +1 @@ -if("__TAURI__"in window){var __TAURI_PLUGIN_CLIPBOARDMANAGER__=function(r){"use strict";async function a(r,a={},e){return window.__TAURI_INTERNALS__.invoke(r,a,e)}return"function"==typeof SuppressedError&&SuppressedError,r.clear=async function(){await a("plugin:clipboard-manager|clear")},r.readImage=async function(){const r=await a("plugin:clipboard|read_image");return Uint8Array.from(r.image.buffer)},r.readText=async function(){return(await a("plugin:clipboard-manager|read_text")).plainText.text},r.writeHtml=async function(r,e){return a("plugin:clipboard-manager|write_html",{data:{html:{html:r,altHtml:e}}})},r.writeImage=async function(r){return a("plugin:clipboard|write_image",{data:{image:{buffer:Array.isArray(r)?r:Array.from(r)}}})},r.writeText=async function(r,e){return a("plugin:clipboard-manager|write_text",{data:{plainText:{label:e?.label,text:r}}})},r}({});Object.defineProperty(window.__TAURI__,"clipboardManager",{value:__TAURI_PLUGIN_CLIPBOARDMANAGER__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_CLIPBOARDMANAGER__=function(r){"use strict";async function a(r,a={},e){return window.__TAURI_INTERNALS__.invoke(r,a,e)}return"function"==typeof SuppressedError&&SuppressedError,r.clear=async function(){await a("plugin:clipboard-manager|clear")},r.readImage=async function(){const r=await a("plugin:clipboard-manager|read_image");return Uint8Array.from(r.image.buffer)},r.readText=async function(){return(await a("plugin:clipboard-manager|read_text")).plainText.text},r.writeHtml=async function(r,e){return a("plugin:clipboard-manager|write_html",{data:{html:{html:r,altHtml:e}}})},r.writeImage=async function(r){return a("plugin:clipboard-manager|write_image",{data:{image:{buffer:Array.isArray(r)?r:Array.from(r)}}})},r.writeText=async function(r,e){return a("plugin:clipboard-manager|write_text",{data:{plainText:{label:e?.label,text:r}}})},r}({});Object.defineProperty(window.__TAURI__,"clipboardManager",{value:__TAURI_PLUGIN_CLIPBOARDMANAGER__})} From 5007a4865b3bfebb544787bf36a968b9945af25e Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Thu, 7 Mar 2024 14:21:28 -0300 Subject: [PATCH 07/17] update example --- .changes/clipboard-manager-image.md | 2 +- examples/api/src-tauri/capabilities/base.json | 2 + examples/api/src/lib/utils.js | 11 ++++ examples/api/src/views/Clipboard.svelte | 53 ++++++++++++++++--- examples/api/src/views/FileSystem.svelte | 19 ++----- 5 files changed, 65 insertions(+), 22 deletions(-) create mode 100644 examples/api/src/lib/utils.js diff --git a/.changes/clipboard-manager-image.md b/.changes/clipboard-manager-image.md index 37699467e9..9151d108b1 100644 --- a/.changes/clipboard-manager-image.md +++ b/.changes/clipboard-manager-image.md @@ -3,4 +3,4 @@ "clipboard-manager-js": "minor" --- -Add support for `read_image` and `write_image` to the clipboard plugin. \ No newline at end of file +Add support for `read_image` and `write_image` to the clipboard plugin (desktop). diff --git a/examples/api/src-tauri/capabilities/base.json b/examples/api/src-tauri/capabilities/base.json index c38bcc00a7..a03a9be898 100644 --- a/examples/api/src-tauri/capabilities/base.json +++ b/examples/api/src-tauri/capabilities/base.json @@ -56,6 +56,8 @@ "shell:allow-stdin-write", "clipboard-manager:allow-read-text", "clipboard-manager:allow-write-text", + "clipboard-manager:allow-read-image", + "clipboard-manager:allow-write-image", "fs:allow-rename", "fs:allow-mkdir", "fs:allow-remove", diff --git a/examples/api/src/lib/utils.js b/examples/api/src/lib/utils.js new file mode 100644 index 0000000000..6c8dbe4d38 --- /dev/null +++ b/examples/api/src/lib/utils.js @@ -0,0 +1,11 @@ +export function arrayBufferToBase64(buffer, callback) { + const blob = new Blob([buffer], { + type: "application/octet-binary", + }); + const reader = new FileReader(); + reader.onload = function (evt) { + const dataurl = evt.target.result; + callback(dataurl.substr(dataurl.indexOf(",") + 1)); + }; + reader.readAsDataURL(blob); +} diff --git a/examples/api/src/views/Clipboard.svelte b/examples/api/src/views/Clipboard.svelte index f571b2eef0..230bbda9a3 100644 --- a/examples/api/src/views/Clipboard.svelte +++ b/examples/api/src/views/Clipboard.svelte @@ -1,23 +1,60 @@ @@ -27,6 +64,8 @@ placeholder="Text to write to the clipboard" bind:value={text} /> - + + + diff --git a/examples/api/src/views/FileSystem.svelte b/examples/api/src/views/FileSystem.svelte index dce83663e8..f7222ef87c 100644 --- a/examples/api/src/views/FileSystem.svelte +++ b/examples/api/src/views/FileSystem.svelte @@ -1,6 +1,7 @@