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 {
35 "TEXT" | "STRING" | "UTF8_STRING" => true,
36 x if x.starts_with("text/") => true,
37 x if x.contains("json")
39 | x.ends_with("script")
40 | x.ends_with("xml")
41 | x.ends_with("yaml")
42 | x.ends_with("csv")
43 | x.ends_with("ini") =>
44 {
45 true
46 }
47 _ => false,
48 }
49}
50
51struct PrimarySelectionState {
52 seat: Option<WlSeat>,
54 clipboard_manager: Option<Manager>,
55 saw_zwlr_v1: bool,
56 got_primary_selection: bool,
57}
58
59impl Dispatch<WlRegistry, ()> for PrimarySelectionState {
60 fn event(
61 state: &mut Self,
62 registry: &WlRegistry,
63 event: <WlRegistry as wayland_client::Proxy>::Event,
64 _data: &(),
65 _conn: &Connection,
66 qh: &wayland_client::QueueHandle<Self>,
67 ) {
68 if let wl_registry::Event::Global {
69 name,
70 interface,
71 version,
72 } = event
73 {
74 if interface == WlSeat::interface().name && version >= 2 && state.seat.is_none() {
75 let seat = registry.bind(name, 2, qh, ());
76 state.seat = Some(seat);
77 }
78
79 if state.clipboard_manager.is_none() {
80 if interface == ZwlrDataControlManagerV1::interface().name {
81 if version == 1 {
82 state.saw_zwlr_v1 = true;
83 } else {
84 let manager = registry.bind(name, 2, qh, ());
85 state.clipboard_manager = Some(Manager::Zwlr(manager));
86 }
87 }
88
89 if interface == ExtDataControlManagerV1::interface().name {
90 let manager = registry.bind(name, 1, qh, ());
91 state.clipboard_manager = Some(Manager::Ext(manager));
92 }
93 }
94 }
95 }
96}
97
98impl Dispatch<WlSeat, ()> for PrimarySelectionState {
99 fn event(
100 _state: &mut Self,
101 _proxy: &WlSeat,
102 _event: <WlSeat as Proxy>::Event,
103 _data: &(),
104 _conn: &Connection,
105 _qhandle: &wayland_client::QueueHandle<Self>,
106 ) {
107 }
108}
109
110impl_dispatch_manager!(PrimarySelectionState);
111
112impl_dispatch_device!(PrimarySelectionState, (), |state: &mut Self, event, _| {
113 if let Event::PrimarySelection { id: _ } = event {
114 state.got_primary_selection = true;
115 }
116});
117
118impl_dispatch_offer!(PrimarySelectionState);
119
120#[derive(thiserror::Error, Debug)]
122pub enum PrimarySelectionCheckError {
123 #[error("There are no seats")]
124 NoSeats,
125
126 #[error("Couldn't open the provided Wayland socket")]
127 SocketOpenError(#[source] io::Error),
128
129 #[error("Couldn't connect to the Wayland compositor")]
130 WaylandConnection(#[source] ConnectError),
131
132 #[error("Wayland compositor communication error")]
133 WaylandCommunication(#[source] DispatchError),
134
135 #[error(
136 "A required Wayland protocol (ext-data-control, or wlr-data-control version 1) \
137 is not supported by the compositor"
138 )]
139 MissingProtocol,
140}
141
142#[inline]
177pub fn is_primary_selection_supported() -> Result<bool, PrimarySelectionCheckError> {
178 is_primary_selection_supported_internal(None)
179}
180
181pub(crate) fn is_primary_selection_supported_internal(
182 socket_name: Option<OsString>,
183) -> Result<bool, PrimarySelectionCheckError> {
184 let conn = match socket_name {
186 Some(name) => {
187 let mut socket_path = env::var_os("XDG_RUNTIME_DIR")
188 .map(Into::<PathBuf>::into)
189 .ok_or(ConnectError::NoCompositor)
190 .map_err(PrimarySelectionCheckError::WaylandConnection)?;
191 if !socket_path.is_absolute() {
192 return Err(PrimarySelectionCheckError::WaylandConnection(
193 ConnectError::NoCompositor,
194 ));
195 }
196 socket_path.push(name);
197
198 let stream = UnixStream::connect(socket_path)
199 .map_err(PrimarySelectionCheckError::SocketOpenError)?;
200 Connection::from_socket(stream)
201 }
202 None => Connection::connect_to_env(),
203 }
204 .map_err(PrimarySelectionCheckError::WaylandConnection)?;
205 let display = conn.display();
206
207 let mut queue = conn.new_event_queue();
208 let qh = queue.handle();
209
210 let mut state = PrimarySelectionState {
211 seat: None,
212 clipboard_manager: None,
213 saw_zwlr_v1: false,
214 got_primary_selection: false,
215 };
216
217 let _registry = display.get_registry(&qh, ());
219 queue
220 .roundtrip(&mut state)
221 .map_err(PrimarySelectionCheckError::WaylandCommunication)?;
222
223 if state.clipboard_manager.is_none() && state.saw_zwlr_v1 {
226 return Ok(false);
227 }
228
229 let Some(ref clipboard_manager) = state.clipboard_manager else {
231 return Err(PrimarySelectionCheckError::MissingProtocol);
232 };
233
234 let Some(ref seat) = state.seat else {
236 return Err(PrimarySelectionCheckError::NoSeats);
237 };
238
239 clipboard_manager.get_data_device(seat, &qh, ());
240
241 queue
242 .roundtrip(&mut state)
243 .map_err(PrimarySelectionCheckError::WaylandCommunication)?;
244
245 Ok(state.got_primary_selection)
246}