diff --git a/Cargo.toml b/Cargo.toml index cc39ccf..66556f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ serde = "1.0" thiserror = "1.0" base64 = "0.22.1" image = "0.25.1" -clipboard-rs = "0.2.0" +clipboard-rs = "0.2.1" [build-dependencies] -tauri-plugin = { version = "2.0.0-beta.15", features = ["build"] } +tauri-plugin = { version = "2.0.0-beta", features = ["build"] } diff --git a/examples/demo/package.json b/examples/demo/package.json index e55813f..2bdf4f8 100644 --- a/examples/demo/package.json +++ b/examples/demo/package.json @@ -37,7 +37,7 @@ "type": "module", "dependencies": { "@floating-ui/dom": "1.5.3", - "@tauri-apps/api": "2.0.0-beta.11", + "@tauri-apps/api": "^2.0.0-beta", "tauri-plugin-clipboard-api": "link:../../" } } \ No newline at end of file diff --git a/examples/demo/src-tauri/capabilities/default.json b/examples/demo/src-tauri/capabilities/default.json index 5448344..daee6e1 100644 --- a/examples/demo/src-tauri/capabilities/default.json +++ b/examples/demo/src-tauri/capabilities/default.json @@ -6,15 +6,9 @@ "main" ], "permissions": [ - "path:default", - "event:default", - "window:default", - "webview:default", - "app:default", - "resources:default", - "image:default", - "menu:default", - "tray:default", + "core:event:default", + "core:path:default", + "core:window:default", "clipboard:read-all", "clipboard:write-all", "clipboard:monitor-all" diff --git a/examples/demo/src/lib/components/listener.svelte b/examples/demo/src/lib/components/listener.svelte index 3ebdf9f..83d6e00 100644 --- a/examples/demo/src/lib/components/listener.svelte +++ b/examples/demo/src/lib/components/listener.svelte @@ -29,6 +29,7 @@ let unlistenImageUpdate: UnlistenFn; let unlistenHtmlUpdate: UnlistenFn; let unlistenRTF: UnlistenFn; + let unlistenAvail: UnlistenFn; let unlistenClipboard: () => Promise; let unlistenFiles: UnlistenFn; const has = { @@ -65,14 +66,14 @@ }); unlistenClipboard = await startListening(); - onClipboardUpdate(async () => { + unlistenAvail = await onClipboardUpdate(async (values) => { clear(); - has.hasHTML = await hasHTML(); - has.hasImage = await hasImage(); - has.hasText = await hasText(); - has.hasRTF = await hasRTF(); - has.hasFiles = await hasFiles(); - console.log('plugin:clipboard://clipboard-monitor/update event received'); + console.log('plugin:clipboard://clipboard-monitor/update event received', values); + has.hasHTML = values.html; + has.hasImage = values.image; + has.hasText = values.text; + has.hasRTF = values.rtf + has.hasFiles = values.files; }); }); @@ -86,6 +87,7 @@ if (unlistenHtmlUpdate) unlistenHtmlUpdate(); if (unlistenFiles) unlistenFiles(); if (unlistenClipboard) unlistenClipboard(); + if (unlistenAvail) unlistenAvail(); }); diff --git a/guest-js/api.ts b/guest-js/api.ts index 15e4ee7..b98ebb8 100644 --- a/guest-js/api.ts +++ b/guest-js/api.ts @@ -48,6 +48,14 @@ export const ClipboardChangedFilesPayloadSchema = v.object({ }) export type ClipboardChangedPayload = v.InferOutput +export interface ClipboardState { + text: boolean, + html: boolean, + rtf: boolean, + image: boolean, + files: boolean, +} + export function hasText() { return invoke(HAS_TEXT_COMMAND) } @@ -333,13 +341,13 @@ export function listenToClipboard( /** * This listen to clipboard monitor update event, and trigger the callback function. - * However from this event we don't know whether it's text or image, no real data is returned. - * Use with listenToClipboard function. * @param cb callback * @returns unlisten function */ -export function onClipboardUpdate(cb: () => void) { - return listen(MONITOR_UPDATE_EVENT, cb) +export async function onClipboardUpdate(cb: (state: ClipboardState) => void) { + return await listen(MONITOR_UPDATE_EVENT, (event) => { + cb(event.payload) + }) } export async function onTextUpdate(cb: (text: string) => void): Promise { diff --git a/package.json b/package.json index e32b206..8991dd3 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "peerDependencies": {}, "dependencies": { "valibot": "^0.40.0", - "@tauri-apps/api": "2.0.0-beta.11" + "@tauri-apps/api": "^2.0.0-beta" }, "devDependencies": { "@rollup/plugin-terser": "^0.4.4", diff --git a/permissions/autogenerated/reference.md b/permissions/autogenerated/reference.md index c89da59..3204e0a 100644 --- a/permissions/autogenerated/reference.md +++ b/permissions/autogenerated/reference.md @@ -1,5 +1,5 @@ -### Permission Table +## Permission Table diff --git a/permissions/schemas/schema.json b/permissions/schemas/schema.json index 1d207a8..f572389 100644 --- a/permissions/schemas/schema.json +++ b/permissions/schemas/schema.json @@ -295,403 +295,289 @@ "type": "string", "oneOf": [ { - "description": "allow-available-types -> Enables the available_types command without any pre-configured scope.", + "description": "Enables the available_types command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-available-types" - ] + "const": "allow-available-types" }, { - "description": "deny-available-types -> Denies the available_types command without any pre-configured scope.", + "description": "Denies the available_types command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-available-types" - ] + "const": "deny-available-types" }, { - "description": "allow-clear -> Enables the clear command without any pre-configured scope.", + "description": "Enables the clear command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-clear" - ] + "const": "allow-clear" }, { - "description": "deny-clear -> Denies the clear command without any pre-configured scope.", + "description": "Denies the clear command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-clear" - ] + "const": "deny-clear" }, { - "description": "allow-execute -> Enables the execute command without any pre-configured scope.", + "description": "Enables the execute command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-execute" - ] + "const": "allow-execute" }, { - "description": "deny-execute -> Denies the execute command without any pre-configured scope.", + "description": "Denies the execute command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-execute" - ] + "const": "deny-execute" }, { - "description": "allow-has-files -> Enables the has_files command without any pre-configured scope.", + "description": "Enables the has_files command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-has-files" - ] + "const": "allow-has-files" }, { - "description": "deny-has-files -> Denies the has_files command without any pre-configured scope.", + "description": "Denies the has_files command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-has-files" - ] + "const": "deny-has-files" }, { - "description": "allow-has-html -> Enables the has_html command without any pre-configured scope.", + "description": "Enables the has_html command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-has-html" - ] + "const": "allow-has-html" }, { - "description": "deny-has-html -> Denies the has_html command without any pre-configured scope.", + "description": "Denies the has_html command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-has-html" - ] + "const": "deny-has-html" }, { - "description": "allow-has-image -> Enables the has_image command without any pre-configured scope.", + "description": "Enables the has_image command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-has-image" - ] + "const": "allow-has-image" }, { - "description": "deny-has-image -> Denies the has_image command without any pre-configured scope.", + "description": "Denies the has_image command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-has-image" - ] + "const": "deny-has-image" }, { - "description": "allow-has-rtf -> Enables the has_rtf command without any pre-configured scope.", + "description": "Enables the has_rtf command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-has-rtf" - ] + "const": "allow-has-rtf" }, { - "description": "deny-has-rtf -> Denies the has_rtf command without any pre-configured scope.", + "description": "Denies the has_rtf command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-has-rtf" - ] + "const": "deny-has-rtf" }, { - "description": "allow-has-text -> Enables the has_text command without any pre-configured scope.", + "description": "Enables the has_text command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-has-text" - ] + "const": "allow-has-text" }, { - "description": "deny-has-text -> Denies the has_text command without any pre-configured scope.", + "description": "Denies the has_text command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-has-text" - ] + "const": "deny-has-text" }, { - "description": "allow-is-monitor-running -> Enables the is_monitor_running command without any pre-configured scope.", + "description": "Enables the is_monitor_running command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-is-monitor-running" - ] + "const": "allow-is-monitor-running" }, { - "description": "deny-is-monitor-running -> Denies the is_monitor_running command without any pre-configured scope.", + "description": "Denies the is_monitor_running command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-is-monitor-running" - ] + "const": "deny-is-monitor-running" }, { - "description": "allow-ping -> Enables the ping command without any pre-configured scope.", + "description": "Enables the ping command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-ping" - ] + "const": "allow-ping" }, { - "description": "deny-ping -> Denies the ping command without any pre-configured scope.", + "description": "Denies the ping command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-ping" - ] + "const": "deny-ping" }, { - "description": "allow-read-files -> Enables the read_files command without any pre-configured scope.", + "description": "Enables the read_files command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-read-files" - ] + "const": "allow-read-files" }, { - "description": "deny-read-files -> Denies the read_files command without any pre-configured scope.", + "description": "Denies the read_files command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-read-files" - ] + "const": "deny-read-files" }, { - "description": "allow-read-files-uris -> Enables the read_files_uris command without any pre-configured scope.", + "description": "Enables the read_files_uris command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-read-files-uris" - ] + "const": "allow-read-files-uris" }, { - "description": "deny-read-files-uris -> Denies the read_files_uris command without any pre-configured scope.", + "description": "Denies the read_files_uris command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-read-files-uris" - ] + "const": "deny-read-files-uris" }, { - "description": "allow-read-html -> Enables the read_html command without any pre-configured scope.", + "description": "Enables the read_html command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-read-html" - ] + "const": "allow-read-html" }, { - "description": "deny-read-html -> Denies the read_html command without any pre-configured scope.", + "description": "Denies the read_html command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-read-html" - ] + "const": "deny-read-html" }, { - "description": "allow-read-image-base64 -> Enables the read_image_base64 command without any pre-configured scope.", + "description": "Enables the read_image_base64 command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-read-image-base64" - ] + "const": "allow-read-image-base64" }, { - "description": "deny-read-image-base64 -> Denies the read_image_base64 command without any pre-configured scope.", + "description": "Denies the read_image_base64 command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-read-image-base64" - ] + "const": "deny-read-image-base64" }, { - "description": "allow-read-image-binary -> Enables the read_image_binary command without any pre-configured scope.", + "description": "Enables the read_image_binary command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-read-image-binary" - ] + "const": "allow-read-image-binary" }, { - "description": "deny-read-image-binary -> Denies the read_image_binary command without any pre-configured scope.", + "description": "Denies the read_image_binary command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-read-image-binary" - ] + "const": "deny-read-image-binary" }, { - "description": "allow-read-rtf -> Enables the read_rtf command without any pre-configured scope.", + "description": "Enables the read_rtf command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-read-rtf" - ] + "const": "allow-read-rtf" }, { - "description": "deny-read-rtf -> Denies the read_rtf command without any pre-configured scope.", + "description": "Denies the read_rtf command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-read-rtf" - ] + "const": "deny-read-rtf" }, { - "description": "allow-read-text -> Enables the read_text command without any pre-configured scope.", + "description": "Enables the read_text command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-read-text" - ] + "const": "allow-read-text" }, { - "description": "deny-read-text -> Denies the read_text command without any pre-configured scope.", + "description": "Denies the read_text command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-read-text" - ] + "const": "deny-read-text" }, { - "description": "allow-start-monitor -> Enables the start_monitor command without any pre-configured scope.", + "description": "Enables the start_monitor command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-start-monitor" - ] + "const": "allow-start-monitor" }, { - "description": "deny-start-monitor -> Denies the start_monitor command without any pre-configured scope.", + "description": "Denies the start_monitor command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-start-monitor" - ] + "const": "deny-start-monitor" }, { - "description": "allow-stop-monitor -> Enables the stop_monitor command without any pre-configured scope.", + "description": "Enables the stop_monitor command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-stop-monitor" - ] + "const": "allow-stop-monitor" }, { - "description": "deny-stop-monitor -> Denies the stop_monitor command without any pre-configured scope.", + "description": "Denies the stop_monitor command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-stop-monitor" - ] + "const": "deny-stop-monitor" }, { - "description": "allow-write-files -> Enables the write_files command without any pre-configured scope.", + "description": "Enables the write_files command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-write-files" - ] + "const": "allow-write-files" }, { - "description": "deny-write-files -> Denies the write_files command without any pre-configured scope.", + "description": "Denies the write_files command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-write-files" - ] + "const": "deny-write-files" }, { - "description": "allow-write-files-uris -> Enables the write_files_uris command without any pre-configured scope.", + "description": "Enables the write_files_uris command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-write-files-uris" - ] + "const": "allow-write-files-uris" }, { - "description": "deny-write-files-uris -> Denies the write_files_uris command without any pre-configured scope.", + "description": "Denies the write_files_uris command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-write-files-uris" - ] + "const": "deny-write-files-uris" }, { - "description": "allow-write-html -> Enables the write_html command without any pre-configured scope.", + "description": "Enables the write_html command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-write-html" - ] + "const": "allow-write-html" }, { - "description": "deny-write-html -> Denies the write_html command without any pre-configured scope.", + "description": "Denies the write_html command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-write-html" - ] + "const": "deny-write-html" }, { - "description": "allow-write-html-and-text -> Enables the write_html_and_text command without any pre-configured scope.", + "description": "Enables the write_html_and_text command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-write-html-and-text" - ] + "const": "allow-write-html-and-text" }, { - "description": "deny-write-html-and-text -> Denies the write_html_and_text command without any pre-configured scope.", + "description": "Denies the write_html_and_text command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-write-html-and-text" - ] + "const": "deny-write-html-and-text" }, { - "description": "allow-write-image-base64 -> Enables the write_image_base64 command without any pre-configured scope.", + "description": "Enables the write_image_base64 command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-write-image-base64" - ] + "const": "allow-write-image-base64" }, { - "description": "deny-write-image-base64 -> Denies the write_image_base64 command without any pre-configured scope.", + "description": "Denies the write_image_base64 command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-write-image-base64" - ] + "const": "deny-write-image-base64" }, { - "description": "allow-write-image-binary -> Enables the write_image_binary command without any pre-configured scope.", + "description": "Enables the write_image_binary command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-write-image-binary" - ] + "const": "allow-write-image-binary" }, { - "description": "deny-write-image-binary -> Denies the write_image_binary command without any pre-configured scope.", + "description": "Denies the write_image_binary command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-write-image-binary" - ] + "const": "deny-write-image-binary" }, { - "description": "allow-write-rtf -> Enables the write_rtf command without any pre-configured scope.", + "description": "Enables the write_rtf command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-write-rtf" - ] + "const": "allow-write-rtf" }, { - "description": "deny-write-rtf -> Denies the write_rtf command without any pre-configured scope.", + "description": "Denies the write_rtf command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-write-rtf" - ] + "const": "deny-write-rtf" }, { - "description": "allow-write-text -> Enables the write_text command without any pre-configured scope.", + "description": "Enables the write_text command without any pre-configured scope.", "type": "string", - "enum": [ - "allow-write-text" - ] + "const": "allow-write-text" }, { - "description": "deny-write-text -> Denies the write_text command without any pre-configured scope.", + "description": "Denies the write_text command without any pre-configured scope.", "type": "string", - "enum": [ - "deny-write-text" - ] + "const": "deny-write-text" }, { - "description": "monitor-all -> This enables all monitor related commands", + "description": "This enables all monitor related commands", "type": "string", - "enum": [ - "monitor-all" - ] + "const": "monitor-all" }, { - "description": "read-all -> This enables all read related commands to clipboard", + "description": "This enables all read related commands to clipboard", "type": "string", - "enum": [ - "read-all" - ] + "const": "read-all" }, { - "description": "write-all -> This enables all write related commands to clipboard", + "description": "This enables all write related commands to clipboard", "type": "string", - "enum": [ - "write-all" - ] + "const": "write-all" } ] } diff --git a/src/desktop.rs b/src/desktop.rs index fe9ed89..d1e2507 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -9,6 +9,20 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::sync::{Arc, Mutex}; use tauri::{plugin::PluginApi, AppHandle, Emitter, Runtime}; +#[cfg(target_os = "linux")] +pub fn init(_api: PluginApi) -> crate::Result { + use clipboard_rs::ClipboardContextX11Options; + + Ok(Clipboard { + clipboard: Arc::new(Mutex::new( + ClipboardRsContext::new_with_options(ClipboardContextX11Options { read_timeout: None }) + .unwrap(), + )), + watcher_shutdown: Arc::default(), + }) +} + +#[cfg(not(target_os = "linux"))] pub fn init(_api: PluginApi) -> crate::Result { Ok(Clipboard { clipboard: Arc::new(Mutex::new(ClipboardRsContext::new().unwrap())), @@ -16,7 +30,7 @@ pub fn init(_api: PluginApi) -> crate::Re }) } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct AvailableTypes { pub text: bool, pub html: bool, @@ -30,6 +44,7 @@ pub struct Clipboard { pub clipboard: Arc>, pub watcher_shutdown: Arc>>, } + impl Clipboard { pub fn has(&self, format: ContentFormat) -> Result { Ok(self @@ -40,12 +55,14 @@ impl Clipboard { } pub fn available_types(&self) -> Result { + let locked = self.clipboard.lock().map_err(|err| err.to_string())?; + Ok(AvailableTypes { - text: self.has(ContentFormat::Text)?, - html: self.has(ContentFormat::Html)?, - rtf: self.has(ContentFormat::Rtf)?, - image: self.has(ContentFormat::Image)?, - files: self.has(ContentFormat::Files)?, + text: locked.has(ContentFormat::Text), + html: locked.has(ContentFormat::Html), + rtf: locked.has(ContentFormat::Rtf), + image: locked.has(ContentFormat::Image), + files: locked.has(ContentFormat::Files), }) } @@ -250,7 +267,7 @@ impl Clipboard { pub fn start_monitor(&self, app_handle: AppHandle) -> Result<(), String> { let _ = app_handle.emit("plugin:clipboard://clipboard-monitor/status", true); - let clipboard = ClipboardMonitor::new(app_handle); + let clipboard = ClipboardMonitor::new(app_handle, self.clipboard.clone()); let mut watcher: ClipboardWatcherContext> = ClipboardWatcherContext::new().unwrap(); let watcher_shutdown = watcher.add_handler(clipboard).get_shutdown_channel(); @@ -285,14 +302,21 @@ where R: Runtime, { app_handle: tauri::AppHandle, + clipboard_ctx: Arc>, } impl ClipboardMonitor where R: Runtime, { - pub fn new(app_handle: tauri::AppHandle) -> Self { - Self { app_handle } + pub fn new( + app_handle: tauri::AppHandle, + clipboard_ctx: Arc>, + ) -> Self { + Self { + app_handle, + clipboard_ctx, + } } } @@ -301,9 +325,22 @@ where R: Runtime, { fn on_clipboard_change(&mut self) { - let _ = self.app_handle.emit( - "plugin:clipboard://clipboard-monitor/update", - "clipboard update", - ); + match self.clipboard_ctx.lock() { + Ok(locked) => { + let _ = self.app_handle.emit( + "plugin:clipboard://clipboard-monitor/update", + AvailableTypes { + text: locked.has(ContentFormat::Text), + html: locked.has(ContentFormat::Html), + rtf: locked.has(ContentFormat::Rtf), + image: locked.has(ContentFormat::Image), + files: locked.has(ContentFormat::Files), + } + ); + } + Err(err) => { + println!("Error accessing clipboard: {}", err); + } + } } }