diff --git a/Cargo.toml b/Cargo.toml index c2c360b..f82fa08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ keywords = ["clipboard", "wayland"] [dependencies] sctk = { package = "smithay-client-toolkit", version = "0.5" } +nix = "0.13.0" [dev-dependencies] andrew = "0.2" diff --git a/src/lib.rs b/src/lib.rs index 044f9a0..1f84d61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ //! ```no_run //! let (display, _) = //! Display::connect_to_env().expect("Failed to connect to the wayland server."); -//! let mut clipboard = smithay_clipboard::WaylandClipboard::new_threaded(&display); +//! let mut clipboard = smithay_clipboard::WaylandClipboard::new(&display); //! clipboard.store(None, "Test data"); //! println!("{}", clipboard.load(None)); //! ``` diff --git a/src/threaded.rs b/src/threaded.rs index 538cde5..eb2563e 100644 --- a/src/threaded.rs +++ b/src/threaded.rs @@ -1,11 +1,15 @@ use std::collections::HashMap; use std::io::{Read, Write}; use std::ops::Deref; +use std::os::unix::io::FromRawFd; use std::sync::mpsc; use std::sync::{Arc, Mutex}; use std::thread::sleep; use std::time::Duration; +use nix::fcntl::OFlag; +use nix::unistd::{close, pipe2}; + use sctk::data_device::{DataDevice, DataSource, DataSourceEvent}; use sctk::keyboard::{map_keyboard_auto, Event as KbEvent}; use sctk::reexports::client::protocol::{ @@ -13,10 +17,27 @@ use sctk::reexports::client::protocol::{ wl_seat, }; use sctk::reexports::client::{Display, EventQueue, GlobalEvent, GlobalManager, NewProxy}; +use sctk::reexports::protocols::unstable::primary_selection::v1::client::{ + zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1 as PrimarySelectionDeviceMgr, + zwp_primary_selection_device_v1::{ + Event as ZwpPrimarySelectionDeviceEvent, + ZwpPrimarySelectionDeviceV1 as PrimarySelectionDevice, + }, + zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1 as PrimarySelectionOffer, + zwp_primary_selection_source_v1, +}; use sctk::wayland_client::sys::client::wl_display; /// Used to store registered seats and their last event serial -type SeatMap = HashMap>, u32)>; +type SeatMap = HashMap< + String, + ( + Arc>, + u32, + Arc>>, + Arc>>, + ), +>; /// Object representing the Wayland clipboard pub struct ThreadedClipboard { @@ -61,7 +82,7 @@ impl ThreadedClipboard { /// /// Spawns a new thread to dispatch messages to the wayland server every /// 50ms to ensure the server can read stored data - pub unsafe fn new_threaded_from_external(display_ptr: *mut wl_display) -> Self { + pub unsafe fn new_from_external(display_ptr: *mut wl_display) -> Self { let (request_send, request_recv) = mpsc::channel(); let (load_send, load_recv) = mpsc::channel(); let display = display_ptr.as_mut().unwrap(); @@ -102,6 +123,31 @@ impl ThreadedClipboard { .send(ThreadRequest::Store(seat_name, text)) .unwrap() } + + /// Returns text from the primary selection of the wayland clipboard + /// + /// If provided with a seat name that seat must be in + /// focus to work. Otherwise if no seat name is provided + /// the name of the seat to last generate a key or pointer event + /// is used + pub fn load_primary(&mut self, seat_name: Option) -> String { + self.request_send + .send(ThreadRequest::LoadPrimary(seat_name)) + .unwrap(); + self.load_recv.recv().unwrap() + } + + /// Stores text in the primary selection of the wayland clipboard + /// + /// If provided with a seat name that seat must be in + /// focus to work. Otherwise if no seat name is provided + /// the name of the seat to last generate a key or pointer event + /// is used + pub fn store_primary(&mut self, seat_name: Option, text: String) { + self.request_send + .send(ThreadRequest::StorePrimary(seat_name, text)) + .unwrap() + } } /// Requests sent to the clipboard thread @@ -110,6 +156,10 @@ enum ThreadRequest { Store(Option, String), /// Load text from a specific seats clipboard Load(Option), + /// Store text in a specific seats primary clipboard + StorePrimary(Option, String), + /// Load text in a specific seats primary clipboard + LoadPrimary(Option), /// Kill the thread Kill, } @@ -128,10 +178,13 @@ fn clipboard_thread( let data_device_manager = Arc::new(Mutex::new(None)); let mut unimplemented_seats = Vec::new(); + let primary_selection_device_manager = Arc::new(Mutex::new(None)); + // Store the name of the seat that last sends an event for use as the default seat let last_seat_name = Arc::new(Mutex::new(String::new())); let data_device_manager_clone = data_device_manager.clone(); + let primary_selection_device_manager_clone = primary_selection_device_manager.clone(); let seat_map_clone = seat_map.clone(); let last_seat_name_clone = last_seat_name.clone(); @@ -155,6 +208,7 @@ fn clipboard_thread( last_seat_name_clone.clone(), data_device_manager, ®, + primary_selection_device_manager_clone.clone(), ); } else { // Store the seat for implementation once wl_data_device_manager is registered @@ -179,8 +233,19 @@ fn clipboard_thread( last_seat_name_clone.clone(), data_device_manager_clone.lock().unwrap().as_ref().unwrap(), ®, + primary_selection_device_manager_clone.clone(), ); } + } else if "zwp_primary_selection_device_manager_v1" == interface.as_str() { + // Register the zwp_primary_selection_device_manager + *primary_selection_device_manager_clone.lock().unwrap() = Some( + reg.bind::( + version, + id, + NewProxy::implement_dummy, + ) + .unwrap(), + ); } } }); @@ -195,7 +260,7 @@ fn clipboard_thread( event_queue.sync_roundtrip().unwrap(); let seat_map = seat_map.lock().unwrap().clone(); - // Get the requested seat from the seat map + // Get the clipboard contents of the requested seat from the seat map let contents = seat_map .get(&seat_name.unwrap_or_else(|| last_seat_name.lock().unwrap().clone())) .map_or(String::new(), |seat| { @@ -228,7 +293,7 @@ fn clipboard_thread( let seat_map = seat_map.lock().unwrap().clone(); // Get the requested seat from the seat map - if let Some((device, enter_serial)) = seat_map + if let Some((device, enter_serial, _, _)) = seat_map .get(&seat_name.unwrap_or_else(|| last_seat_name.lock().unwrap().clone())) { let data_source = DataSource::new( @@ -248,6 +313,68 @@ fn clipboard_thread( event_queue.sync_roundtrip().unwrap(); } } + // Load text from primary clipboard + ThreadRequest::LoadPrimary(seat_name) => { + event_queue.sync_roundtrip().unwrap(); + let seat_map = seat_map.lock().unwrap().clone(); + + // Get the primary clipboard contents of the requested seat from the seat map + let contents = seat_map + .get(&seat_name.unwrap_or_else(|| last_seat_name.lock().unwrap().clone())) + .map_or(String::new(), |seat| { + seat.3 + .lock() + .unwrap() + .as_ref() + .map_or(String::new(), |primary_offer| { + let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC).unwrap(); + let mut file = unsafe { std::fs::File::from_raw_fd(readfd) }; + primary_offer + .receive("text/plain;charset=utf-8".to_string(), writefd); + close(writefd).unwrap(); + let mut contents = String::new(); + event_queue.sync_roundtrip().unwrap(); + file.read_to_string(&mut contents).unwrap(); + contents + }) + }); + load_send.send(contents).unwrap(); + } + // Store text in the primary clipboard + ThreadRequest::StorePrimary(seat_name, contents) => { + event_queue.sync_roundtrip().unwrap(); + let seat_map = seat_map.lock().unwrap().clone(); + + // Get the requested seat from the seat map + if let Some((_, enter_serial, primary_device, _)) = seat_map + .get(&seat_name.unwrap_or_else(|| last_seat_name.lock().unwrap().clone())) + { + if let Some(manager) = &*primary_selection_device_manager.lock().unwrap() { + if let Some(primary_device) = &*primary_device.lock().unwrap() { + let source = manager.create_source(|proxy| { + proxy.implement_closure( + move |event, _| { + if let zwp_primary_selection_source_v1::Event::Send { + mime_type, + fd, + } = event + { + if mime_type == "text/plain;charset=utf-8" { + let mut file = + unsafe { std::fs::File::from_raw_fd(fd) }; + file.write_fmt(format_args!("{}", contents)) + .unwrap(); + } + } + }, + (), + ) + }); + primary_device.set_selection(source.ok().as_ref(), *enter_serial); + } + } + } + } ThreadRequest::Kill => break, } } @@ -265,6 +392,7 @@ fn implement_seat( last_seat_name: Arc>, data_device_manager: &wl_data_device_manager::WlDataDeviceManager, reg: &wl_registry::WlRegistry, + primary_device_manager: Arc>>, ) { let seat_name = Arc::new(Mutex::new(String::new())); let seat_name_clone = seat_name.clone(); @@ -290,8 +418,52 @@ fn implement_seat( |_| {}, ))); + let primary_offer = Arc::new(Mutex::new(None)); + let primary_offer_clone = primary_offer.clone(); + let seat_map_clone = seat_map.clone(); + let seat_name_clone = seat_name.clone(); + let primary_device = if let Some(manager) = &*primary_device_manager.lock().unwrap() { + Arc::new(Mutex::new( + manager + .get_device(&seat, |proxy| { + let primary_offer_clone = primary_offer_clone.clone(); + proxy.implement_closure( + move |event, _| { + if let ZwpPrimarySelectionDeviceEvent::DataOffer { offer } = event { + *primary_offer_clone.lock().unwrap() = + Some(offer.implement_dummy()); + + let map_contents = seat_map_clone + .lock() + .unwrap() + .get(&seat_name_clone.lock().unwrap().clone()) + .map(|c| c.clone()); + if let Some(map_contents) = map_contents { + seat_map_clone.lock().unwrap().insert( + seat_name_clone.lock().unwrap().clone(), + ( + map_contents.0.clone(), + map_contents.1, + map_contents.2.clone(), + primary_offer_clone.clone(), + ), + ); + } + } + }, + (), + ) + }) + .ok(), + )) + } else { + Arc::new(Mutex::new(None)) + }; + let seat_map_clone = seat_map.clone(); let device_clone = device.clone(); + let primary_device_clone = primary_device.clone(); + let primary_offer_clone = primary_offer_clone.clone(); let seat_name_clone = seat_name.clone(); let last_seat_name_clone = last_seat_name.clone(); map_keyboard_auto(&seat, move |event, _| { @@ -303,13 +475,23 @@ fn implement_seat( KbEvent::Enter { serial, .. } => { seat_map_clone.lock().unwrap().insert( seat_name_clone.lock().unwrap().clone(), - (device_clone.clone(), serial), + ( + device_clone.clone(), + serial, + primary_device_clone.clone(), + primary_offer_clone.clone(), + ), ); } KbEvent::Key { serial, .. } => { seat_map_clone.lock().unwrap().insert( seat_name_clone.lock().unwrap().clone(), - (device_clone.clone(), serial), + ( + device_clone.clone(), + serial, + primary_device_clone.clone(), + primary_offer_clone.clone(), + ), ); } KbEvent::Leave { .. } => { @@ -332,16 +514,26 @@ fn implement_seat( // Get serials from recieved events from the seat pointer match evt { PtrEvent::Enter { serial, .. } => { - seat_map - .lock() - .unwrap() - .insert(seat_name.lock().unwrap().clone(), (device.clone(), serial)); + seat_map.lock().unwrap().insert( + seat_name.lock().unwrap().clone(), + ( + device.clone(), + serial, + primary_device.clone(), + primary_offer.clone(), + ), + ); } PtrEvent::Button { serial, .. } => { - seat_map - .lock() - .unwrap() - .insert(seat_name.lock().unwrap().clone(), (device.clone(), serial)); + seat_map.lock().unwrap().insert( + seat_name.lock().unwrap().clone(), + ( + device.clone(), + serial, + primary_device.clone(), + primary_offer.clone(), + ), + ); } PtrEvent::Leave { .. } => { seat_map.lock().unwrap().remove(&*seat_name.lock().unwrap());