use anyhow::{Context, Result};
use std::fs::File;
use std::io::Write;
use wayland_client::{
Connection, Dispatch, Proxy, QueueHandle, delegate_noop, event_created_child,
globals::{GlobalListContents, registry_queue_init},
protocol::{wl_registry::WlRegistry, wl_seat::WlSeat},
};
use wayland_protocols_wlr::data_control::v1::client::{
zwlr_data_control_device_v1::{self, ZwlrDataControlDeviceV1},
zwlr_data_control_manager_v1::ZwlrDataControlManagerV1,
zwlr_data_control_offer_v1::ZwlrDataControlOfferV1,
zwlr_data_control_source_v1::{self, ZwlrDataControlSourceV1},
};
struct ClipState {
mime: String,
data: Vec<u8>,
done: bool,
}
pub fn serve(mime: &str, data: Vec<u8>) -> Result<()> {
let conn = Connection::connect_to_env().context("Wayland connection")?;
let (globals, mut queue) =
registry_queue_init::<ClipState>(&conn).context("registre Wayland")?;
let qh = queue.handle();
let mgr: ZwlrDataControlManagerV1 = globals.bind(&qh, 1..=2, ()).context(
"zwlr_data_control_manager_v1 missing (compositor has no wlroots clipboard support)",
)?;
let seat: WlSeat = globals.bind(&qh, 1..=8, ()).context("wl_seat missing")?;
let device = mgr.get_data_device(&seat, &qh, ());
let source = mgr.create_data_source(&qh, ());
source.offer(mime.to_string());
device.set_selection(Some(&source));
let mut state = ClipState {
mime: mime.to_string(),
data,
done: false,
};
queue.roundtrip(&mut state).context("set_selection")?;
while !state.done {
queue
.blocking_dispatch(&mut state)
.context("clipboard event loop")?;
}
Ok(())
}
pub fn spawn_detached(bytes: &[u8], args: &[&str]) -> Result<()> {
use std::os::unix::process::CommandExt;
use std::process::{Command, Stdio};
let exe = std::env::current_exe().context("current executable path")?;
let mut cmd = Command::new(exe);
cmd.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null());
unsafe {
cmd.pre_exec(|| rustix::process::setsid().map(|_| ()).map_err(Into::into));
}
let mut child = cmd.spawn().context("spawning clipboard daemon")?;
child
.stdin
.take()
.context("daemon stdin")?
.write_all(bytes)
.context("sending data to daemon")?;
Ok(())
}
impl Dispatch<WlRegistry, GlobalListContents> for ClipState {
fn event(
_: &mut Self,
_: &WlRegistry,
_: <WlRegistry as Proxy>::Event,
_: &GlobalListContents,
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
impl Dispatch<ZwlrDataControlSourceV1, ()> for ClipState {
fn event(
state: &mut Self,
_: &ZwlrDataControlSourceV1,
event: zwlr_data_control_source_v1::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
use zwlr_data_control_source_v1::Event;
match event {
Event::Send { mime_type, fd } => {
if mime_type == state.mime {
let _ = File::from(fd).write_all(&state.data);
}
}
Event::Cancelled => state.done = true,
_ => {}
}
}
}
impl Dispatch<ZwlrDataControlDeviceV1, ()> for ClipState {
fn event(
_: &mut Self,
_: &ZwlrDataControlDeviceV1,
_: zwlr_data_control_device_v1::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
}
event_created_child!(ClipState, ZwlrDataControlDeviceV1, [
zwlr_data_control_device_v1::EVT_DATA_OFFER_OPCODE => (ZwlrDataControlOfferV1, ()),
]);
}
delegate_noop!(ClipState: ignore ZwlrDataControlManagerV1);
delegate_noop!(ClipState: ignore ZwlrDataControlOfferV1);
delegate_noop!(ClipState: ignore WlSeat);