diff --git a/.changes/clipboard-drop.md b/.changes/clipboard-drop.md new file mode 100644 index 00000000..75624c3f --- /dev/null +++ b/.changes/clipboard-drop.md @@ -0,0 +1,6 @@ +--- +clipboard-manager: patch +clipboard-manager-js: patch +--- + +Explicitly drop `arboard::Clipboard` on exit. Add recommendation to not use read methods on the mainthread. diff --git a/plugins/clipboard-manager/src/desktop.rs b/plugins/clipboard-manager/src/desktop.rs index 5edd4934..f3570cc0 100644 --- a/plugins/clipboard-manager/src/desktop.rs +++ b/plugins/clipboard-manager/src/desktop.rs @@ -14,7 +14,7 @@ pub fn init( ) -> crate::Result> { Ok(Clipboard { app: app.clone(), - clipboard: arboard::Clipboard::new().map(Mutex::new), + clipboard: arboard::Clipboard::new().map(|c| Mutex::new(Some(c))), }) } @@ -22,13 +22,21 @@ pub fn init( pub struct Clipboard { #[allow(dead_code)] app: AppHandle, - clipboard: Result, arboard::Error>, + // According to arboard docs the clipboard must be dropped before exit. + // Since tauri doesn't call drop on exit we'll use an Option to take() on RunEvent::Exit. + clipboard: Result>, arboard::Error>, } impl Clipboard { pub fn write_text<'a, T: Into>>(&self, text: T) -> crate::Result<()> { match &self.clipboard { - Ok(clipboard) => clipboard.lock().unwrap().set_text(text).map_err(Into::into), + Ok(clipboard) => clipboard + .lock() + .unwrap() + .as_mut() + .unwrap() + .set_text(text) + .map_err(Into::into), Err(e) => Err(crate::Error::Clipboard(e.to_string())), } } @@ -38,6 +46,8 @@ impl Clipboard { Ok(clipboard) => clipboard .lock() .unwrap() + .as_mut() + .unwrap() .set_image(ImageData { bytes: Cow::Borrowed(image.rgba()), width: image.width() as usize, @@ -48,10 +58,11 @@ impl Clipboard { } } + /// 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().get_text()?; + let text = clipboard.lock().unwrap().as_mut().unwrap().get_text()?; Ok(text) } Err(e) => Err(crate::Error::Clipboard(e.to_string())), @@ -67,6 +78,8 @@ impl 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())), @@ -75,15 +88,22 @@ impl Clipboard { pub fn clear(&self) -> crate::Result<()> { match &self.clipboard { - Ok(clipboard) => clipboard.lock().unwrap().clear().map_err(Into::into), + Ok(clipboard) => clipboard + .lock() + .unwrap() + .as_mut() + .unwrap() + .clear() + .map_err(Into::into), Err(e) => Err(crate::Error::Clipboard(e.to_string())), } } + /// 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().get_image()?; + let image = clipboard.lock().unwrap().as_mut().unwrap().get_image()?; let image = Image::new_owned( image.bytes.to_vec(), image.width as u32, @@ -94,4 +114,10 @@ impl Clipboard { Err(e) => Err(crate::Error::Clipboard(e.to_string())), } } + + pub(crate) fn cleanup(&self) { + if let Ok(clipboard) = &self.clipboard { + clipboard.lock().unwrap().take(); + } + } } diff --git a/plugins/clipboard-manager/src/lib.rs b/plugins/clipboard-manager/src/lib.rs index 133020d9..0cbb4e41 100644 --- a/plugins/clipboard-manager/src/lib.rs +++ b/plugins/clipboard-manager/src/lib.rs @@ -11,7 +11,7 @@ use tauri::{ plugin::{Builder, TauriPlugin}, - Manager, Runtime, + Manager, RunEvent, Runtime, }; #[cfg(desktop)] @@ -59,5 +59,11 @@ pub fn init() -> TauriPlugin { app.manage(clipboard); Ok(()) }) + .on_event(|_app, _event| { + #[cfg(desktop)] + if let RunEvent::Exit = _event { + _app.clipboard().cleanup(); + } + }) .build() }