Skip to content

Commit e977111

Browse files
committed
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.
1 parent 148d6c7 commit e977111

File tree

11 files changed

+600
-327
lines changed

11 files changed

+600
-327
lines changed

Diff for: Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ thiserror = "2"
3838
tree_magic_mini = "3.1.6"
3939
wayland-backend = "0.3.8"
4040
wayland-client = "0.31.8"
41-
wayland-protocols = { version = "0.32.6", features = ["client"] }
41+
wayland-protocols = { version = "0.32.6", features = ["client", "staging"] }
4242
wayland-protocols-wlr = { version = "0.3.6", features = ["client"] }
4343

4444
[dev-dependencies]
4545
wayland-server = "0.31.7"
46-
wayland-protocols = { version = "0.32.6", features = ["server"] }
46+
wayland-protocols = { version = "0.32.6", features = ["server", "staging"] }
4747
wayland-protocols-wlr = { version = "0.3.6", features = ["server"] }
4848
proptest = "1.6.0"
4949
proptest-derive = "0.5.1"

Diff for: README.md

+11-11
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ please use the appropriate Wayland protocols for interacting with the Wayland cl
1515
primary selection), for example via the
1616
[smithay-clipboard](https://crates.io/crates/smithay-clipboard) crate.
1717

18-
The protocol used for clipboard interaction is `data-control` from
19-
[wlroots](https://github.com/swaywm/wlr-protocols). When using the regular clipboard, the
20-
compositor must support the first version of the protocol. When using the "primary" clipboard,
21-
the compositor must support the second version of the protocol (or higher).
18+
The protocol used for clipboard interaction is `ext-data-control` or `wlr-data-control`. When
19+
using the regular clipboard, the compositor must support any version of either protocol. When
20+
using the "primary" clipboard, the compositor must support any version of `ext-data-control`,
21+
or the second version of the `wlr-data-control` protocol.
2222

2323
For example applications using these features, see `wl-clipboard-rs-tools/src/bin/wl_copy.rs`
2424
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
7272

7373
match is_primary_selection_supported() {
7474
Ok(supported) => {
75-
// We have our definitive result. False means that either data-control version 1
76-
// is present (which does not support the primary selection), or that data-control
77-
// version 2 is present and it did not signal the primary selection support.
75+
// We have our definitive result. False means that ext/wlr-data-control is present
76+
// and did not signal the primary selection support, or that only wlr-data-control
77+
// version 1 is present (which does not support primary selection).
7878
},
7979
Err(PrimarySelectionCheckError::NoSeats) => {
8080
// Impossible to give a definitive result. Primary selection may or may not be
8181
// supported.
8282

83-
// The required protocol (data-control version 2) is there, but there are no seats.
84-
// Unfortunately, at least one seat is needed to check for the primary clipboard
85-
// support.
83+
// The required protocol (ext-data-control, or wlr-data-control version 2) is there,
84+
// but there are no seats. Unfortunately, at least one seat is needed to check for the
85+
// primary clipboard support.
8686
},
87-
Err(PrimarySelectionCheckError::MissingProtocol { .. }) => {
87+
Err(PrimarySelectionCheckError::MissingProtocol) => {
8888
// The data-control protocol (required for wl-clipboard-rs operation) is not
8989
// supported by the compositor.
9090
},

Diff for: src/common.rs

+16-15
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@ use std::path::PathBuf;
55
use std::{env, io};
66

77
use wayland_backend::client::WaylandError;
8-
use wayland_client::globals::{registry_queue_init, BindError, GlobalError, GlobalListContents};
8+
use wayland_client::globals::{registry_queue_init, GlobalError, GlobalListContents};
99
use wayland_client::protocol::wl_registry::WlRegistry;
1010
use wayland_client::protocol::wl_seat::{self, WlSeat};
1111
use wayland_client::{ConnectError, Connection, Dispatch, EventQueue, Proxy};
12+
use wayland_protocols::ext::data_control::v1::client::ext_data_control_manager_v1::ExtDataControlManagerV1;
1213
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;
1314

15+
use crate::data_control::Manager;
1416
use crate::seat_data::SeatData;
1517

1618
pub struct State {
1719
pub seats: HashMap<WlSeat, SeatData>,
18-
pub clipboard_manager: ZwlrDataControlManagerV1,
20+
pub clipboard_manager: Manager,
1921
}
2022

2123
#[derive(thiserror::Error, Debug)]
@@ -30,9 +32,10 @@ pub enum Error {
3032
WaylandCommunication(#[source] WaylandError),
3133

3234
#[error(
33-
"A required Wayland protocol ({name} version {version}) is not supported by the compositor"
35+
"A required Wayland protocol (ext-data-control, or wlr-data-control version {version}) \
36+
is not supported by the compositor"
3437
)]
35-
MissingProtocol { name: &'static str, version: u32 },
38+
MissingProtocol { version: u32 },
3639
}
3740

3841
impl<S> Dispatch<WlSeat, (), S> for State
@@ -62,6 +65,7 @@ pub fn initialize<S>(
6265
where
6366
S: Dispatch<WlRegistry, GlobalListContents> + 'static,
6467
S: Dispatch<ZwlrDataControlManagerV1, ()>,
68+
S: Dispatch<ExtDataControlManagerV1, ()>,
6569
S: Dispatch<WlSeat, ()>,
6670
S: AsMut<State>,
6771
{
@@ -95,18 +99,15 @@ where
9599
})?;
96100
let qh = &queue.handle();
97101

98-
let data_control_version = if primary { 2 } else { 1 };
99-
100102
// Verify that we got the clipboard manager.
101-
let clipboard_manager = match globals.bind(qh, data_control_version..=data_control_version, ())
102-
{
103-
Ok(manager) => manager,
104-
Err(BindError::NotPresent | BindError::UnsupportedVersion) => {
105-
return Err(Error::MissingProtocol {
106-
name: ZwlrDataControlManagerV1::interface().name,
107-
version: data_control_version,
108-
})
109-
}
103+
let ext_manager = globals.bind(qh, 1..=1, ()).ok().map(Manager::Ext);
104+
105+
let wlr_v = if primary { 2 } else { 1 };
106+
let wlr_manager = || globals.bind(qh, wlr_v..=wlr_v, ()).ok().map(Manager::Zwlr);
107+
108+
let clipboard_manager = match ext_manager.or_else(wlr_manager) {
109+
Some(manager) => manager,
110+
None => return Err(Error::MissingProtocol { version: wlr_v }),
110111
};
111112

112113
let registry = globals.registry();

Diff for: src/copy.rs

+68-115
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,12 @@ use wayland_client::protocol::wl_registry::WlRegistry;
1616
use wayland_client::protocol::wl_seat::WlSeat;
1717
use wayland_client::{
1818
delegate_dispatch, event_created_child, ConnectError, Dispatch, DispatchError, EventQueue,
19-
Proxy,
20-
};
21-
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_device_v1::{
22-
self, ZwlrDataControlDeviceV1,
23-
};
24-
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;
25-
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1;
26-
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::{
27-
self, ZwlrDataControlSourceV1,
2819
};
2920

3021
use crate::common::{self, initialize};
22+
use crate::data_control::{
23+
self, impl_dispatch_device, impl_dispatch_manager, impl_dispatch_offer, impl_dispatch_source,
24+
};
3125
use crate::seat_data::SeatData;
3226
use crate::utils::is_text;
3327

@@ -40,8 +34,8 @@ pub enum ClipboardType {
4034
Regular,
4135
/// The "primary" clipboard.
4236
///
43-
/// Working with the "primary" clipboard requires the compositor to support the data-control
44-
/// protocol of version 2 or above.
37+
/// Working with the "primary" clipboard requires the compositor to support ext-data-control,
38+
/// or wlr-data-control version 2 or above.
4539
Primary,
4640
/// Operate on both clipboards at once.
4741
///
@@ -144,7 +138,7 @@ pub struct Options {
144138
pub struct PreparedCopy {
145139
queue: EventQueue<State>,
146140
state: State,
147-
sources: Vec<ZwlrDataControlSourceV1>,
141+
sources: Vec<data_control::Source>,
148142
}
149143

150144
/// Errors that can occur for copying the source data to a temporary file.
@@ -194,11 +188,10 @@ pub enum Error {
194188
WaylandCommunication(#[source] DispatchError),
195189

196190
#[error(
197-
"A required Wayland protocol ({} version {}) is not supported by the compositor",
198-
name,
199-
version
191+
"A required Wayland protocol (ext-data-control, or wlr-data-control version {version}) \
192+
is not supported by the compositor"
200193
)]
201-
MissingProtocol { name: &'static str, version: u32 },
194+
MissingProtocol { version: u32 },
202195

203196
#[error("The compositor does not support primary selection")]
204197
PrimarySelectionUnsupported,
@@ -227,7 +220,7 @@ impl From<common::Error> for Error {
227220
SocketOpenError(err) => Error::SocketOpenError(err),
228221
WaylandConnection(err) => Error::WaylandConnection(err),
229222
WaylandCommunication(err) => Error::WaylandCommunication(err.into()),
230-
MissingProtocol { name, version } => Error::MissingProtocol { name, version },
223+
MissingProtocol { version } => Error::MissingProtocol { version },
231224
}
232225
}
233226
}
@@ -273,115 +266,75 @@ impl Dispatch<WlRegistry, GlobalListContents> for State {
273266
}
274267
}
275268

276-
impl Dispatch<ZwlrDataControlManagerV1, ()> for State {
277-
fn event(
278-
_state: &mut Self,
279-
_proxy: &ZwlrDataControlManagerV1,
280-
_event: <ZwlrDataControlManagerV1 as wayland_client::Proxy>::Event,
281-
_data: &(),
282-
_conn: &wayland_client::Connection,
283-
_qhandle: &wayland_client::QueueHandle<Self>,
284-
) {
285-
}
286-
}
269+
impl_dispatch_manager!(State);
287270

288-
impl Dispatch<ZwlrDataControlDeviceV1, WlSeat> for State {
289-
fn event(
290-
state: &mut Self,
291-
_device: &ZwlrDataControlDeviceV1,
292-
event: <ZwlrDataControlDeviceV1 as Proxy>::Event,
293-
seat: &WlSeat,
294-
_conn: &wayland_client::Connection,
295-
_qhandle: &wayland_client::QueueHandle<Self>,
296-
) {
297-
match event {
298-
zwlr_data_control_device_v1::Event::DataOffer { id } => id.destroy(),
299-
zwlr_data_control_device_v1::Event::Finished => {
300-
state.common.seats.get_mut(seat).unwrap().set_device(None);
301-
}
302-
zwlr_data_control_device_v1::Event::PrimarySelection { .. } => {
303-
state.got_primary_selection = true;
304-
}
305-
_ => (),
271+
impl_dispatch_device!(State, WlSeat, |state: &mut Self, event, seat| {
272+
match event {
273+
Event::DataOffer { id } => id.destroy(),
274+
Event::Finished => {
275+
state.common.seats.get_mut(seat).unwrap().set_device(None);
306276
}
277+
Event::PrimarySelection { .. } => {
278+
state.got_primary_selection = true;
279+
}
280+
_ => (),
307281
}
282+
});
283+
284+
impl_dispatch_offer!(State);
285+
286+
impl_dispatch_source!(State, |state: &mut Self,
287+
source: data_control::Source,
288+
event| {
289+
match event {
290+
Event::Send { mime_type, fd } => {
291+
// Check if some other source already handled a paste request and indicated that we should
292+
// quit.
293+
if state.should_quit {
294+
source.destroy();
295+
return;
296+
}
308297

309-
event_created_child!(State, ZwlrDataControlDeviceV1, [
310-
zwlr_data_control_device_v1::EVT_DATA_OFFER_OPCODE => (ZwlrDataControlOfferV1, ()),
311-
]);
312-
}
313-
314-
impl Dispatch<ZwlrDataControlOfferV1, ()> for State {
315-
fn event(
316-
_state: &mut Self,
317-
_offer: &ZwlrDataControlOfferV1,
318-
_event: <ZwlrDataControlOfferV1 as wayland_client::Proxy>::Event,
319-
_data: &(),
320-
_conn: &wayland_client::Connection,
321-
_qhandle: &wayland_client::QueueHandle<Self>,
322-
) {
323-
}
324-
}
325-
326-
impl Dispatch<ZwlrDataControlSourceV1, ()> for State {
327-
fn event(
328-
state: &mut Self,
329-
source: &ZwlrDataControlSourceV1,
330-
event: <ZwlrDataControlSourceV1 as Proxy>::Event,
331-
_data: &(),
332-
_conn: &wayland_client::Connection,
333-
_qhandle: &wayland_client::QueueHandle<Self>,
334-
) {
335-
match event {
336-
zwlr_data_control_source_v1::Event::Send { mime_type, fd } => {
337-
// Check if some other source already handled a paste request and indicated that we should
338-
// quit.
339-
if state.should_quit {
340-
source.destroy();
341-
return;
342-
}
298+
// I'm not sure if it's the compositor's responsibility to check that the mime type is
299+
// valid. Let's check here just in case.
300+
if !state.data_paths.contains_key(&mime_type) {
301+
return;
302+
}
343303

344-
// I'm not sure if it's the compositor's responsibility to check that the mime type is
345-
// valid. Let's check here just in case.
346-
if !state.data_paths.contains_key(&mime_type) {
347-
return;
348-
}
304+
let data_path = &state.data_paths[&mime_type];
349305

350-
let data_path = &state.data_paths[&mime_type];
306+
let file = File::open(data_path).map_err(DataSourceError::FileOpen);
307+
let result = file.and_then(|mut data_file| {
308+
// Clear O_NONBLOCK, otherwise io::copy() will stop halfway.
309+
fcntl_setfl(&fd, OFlags::empty())
310+
.map_err(io::Error::from)
311+
.map_err(DataSourceError::Copy)?;
351312

352-
let file = File::open(data_path).map_err(DataSourceError::FileOpen);
353-
let result = file.and_then(|mut data_file| {
354-
// Clear O_NONBLOCK, otherwise io::copy() will stop halfway.
355-
fcntl_setfl(&fd, OFlags::empty())
356-
.map_err(io::Error::from)
357-
.map_err(DataSourceError::Copy)?;
313+
let mut target_file = File::from(fd);
314+
io::copy(&mut data_file, &mut target_file).map_err(DataSourceError::Copy)
315+
});
358316

359-
let mut target_file = File::from(fd);
360-
io::copy(&mut data_file, &mut target_file).map_err(DataSourceError::Copy)
361-
});
317+
if let Err(err) = result {
318+
state.error = Some(err);
319+
}
362320

363-
if let Err(err) = result {
364-
state.error = Some(err);
365-
}
321+
let done = if let ServeRequests::Only(left) = state.serve_requests {
322+
let left = left.checked_sub(1).unwrap();
323+
state.serve_requests = ServeRequests::Only(left);
324+
left == 0
325+
} else {
326+
false
327+
};
366328

367-
let done = if let ServeRequests::Only(left) = state.serve_requests {
368-
let left = left.checked_sub(1).unwrap();
369-
state.serve_requests = ServeRequests::Only(left);
370-
left == 0
371-
} else {
372-
false
373-
};
374-
375-
if done || state.error.is_some() {
376-
state.should_quit = true;
377-
source.destroy();
378-
}
329+
if done || state.error.is_some() {
330+
state.should_quit = true;
331+
source.destroy();
379332
}
380-
zwlr_data_control_source_v1::Event::Cancelled => source.destroy(),
381-
_ => (),
382333
}
334+
Event::Cancelled => source.destroy(),
335+
_ => (),
383336
}
384-
}
337+
});
385338

386339
impl Options {
387340
/// Creates a blank new set of options ready for configuration.
@@ -676,7 +629,7 @@ fn get_devices(
676629
primary: bool,
677630
seat: Seat,
678631
socket_name: Option<OsString>,
679-
) -> Result<(EventQueue<State>, State, Vec<ZwlrDataControlDeviceV1>), Error> {
632+
) -> Result<(EventQueue<State>, State, Vec<data_control::Device>), Error> {
680633
let (mut queue, mut common) = initialize(primary, socket_name)?;
681634

682635
// Check if there are no seats.
@@ -980,7 +933,7 @@ fn prepare_copy_internal(
980933
let data_source = state
981934
.common
982935
.clipboard_manager
983-
.create_data_source(&queue.handle(), ());
936+
.create_data_source(&queue.handle());
984937

985938
for mime_type in state.data_paths.keys() {
986939
data_source.offer(mime_type.clone());

0 commit comments

Comments
 (0)