diff --git a/examples/clipboard.rs b/examples/clipboard.rs index 65cd7c5..08c406b 100644 --- a/examples/clipboard.rs +++ b/examples/clipboard.rs @@ -6,7 +6,7 @@ use sctk::utils::{DoubleMemPool, MemPool}; use sctk::window::{ConceptFrame, Event as WEvent, Window}; use sctk::Environment; -use sctk::reexports::client::protocol::{wl_shm, wl_surface}; +use sctk::reexports::client::protocol::{wl_seat, wl_shm, wl_surface}; use sctk::reexports::client::{Display, NewProxy}; use andrew::shapes::rectangle; @@ -18,14 +18,23 @@ fn main() { Display::connect_to_env().expect("Failed to connect to the wayland server."); let env = Environment::from_display(&*display, &mut event_queue).unwrap(); - let mut clipboard = smithay_clipboard::WaylandClipboard::new_threaded( - display.get_display_ptr() as *mut std::ffi::c_void, - ); + let mut clipboard = smithay_clipboard::WaylandClipboard::new_threaded(&display); let cb_contents = Arc::new(Mutex::new(String::new())); + let seat_name = Arc::new(Mutex::new(String::new())); + let seat_name_clone = seat_name.clone(); let seat = env .manager - .instantiate_range(1, 6, NewProxy::implement_dummy) + .instantiate_range(2, 6, move |proxy| { + proxy.implement_closure( + move |event, _| { + if let wl_seat::Event::Name { name } = event { + *seat_name_clone.lock().unwrap() = name + } + }, + (), + ) + }) .unwrap(); let need_redraw = Arc::new(atomic::AtomicBool::new(false)); @@ -39,11 +48,15 @@ fn main() { } = event { if text == " " { - *cb_contents_clone.lock().unwrap() = dbg!(clipboard.load()); + *cb_contents_clone.lock().unwrap() = + dbg!(clipboard.load(seat_name.lock().unwrap().clone())); need_redraw_clone.store(true, atomic::Ordering::Relaxed) } else if text == "s" { - clipboard - .store("This is an example text thats been copied to the wayland clipboard :)"); + clipboard.store( + seat_name.lock().unwrap().clone(), + "This is an example text thats been copied to the wayland clipboard :)" + .to_string(), + ); } } }) diff --git a/src/lib.rs b/src/lib.rs index 916b3aa..4b199fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,8 +15,9 @@ #![warn(missing_docs)] +use std::collections::HashMap; use std::io::{Read, Write}; -use std::os::raw::c_void; +use std::ops::Deref; use std::sync::mpsc; use std::sync::{Arc, Mutex}; use std::thread::sleep; @@ -26,13 +27,17 @@ use sctk::data_device::DataDevice; use sctk::data_device::DataSource; use sctk::data_device::DataSourceEvent; use sctk::keyboard::{map_keyboard_auto, Event as KbEvent}; -use sctk::reexports::client::Display; +use sctk::reexports::client::protocol::{ + wl_data_device_manager, wl_display::WlDisplay, wl_registry, wl_seat, +}; +use sctk::reexports::client::{Display, EventQueue, GlobalEvent, GlobalManager}; use sctk::wayland_client::sys::client::wl_display; -use sctk::Environment; + +type SeatMap = HashMap>, u32)>; enum WaylandRequest { - Store(String), - Load, + Store(String, String), + Load(String), Kill, } @@ -53,49 +58,158 @@ impl WaylandClipboard { /// /// Spawns a new thread to dispatch messages to the wayland server every /// 50ms to ensure the server can read stored data - pub fn new_threaded(wayland_display: *mut c_void) -> Self { + pub fn new_threaded(display: &Display) -> Self { let (request_send, request_recv) = mpsc::channel::(); let (load_send, load_recv) = mpsc::channel(); - - let wayland_display = unsafe { (wayland_display as *mut wl_display).as_mut().unwrap() }; + let display = display.clone(); std::thread::spawn(move || { - let (display, mut event_queue) = - unsafe { Display::from_external_display(wayland_display as *mut wl_display) }; - let env = Environment::from_display(&*display, &mut event_queue).unwrap(); - - let seat = env - .manager - .instantiate_range(1, 6, |seat| seat.implement_dummy()) + let mut event_queue = display.create_event_queue(); + let display = (*display) + .as_ref() + .make_wrapper(&event_queue.get_token()) .unwrap(); + Self::clipboard_thread(&display, &mut event_queue, request_recv, load_send); + }); - let device = DataDevice::init_for_seat(&env.data_device_manager, &seat, |_| {}); + WaylandClipboard { + request_send, + load_recv, + } + } - let enter_serial = Arc::new(Mutex::new(None)); - let my_enter_serial = enter_serial.clone(); - let _keyboard = map_keyboard_auto(&seat, move |event, _| { - if let KbEvent::Enter { serial, .. } = event { - *(my_enter_serial.lock().unwrap()) = Some(serial); + /// Creates a new WaylandClipboard object from a mutable `wl_display` ptr + /// + /// 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 { + let (request_send, request_recv) = mpsc::channel::(); + let (load_send, load_recv) = mpsc::channel(); + let display = display_ptr.as_mut().unwrap(); + std::thread::spawn(move || { + let (display, mut event_queue) = Display::from_external_display(display); + Self::clipboard_thread(&display, &mut event_queue, request_recv, load_send); + }); + + WaylandClipboard { + request_send, + load_recv, + } + } + + fn implement_seat( + id: u32, + version: u32, + seat_map: Arc>, + data_device_manager: &wl_data_device_manager::WlDataDeviceManager, + reg: &wl_registry::WlRegistry, + ) { + let seat_name = Arc::new(Mutex::new(String::new())); + let seat_name_clone = seat_name.clone(); + let seat = reg + .bind::(version, id, move |proxy| { + proxy.implement_closure( + move |event, _| { + if let wl_seat::Event::Name { name } = event { + *seat_name_clone.lock().unwrap() = name + } + }, + (), + ) + }) + .unwrap(); + let device = Arc::new(Mutex::new(DataDevice::init_for_seat( + data_device_manager, + &seat, + |_| {}, + ))); + map_keyboard_auto(&seat, move |event, _| match event { + KbEvent::Enter { serial, .. } => { + seat_map + .lock() + .unwrap() + .insert(seat_name.lock().unwrap().clone(), (device.clone(), serial)); + } + KbEvent::Leave { .. } => { + seat_map.lock().unwrap().remove(&*seat_name.lock().unwrap()); + } + _ => {} + }) + .unwrap(); + } + + fn clipboard_thread( + display: &WlDisplay, + event_queue: &mut EventQueue, + request_recv: mpsc::Receiver, + load_send: mpsc::Sender, + ) { + let seat_map = Arc::new(Mutex::new(SeatMap::new())); + + let data_device_manager = Arc::new(Mutex::new(None)); + let mut unimplemented_seats = Vec::new(); + + let data_device_manager_clone = data_device_manager.clone(); + let seat_map_clone = seat_map.clone(); + GlobalManager::new_with_cb(&display, move |event, reg| { + if let GlobalEvent::New { + id, + ref interface, + version, + } = event + { + if "wl_seat" == interface.as_str() && version >= 2 { + if let Some(ref data_device_manager) = + data_device_manager_clone.lock().unwrap().deref() + { + Self::implement_seat( + id, + version, + seat_map_clone.clone(), + data_device_manager, + ®, + ); + } else { + unimplemented_seats.push((id, version)); + } + } else if "wl_data_device_manager" == interface.as_str() { + *data_device_manager_clone.lock().unwrap() = Some( + reg.bind::( + version, + id, + |proxy| proxy.implement_dummy(), + ) + .unwrap(), + ); + for (id, version) in &unimplemented_seats { + Self::implement_seat( + *id, + *version, + seat_map_clone.clone(), + data_device_manager_clone.lock().unwrap().as_ref().unwrap(), + ®, + ); + } } - }); + } + }); - loop { - if let Ok(request) = request_recv.try_recv() { - match request { - WaylandRequest::Load => { + loop { + if let Ok(request) = request_recv.try_recv() { + match request { + WaylandRequest::Load(seat_name) => { + if let Some((device, _)) = seat_map.lock().unwrap().get(&seat_name) { // Load let mut reader = None; - device.with_selection(|offer| { + device.lock().unwrap().with_selection(|offer| { if let Some(offer) = offer { offer.with_mime_types(|types| { - for t in types { - if t == "text/plain;charset=utf-8" { - reader = Some( - offer - .receive("text/plain;charset=utf-8".into()) - .unwrap(), - ); - } + if types.contains(&"text/plain;charset=utf-8".to_string()) { + reader = Some( + offer + .receive("text/plain;charset=utf-8".into()) + .unwrap(), + ); } }); } @@ -109,9 +223,13 @@ impl WaylandClipboard { load_send.send("".to_string()).unwrap(); } } - WaylandRequest::Store(contents) => { + } + WaylandRequest::Store(seat_name, contents) => { + if let Some((device, enter_serial)) = + seat_map.lock().unwrap().get(&seat_name) + { let data_source = DataSource::new( - &env.data_device_manager, + data_device_manager.lock().unwrap().as_ref().unwrap(), &["text/plain;charset=utf-8"], move |source_event| { if let DataSourceEvent::Send { mut pipe, .. } = source_event { @@ -119,41 +237,39 @@ impl WaylandClipboard { } }, ); - if let Some(enter_serial) = *enter_serial.lock().unwrap() { - device.set_selection(&Some(data_source), enter_serial); - } + device + .lock() + .unwrap() + .set_selection(&Some(data_source), *enter_serial); event_queue.sync_roundtrip().unwrap(); } - WaylandRequest::Kill => break, } + WaylandRequest::Kill => break, } - event_queue.dispatch_pending().unwrap(); - sleep(Duration::from_millis(50)); } - }); - - WaylandClipboard { - request_send, - load_recv, + event_queue.dispatch_pending().unwrap(); + sleep(Duration::from_millis(50)); } } /// Returns text from the wayland clipboard /// - /// Only works when the window connected to the WlDisplay has - /// keyboard focus - pub fn load(&mut self) -> String { - self.request_send.send(WaylandRequest::Load).unwrap(); + /// Must be provided with a seat name and that seat must be in + /// focus to work + pub fn load>(&mut self, seat_name: S) -> String { + self.request_send + .send(WaylandRequest::Load(seat_name.into())) + .unwrap(); self.load_recv.recv().unwrap() } /// Stores text in the wayland clipboard /// - /// Only works when the window connected to the WlDisplay has - /// keyboard focus - pub fn store>(&mut self, text: S) { + /// Must be provided with a seat name and that seat must be in + /// focus to work + pub fn store>(&mut self, seat_name: S, text: S) { self.request_send - .send(WaylandRequest::Store(text.into())) + .send(WaylandRequest::Store(seat_name.into(), text.into())) .unwrap() } }