From 83cbe85547a25f838188a91a1f59eefd514fb61b Mon Sep 17 00:00:00 2001
From: Matthieu Felix <matthieufelix@gmail.com>
Date: Sun, 11 Jul 2021 14:11:18 -0400
Subject: [PATCH] Add mutex for OSX.

See https://github.com/alacritty/copypasta/issues/11
---
 Cargo.toml           |  1 +
 src/osx_clipboard.rs | 19 +++++++++++++++++++
 tests/concurrency.rs | 29 +++++++++++++++++++++++++++++
 3 files changed, 49 insertions(+)
 create mode 100644 tests/concurrency.rs

diff --git a/Cargo.toml b/Cargo.toml
index 34e001c..fa92f15 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,6 +20,7 @@ wayland = ["smithay-clipboard"]
 clipboard-win = "3.0.2"
 
 [target.'cfg(target_os = "macos")'.dependencies]
+lazy_static = "1.4"
 objc = "0.2"
 objc_id = "0.1"
 objc-foundation = "0.1"
diff --git a/src/osx_clipboard.rs b/src/osx_clipboard.rs
index ef1e7d9..a2b72a5 100644
--- a/src/osx_clipboard.rs
+++ b/src/osx_clipboard.rs
@@ -13,7 +13,9 @@
 // limitations under the License.
 
 use std::mem::transmute;
+use std::sync::Mutex;
 
+use lazy_static::lazy_static;
 use objc::runtime::{Class, Object};
 use objc::{msg_send, sel, sel_impl};
 use objc_foundation::{INSArray, INSObject, INSString};
@@ -22,6 +24,11 @@ use objc_id::{Id, Owned};
 
 use crate::common::*;
 
+// creating or accessing the context is not thread-safe, and needs to be protected
+lazy_static! {
+    static ref CLIPBOARD_CONTEXT_MUTEX: Mutex<()> = Mutex::new(());
+}
+
 pub struct OSXClipboardContext {
     pasteboard: Id<Object>,
 }
@@ -32,6 +39,10 @@ extern "C" {}
 
 impl OSXClipboardContext {
     pub fn new() -> Result<OSXClipboardContext> {
+        let lock = CLIPBOARD_CONTEXT_MUTEX.lock();
+        if !lock.is_ok() {
+            return Err("could not acquire mutex".into());
+        }
         let cls = Class::get("NSPasteboard").ok_or("Class::get(\"NSPasteboard\")")?;
         let pasteboard: *mut Object = unsafe { msg_send![cls, generalPasteboard] };
         if pasteboard.is_null() {
@@ -44,6 +55,10 @@ impl OSXClipboardContext {
 
 impl ClipboardProvider for OSXClipboardContext {
     fn get_contents(&mut self) -> Result<String> {
+        let lock = CLIPBOARD_CONTEXT_MUTEX.lock();
+        if !lock.is_ok() {
+            return Err("could not acquire mutex".into());
+        }
         let string_class: Id<NSObject> = {
             let cls: Id<Class> = unsafe { Id::from_ptr(class("NSString")) };
             unsafe { transmute(cls) }
@@ -66,6 +81,10 @@ impl ClipboardProvider for OSXClipboardContext {
     }
 
     fn set_contents(&mut self, data: String) -> Result<()> {
+        let lock = CLIPBOARD_CONTEXT_MUTEX.lock();
+        if !lock.is_ok() {
+            return Err("could not acquire mutex".into());
+        }
         let string_array = NSArray::from_vec(vec![NSString::from_str(&data)]);
         let _: usize = unsafe { msg_send![self.pasteboard, clearContents] };
         let success: bool = unsafe { msg_send![self.pasteboard, writeObjects: string_array] };
diff --git a/tests/concurrency.rs b/tests/concurrency.rs
new file mode 100644
index 0000000..f2b1204
--- /dev/null
+++ b/tests/concurrency.rs
@@ -0,0 +1,29 @@
+use copypasta::{ClipboardContext, ClipboardProvider};
+
+fn some_other_fn() {
+    let mut ctx = ClipboardContext::new().unwrap();
+    ctx.get_contents().unwrap();
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn foo() {
+        let mut ctx = ClipboardContext::new().unwrap();
+        ctx.set_contents("Dummy".into()).unwrap();
+        ctx.get_contents().unwrap();
+
+        some_other_fn();
+    }
+
+    #[test]
+    fn bar() {
+        let mut ctx = ClipboardContext::new().unwrap();
+        ctx.set_contents("Dummy".into()).unwrap();
+        ctx.get_contents().unwrap();
+
+        some_other_fn();
+    }
+}