mirror of
https://github.com/YaLTeR/wl-clipboard-rs.git
synced 2025-10-06 00:32:41 +02:00
Add support for ext-data-control
Since the protocol is more or less exactly the same as wlr-data-control, I added some wrappers and macros to re-use the same existing code for both protocols.
This commit is contained in:
@@ -38,12 +38,12 @@ thiserror = "2"
|
||||
tree_magic_mini = "3.1.6"
|
||||
wayland-backend = "0.3.8"
|
||||
wayland-client = "0.31.8"
|
||||
wayland-protocols = { version = "0.32.6", features = ["client"] }
|
||||
wayland-protocols = { version = "0.32.6", features = ["client", "staging"] }
|
||||
wayland-protocols-wlr = { version = "0.3.6", features = ["client"] }
|
||||
|
||||
[dev-dependencies]
|
||||
wayland-server = "0.31.7"
|
||||
wayland-protocols = { version = "0.32.6", features = ["server"] }
|
||||
wayland-protocols = { version = "0.32.6", features = ["server", "staging"] }
|
||||
wayland-protocols-wlr = { version = "0.3.6", features = ["server"] }
|
||||
proptest = "1.6.0"
|
||||
proptest-derive = "0.5.1"
|
||||
|
22
README.md
22
README.md
@@ -15,10 +15,10 @@ please use the appropriate Wayland protocols for interacting with the Wayland cl
|
||||
primary selection), for example via the
|
||||
[smithay-clipboard](https://crates.io/crates/smithay-clipboard) crate.
|
||||
|
||||
The protocol used for clipboard interaction is `data-control` from
|
||||
[wlroots](https://github.com/swaywm/wlr-protocols). When using the regular clipboard, the
|
||||
compositor must support the first version of the protocol. When using the "primary" clipboard,
|
||||
the compositor must support the second version of the protocol (or higher).
|
||||
The protocol used for clipboard interaction is `ext-data-control` or `wlr-data-control`. When
|
||||
using the regular clipboard, the compositor must support any version of either protocol. When
|
||||
using the "primary" clipboard, the compositor must support any version of `ext-data-control`,
|
||||
or the second version of the `wlr-data-control` protocol.
|
||||
|
||||
For example applications using these features, see `wl-clipboard-rs-tools/src/bin/wl_copy.rs`
|
||||
and `wl-clipboard-rs-tools/src/bin/wl_paste.rs` which implement terminal apps similar to
|
||||
@@ -72,19 +72,19 @@ use wl_clipboard_rs::utils::{is_primary_selection_supported, PrimarySelectionChe
|
||||
|
||||
match is_primary_selection_supported() {
|
||||
Ok(supported) => {
|
||||
// We have our definitive result. False means that either data-control version 1
|
||||
// is present (which does not support the primary selection), or that data-control
|
||||
// version 2 is present and it did not signal the primary selection support.
|
||||
// We have our definitive result. False means that ext/wlr-data-control is present
|
||||
// and did not signal the primary selection support, or that only wlr-data-control
|
||||
// version 1 is present (which does not support primary selection).
|
||||
},
|
||||
Err(PrimarySelectionCheckError::NoSeats) => {
|
||||
// Impossible to give a definitive result. Primary selection may or may not be
|
||||
// supported.
|
||||
|
||||
// The required protocol (data-control version 2) is there, but there are no seats.
|
||||
// Unfortunately, at least one seat is needed to check for the primary clipboard
|
||||
// support.
|
||||
// The required protocol (ext-data-control, or wlr-data-control version 2) is there,
|
||||
// but there are no seats. Unfortunately, at least one seat is needed to check for the
|
||||
// primary clipboard support.
|
||||
},
|
||||
Err(PrimarySelectionCheckError::MissingProtocol { .. }) => {
|
||||
Err(PrimarySelectionCheckError::MissingProtocol) => {
|
||||
// The data-control protocol (required for wl-clipboard-rs operation) is not
|
||||
// supported by the compositor.
|
||||
},
|
||||
|
@@ -5,17 +5,19 @@ use std::path::PathBuf;
|
||||
use std::{env, io};
|
||||
|
||||
use wayland_backend::client::WaylandError;
|
||||
use wayland_client::globals::{registry_queue_init, BindError, GlobalError, GlobalListContents};
|
||||
use wayland_client::globals::{registry_queue_init, GlobalError, GlobalListContents};
|
||||
use wayland_client::protocol::wl_registry::WlRegistry;
|
||||
use wayland_client::protocol::wl_seat::{self, WlSeat};
|
||||
use wayland_client::{ConnectError, Connection, Dispatch, EventQueue, Proxy};
|
||||
use wayland_protocols::ext::data_control::v1::client::ext_data_control_manager_v1::ExtDataControlManagerV1;
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;
|
||||
|
||||
use crate::data_control::Manager;
|
||||
use crate::seat_data::SeatData;
|
||||
|
||||
pub struct State {
|
||||
pub seats: HashMap<WlSeat, SeatData>,
|
||||
pub clipboard_manager: ZwlrDataControlManagerV1,
|
||||
pub clipboard_manager: Manager,
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
@@ -30,9 +32,10 @@ pub enum Error {
|
||||
WaylandCommunication(#[source] WaylandError),
|
||||
|
||||
#[error(
|
||||
"A required Wayland protocol ({name} version {version}) is not supported by the compositor"
|
||||
"A required Wayland protocol (ext-data-control, or wlr-data-control version {version}) \
|
||||
is not supported by the compositor"
|
||||
)]
|
||||
MissingProtocol { name: &'static str, version: u32 },
|
||||
MissingProtocol { version: u32 },
|
||||
}
|
||||
|
||||
impl<S> Dispatch<WlSeat, (), S> for State
|
||||
@@ -62,6 +65,7 @@ pub fn initialize<S>(
|
||||
where
|
||||
S: Dispatch<WlRegistry, GlobalListContents> + 'static,
|
||||
S: Dispatch<ZwlrDataControlManagerV1, ()>,
|
||||
S: Dispatch<ExtDataControlManagerV1, ()>,
|
||||
S: Dispatch<WlSeat, ()>,
|
||||
S: AsMut<State>,
|
||||
{
|
||||
@@ -95,18 +99,15 @@ where
|
||||
})?;
|
||||
let qh = &queue.handle();
|
||||
|
||||
let data_control_version = if primary { 2 } else { 1 };
|
||||
|
||||
// Verify that we got the clipboard manager.
|
||||
let clipboard_manager = match globals.bind(qh, data_control_version..=data_control_version, ())
|
||||
{
|
||||
Ok(manager) => manager,
|
||||
Err(BindError::NotPresent | BindError::UnsupportedVersion) => {
|
||||
return Err(Error::MissingProtocol {
|
||||
name: ZwlrDataControlManagerV1::interface().name,
|
||||
version: data_control_version,
|
||||
})
|
||||
}
|
||||
let ext_manager = globals.bind(qh, 1..=1, ()).ok().map(Manager::Ext);
|
||||
|
||||
let wlr_v = if primary { 2 } else { 1 };
|
||||
let wlr_manager = || globals.bind(qh, wlr_v..=wlr_v, ()).ok().map(Manager::Zwlr);
|
||||
|
||||
let clipboard_manager = match ext_manager.or_else(wlr_manager) {
|
||||
Some(manager) => manager,
|
||||
None => return Err(Error::MissingProtocol { version: wlr_v }),
|
||||
};
|
||||
|
||||
let registry = globals.registry();
|
||||
|
201
src/copy.rs
201
src/copy.rs
@@ -16,18 +16,12 @@ use wayland_client::protocol::wl_registry::WlRegistry;
|
||||
use wayland_client::protocol::wl_seat::WlSeat;
|
||||
use wayland_client::{
|
||||
delegate_dispatch, event_created_child, ConnectError, Dispatch, DispatchError, EventQueue,
|
||||
Proxy,
|
||||
};
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_device_v1::{
|
||||
self, ZwlrDataControlDeviceV1,
|
||||
};
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1;
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::{
|
||||
self, ZwlrDataControlSourceV1,
|
||||
};
|
||||
|
||||
use crate::common::{self, initialize};
|
||||
use crate::data_control::{
|
||||
self, impl_dispatch_device, impl_dispatch_manager, impl_dispatch_offer, impl_dispatch_source,
|
||||
};
|
||||
use crate::seat_data::SeatData;
|
||||
use crate::utils::is_text;
|
||||
|
||||
@@ -40,8 +34,8 @@ pub enum ClipboardType {
|
||||
Regular,
|
||||
/// The "primary" clipboard.
|
||||
///
|
||||
/// Working with the "primary" clipboard requires the compositor to support the data-control
|
||||
/// protocol of version 2 or above.
|
||||
/// Working with the "primary" clipboard requires the compositor to support ext-data-control,
|
||||
/// or wlr-data-control version 2 or above.
|
||||
Primary,
|
||||
/// Operate on both clipboards at once.
|
||||
///
|
||||
@@ -144,7 +138,7 @@ pub struct Options {
|
||||
pub struct PreparedCopy {
|
||||
queue: EventQueue<State>,
|
||||
state: State,
|
||||
sources: Vec<ZwlrDataControlSourceV1>,
|
||||
sources: Vec<data_control::Source>,
|
||||
}
|
||||
|
||||
/// Errors that can occur for copying the source data to a temporary file.
|
||||
@@ -194,11 +188,10 @@ pub enum Error {
|
||||
WaylandCommunication(#[source] DispatchError),
|
||||
|
||||
#[error(
|
||||
"A required Wayland protocol ({} version {}) is not supported by the compositor",
|
||||
name,
|
||||
version
|
||||
"A required Wayland protocol (ext-data-control, or wlr-data-control version {version}) \
|
||||
is not supported by the compositor"
|
||||
)]
|
||||
MissingProtocol { name: &'static str, version: u32 },
|
||||
MissingProtocol { version: u32 },
|
||||
|
||||
#[error("The compositor does not support primary selection")]
|
||||
PrimarySelectionUnsupported,
|
||||
@@ -227,7 +220,7 @@ impl From<common::Error> for Error {
|
||||
SocketOpenError(err) => Error::SocketOpenError(err),
|
||||
WaylandConnection(err) => Error::WaylandConnection(err),
|
||||
WaylandCommunication(err) => Error::WaylandCommunication(err.into()),
|
||||
MissingProtocol { name, version } => Error::MissingProtocol { name, version },
|
||||
MissingProtocol { version } => Error::MissingProtocol { version },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,115 +266,75 @@ impl Dispatch<WlRegistry, GlobalListContents> for State {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwlrDataControlManagerV1, ()> for State {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_proxy: &ZwlrDataControlManagerV1,
|
||||
_event: <ZwlrDataControlManagerV1 as wayland_client::Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
impl_dispatch_manager!(State);
|
||||
|
||||
impl Dispatch<ZwlrDataControlDeviceV1, WlSeat> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_device: &ZwlrDataControlDeviceV1,
|
||||
event: <ZwlrDataControlDeviceV1 as Proxy>::Event,
|
||||
seat: &WlSeat,
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
match event {
|
||||
zwlr_data_control_device_v1::Event::DataOffer { id } => id.destroy(),
|
||||
zwlr_data_control_device_v1::Event::Finished => {
|
||||
state.common.seats.get_mut(seat).unwrap().set_device(None);
|
||||
}
|
||||
zwlr_data_control_device_v1::Event::PrimarySelection { .. } => {
|
||||
state.got_primary_selection = true;
|
||||
}
|
||||
_ => (),
|
||||
impl_dispatch_device!(State, WlSeat, |state: &mut Self, event, seat| {
|
||||
match event {
|
||||
Event::DataOffer { id } => id.destroy(),
|
||||
Event::Finished => {
|
||||
state.common.seats.get_mut(seat).unwrap().set_device(None);
|
||||
}
|
||||
}
|
||||
|
||||
event_created_child!(State, ZwlrDataControlDeviceV1, [
|
||||
zwlr_data_control_device_v1::EVT_DATA_OFFER_OPCODE => (ZwlrDataControlOfferV1, ()),
|
||||
]);
|
||||
}
|
||||
|
||||
impl Dispatch<ZwlrDataControlOfferV1, ()> for State {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_offer: &ZwlrDataControlOfferV1,
|
||||
_event: <ZwlrDataControlOfferV1 as wayland_client::Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwlrDataControlSourceV1, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
source: &ZwlrDataControlSourceV1,
|
||||
event: <ZwlrDataControlSourceV1 as Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
match event {
|
||||
zwlr_data_control_source_v1::Event::Send { mime_type, fd } => {
|
||||
// Check if some other source already handled a paste request and indicated that we should
|
||||
// quit.
|
||||
if state.should_quit {
|
||||
source.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// I'm not sure if it's the compositor's responsibility to check that the mime type is
|
||||
// valid. Let's check here just in case.
|
||||
if !state.data_paths.contains_key(&mime_type) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data_path = &state.data_paths[&mime_type];
|
||||
|
||||
let file = File::open(data_path).map_err(DataSourceError::FileOpen);
|
||||
let result = file.and_then(|mut data_file| {
|
||||
// Clear O_NONBLOCK, otherwise io::copy() will stop halfway.
|
||||
fcntl_setfl(&fd, OFlags::empty())
|
||||
.map_err(io::Error::from)
|
||||
.map_err(DataSourceError::Copy)?;
|
||||
|
||||
let mut target_file = File::from(fd);
|
||||
io::copy(&mut data_file, &mut target_file).map_err(DataSourceError::Copy)
|
||||
});
|
||||
|
||||
if let Err(err) = result {
|
||||
state.error = Some(err);
|
||||
}
|
||||
|
||||
let done = if let ServeRequests::Only(left) = state.serve_requests {
|
||||
let left = left.checked_sub(1).unwrap();
|
||||
state.serve_requests = ServeRequests::Only(left);
|
||||
left == 0
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if done || state.error.is_some() {
|
||||
state.should_quit = true;
|
||||
source.destroy();
|
||||
}
|
||||
}
|
||||
zwlr_data_control_source_v1::Event::Cancelled => source.destroy(),
|
||||
_ => (),
|
||||
Event::PrimarySelection { .. } => {
|
||||
state.got_primary_selection = true;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
impl_dispatch_offer!(State);
|
||||
|
||||
impl_dispatch_source!(State, |state: &mut Self,
|
||||
source: data_control::Source,
|
||||
event| {
|
||||
match event {
|
||||
Event::Send { mime_type, fd } => {
|
||||
// Check if some other source already handled a paste request and indicated that we should
|
||||
// quit.
|
||||
if state.should_quit {
|
||||
source.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// I'm not sure if it's the compositor's responsibility to check that the mime type is
|
||||
// valid. Let's check here just in case.
|
||||
if !state.data_paths.contains_key(&mime_type) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data_path = &state.data_paths[&mime_type];
|
||||
|
||||
let file = File::open(data_path).map_err(DataSourceError::FileOpen);
|
||||
let result = file.and_then(|mut data_file| {
|
||||
// Clear O_NONBLOCK, otherwise io::copy() will stop halfway.
|
||||
fcntl_setfl(&fd, OFlags::empty())
|
||||
.map_err(io::Error::from)
|
||||
.map_err(DataSourceError::Copy)?;
|
||||
|
||||
let mut target_file = File::from(fd);
|
||||
io::copy(&mut data_file, &mut target_file).map_err(DataSourceError::Copy)
|
||||
});
|
||||
|
||||
if let Err(err) = result {
|
||||
state.error = Some(err);
|
||||
}
|
||||
|
||||
let done = if let ServeRequests::Only(left) = state.serve_requests {
|
||||
let left = left.checked_sub(1).unwrap();
|
||||
state.serve_requests = ServeRequests::Only(left);
|
||||
left == 0
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if done || state.error.is_some() {
|
||||
state.should_quit = true;
|
||||
source.destroy();
|
||||
}
|
||||
}
|
||||
Event::Cancelled => source.destroy(),
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
impl Options {
|
||||
/// Creates a blank new set of options ready for configuration.
|
||||
@@ -676,7 +629,7 @@ fn get_devices(
|
||||
primary: bool,
|
||||
seat: Seat,
|
||||
socket_name: Option<OsString>,
|
||||
) -> Result<(EventQueue<State>, State, Vec<ZwlrDataControlDeviceV1>), Error> {
|
||||
) -> Result<(EventQueue<State>, State, Vec<data_control::Device>), Error> {
|
||||
let (mut queue, mut common) = initialize(primary, socket_name)?;
|
||||
|
||||
// Check if there are no seats.
|
||||
@@ -980,7 +933,7 @@ fn prepare_copy_internal(
|
||||
let data_source = state
|
||||
.common
|
||||
.clipboard_manager
|
||||
.create_data_source(&queue.handle(), ());
|
||||
.create_data_source(&queue.handle());
|
||||
|
||||
for mime_type in state.data_paths.keys() {
|
||||
data_source.offer(mime_type.clone());
|
||||
|
303
src/data_control.rs
Normal file
303
src/data_control.rs
Normal file
@@ -0,0 +1,303 @@
|
||||
//! Abstraction over ext/wlr-data-control.
|
||||
|
||||
use std::os::fd::BorrowedFd;
|
||||
|
||||
use ext::ext_data_control_device_v1::ExtDataControlDeviceV1;
|
||||
use ext::ext_data_control_manager_v1::ExtDataControlManagerV1;
|
||||
use ext::ext_data_control_offer_v1::ExtDataControlOfferV1;
|
||||
use ext::ext_data_control_source_v1::ExtDataControlSourceV1;
|
||||
use wayland_client::protocol::wl_seat::WlSeat;
|
||||
use wayland_client::{Dispatch, Proxy as _, QueueHandle};
|
||||
use wayland_protocols::ext::data_control::v1::client as ext;
|
||||
use wayland_protocols_wlr::data_control::v1::client as zwlr;
|
||||
use zwlr::zwlr_data_control_device_v1::ZwlrDataControlDeviceV1;
|
||||
use zwlr::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;
|
||||
use zwlr::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1;
|
||||
use zwlr::zwlr_data_control_source_v1::ZwlrDataControlSourceV1;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Manager {
|
||||
Zwlr(ZwlrDataControlManagerV1),
|
||||
Ext(ExtDataControlManagerV1),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Device {
|
||||
Zwlr(ZwlrDataControlDeviceV1),
|
||||
Ext(ExtDataControlDeviceV1),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Source {
|
||||
Zwlr(ZwlrDataControlSourceV1),
|
||||
Ext(ExtDataControlSourceV1),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Offer {
|
||||
Zwlr(ZwlrDataControlOfferV1),
|
||||
Ext(ExtDataControlOfferV1),
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
pub fn get_data_device<D, U>(&self, seat: &WlSeat, qh: &QueueHandle<D>, udata: U) -> Device
|
||||
where
|
||||
D: Dispatch<ZwlrDataControlDeviceV1, U> + 'static,
|
||||
D: Dispatch<ExtDataControlDeviceV1, U> + 'static,
|
||||
U: Send + Sync + 'static,
|
||||
{
|
||||
match self {
|
||||
Manager::Zwlr(manager) => Device::Zwlr(manager.get_data_device(seat, qh, udata)),
|
||||
Manager::Ext(manager) => Device::Ext(manager.get_data_device(seat, qh, udata)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_data_source<D>(&self, qh: &QueueHandle<D>) -> Source
|
||||
where
|
||||
D: Dispatch<ZwlrDataControlSourceV1, ()> + 'static,
|
||||
D: Dispatch<ExtDataControlSourceV1, ()> + 'static,
|
||||
{
|
||||
match self {
|
||||
Manager::Zwlr(manager) => Source::Zwlr(manager.create_data_source(qh, ())),
|
||||
Manager::Ext(manager) => Source::Ext(manager.create_data_source(qh, ())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Device {
|
||||
pub fn destroy(&self) {
|
||||
match self {
|
||||
Device::Zwlr(device) => device.destroy(),
|
||||
Device::Ext(device) => device.destroy(),
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn set_selection(&self, source: Option<&Source>) {
|
||||
match self {
|
||||
Device::Zwlr(device) => device.set_selection(source.map(Source::zwlr)),
|
||||
Device::Ext(device) => device.set_selection(source.map(Source::ext)),
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn set_primary_selection(&self, source: Option<&Source>) {
|
||||
match self {
|
||||
Device::Zwlr(device) => device.set_primary_selection(source.map(Source::zwlr)),
|
||||
Device::Ext(device) => device.set_primary_selection(source.map(Source::ext)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Source {
|
||||
pub fn destroy(&self) {
|
||||
match self {
|
||||
Source::Zwlr(source) => source.destroy(),
|
||||
Source::Ext(source) => source.destroy(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn offer(&self, mime_type: String) {
|
||||
match self {
|
||||
Source::Zwlr(source) => source.offer(mime_type),
|
||||
Source::Ext(source) => source.offer(mime_type),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_alive(&self) -> bool {
|
||||
match self {
|
||||
Source::Zwlr(source) => source.is_alive(),
|
||||
Source::Ext(source) => source.is_alive(),
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn zwlr(&self) -> &ZwlrDataControlSourceV1 {
|
||||
if let Self::Zwlr(v) = self {
|
||||
v
|
||||
} else {
|
||||
panic!("tried to convert non-Zwlr Source to Zwlr")
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn ext(&self) -> &ExtDataControlSourceV1 {
|
||||
if let Self::Ext(v) = self {
|
||||
v
|
||||
} else {
|
||||
panic!("tried to convert non-Ext Source to Ext")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Offer {
|
||||
pub fn destroy(&self) {
|
||||
match self {
|
||||
Offer::Zwlr(offer) => offer.destroy(),
|
||||
Offer::Ext(offer) => offer.destroy(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn receive(&self, mime_type: String, fd: BorrowedFd) {
|
||||
match self {
|
||||
Offer::Zwlr(offer) => offer.receive(mime_type, fd),
|
||||
Offer::Ext(offer) => offer.receive(mime_type, fd),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ZwlrDataControlSourceV1> for Source {
|
||||
fn from(v: ZwlrDataControlSourceV1) -> Self {
|
||||
Self::Zwlr(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExtDataControlSourceV1> for Source {
|
||||
fn from(v: ExtDataControlSourceV1) -> Self {
|
||||
Self::Ext(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ZwlrDataControlOfferV1> for Offer {
|
||||
fn from(v: ZwlrDataControlOfferV1) -> Self {
|
||||
Self::Zwlr(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExtDataControlOfferV1> for Offer {
|
||||
fn from(v: ExtDataControlOfferV1) -> Self {
|
||||
Self::Ext(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Some mildly cursed macros to avoid code duplication.
|
||||
macro_rules! impl_dispatch_manager {
|
||||
($handler:ty => [$($iface:ty),*]) => {
|
||||
$(
|
||||
impl Dispatch<$iface, ()> for $handler {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_proxy: &$iface,
|
||||
_event: <$iface as wayland_client::Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
|
||||
($handler:ty) => {
|
||||
impl_dispatch_manager!($handler => [
|
||||
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1,
|
||||
wayland_protocols::ext::data_control::v1::client::ext_data_control_manager_v1::ExtDataControlManagerV1
|
||||
]);
|
||||
};
|
||||
}
|
||||
pub(crate) use impl_dispatch_manager;
|
||||
|
||||
macro_rules! impl_dispatch_device {
|
||||
($handler:ty, $udata:ty, $code:expr => [$(($iface:ty, $opcode:path, $offer:ty)),*]) => {
|
||||
$(
|
||||
impl Dispatch<$iface, $udata> for $handler {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_proxy: &$iface,
|
||||
event: <$iface as wayland_client::Proxy>::Event,
|
||||
data: &$udata,
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
type Event = <$iface as wayland_client::Proxy>::Event;
|
||||
|
||||
($code)(state, event, data)
|
||||
}
|
||||
|
||||
event_created_child!($handler, $iface, [
|
||||
$opcode => ($offer, ()),
|
||||
]);
|
||||
}
|
||||
)*
|
||||
};
|
||||
|
||||
($handler:ty, $udata:ty, $code:expr) => {
|
||||
impl_dispatch_device!($handler, $udata, $code => [
|
||||
(
|
||||
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_device_v1::ZwlrDataControlDeviceV1,
|
||||
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_device_v1::EVT_DATA_OFFER_OPCODE,
|
||||
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1
|
||||
),
|
||||
(
|
||||
wayland_protocols::ext::data_control::v1::client::ext_data_control_device_v1::ExtDataControlDeviceV1,
|
||||
wayland_protocols::ext::data_control::v1::client::ext_data_control_device_v1::EVT_DATA_OFFER_OPCODE,
|
||||
wayland_protocols::ext::data_control::v1::client::ext_data_control_offer_v1::ExtDataControlOfferV1
|
||||
)
|
||||
]);
|
||||
};
|
||||
}
|
||||
pub(crate) use impl_dispatch_device;
|
||||
|
||||
macro_rules! impl_dispatch_source {
|
||||
($handler:ty, $code:expr => [$($iface:ty),*]) => {
|
||||
$(
|
||||
impl Dispatch<$iface, ()> for $handler {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
proxy: &$iface,
|
||||
event: <$iface as wayland_client::Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
type Event = <$iface as wayland_client::Proxy>::Event;
|
||||
|
||||
let source = $crate::data_control::Source::from(proxy.clone());
|
||||
($code)(state, source, event)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
|
||||
($handler:ty, $code:expr) => {
|
||||
impl_dispatch_source!($handler, $code => [
|
||||
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::ZwlrDataControlSourceV1,
|
||||
wayland_protocols::ext::data_control::v1::client::ext_data_control_source_v1::ExtDataControlSourceV1
|
||||
]);
|
||||
};
|
||||
}
|
||||
pub(crate) use impl_dispatch_source;
|
||||
|
||||
macro_rules! impl_dispatch_offer {
|
||||
($handler:ty, $code:expr => [$($iface:ty),*]) => {
|
||||
$(
|
||||
impl Dispatch<$iface, ()> for $handler {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
proxy: &$iface,
|
||||
event: <$iface as wayland_client::Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
type Event = <$iface as wayland_client::Proxy>::Event;
|
||||
|
||||
let offer = $crate::data_control::Offer::from(proxy.clone());
|
||||
($code)(state, offer, event)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
|
||||
($handler:ty, $code:expr) => {
|
||||
impl_dispatch_offer!($handler, $code => [
|
||||
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1,
|
||||
wayland_protocols::ext::data_control::v1::client::ext_data_control_offer_v1::ExtDataControlOfferV1
|
||||
]);
|
||||
};
|
||||
|
||||
($handler:ty) => {
|
||||
impl_dispatch_offer!($handler, |_, _, _: Event| ());
|
||||
};
|
||||
}
|
||||
pub(crate) use impl_dispatch_offer;
|
23
src/lib.rs
23
src/lib.rs
@@ -7,10 +7,10 @@
|
||||
//! primary selection), for example via the
|
||||
//! [smithay-clipboard](https://crates.io/crates/smithay-clipboard) crate.
|
||||
//!
|
||||
//! The protocol used for clipboard interaction is `data-control` from
|
||||
//! [wlroots](https://github.com/swaywm/wlr-protocols). When using the regular clipboard, the
|
||||
//! compositor must support the first version of the protocol. When using the "primary" clipboard,
|
||||
//! the compositor must support the second version of the protocol (or higher).
|
||||
//! The protocol used for clipboard interaction is `ext-data-control` or `wlr-data-control`. When
|
||||
//! using the regular clipboard, the compositor must support any version of either protocol. When
|
||||
//! using the "primary" clipboard, the compositor must support any version of `ext-data-control`,
|
||||
//! or the second version of the `wlr-data-control` protocol.
|
||||
//!
|
||||
//! For example applications using these features, see `wl-clipboard-rs-tools/src/bin/wl_copy.rs`
|
||||
//! and `wl-clipboard-rs-tools/src/bin/wl_paste.rs` which implement terminal apps similar to
|
||||
@@ -74,19 +74,19 @@
|
||||
//!
|
||||
//! match is_primary_selection_supported() {
|
||||
//! Ok(supported) => {
|
||||
//! // We have our definitive result. False means that either data-control version 1
|
||||
//! // is present (which does not support the primary selection), or that data-control
|
||||
//! // version 2 is present and it did not signal the primary selection support.
|
||||
//! // We have our definitive result. False means that ext/wlr-data-control is present
|
||||
//! // and did not signal the primary selection support, or that only wlr-data-control
|
||||
//! // version 1 is present (which does not support primary selection).
|
||||
//! },
|
||||
//! Err(PrimarySelectionCheckError::NoSeats) => {
|
||||
//! // Impossible to give a definitive result. Primary selection may or may not be
|
||||
//! // supported.
|
||||
//!
|
||||
//! // The required protocol (data-control version 2) is there, but there are no seats.
|
||||
//! // Unfortunately, at least one seat is needed to check for the primary clipboard
|
||||
//! // support.
|
||||
//! // The required protocol (ext-data-control, or wlr-data-control version 2) is there,
|
||||
//! // but there are no seats. Unfortunately, at least one seat is needed to check for the
|
||||
//! // primary clipboard support.
|
||||
//! },
|
||||
//! Err(PrimarySelectionCheckError::MissingProtocol { .. }) => {
|
||||
//! Err(PrimarySelectionCheckError::MissingProtocol) => {
|
||||
//! // The data-control protocol (required for wl-clipboard-rs operation) is not
|
||||
//! // supported by the compositor.
|
||||
//! },
|
||||
@@ -109,6 +109,7 @@
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
mod common;
|
||||
mod data_control;
|
||||
mod seat_data;
|
||||
|
||||
#[cfg(test)]
|
||||
|
116
src/paste.rs
116
src/paste.rs
@@ -12,15 +12,9 @@ use wayland_client::protocol::wl_seat::WlSeat;
|
||||
use wayland_client::{
|
||||
delegate_dispatch, event_created_child, ConnectError, Dispatch, DispatchError, EventQueue,
|
||||
};
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_device_v1::{
|
||||
self, ZwlrDataControlDeviceV1,
|
||||
};
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::{
|
||||
self, ZwlrDataControlOfferV1,
|
||||
};
|
||||
|
||||
use crate::common::{self, initialize};
|
||||
use crate::data_control::{self, impl_dispatch_device, impl_dispatch_manager, impl_dispatch_offer};
|
||||
use crate::utils::is_text;
|
||||
|
||||
/// The clipboard to operate on.
|
||||
@@ -32,8 +26,8 @@ pub enum ClipboardType {
|
||||
Regular,
|
||||
/// The "primary" clipboard.
|
||||
///
|
||||
/// Working with the "primary" clipboard requires the compositor to support the data-control
|
||||
/// protocol of version 2 or above.
|
||||
/// Working with the "primary" clipboard requires the compositor to support ext-data-control,
|
||||
/// or wlr-data-control version 2 or above.
|
||||
Primary,
|
||||
}
|
||||
|
||||
@@ -76,7 +70,7 @@ struct State {
|
||||
common: common::State,
|
||||
// The value is the set of MIME types in the offer.
|
||||
// TODO: We never remove offers from here, even if we don't use them or after destroying them.
|
||||
offers: HashMap<ZwlrDataControlOfferV1, HashSet<String>>,
|
||||
offers: HashMap<data_control::Offer, HashSet<String>>,
|
||||
got_primary_selection: bool,
|
||||
}
|
||||
|
||||
@@ -114,11 +108,10 @@ pub enum Error {
|
||||
WaylandCommunication(#[source] DispatchError),
|
||||
|
||||
#[error(
|
||||
"A required Wayland protocol ({} version {}) is not supported by the compositor",
|
||||
name,
|
||||
version
|
||||
"A required Wayland protocol (ext-data-control, or wlr-data-control version {version}) \
|
||||
is not supported by the compositor"
|
||||
)]
|
||||
MissingProtocol { name: &'static str, version: u32 },
|
||||
MissingProtocol { version: u32 },
|
||||
|
||||
#[error("The compositor does not support primary selection")]
|
||||
PrimarySelectionUnsupported,
|
||||
@@ -138,7 +131,7 @@ impl From<common::Error> for Error {
|
||||
SocketOpenError(err) => Error::SocketOpenError(err),
|
||||
WaylandConnection(err) => Error::WaylandConnection(err),
|
||||
WaylandCommunication(err) => Error::WaylandCommunication(err.into()),
|
||||
MissingProtocol { name, version } => Error::MissingProtocol { name, version },
|
||||
MissingProtocol { version } => Error::MissingProtocol { version },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,76 +148,47 @@ impl Dispatch<WlRegistry, GlobalListContents> for State {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwlrDataControlManagerV1, ()> for State {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_proxy: &ZwlrDataControlManagerV1,
|
||||
_event: <ZwlrDataControlManagerV1 as wayland_client::Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
impl_dispatch_manager!(State);
|
||||
|
||||
impl Dispatch<ZwlrDataControlDeviceV1, WlSeat> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_device: &ZwlrDataControlDeviceV1,
|
||||
event: <ZwlrDataControlDeviceV1 as wayland_client::Proxy>::Event,
|
||||
seat: &WlSeat,
|
||||
_conn: &wayland_client::Connection,
|
||||
_qh: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
match event {
|
||||
zwlr_data_control_device_v1::Event::DataOffer { id } => {
|
||||
state.offers.insert(id, HashSet::new());
|
||||
}
|
||||
zwlr_data_control_device_v1::Event::Selection { id } => {
|
||||
state.common.seats.get_mut(seat).unwrap().set_offer(id);
|
||||
}
|
||||
zwlr_data_control_device_v1::Event::Finished => {
|
||||
// Destroy the device stored in the seat as it's no longer valid.
|
||||
state.common.seats.get_mut(seat).unwrap().set_device(None);
|
||||
}
|
||||
zwlr_data_control_device_v1::Event::PrimarySelection { id } => {
|
||||
state.got_primary_selection = true;
|
||||
state
|
||||
.common
|
||||
.seats
|
||||
.get_mut(seat)
|
||||
.unwrap()
|
||||
.set_primary_offer(id);
|
||||
}
|
||||
_ => (),
|
||||
impl_dispatch_device!(State, WlSeat, |state: &mut Self, event, seat| {
|
||||
match event {
|
||||
Event::DataOffer { id } => {
|
||||
let offer = data_control::Offer::from(id);
|
||||
state.offers.insert(offer, HashSet::new());
|
||||
}
|
||||
}
|
||||
|
||||
event_created_child!(State, ZwlrDataControlDeviceV1, [
|
||||
zwlr_data_control_device_v1::EVT_DATA_OFFER_OPCODE => (ZwlrDataControlOfferV1, ()),
|
||||
]);
|
||||
}
|
||||
|
||||
impl Dispatch<ZwlrDataControlOfferV1, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
offer: &ZwlrDataControlOfferV1,
|
||||
event: <ZwlrDataControlOfferV1 as wayland_client::Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
if let zwlr_data_control_offer_v1::Event::Offer { mime_type } = event {
|
||||
state.offers.get_mut(offer).unwrap().insert(mime_type);
|
||||
Event::Selection { id } => {
|
||||
let offer = id.map(data_control::Offer::from);
|
||||
let seat = state.common.seats.get_mut(seat).unwrap();
|
||||
seat.set_offer(offer);
|
||||
}
|
||||
Event::Finished => {
|
||||
// Destroy the device stored in the seat as it's no longer valid.
|
||||
let seat = state.common.seats.get_mut(seat).unwrap();
|
||||
seat.set_device(None);
|
||||
}
|
||||
Event::PrimarySelection { id } => {
|
||||
let offer = id.map(data_control::Offer::from);
|
||||
state.got_primary_selection = true;
|
||||
let seat = state.common.seats.get_mut(seat).unwrap();
|
||||
seat.set_primary_offer(offer);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
impl_dispatch_offer!(State, |state: &mut Self,
|
||||
offer: data_control::Offer,
|
||||
event| {
|
||||
if let Event::Offer { mime_type } = event {
|
||||
state.offers.get_mut(&offer).unwrap().insert(mime_type);
|
||||
}
|
||||
});
|
||||
|
||||
fn get_offer(
|
||||
primary: bool,
|
||||
seat: Seat<'_>,
|
||||
socket_name: Option<OsString>,
|
||||
) -> Result<(EventQueue<State>, State, ZwlrDataControlOfferV1), Error> {
|
||||
) -> Result<(EventQueue<State>, State, data_control::Offer), Error> {
|
||||
let (mut queue, mut common) = initialize(primary, socket_name)?;
|
||||
|
||||
// Check if there are no seats.
|
||||
|
@@ -1,5 +1,4 @@
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_device_v1::ZwlrDataControlDeviceV1;
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1;
|
||||
use crate::data_control::{Device, Offer};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SeatData {
|
||||
@@ -7,13 +6,13 @@ pub struct SeatData {
|
||||
pub name: Option<String>,
|
||||
|
||||
/// The data device of this seat, if any.
|
||||
pub device: Option<ZwlrDataControlDeviceV1>,
|
||||
pub device: Option<Device>,
|
||||
|
||||
/// The data offer of this seat, if any.
|
||||
pub offer: Option<ZwlrDataControlOfferV1>,
|
||||
pub offer: Option<Offer>,
|
||||
|
||||
/// The primary-selection data offer of this seat, if any.
|
||||
pub primary_offer: Option<ZwlrDataControlOfferV1>,
|
||||
pub primary_offer: Option<Offer>,
|
||||
}
|
||||
|
||||
impl SeatData {
|
||||
@@ -25,7 +24,7 @@ impl SeatData {
|
||||
/// Sets this seat's device.
|
||||
///
|
||||
/// Destroys the old one, if any.
|
||||
pub fn set_device(&mut self, device: Option<ZwlrDataControlDeviceV1>) {
|
||||
pub fn set_device(&mut self, device: Option<Device>) {
|
||||
let old_device = self.device.take();
|
||||
self.device = device;
|
||||
|
||||
@@ -37,7 +36,7 @@ impl SeatData {
|
||||
/// Sets this seat's data offer.
|
||||
///
|
||||
/// Destroys the old one, if any.
|
||||
pub fn set_offer(&mut self, new_offer: Option<ZwlrDataControlOfferV1>) {
|
||||
pub fn set_offer(&mut self, new_offer: Option<Offer>) {
|
||||
let old_offer = self.offer.take();
|
||||
self.offer = new_offer;
|
||||
|
||||
@@ -49,7 +48,7 @@ impl SeatData {
|
||||
/// Sets this seat's primary-selection data offer.
|
||||
///
|
||||
/// Destroys the old one, if any.
|
||||
pub fn set_primary_offer(&mut self, new_offer: Option<ZwlrDataControlOfferV1>) {
|
||||
pub fn set_primary_offer(&mut self, new_offer: Option<Offer>) {
|
||||
let old_offer = self.primary_offer.take();
|
||||
self.primary_offer = new_offer;
|
||||
|
||||
|
@@ -65,13 +65,7 @@ fn get_mime_types_no_data_control() {
|
||||
|
||||
let result =
|
||||
get_mime_types_internal(ClipboardType::Regular, Seat::Unspecified, Some(socket_name));
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(Error::MissingProtocol {
|
||||
name: "zwlr_data_control_manager_v1",
|
||||
version: 1
|
||||
})
|
||||
));
|
||||
assert!(matches!(result, Err(Error::MissingProtocol { version: 1 })));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -94,13 +88,7 @@ fn get_mime_types_no_data_control_2() {
|
||||
|
||||
let result =
|
||||
get_mime_types_internal(ClipboardType::Primary, Seat::Unspecified, Some(socket_name));
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(Error::MissingProtocol {
|
||||
name: "zwlr_data_control_manager_v1",
|
||||
version: 2
|
||||
})
|
||||
));
|
||||
assert!(matches!(result, Err(Error::MissingProtocol { version: 2 })));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@@ -1,3 +1,7 @@
|
||||
use wayland_protocols::ext::data_control::v1::server::ext_data_control_device_v1::ExtDataControlDeviceV1;
|
||||
use wayland_protocols::ext::data_control::v1::server::ext_data_control_manager_v1::{
|
||||
self, ExtDataControlManagerV1,
|
||||
};
|
||||
use wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_device_v1::ZwlrDataControlDeviceV1;
|
||||
use wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_manager_v1::{
|
||||
self, ZwlrDataControlManagerV1,
|
||||
@@ -13,8 +17,8 @@ struct State {
|
||||
advertise_primary_selection: bool,
|
||||
}
|
||||
|
||||
server_ignore_global_impl!(State => [WlSeat, ZwlrDataControlManagerV1]);
|
||||
server_ignore_impl!(State => [WlSeat, ZwlrDataControlDeviceV1]);
|
||||
server_ignore_global_impl!(State => [WlSeat, ZwlrDataControlManagerV1, ExtDataControlManagerV1]);
|
||||
server_ignore_impl!(State => [WlSeat, ZwlrDataControlDeviceV1, ExtDataControlDeviceV1]);
|
||||
|
||||
impl Dispatch<ZwlrDataControlManagerV1, ()> for State {
|
||||
fn request(
|
||||
@@ -36,6 +40,26 @@ impl Dispatch<ZwlrDataControlManagerV1, ()> for State {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ExtDataControlManagerV1, ()> for State {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_client: &wayland_server::Client,
|
||||
_resource: &ExtDataControlManagerV1,
|
||||
request: <ExtDataControlManagerV1 as wayland_server::Resource>::Request,
|
||||
_data: &(),
|
||||
_dhandle: &wayland_server::DisplayHandle,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
if let ext_data_control_manager_v1::Request::GetDataDevice { id, .. } = request {
|
||||
let data_device = data_init.init(id, ());
|
||||
|
||||
if state.advertise_primary_selection {
|
||||
data_device.primary_selection(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_primary_selection_supported_test() {
|
||||
let server = TestServer::new();
|
||||
@@ -165,9 +189,79 @@ fn is_primary_selection_supported_no_data_control() {
|
||||
let result = is_primary_selection_supported_internal(Some(socket_name));
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(PrimarySelectionCheckError::MissingProtocol {
|
||||
name: "zwlr_data_control_manager_v1",
|
||||
version: 1
|
||||
})
|
||||
Err(PrimarySelectionCheckError::MissingProtocol)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_primary_selection_supported_ext_data_control() {
|
||||
let server = TestServer::new();
|
||||
server
|
||||
.display
|
||||
.handle()
|
||||
.create_global::<State, WlSeat, ()>(6, ());
|
||||
server
|
||||
.display
|
||||
.handle()
|
||||
.create_global::<State, ExtDataControlManagerV1, ()>(1, ());
|
||||
|
||||
let state = State {
|
||||
advertise_primary_selection: true,
|
||||
};
|
||||
|
||||
let socket_name = server.socket_name().to_owned();
|
||||
server.run(state);
|
||||
|
||||
let result = is_primary_selection_supported_internal(Some(socket_name)).unwrap();
|
||||
assert!(result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_primary_selection_supported_primary_selection_unsupported_ext_data_control() {
|
||||
let server = TestServer::new();
|
||||
server
|
||||
.display
|
||||
.handle()
|
||||
.create_global::<State, WlSeat, ()>(6, ());
|
||||
server
|
||||
.display
|
||||
.handle()
|
||||
.create_global::<State, ExtDataControlManagerV1, ()>(1, ());
|
||||
|
||||
let state = State {
|
||||
advertise_primary_selection: false,
|
||||
};
|
||||
|
||||
let socket_name = server.socket_name().to_owned();
|
||||
server.run(state);
|
||||
|
||||
let result = is_primary_selection_supported_internal(Some(socket_name)).unwrap();
|
||||
assert!(!result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_primary_selection_supported_data_control_v1_and_ext_data_control() {
|
||||
let server = TestServer::new();
|
||||
server
|
||||
.display
|
||||
.handle()
|
||||
.create_global::<State, WlSeat, ()>(6, ());
|
||||
server
|
||||
.display
|
||||
.handle()
|
||||
.create_global::<State, ZwlrDataControlManagerV1, ()>(1, ());
|
||||
server
|
||||
.display
|
||||
.handle()
|
||||
.create_global::<State, ExtDataControlManagerV1, ()>(1, ());
|
||||
|
||||
let state = State {
|
||||
advertise_primary_selection: true,
|
||||
};
|
||||
|
||||
let socket_name = server.socket_name().to_owned();
|
||||
server.run(state);
|
||||
|
||||
let result = is_primary_selection_supported_internal(Some(socket_name)).unwrap();
|
||||
assert!(result);
|
||||
}
|
||||
|
110
src/utils.rs
110
src/utils.rs
@@ -10,11 +10,12 @@ use wayland_client::protocol::wl_seat::WlSeat;
|
||||
use wayland_client::{
|
||||
event_created_child, ConnectError, Connection, Dispatch, DispatchError, Proxy,
|
||||
};
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_device_v1::{
|
||||
self, ZwlrDataControlDeviceV1,
|
||||
};
|
||||
use wayland_protocols::ext::data_control::v1::client::ext_data_control_manager_v1::ExtDataControlManagerV1;
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1;
|
||||
|
||||
use crate::data_control::{
|
||||
impl_dispatch_device, impl_dispatch_manager, impl_dispatch_offer, Manager,
|
||||
};
|
||||
|
||||
/// Checks if the given MIME type represents plain text.
|
||||
///
|
||||
@@ -37,8 +38,8 @@ pub fn is_text(mime_type: &str) -> bool {
|
||||
struct PrimarySelectionState {
|
||||
// Any seat that we get from the compositor.
|
||||
seat: Option<WlSeat>,
|
||||
clipboard_manager: Option<ZwlrDataControlManagerV1>,
|
||||
clipboard_manager_was_v1: bool,
|
||||
clipboard_manager: Option<Manager>,
|
||||
saw_zwlr_v1: bool,
|
||||
got_primary_selection: bool,
|
||||
}
|
||||
|
||||
@@ -62,14 +63,19 @@ impl Dispatch<WlRegistry, ()> for PrimarySelectionState {
|
||||
state.seat = Some(seat);
|
||||
}
|
||||
|
||||
if interface == ZwlrDataControlManagerV1::interface().name {
|
||||
assert_eq!(state.clipboard_manager, None);
|
||||
if state.clipboard_manager.is_none() {
|
||||
if interface == ZwlrDataControlManagerV1::interface().name {
|
||||
if version == 1 {
|
||||
state.saw_zwlr_v1 = true;
|
||||
} else {
|
||||
let manager = registry.bind(name, 2, qh, ());
|
||||
state.clipboard_manager = Some(Manager::Zwlr(manager));
|
||||
}
|
||||
}
|
||||
|
||||
if version == 1 {
|
||||
state.clipboard_manager_was_v1 = true;
|
||||
} else {
|
||||
let manager = registry.bind(name, 2, qh, ());
|
||||
state.clipboard_manager = Some(manager);
|
||||
if interface == ExtDataControlManagerV1::interface().name {
|
||||
let manager = registry.bind(name, 1, qh, ());
|
||||
state.clipboard_manager = Some(Manager::Ext(manager));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,48 +94,15 @@ impl Dispatch<WlSeat, ()> for PrimarySelectionState {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwlrDataControlManagerV1, ()> for PrimarySelectionState {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_proxy: &ZwlrDataControlManagerV1,
|
||||
_event: <ZwlrDataControlManagerV1 as Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
impl_dispatch_manager!(PrimarySelectionState);
|
||||
|
||||
impl Dispatch<ZwlrDataControlDeviceV1, ()> for PrimarySelectionState {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_device: &ZwlrDataControlDeviceV1,
|
||||
event: <ZwlrDataControlDeviceV1 as wayland_client::Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &wayland_client::Connection,
|
||||
_qh: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
if let zwlr_data_control_device_v1::Event::PrimarySelection { id: _ } = event {
|
||||
state.got_primary_selection = true;
|
||||
}
|
||||
impl_dispatch_device!(PrimarySelectionState, (), |state: &mut Self, event, _| {
|
||||
if let Event::PrimarySelection { id: _ } = event {
|
||||
state.got_primary_selection = true;
|
||||
}
|
||||
});
|
||||
|
||||
event_created_child!(PrimarySelectionState, ZwlrDataControlDeviceV1, [
|
||||
zwlr_data_control_device_v1::EVT_DATA_OFFER_OPCODE => (ZwlrDataControlOfferV1, ()),
|
||||
]);
|
||||
}
|
||||
|
||||
impl Dispatch<ZwlrDataControlOfferV1, ()> for PrimarySelectionState {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_offer: &ZwlrDataControlOfferV1,
|
||||
_event: <ZwlrDataControlOfferV1 as wayland_client::Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
impl_dispatch_offer!(PrimarySelectionState);
|
||||
|
||||
/// Errors that can occur when checking whether the primary selection is supported.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
@@ -147,11 +120,10 @@ pub enum PrimarySelectionCheckError {
|
||||
WaylandCommunication(#[source] DispatchError),
|
||||
|
||||
#[error(
|
||||
"A required Wayland protocol ({} version {}) is not supported by the compositor",
|
||||
name,
|
||||
version
|
||||
"A required Wayland protocol (ext-data-control, or wlr-data-control version 1) \
|
||||
is not supported by the compositor"
|
||||
)]
|
||||
MissingProtocol { name: &'static str, version: u32 },
|
||||
MissingProtocol,
|
||||
}
|
||||
|
||||
/// Checks if the compositor supports the primary selection.
|
||||
@@ -165,19 +137,19 @@ pub enum PrimarySelectionCheckError {
|
||||
///
|
||||
/// match is_primary_selection_supported() {
|
||||
/// Ok(supported) => {
|
||||
/// // We have our definitive result. False means that either data-control version 1
|
||||
/// // is present (which does not support the primary selection), or that data-control
|
||||
/// // version 2 is present and it did not signal the primary selection support.
|
||||
/// // We have our definitive result. False means that ext/wlr-data-control is present
|
||||
/// // and did not signal the primary selection support, or that only wlr-data-control
|
||||
/// // version 1 is present (which does not support primary selection).
|
||||
/// },
|
||||
/// Err(PrimarySelectionCheckError::NoSeats) => {
|
||||
/// // Impossible to give a definitive result. Primary selection may or may not be
|
||||
/// // supported.
|
||||
///
|
||||
/// // The required protocol (data-control version 2) is there, but there are no seats.
|
||||
/// // Unfortunately, at least one seat is needed to check for the primary clipboard
|
||||
/// // support.
|
||||
/// // The required protocol (ext-data-control, or wlr-data-control version 2) is there,
|
||||
/// // but there are no seats. Unfortunately, at least one seat is needed to check for the
|
||||
/// // primary clipboard support.
|
||||
/// },
|
||||
/// Err(PrimarySelectionCheckError::MissingProtocol { .. }) => {
|
||||
/// Err(PrimarySelectionCheckError::MissingProtocol) => {
|
||||
/// // The data-control protocol (required for wl-clipboard-rs operation) is not
|
||||
/// // supported by the compositor.
|
||||
/// },
|
||||
@@ -225,7 +197,7 @@ pub(crate) fn is_primary_selection_supported_internal(
|
||||
let mut state = PrimarySelectionState {
|
||||
seat: None,
|
||||
clipboard_manager: None,
|
||||
clipboard_manager_was_v1: false,
|
||||
saw_zwlr_v1: false,
|
||||
got_primary_selection: false,
|
||||
};
|
||||
|
||||
@@ -235,17 +207,15 @@ pub(crate) fn is_primary_selection_supported_internal(
|
||||
.roundtrip(&mut state)
|
||||
.map_err(PrimarySelectionCheckError::WaylandCommunication)?;
|
||||
|
||||
// If data control is present but is version 1, then return false as version 1 does not support primary clipboard.
|
||||
if state.clipboard_manager_was_v1 {
|
||||
// If data control is present but is version 1, then return false as version 1 does not support
|
||||
// primary clipboard.
|
||||
if state.clipboard_manager.is_none() && state.saw_zwlr_v1 {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Verify that we got the clipboard manager.
|
||||
let Some(ref clipboard_manager) = state.clipboard_manager else {
|
||||
return Err(PrimarySelectionCheckError::MissingProtocol {
|
||||
name: ZwlrDataControlManagerV1::interface().name,
|
||||
version: 1,
|
||||
});
|
||||
return Err(PrimarySelectionCheckError::MissingProtocol);
|
||||
};
|
||||
|
||||
// Check if there are no seats.
|
||||
|
Reference in New Issue
Block a user