1use std::ffi::OsString;
4use std::os::unix::net::UnixStream;
5use std::path::PathBuf;
6use std::{env, io};
7
8use wayland_client::protocol::wl_registry::{self, WlRegistry};
9use wayland_client::protocol::wl_seat::WlSeat;
10use wayland_client::{
11 event_created_child, ConnectError, Connection, Dispatch, DispatchError, Proxy,
12};
13use wayland_protocols::ext::data_control::v1::client::ext_data_control_manager_v1::ExtDataControlManagerV1;
14use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;
15
16use crate::data_control::{
17 impl_dispatch_device, impl_dispatch_manager, impl_dispatch_offer, Manager,
18};
19
20pub fn is_text(mime_type: &str) -> bool {
31 match mime_type {
32 "TEXT" | "STRING" | "UTF8_STRING" => true,
33 x if x.starts_with("text/") => true,
34 _ => false,
35 }
36}
37
38struct PrimarySelectionState {
39 seat: Option<WlSeat>,
41 clipboard_manager: Option<Manager>,
42 saw_zwlr_v1: bool,
43 got_primary_selection: bool,
44}
45
46impl Dispatch<WlRegistry, ()> for PrimarySelectionState {
47 fn event(
48 state: &mut Self,
49 registry: &WlRegistry,
50 event: <WlRegistry as wayland_client::Proxy>::Event,
51 _data: &(),
52 _conn: &Connection,
53 qh: &wayland_client::QueueHandle<Self>,
54 ) {
55 if let wl_registry::Event::Global {
56 name,
57 interface,
58 version,
59 } = event
60 {
61 if interface == WlSeat::interface().name && version >= 2 && state.seat.is_none() {
62 let seat = registry.bind(name, 2, qh, ());
63 state.seat = Some(seat);
64 }
65
66 if state.clipboard_manager.is_none() {
67 if interface == ZwlrDataControlManagerV1::interface().name {
68 if version == 1 {
69 state.saw_zwlr_v1 = true;
70 } else {
71 let manager = registry.bind(name, 2, qh, ());
72 state.clipboard_manager = Some(Manager::Zwlr(manager));
73 }
74 }
75
76 if interface == ExtDataControlManagerV1::interface().name {
77 let manager = registry.bind(name, 1, qh, ());
78 state.clipboard_manager = Some(Manager::Ext(manager));
79 }
80 }
81 }
82 }
83}
84
85impl Dispatch<WlSeat, ()> for PrimarySelectionState {
86 fn event(
87 _state: &mut Self,
88 _proxy: &WlSeat,
89 _event: <WlSeat as Proxy>::Event,
90 _data: &(),
91 _conn: &Connection,
92 _qhandle: &wayland_client::QueueHandle<Self>,
93 ) {
94 }
95}
96
97impl_dispatch_manager!(PrimarySelectionState);
98
99impl_dispatch_device!(PrimarySelectionState, (), |state: &mut Self, event, _| {
100 if let Event::PrimarySelection { id: _ } = event {
101 state.got_primary_selection = true;
102 }
103});
104
105impl_dispatch_offer!(PrimarySelectionState);
106
107#[derive(thiserror::Error, Debug)]
109pub enum PrimarySelectionCheckError {
110 #[error("There are no seats")]
111 NoSeats,
112
113 #[error("Couldn't open the provided Wayland socket")]
114 SocketOpenError(#[source] io::Error),
115
116 #[error("Couldn't connect to the Wayland compositor")]
117 WaylandConnection(#[source] ConnectError),
118
119 #[error("Wayland compositor communication error")]
120 WaylandCommunication(#[source] DispatchError),
121
122 #[error(
123 "A required Wayland protocol (ext-data-control, or wlr-data-control version 1) \
124 is not supported by the compositor"
125 )]
126 MissingProtocol,
127}
128
129#[inline]
164pub fn is_primary_selection_supported() -> Result<bool, PrimarySelectionCheckError> {
165 is_primary_selection_supported_internal(None)
166}
167
168pub(crate) fn is_primary_selection_supported_internal(
169 socket_name: Option<OsString>,
170) -> Result<bool, PrimarySelectionCheckError> {
171 let conn = match socket_name {
173 Some(name) => {
174 let mut socket_path = env::var_os("XDG_RUNTIME_DIR")
175 .map(Into::<PathBuf>::into)
176 .ok_or(ConnectError::NoCompositor)
177 .map_err(PrimarySelectionCheckError::WaylandConnection)?;
178 if !socket_path.is_absolute() {
179 return Err(PrimarySelectionCheckError::WaylandConnection(
180 ConnectError::NoCompositor,
181 ));
182 }
183 socket_path.push(name);
184
185 let stream = UnixStream::connect(socket_path)
186 .map_err(PrimarySelectionCheckError::SocketOpenError)?;
187 Connection::from_socket(stream)
188 }
189 None => Connection::connect_to_env(),
190 }
191 .map_err(PrimarySelectionCheckError::WaylandConnection)?;
192 let display = conn.display();
193
194 let mut queue = conn.new_event_queue();
195 let qh = queue.handle();
196
197 let mut state = PrimarySelectionState {
198 seat: None,
199 clipboard_manager: None,
200 saw_zwlr_v1: false,
201 got_primary_selection: false,
202 };
203
204 let _registry = display.get_registry(&qh, ());
206 queue
207 .roundtrip(&mut state)
208 .map_err(PrimarySelectionCheckError::WaylandCommunication)?;
209
210 if state.clipboard_manager.is_none() && state.saw_zwlr_v1 {
213 return Ok(false);
214 }
215
216 let Some(ref clipboard_manager) = state.clipboard_manager else {
218 return Err(PrimarySelectionCheckError::MissingProtocol);
219 };
220
221 let Some(ref seat) = state.seat else {
223 return Err(PrimarySelectionCheckError::NoSeats);
224 };
225
226 clipboard_manager.get_data_device(seat, &qh, ());
227
228 queue
229 .roundtrip(&mut state)
230 .map_err(PrimarySelectionCheckError::WaylandCommunication)?;
231
232 Ok(state.got_primary_selection)
233}