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