Add primary selection support
This commit is contained in:
parent
1fa455ab6f
commit
60e77cd2b0
3 changed files with 208 additions and 15 deletions
|
|
@ -11,6 +11,7 @@ keywords = ["clipboard", "wayland"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sctk = { package = "smithay-client-toolkit", version = "0.5" }
|
sctk = { package = "smithay-client-toolkit", version = "0.5" }
|
||||||
|
nix = "0.13.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
andrew = "0.2"
|
andrew = "0.2"
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! let (display, _) =
|
//! let (display, _) =
|
||||||
//! Display::connect_to_env().expect("Failed to connect to the wayland server.");
|
//! 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");
|
//! clipboard.store(None, "Test data");
|
||||||
//! println!("{}", clipboard.load(None));
|
//! println!("{}", clipboard.load(None));
|
||||||
//! ```
|
//! ```
|
||||||
|
|
|
||||||
220
src/threaded.rs
220
src/threaded.rs
|
|
@ -1,11 +1,15 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
use std::os::unix::io::FromRawFd;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use nix::fcntl::OFlag;
|
||||||
|
use nix::unistd::{close, pipe2};
|
||||||
|
|
||||||
use sctk::data_device::{DataDevice, DataSource, DataSourceEvent};
|
use sctk::data_device::{DataDevice, DataSource, DataSourceEvent};
|
||||||
use sctk::keyboard::{map_keyboard_auto, Event as KbEvent};
|
use sctk::keyboard::{map_keyboard_auto, Event as KbEvent};
|
||||||
use sctk::reexports::client::protocol::{
|
use sctk::reexports::client::protocol::{
|
||||||
|
|
@ -13,10 +17,27 @@ use sctk::reexports::client::protocol::{
|
||||||
wl_seat,
|
wl_seat,
|
||||||
};
|
};
|
||||||
use sctk::reexports::client::{Display, EventQueue, GlobalEvent, GlobalManager, NewProxy};
|
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;
|
use sctk::wayland_client::sys::client::wl_display;
|
||||||
|
|
||||||
/// Used to store registered seats and their last event serial
|
/// Used to store registered seats and their last event serial
|
||||||
type SeatMap = HashMap<String, (Arc<Mutex<DataDevice>>, u32)>;
|
type SeatMap = HashMap<
|
||||||
|
String,
|
||||||
|
(
|
||||||
|
Arc<Mutex<DataDevice>>,
|
||||||
|
u32,
|
||||||
|
Arc<Mutex<Option<PrimarySelectionDevice>>>,
|
||||||
|
Arc<Mutex<Option<PrimarySelectionOffer>>>,
|
||||||
|
),
|
||||||
|
>;
|
||||||
|
|
||||||
/// Object representing the Wayland clipboard
|
/// Object representing the Wayland clipboard
|
||||||
pub struct ThreadedClipboard {
|
pub struct ThreadedClipboard {
|
||||||
|
|
@ -61,7 +82,7 @@ impl ThreadedClipboard {
|
||||||
///
|
///
|
||||||
/// Spawns a new thread to dispatch messages to the wayland server every
|
/// Spawns a new thread to dispatch messages to the wayland server every
|
||||||
/// 50ms to ensure the server can read stored data
|
/// 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 (request_send, request_recv) = mpsc::channel();
|
||||||
let (load_send, load_recv) = mpsc::channel();
|
let (load_send, load_recv) = mpsc::channel();
|
||||||
let display = display_ptr.as_mut().unwrap();
|
let display = display_ptr.as_mut().unwrap();
|
||||||
|
|
@ -102,6 +123,31 @@ impl ThreadedClipboard {
|
||||||
.send(ThreadRequest::Store(seat_name, text))
|
.send(ThreadRequest::Store(seat_name, text))
|
||||||
.unwrap()
|
.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>) -> 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<String>, text: String) {
|
||||||
|
self.request_send
|
||||||
|
.send(ThreadRequest::StorePrimary(seat_name, text))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requests sent to the clipboard thread
|
/// Requests sent to the clipboard thread
|
||||||
|
|
@ -110,6 +156,10 @@ enum ThreadRequest {
|
||||||
Store(Option<String>, String),
|
Store(Option<String>, String),
|
||||||
/// Load text from a specific seats clipboard
|
/// Load text from a specific seats clipboard
|
||||||
Load(Option<String>),
|
Load(Option<String>),
|
||||||
|
/// Store text in a specific seats primary clipboard
|
||||||
|
StorePrimary(Option<String>, String),
|
||||||
|
/// Load text in a specific seats primary clipboard
|
||||||
|
LoadPrimary(Option<String>),
|
||||||
/// Kill the thread
|
/// Kill the thread
|
||||||
Kill,
|
Kill,
|
||||||
}
|
}
|
||||||
|
|
@ -128,10 +178,13 @@ fn clipboard_thread(
|
||||||
let data_device_manager = Arc::new(Mutex::new(None));
|
let data_device_manager = Arc::new(Mutex::new(None));
|
||||||
let mut unimplemented_seats = Vec::new();
|
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
|
// 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 last_seat_name = Arc::new(Mutex::new(String::new()));
|
||||||
|
|
||||||
let data_device_manager_clone = data_device_manager.clone();
|
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 seat_map_clone = seat_map.clone();
|
||||||
let last_seat_name_clone = last_seat_name.clone();
|
let last_seat_name_clone = last_seat_name.clone();
|
||||||
|
|
||||||
|
|
@ -155,6 +208,7 @@ fn clipboard_thread(
|
||||||
last_seat_name_clone.clone(),
|
last_seat_name_clone.clone(),
|
||||||
data_device_manager,
|
data_device_manager,
|
||||||
®,
|
®,
|
||||||
|
primary_selection_device_manager_clone.clone(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Store the seat for implementation once wl_data_device_manager is registered
|
// Store the seat for implementation once wl_data_device_manager is registered
|
||||||
|
|
@ -179,8 +233,19 @@ fn clipboard_thread(
|
||||||
last_seat_name_clone.clone(),
|
last_seat_name_clone.clone(),
|
||||||
data_device_manager_clone.lock().unwrap().as_ref().unwrap(),
|
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::<PrimarySelectionDeviceMgr, _>(
|
||||||
|
version,
|
||||||
|
id,
|
||||||
|
NewProxy::implement_dummy,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -195,7 +260,7 @@ fn clipboard_thread(
|
||||||
event_queue.sync_roundtrip().unwrap();
|
event_queue.sync_roundtrip().unwrap();
|
||||||
let seat_map = seat_map.lock().unwrap().clone();
|
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
|
let contents = seat_map
|
||||||
.get(&seat_name.unwrap_or_else(|| last_seat_name.lock().unwrap().clone()))
|
.get(&seat_name.unwrap_or_else(|| last_seat_name.lock().unwrap().clone()))
|
||||||
.map_or(String::new(), |seat| {
|
.map_or(String::new(), |seat| {
|
||||||
|
|
@ -228,7 +293,7 @@ fn clipboard_thread(
|
||||||
let seat_map = seat_map.lock().unwrap().clone();
|
let seat_map = seat_map.lock().unwrap().clone();
|
||||||
|
|
||||||
// Get the requested seat from the seat map
|
// 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()))
|
.get(&seat_name.unwrap_or_else(|| last_seat_name.lock().unwrap().clone()))
|
||||||
{
|
{
|
||||||
let data_source = DataSource::new(
|
let data_source = DataSource::new(
|
||||||
|
|
@ -248,6 +313,68 @@ fn clipboard_thread(
|
||||||
event_queue.sync_roundtrip().unwrap();
|
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,
|
ThreadRequest::Kill => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -265,6 +392,7 @@ fn implement_seat(
|
||||||
last_seat_name: Arc<Mutex<String>>,
|
last_seat_name: Arc<Mutex<String>>,
|
||||||
data_device_manager: &wl_data_device_manager::WlDataDeviceManager,
|
data_device_manager: &wl_data_device_manager::WlDataDeviceManager,
|
||||||
reg: &wl_registry::WlRegistry,
|
reg: &wl_registry::WlRegistry,
|
||||||
|
primary_device_manager: Arc<Mutex<Option<PrimarySelectionDeviceMgr>>>,
|
||||||
) {
|
) {
|
||||||
let seat_name = Arc::new(Mutex::new(String::new()));
|
let seat_name = Arc::new(Mutex::new(String::new()));
|
||||||
let seat_name_clone = seat_name.clone();
|
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 seat_map_clone = seat_map.clone();
|
||||||
let device_clone = device.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 seat_name_clone = seat_name.clone();
|
||||||
let last_seat_name_clone = last_seat_name.clone();
|
let last_seat_name_clone = last_seat_name.clone();
|
||||||
map_keyboard_auto(&seat, move |event, _| {
|
map_keyboard_auto(&seat, move |event, _| {
|
||||||
|
|
@ -303,13 +475,23 @@ fn implement_seat(
|
||||||
KbEvent::Enter { serial, .. } => {
|
KbEvent::Enter { serial, .. } => {
|
||||||
seat_map_clone.lock().unwrap().insert(
|
seat_map_clone.lock().unwrap().insert(
|
||||||
seat_name_clone.lock().unwrap().clone(),
|
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, .. } => {
|
KbEvent::Key { serial, .. } => {
|
||||||
seat_map_clone.lock().unwrap().insert(
|
seat_map_clone.lock().unwrap().insert(
|
||||||
seat_name_clone.lock().unwrap().clone(),
|
seat_name_clone.lock().unwrap().clone(),
|
||||||
(device_clone.clone(), serial),
|
(
|
||||||
|
device_clone.clone(),
|
||||||
|
serial,
|
||||||
|
primary_device_clone.clone(),
|
||||||
|
primary_offer_clone.clone(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
KbEvent::Leave { .. } => {
|
KbEvent::Leave { .. } => {
|
||||||
|
|
@ -332,16 +514,26 @@ fn implement_seat(
|
||||||
// Get serials from recieved events from the seat pointer
|
// Get serials from recieved events from the seat pointer
|
||||||
match evt {
|
match evt {
|
||||||
PtrEvent::Enter { serial, .. } => {
|
PtrEvent::Enter { serial, .. } => {
|
||||||
seat_map
|
seat_map.lock().unwrap().insert(
|
||||||
.lock()
|
seat_name.lock().unwrap().clone(),
|
||||||
.unwrap()
|
(
|
||||||
.insert(seat_name.lock().unwrap().clone(), (device.clone(), serial));
|
device.clone(),
|
||||||
|
serial,
|
||||||
|
primary_device.clone(),
|
||||||
|
primary_offer.clone(),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
PtrEvent::Button { serial, .. } => {
|
PtrEvent::Button { serial, .. } => {
|
||||||
seat_map
|
seat_map.lock().unwrap().insert(
|
||||||
.lock()
|
seat_name.lock().unwrap().clone(),
|
||||||
.unwrap()
|
(
|
||||||
.insert(seat_name.lock().unwrap().clone(), (device.clone(), serial));
|
device.clone(),
|
||||||
|
serial,
|
||||||
|
primary_device.clone(),
|
||||||
|
primary_offer.clone(),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
PtrEvent::Leave { .. } => {
|
PtrEvent::Leave { .. } => {
|
||||||
seat_map.lock().unwrap().remove(&*seat_name.lock().unwrap());
|
seat_map.lock().unwrap().remove(&*seat_name.lock().unwrap());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue