use std::collections::HashMap;
use std::io::{Read, Result, Write};
use std::ops::Deref;
use std::os::unix::io::FromRawFd;
use std::sync::mpsc;
use std::sync::{Arc, Mutex};
use std::thread;
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::{
wl_data_device_manager,
wl_display::WlDisplay,
wl_pointer::Event as PtrEvent,
wl_registry,
wl_seat::{self, Capability},
};
use sctk::reexports::client::{Display, EventQueue, GlobalEvent, GlobalManager, NewProxy};
use sctk::reexports::protocols::misc::gtk_primary_selection::client::{
gtk_primary_selection_device::Event as GtkPrimarySelectionDeviceEvent,
gtk_primary_selection_device::GtkPrimarySelectionDevice,
gtk_primary_selection_device_manager::GtkPrimarySelectionDeviceManager,
gtk_primary_selection_offer::GtkPrimarySelectionOffer, gtk_primary_selection_source,
};
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;
type SeatMap = HashMap<
String,
(
Arc<Mutex<Option<DataDevice>>>,
u32,
Arc<Mutex<Option<PrimarySelectionDevice>>>,
Arc<Mutex<Option<PrimarySelectionOffer>>>,
Arc<Mutex<Option<GtkPrimarySelectionDevice>>>,
Arc<Mutex<Option<GtkPrimarySelectionOffer>>>,
),
>;
pub struct ThreadedClipboard {
request_send: mpsc::Sender<ThreadRequest>,
load_recv: mpsc::Receiver<Result<String>>,
}
impl Drop for ThreadedClipboard {
fn drop(&mut self) {
self.request_send.send(ThreadRequest::Kill).unwrap()
}
}
impl ThreadedClipboard {
pub fn new(display: &Display) -> Self {
let (request_send, request_recv) = mpsc::channel();
let (load_send, load_recv) = mpsc::channel();
let display = display.clone();
std::thread::spawn(move || {
let mut event_queue = display.create_event_queue();
let display = (*display)
.as_ref()
.make_wrapper(&event_queue.get_token())
.unwrap();
clipboard_thread(&display, &mut event_queue, request_recv, load_send);
});
ThreadedClipboard {
request_send,
load_recv,
}
}
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();
std::thread::spawn(move || {
let (display, mut event_queue) = Display::from_external_display(display);
clipboard_thread(&display, &mut event_queue, request_recv, load_send);
});
ThreadedClipboard {
request_send,
load_recv,
}
}
pub fn load(&mut self, seat_name: Option<String>) -> Result<String> {
self.request_send
.send(ThreadRequest::Load(seat_name))
.unwrap();
self.load_recv.recv().unwrap()
}
pub fn store<T>(&mut self, seat_name: Option<String>, text: T)
where
T: Into<String>,
{
self.request_send
.send(ThreadRequest::Store(seat_name, text.into()))
.unwrap()
}
pub fn load_primary(&mut self, seat_name: Option<String>) -> Result<String> {
self.request_send
.send(ThreadRequest::LoadPrimary(seat_name))
.unwrap();
self.load_recv.recv().unwrap()
}
pub fn store_primary(&mut self, seat_name: Option<String>, text: String) {
self.request_send
.send(ThreadRequest::StorePrimary(seat_name, text))
.unwrap()
}
}
enum ThreadRequest {
Store(Option<String>, String),
Load(Option<String>),
StorePrimary(Option<String>, String),
LoadPrimary(Option<String>),
Kill,
}
fn clipboard_thread(
display: &WlDisplay,
event_queue: &mut EventQueue,
request_recv: mpsc::Receiver<ThreadRequest>,
load_send: mpsc::Sender<Result<String>>,
) {
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 primary_selection_device_manager = Arc::new(Mutex::new(None));
let gtk_primary_selection_device_manager = Arc::new(Mutex::new(None));
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 gtk_primary_selection_device_manager_clone = gtk_primary_selection_device_manager.clone();
let seat_map_clone = seat_map.clone();
let last_seat_name_clone = last_seat_name.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()
{
implement_seat(
id,
std::cmp::min(version, 6),
seat_map_clone.clone(),
last_seat_name_clone.clone(),
data_device_manager,
®,
primary_selection_device_manager_clone.clone(),
gtk_primary_selection_device_manager_clone.clone(),
);
} else {
unimplemented_seats.push((id, version));
}
} else if "wl_data_device_manager" == interface.as_str() {
*data_device_manager_clone.lock().unwrap() = Some(
reg.bind::<wl_data_device_manager::WlDataDeviceManager, _>(
version,
id,
NewProxy::implement_dummy,
)
.unwrap(),
);
for (id, version) in &unimplemented_seats {
implement_seat(
*id,
std::cmp::min(*version, 6),
seat_map_clone.clone(),
last_seat_name_clone.clone(),
data_device_manager_clone.lock().unwrap().as_ref().unwrap(),
®,
primary_selection_device_manager_clone.clone(),
gtk_primary_selection_device_manager_clone.clone(),
);
}
} else if "zwp_primary_selection_device_manager_v1" == interface.as_str() {
*primary_selection_device_manager_clone.lock().unwrap() = Some(
reg.bind::<PrimarySelectionDeviceMgr, _>(
version,
id,
NewProxy::implement_dummy,
)
.unwrap(),
);
} else if "gtk_primary_selection_device_manager" == interface.as_str() {
*gtk_primary_selection_device_manager_clone.lock().unwrap() = Some(
reg.bind::<GtkPrimarySelectionDeviceManager, _>(
version,
id,
NewProxy::implement_dummy,
)
.unwrap(),
);
}
}
});
event_queue.sync_roundtrip().unwrap();
let mut sleep_amount = 50;
let mut warm_start_amount = 0;
loop {
if let Ok(request) = request_recv.try_recv() {
sleep_amount = 0;
match request {
ThreadRequest::Load(seat_name) => {
event_queue.sync_roundtrip().unwrap();
let seat_map = seat_map.lock().unwrap().clone();
let contents = seat_map
.get(&seat_name.unwrap_or_else(|| last_seat_name.lock().unwrap().clone()))
.map_or(Ok(String::new()), |seat| {
let mut reader = None;
if let Some(device) = seat.0.lock().unwrap().as_ref() {
device.with_selection(|offer| {
if let Some(offer) = offer {
offer.with_mime_types(|types| {
if types
.contains(&"text/plain;charset=utf-8".to_string())
{
reader = Some(
offer
.receive("text/plain;charset=utf-8".into())
.unwrap(),
);
}
});
}
});
event_queue.sync_roundtrip().unwrap();
}
reader.map_or(Ok(String::new()), |mut reader| {
let mut contents = String::new();
if let Err(err) = reader.read_to_string(&mut contents) {
Err(err)
} else {
Ok(contents)
}
})
});
let contents = contents.and_then(|contents| Ok(normilize_to_lf(contents)));
load_send.send(contents).unwrap();
}
ThreadRequest::Store(seat_name, contents) => {
event_queue.sync_roundtrip().unwrap();
let seat_map = seat_map.lock().unwrap().clone();
if let Some((device, enter_serial, _, _, _, _)) = seat_map
.get(&seat_name.unwrap_or_else(|| last_seat_name.lock().unwrap().clone()))
{
if let Some(device) = device.lock().unwrap().as_ref() {
let data_source = DataSource::new(
data_device_manager.lock().unwrap().as_ref().unwrap(),
&["text/plain;charset=utf-8"],
move |source_event| {
if let DataSourceEvent::Send { mut pipe, .. } = source_event {
write!(pipe, "{}", contents).unwrap();
}
},
);
device.set_selection(&Some(data_source), *enter_serial);
event_queue.sync_roundtrip().unwrap();
}
}
}
ThreadRequest::LoadPrimary(seat_name) => {
event_queue.sync_roundtrip().unwrap();
let seat_map = seat_map.lock().unwrap().clone();
let contents = if primary_selection_device_manager.lock().unwrap().is_some() {
seat_map
.get(
&seat_name
.unwrap_or_else(|| last_seat_name.lock().unwrap().clone()),
)
.map_or(Ok(String::new()), |seat| {
seat.3.lock().unwrap().as_ref().map_or(
Ok(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();
if let Err(err) = file.read_to_string(&mut contents) {
Err(err)
} else {
Ok(contents)
}
},
)
})
} else if gtk_primary_selection_device_manager
.lock()
.unwrap()
.is_some()
{
seat_map
.get(
&seat_name
.unwrap_or_else(|| last_seat_name.lock().unwrap().clone()),
)
.map_or(Ok(String::new()), |seat| {
seat.5.lock().unwrap().as_ref().map_or(
Ok(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();
if let Err(err) = file.read_to_string(&mut contents) {
Err(err)
} else {
Ok(contents)
}
},
)
})
} else {
Ok(String::new())
};
let contents = contents.and_then(|contents| Ok(normilize_to_lf(contents)));
load_send.send(contents).unwrap();
}
ThreadRequest::StorePrimary(seat_name, contents) => {
event_queue.sync_roundtrip().unwrap();
let seat_map = seat_map.lock().unwrap().clone();
if let Some((_, enter_serial, primary_device, _, gtk_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();
}
}
},
(),
)
});
if let Ok(source) = &source {
source.offer("text/plain;charset=utf-8".to_string());
}
primary_device.set_selection(source.ok().as_ref(), *enter_serial);
}
} else if let Some(manager) =
&*gtk_primary_selection_device_manager.lock().unwrap()
{
if let Some(gtk_primary_device) = &*gtk_primary_device.lock().unwrap() {
let source = manager.create_source(|proxy| {
proxy.implement_closure(
move |event, _| {
if let gtk_primary_selection_source::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();
}
}
},
(),
)
});
if let Ok(source) = &source {
source.offer("text/plain;charset=utf-8".to_string());
}
gtk_primary_device
.set_selection(source.ok().as_ref(), *enter_serial);
}
}
}
}
ThreadRequest::Kill => break,
}
}
let pending_events = event_queue.dispatch_pending().unwrap();
let num_seats = seat_map.lock().unwrap().len();
if num_seats == 0 && pending_events != 0 {
sleep_amount = 0;
} else if sleep_amount > 0 {
thread::sleep(Duration::from_millis(sleep_amount));
if warm_start_amount < 16 {
warm_start_amount += 1;
if warm_start_amount == 16 {
sleep_amount = 1;
}
} else if sleep_amount < 50 {
sleep_amount = std::cmp::min(2 * sleep_amount, 50);
}
} else if sleep_amount == 0 {
sleep_amount = 1;
warm_start_amount = 0;
}
}
}
fn implement_seat(
id: u32,
version: u32,
seat_map: Arc<Mutex<SeatMap>>,
last_seat_name: Arc<Mutex<String>>,
data_device_manager: &wl_data_device_manager::WlDataDeviceManager,
reg: &wl_registry::WlRegistry,
primary_device_manager: Arc<Mutex<Option<PrimarySelectionDeviceMgr>>>,
gtk_primary_device_manager: Arc<Mutex<Option<GtkPrimarySelectionDeviceManager>>>,
) {
let device = Arc::new(Mutex::new(None));
let device_clone = device.clone();
let seat_name = Arc::new(Mutex::new(String::new()));
let seat_name_clone = seat_name.clone();
let seat_map_clone = seat_map.clone();
let primary_device = Arc::new(Mutex::new(None));
let primary_offer = Arc::new(Mutex::new(None));
let primary_device_clone = primary_device.clone();
let primary_offer_clone = primary_offer.clone();
let gtk_primary_device = Arc::new(Mutex::new(None));
let gtk_primary_offer = Arc::new(Mutex::new(None));
let gtk_primary_device_clone = gtk_primary_device.clone();
let gtk_primary_offer_clone = gtk_primary_offer.clone();
let mut pointer = None;
let mut keyboard = None;
let seat = reg
.bind::<wl_seat::WlSeat, _>(version, id, move |proxy| {
proxy.implement_closure(
move |event, seat| match event {
wl_seat::Event::Name { name } => *seat_name_clone.lock().unwrap() = name,
wl_seat::Event::Capabilities { capabilities } => {
if capabilities.contains(Capability::Pointer) {
if pointer.is_none() {
let device_clone = device_clone.clone();
let primary_device_clone = primary_device_clone.clone();
let primary_offer_clone = primary_offer_clone.clone();
let gtk_primary_device_clone = gtk_primary_device_clone.clone();
let gtk_primary_offer_clone = gtk_primary_offer_clone.clone();
let last_seat_name_clone = last_seat_name.clone();
let seat_map_clone = seat_map_clone.clone();
let seat_name_clone = seat_name_clone.clone();
pointer = Some(
seat.get_pointer(move |pointer| {
pointer.implement_closure(
move |evt, _| {
*last_seat_name_clone.lock().unwrap() =
seat_name_clone.lock().unwrap().clone();
match evt {
PtrEvent::Enter { serial, .. } => {
if let Some(seat) =
seat_map_clone.lock().unwrap().get_mut(
&seat_name_clone
.lock()
.unwrap()
.clone(),
)
{
seat.1 = serial;
return;
}
seat_map_clone.lock().unwrap().insert(
seat_name_clone.lock().unwrap().clone(),
(
device_clone.clone(),
serial,
primary_device_clone.clone(),
primary_offer_clone.clone(),
gtk_primary_device_clone.clone(),
gtk_primary_offer_clone.clone(),
),
);
}
PtrEvent::Button { serial, .. } => {
if let Some(seat) =
seat_map_clone.lock().unwrap().get_mut(
&seat_name_clone
.lock()
.unwrap()
.clone(),
)
{
seat.1 = serial;
return;
}
seat_map_clone.lock().unwrap().insert(
seat_name_clone.lock().unwrap().clone(),
(
device_clone.clone(),
serial,
primary_device_clone.clone(),
primary_offer_clone.clone(),
gtk_primary_device_clone.clone(),
gtk_primary_offer_clone.clone(),
),
);
}
_ => {}
}
},
(),
)
})
.unwrap(),
);
}
} else if let Some(pointer) = pointer.take() {
if pointer.as_ref().version() >= 3 {
pointer.release();
}
}
if capabilities.contains(Capability::Keyboard) {
if keyboard.is_none() {
let device_clone = device_clone.clone();
let primary_device_clone = primary_device_clone.clone();
let primary_offer_clone = primary_offer_clone.clone();
let gtk_primary_device_clone = gtk_primary_device_clone.clone();
let gtk_primary_offer_clone = gtk_primary_offer_clone.clone();
let last_seat_name_clone = last_seat_name.clone();
let seat_map_clone = seat_map_clone.clone();
let seat_name_clone = seat_name_clone.clone();
keyboard = Some(
map_keyboard_auto(&seat, move |event, _| {
*last_seat_name_clone.lock().unwrap() =
seat_name_clone.lock().unwrap().clone();
match event {
KbEvent::Enter { serial, .. } => {
seat_map_clone.lock().unwrap().insert(
seat_name_clone.lock().unwrap().clone(),
(
device_clone.clone(),
serial,
primary_device_clone.clone(),
primary_offer_clone.clone(),
gtk_primary_device_clone.clone(),
gtk_primary_offer_clone.clone(),
),
);
}
KbEvent::Key { serial, .. } => {
seat_map_clone.lock().unwrap().insert(
seat_name_clone.lock().unwrap().clone(),
(
device_clone.clone(),
serial,
primary_device_clone.clone(),
primary_offer_clone.clone(),
gtk_primary_device_clone.clone(),
gtk_primary_offer_clone.clone(),
),
);
}
KbEvent::Leave { .. } => {
seat_map_clone
.lock()
.unwrap()
.remove(&*seat_name_clone.lock().unwrap());
}
_ => {}
}
})
.unwrap(),
);
}
} else if let Some(keyboard) = keyboard.take() {
if keyboard.as_ref().version() >= 3 {
keyboard.release();
}
}
}
_ => (),
},
(),
)
})
.unwrap();
*device.lock().unwrap() = Some(DataDevice::init_for_seat(
data_device_manager,
&seat,
|_| {},
));
if let Some(manager) = &*primary_device_manager.lock().unwrap() {
*primary_device.lock().unwrap() = manager
.get_device(&seat, |proxy| {
proxy.implement_closure(
move |event, _| {
if let ZwpPrimarySelectionDeviceEvent::DataOffer { offer } = event {
*primary_offer.lock().unwrap() = Some(offer.implement_dummy());
let map_contents = seat_map
.lock()
.unwrap()
.get(&seat_name.lock().unwrap().clone())
.cloned();
if let Some(map_contents) = map_contents {
seat_map.lock().unwrap().insert(
seat_name.lock().unwrap().clone(),
(
map_contents.0.clone(),
map_contents.1,
map_contents.2,
primary_offer.clone(),
Arc::new(Mutex::new(None)),
Arc::new(Mutex::new(None)),
),
);
}
}
},
(),
)
})
.ok();
} else if let Some(manager) = &*gtk_primary_device_manager.lock().unwrap() {
*gtk_primary_device.lock().unwrap() = manager
.get_device(&seat, |proxy| {
proxy.implement_closure(
move |event, _| {
if let GtkPrimarySelectionDeviceEvent::DataOffer { offer } = event {
*gtk_primary_offer.lock().unwrap() = Some(offer.implement_dummy());
let map_contents = seat_map
.lock()
.unwrap()
.get(&seat_name.lock().unwrap().clone())
.cloned();
if let Some(map_contents) = map_contents {
seat_map.lock().unwrap().insert(
seat_name.lock().unwrap().clone(),
(
map_contents.0.clone(),
map_contents.1,
Arc::new(Mutex::new(None)),
Arc::new(Mutex::new(None)),
map_contents.4,
gtk_primary_offer.clone(),
),
);
}
}
},
(),
)
})
.ok();
}
}
fn normilize_to_lf(text: String) -> String {
text.replace("\r\n", "\n").replace("\r", "\n")
}