mirror of
https://github.com/wez/wezterm.git
synced 2024-11-22 22:42:48 +03:00
Implement primary selection support
This commit is contained in:
parent
561d360e0a
commit
125e89c3ae
@ -1,7 +1,15 @@
|
||||
use anyhow::{anyhow, Error};
|
||||
use anyhow::{anyhow, Error, bail};
|
||||
use filedescriptor::{FileDescriptor, Pipe};
|
||||
use smithay_client_toolkit as toolkit;
|
||||
use std::os::fd::AsRawFd;
|
||||
use toolkit::globals::GlobalData;
|
||||
use wayland_client::{Dispatch, event_created_child};
|
||||
use wayland_client::globals::{GlobalList, BindError};
|
||||
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1;
|
||||
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_device_v1::{ZwpPrimarySelectionDeviceV1, self, Event as PrimarySelectionDeviceEvent};
|
||||
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::{ZwpPrimarySelectionOfferV1, Event as PrimarySelectionOfferEvent};
|
||||
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_source_v1::{ZwpPrimarySelectionSourceV1, Event as PrimarySelectionSourceEvent};
|
||||
use std::io::Write;
|
||||
use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use toolkit::reexports::client::protocol::wl_data_offer::WlDataOffer;
|
||||
|
||||
@ -30,32 +38,71 @@ impl CopyAndPaste {
|
||||
|
||||
pub(super) fn get_clipboard_data(
|
||||
&mut self,
|
||||
_clipboard: Clipboard,
|
||||
clipboard: Clipboard,
|
||||
) -> anyhow::Result<FileDescriptor> {
|
||||
// TODO; primary selection
|
||||
let offer = self
|
||||
.data_offer
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("no data offer"))?;
|
||||
let pipe = Pipe::new().map_err(Error::msg)?;
|
||||
offer.receive(TEXT_MIME_TYPE.to_string(), pipe.write.as_raw_fd());
|
||||
Ok(pipe.read)
|
||||
let conn = crate::Connection::get().unwrap().wayland();
|
||||
let wayland_state = conn.wayland_state.borrow();
|
||||
let primary_selection = if let Clipboard::PrimarySelection = clipboard {
|
||||
wayland_state.primary_selection_manager.as_ref()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match primary_selection {
|
||||
Some(primary_selection) => {
|
||||
let inner = primary_selection.inner.lock().unwrap();
|
||||
let offer = inner
|
||||
.offer
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("no primary selection offer"))?;
|
||||
let pipe = Pipe::new().map_err(Error::msg)?;
|
||||
offer.receive(TEXT_MIME_TYPE.to_string(), pipe.write.as_raw_fd());
|
||||
Ok(pipe.read)
|
||||
}
|
||||
None => {
|
||||
let offer = self
|
||||
.data_offer
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("no data offer"))?;
|
||||
let pipe = Pipe::new().map_err(Error::msg)?;
|
||||
offer.receive(TEXT_MIME_TYPE.to_string(), pipe.write.as_raw_fd());
|
||||
Ok(pipe.read)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn set_clipboard_data(&mut self, _clipboard: Clipboard, data: String) {
|
||||
// TODO: primary selection
|
||||
|
||||
pub(super) fn set_clipboard_data(&mut self, clipboard: Clipboard, data: String) {
|
||||
let conn = crate::Connection::get().unwrap().wayland();
|
||||
let qh = conn.event_queue.borrow().handle();
|
||||
let mut wayland_state = conn.wayland_state.borrow_mut();
|
||||
let last_serial = *wayland_state.last_serial.borrow();
|
||||
let data_device = &wayland_state.data_device;
|
||||
|
||||
let source = wayland_state
|
||||
.data_device_manager_state
|
||||
.create_copy_paste_source(&qh, vec![TEXT_MIME_TYPE]);
|
||||
source.set_selection(data_device.as_ref().unwrap(), last_serial);
|
||||
wayland_state.copy_paste_source.replace((source, data));
|
||||
let primary_selection = if let Clipboard::PrimarySelection = clipboard {
|
||||
wayland_state.primary_selection_manager.as_ref()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match primary_selection {
|
||||
Some(primary_selection) => {
|
||||
let manager = &primary_selection.manager;
|
||||
let selection_device = wayland_state.primary_select_device.as_ref().unwrap();
|
||||
let source = manager.create_source(&qh, PrimarySelectionManagerData::default());
|
||||
source.offer(TEXT_MIME_TYPE.to_string());
|
||||
selection_device.set_selection(Some(&source), last_serial);
|
||||
wayland_state
|
||||
.primary_selection_source
|
||||
.replace((source, data));
|
||||
}
|
||||
None => {
|
||||
let data_device = &wayland_state.data_device;
|
||||
let source = wayland_state
|
||||
.data_device_manager_state
|
||||
.create_copy_paste_source(&qh, vec![TEXT_MIME_TYPE]);
|
||||
source.set_selection(data_device.as_ref().unwrap(), last_serial);
|
||||
wayland_state.copy_paste_source.replace((source, data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn confirm_selection(&mut self, offer: WlDataOffer) {
|
||||
@ -74,3 +121,186 @@ impl WaylandState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn write_selection_to_pipe(fd: FileDescriptor, text: &str) {
|
||||
if let Err(e) = write_pipe_with_timeout(fd, text.as_bytes()) {
|
||||
log::error!("while sending primary selection to pipe: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_pipe_with_timeout(mut file: FileDescriptor, data: &[u8]) -> anyhow::Result<()> {
|
||||
file.set_non_blocking(true)?;
|
||||
let mut pfd = libc::pollfd {
|
||||
fd: file.as_raw_fd(),
|
||||
events: libc::POLLOUT,
|
||||
revents: 0,
|
||||
};
|
||||
|
||||
let mut buf = data;
|
||||
|
||||
while !buf.is_empty() {
|
||||
if unsafe { libc::poll(&mut pfd, 1, 3000) == 1 } {
|
||||
match file.write(buf) {
|
||||
Ok(size) if size == 0 => {
|
||||
bail!("zero byte write");
|
||||
}
|
||||
Ok(size) => {
|
||||
buf = &buf[size..];
|
||||
}
|
||||
Err(e) => bail!("error writing to pipe: {}", e),
|
||||
}
|
||||
} else {
|
||||
bail!("timed out writing to pipe");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Smithay has their own primary selection handler in 0.18
|
||||
// Some code borrowed from https://github.com/Smithay/client-toolkit/commit/4a5c4f59f640bc588a55277261bbed1bd2abea98
|
||||
pub(super) struct PrimarySelectionManagerState {
|
||||
pub(super) manager: ZwpPrimarySelectionDeviceManagerV1,
|
||||
inner: Mutex<PrimaryInner>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct PrimaryInner {
|
||||
pending_offer: Option<ZwpPrimarySelectionOfferV1>,
|
||||
offer: Option<ZwpPrimarySelectionOfferV1>,
|
||||
valid_mime: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct PrimarySelectionManagerData {}
|
||||
|
||||
impl PrimarySelectionManagerState {
|
||||
pub(super) fn bind(
|
||||
globals: &GlobalList,
|
||||
queue_handle: &wayland_client::QueueHandle<WaylandState>,
|
||||
) -> Result<Self, BindError> {
|
||||
let manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
|
||||
Ok(Self {
|
||||
manager,
|
||||
inner: Mutex::new(PrimaryInner::default()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpPrimarySelectionDeviceManagerV1, GlobalData, WaylandState>
|
||||
for PrimarySelectionManagerState
|
||||
{
|
||||
fn event(
|
||||
_state: &mut WaylandState,
|
||||
_proxy: &ZwpPrimarySelectionDeviceManagerV1,
|
||||
_event: <ZwpPrimarySelectionDeviceManagerV1 as wayland_client::Proxy>::Event,
|
||||
_data: &GlobalData,
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<WaylandState>,
|
||||
) {
|
||||
unreachable!("primary selection manager has no events");
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpPrimarySelectionSourceV1, PrimarySelectionManagerData, WaylandState>
|
||||
for PrimarySelectionManagerState
|
||||
{
|
||||
fn event(
|
||||
state: &mut WaylandState,
|
||||
source: &ZwpPrimarySelectionSourceV1,
|
||||
event: <ZwpPrimarySelectionSourceV1 as wayland_client::Proxy>::Event,
|
||||
_data: &PrimarySelectionManagerData,
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<WaylandState>,
|
||||
) {
|
||||
match event {
|
||||
PrimarySelectionSourceEvent::Send { mime_type, fd } => {
|
||||
if mime_type != TEXT_MIME_TYPE {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some((ps_source, data)) = &state.primary_selection_source {
|
||||
if ps_source != source {
|
||||
return;
|
||||
}
|
||||
let fd = unsafe { FileDescriptor::from_raw_fd(fd.into_raw_fd()) };
|
||||
write_selection_to_pipe(fd, data);
|
||||
}
|
||||
}
|
||||
PrimarySelectionSourceEvent::Cancelled => {
|
||||
state.primary_selection_source.take();
|
||||
source.destroy();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpPrimarySelectionOfferV1, PrimarySelectionManagerData, WaylandState>
|
||||
for PrimarySelectionManagerState
|
||||
{
|
||||
fn event(
|
||||
state: &mut WaylandState,
|
||||
_proxy: &ZwpPrimarySelectionOfferV1,
|
||||
event: <ZwpPrimarySelectionOfferV1 as wayland_client::Proxy>::Event,
|
||||
_data: &PrimarySelectionManagerData,
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<WaylandState>,
|
||||
) {
|
||||
match event {
|
||||
PrimarySelectionOfferEvent::Offer { mime_type } => {
|
||||
if mime_type == TEXT_MIME_TYPE {
|
||||
let mgr = state.primary_selection_manager.as_ref().unwrap();
|
||||
let mut inner = mgr.inner.lock().unwrap();
|
||||
inner.valid_mime = true;
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpPrimarySelectionDeviceV1, PrimarySelectionManagerData, WaylandState>
|
||||
for PrimarySelectionManagerState
|
||||
{
|
||||
event_created_child!(WaylandState, ZwpPrimarySelectionDeviceV1, [
|
||||
zwp_primary_selection_device_v1::EVT_DATA_OFFER_OPCODE => (ZwpPrimarySelectionOfferV1, PrimarySelectionManagerData::default())
|
||||
]);
|
||||
|
||||
fn event(
|
||||
state: &mut WaylandState,
|
||||
_primary_selection_device: &ZwpPrimarySelectionDeviceV1,
|
||||
event: <ZwpPrimarySelectionDeviceV1 as wayland_client::Proxy>::Event,
|
||||
_data: &PrimarySelectionManagerData,
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<WaylandState>,
|
||||
) {
|
||||
let psm = state.primary_selection_manager.as_ref().unwrap();
|
||||
let mut inner = psm.inner.lock().unwrap();
|
||||
match event {
|
||||
PrimarySelectionDeviceEvent::DataOffer { offer } => {
|
||||
inner.pending_offer = Some(offer);
|
||||
}
|
||||
PrimarySelectionDeviceEvent::Selection { id } => {
|
||||
if !inner.valid_mime {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(offer) = inner.offer.take() {
|
||||
offer.destroy();
|
||||
}
|
||||
if id == inner.pending_offer {
|
||||
inner.offer = inner.pending_offer.take();
|
||||
} else {
|
||||
// Remove the pending offer, assign the new delivered one.
|
||||
if let Some(offer) = inner.pending_offer.take() {
|
||||
offer.destroy()
|
||||
}
|
||||
|
||||
inner.offer = id;
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
use std::io::Write;
|
||||
use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd};
|
||||
use std::os::fd::{FromRawFd, IntoRawFd};
|
||||
|
||||
use anyhow::bail;
|
||||
use filedescriptor::FileDescriptor;
|
||||
use smithay_client_toolkit::data_device_manager::data_device::{
|
||||
DataDevice, DataDeviceDataExt, DataDeviceHandler,
|
||||
@ -16,6 +14,7 @@ use crate::wayland::drag_and_drop::SurfaceAndOffer;
|
||||
use crate::wayland::pointer::PointerUserData;
|
||||
use crate::wayland::SurfaceUserData;
|
||||
|
||||
use super::copy_and_paste::write_selection_to_pipe;
|
||||
use super::drag_and_drop::{DragAndDrop, SurfaceAndPipe};
|
||||
use super::state::WaylandState;
|
||||
|
||||
@ -239,38 +238,3 @@ impl DataSourceHandler for WaylandState {
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
fn write_selection_to_pipe(fd: FileDescriptor, text: &str) {
|
||||
if let Err(e) = write_pipe_with_timeout(fd, text.as_bytes()) {
|
||||
log::error!("while sending primary selection to pipe: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_pipe_with_timeout(mut file: FileDescriptor, data: &[u8]) -> anyhow::Result<()> {
|
||||
file.set_non_blocking(true)?;
|
||||
let mut pfd = libc::pollfd {
|
||||
fd: file.as_raw_fd(),
|
||||
events: libc::POLLOUT,
|
||||
revents: 0,
|
||||
};
|
||||
|
||||
let mut buf = data;
|
||||
|
||||
while !buf.is_empty() {
|
||||
if unsafe { libc::poll(&mut pfd, 1, 3000) == 1 } {
|
||||
match file.write(buf) {
|
||||
Ok(size) if size == 0 => {
|
||||
bail!("zero byte write");
|
||||
}
|
||||
Ok(size) => {
|
||||
buf = &buf[size..];
|
||||
}
|
||||
Err(e) => bail!("error writing to pipe: {}", e),
|
||||
}
|
||||
} else {
|
||||
bail!("timed out writing to pipe");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ use smithay_client_toolkit::seat::{Capability, SeatHandler, SeatState};
|
||||
use wayland_client::protocol::wl_seat::WlSeat;
|
||||
use wayland_client::{Connection, QueueHandle};
|
||||
|
||||
use crate::wayland::copy_and_paste::PrimarySelectionManagerData;
|
||||
use crate::wayland::keyboard::KeyboardData;
|
||||
use crate::wayland::pointer::PointerUserData;
|
||||
|
||||
@ -48,6 +49,12 @@ impl SeatHandler for WaylandState {
|
||||
let data_device_manager = &self.data_device_manager_state;
|
||||
let data_device = data_device_manager.get_data_device(qh, &seat);
|
||||
self.data_device.replace(data_device);
|
||||
|
||||
let primary_select_device = self.primary_selection_manager.as_ref().map(|m| {
|
||||
m.manager
|
||||
.get_device(&seat, qh, PrimarySelectionManagerData::default())
|
||||
});
|
||||
self.primary_select_device = primary_select_device;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,11 +30,16 @@ use wayland_client::protocol::wl_output::WlOutput;
|
||||
use wayland_client::protocol::wl_pointer::WlPointer;
|
||||
use wayland_client::protocol::wl_surface::WlSurface;
|
||||
use wayland_client::{delegate_dispatch, Connection, QueueHandle};
|
||||
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1;
|
||||
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1;
|
||||
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1;
|
||||
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1;
|
||||
use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
|
||||
use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3;
|
||||
|
||||
use crate::x11::KeyboardWithFallback;
|
||||
|
||||
use super::copy_and_paste::{PrimarySelectionManagerData, PrimarySelectionManagerState};
|
||||
use super::inputhandler::{TextInputData, TextInputState};
|
||||
use super::pointer::{PendingMouse, PointerUserData};
|
||||
use super::{OutputManagerData, OutputManagerState, SurfaceUserData, WaylandWindowInner};
|
||||
@ -65,6 +70,9 @@ pub(super) struct WaylandState {
|
||||
pub(super) data_device_manager_state: DataDeviceManagerState,
|
||||
pub(super) data_device: Option<DataDevice>,
|
||||
pub(super) copy_paste_source: Option<(CopyPasteSource, String)>,
|
||||
pub(super) primary_selection_manager: Option<PrimarySelectionManagerState>,
|
||||
pub(super) primary_select_device: Option<ZwpPrimarySelectionDeviceV1>,
|
||||
pub(super) primary_selection_source: Option<(ZwpPrimarySelectionSourceV1, String)>,
|
||||
pub(super) shm: Shm,
|
||||
pub(super) mem_pool: RefCell<SlotPool>,
|
||||
}
|
||||
@ -98,6 +106,9 @@ impl WaylandState {
|
||||
data_device_manager_state: DataDeviceManagerState::bind(globals, qh)?,
|
||||
data_device: None,
|
||||
copy_paste_source: None,
|
||||
primary_selection_manager: PrimarySelectionManagerState::bind(globals, qh).ok(),
|
||||
primary_select_device: None,
|
||||
primary_selection_source: None,
|
||||
shm,
|
||||
mem_pool: RefCell::new(mem_pool),
|
||||
};
|
||||
@ -169,3 +180,8 @@ delegate_dispatch!(WaylandState: [ZwpTextInputV3: TextInputData] => TextInputSta
|
||||
delegate_dispatch!(WaylandState: [ZwlrOutputManagerV1: GlobalData] => OutputManagerState);
|
||||
delegate_dispatch!(WaylandState: [ZwlrOutputHeadV1: OutputManagerData] => OutputManagerState);
|
||||
delegate_dispatch!(WaylandState: [ZwlrOutputModeV1: OutputManagerData] => OutputManagerState);
|
||||
|
||||
delegate_dispatch!(WaylandState: [ZwpPrimarySelectionDeviceManagerV1: GlobalData] => PrimarySelectionManagerState);
|
||||
delegate_dispatch!(WaylandState: [ZwpPrimarySelectionDeviceV1: PrimarySelectionManagerData] => PrimarySelectionManagerState);
|
||||
delegate_dispatch!(WaylandState: [ZwpPrimarySelectionSourceV1: PrimarySelectionManagerData] => PrimarySelectionManagerState);
|
||||
delegate_dispatch!(WaylandState: [ZwpPrimarySelectionOfferV1: PrimarySelectionManagerData] => PrimarySelectionManagerState);
|
||||
|
Loading…
Reference in New Issue
Block a user