Skip to main content

wl_clipboard_rs/
common.rs

1use std::collections::HashMap;
2use std::ffi::OsString;
3use std::os::unix::net::UnixStream;
4use std::path::PathBuf;
5use std::{env, io};
6
7use wayland_backend::client::WaylandError;
8use wayland_client::globals::{registry_queue_init, GlobalError, GlobalListContents};
9use wayland_client::protocol::wl_registry::WlRegistry;
10use wayland_client::protocol::wl_seat::{self, WlSeat};
11use wayland_client::{ConnectError, Connection, Dispatch, EventQueue, Proxy};
12use wayland_protocols::ext::data_control::v1::client::ext_data_control_manager_v1::ExtDataControlManagerV1;
13use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;
14
15use crate::data_control::Manager;
16use crate::seat_data::SeatData;
17
18pub struct State {
19    pub seats: HashMap<WlSeat, SeatData>,
20    pub clipboard_manager: Manager,
21}
22
23#[derive(thiserror::Error, Debug)]
24pub enum Error {
25    #[allow(clippy::enum_variant_names)]
26    #[error("Couldn't open the provided Wayland socket")]
27    SocketOpenError(#[source] io::Error),
28
29    #[error("Couldn't connect to the Wayland compositor")]
30    WaylandConnection(#[source] ConnectError),
31
32    #[error("Wayland compositor communication error")]
33    WaylandCommunication(#[source] WaylandError),
34
35    #[error(
36        "A required Wayland protocol ({name} version {version}) is not supported by the compositor"
37    )]
38    MissingProtocol { name: &'static str, version: u32 },
39}
40
41impl<S> Dispatch<WlSeat, (), S> for State
42where
43    S: Dispatch<WlSeat, ()> + AsMut<State>,
44{
45    fn event(
46        parent: &mut S,
47        seat: &WlSeat,
48        event: <WlSeat as wayland_client::Proxy>::Event,
49        _data: &(),
50        _conn: &wayland_client::Connection,
51        _qh: &wayland_client::QueueHandle<S>,
52    ) {
53        let state = parent.as_mut();
54
55        if let wl_seat::Event::Name { name } = event {
56            state.seats.get_mut(seat).unwrap().set_name(name);
57        }
58    }
59}
60
61pub fn initialize<S>(
62    primary: bool,
63    socket_name: Option<OsString>,
64) -> Result<(EventQueue<S>, State), Error>
65where
66    S: Dispatch<WlRegistry, GlobalListContents> + 'static,
67    S: Dispatch<ZwlrDataControlManagerV1, ()>,
68    S: Dispatch<ExtDataControlManagerV1, ()>,
69    S: Dispatch<WlSeat, ()>,
70    S: AsMut<State>,
71{
72    // Connect to the Wayland compositor.
73    let conn = match socket_name {
74        Some(name) => {
75            let mut socket_path = env::var_os("XDG_RUNTIME_DIR")
76                .map(Into::<PathBuf>::into)
77                .ok_or(ConnectError::NoCompositor)
78                .map_err(Error::WaylandConnection)?;
79            if !socket_path.is_absolute() {
80                return Err(Error::WaylandConnection(ConnectError::NoCompositor));
81            }
82            socket_path.push(name);
83
84            let stream = UnixStream::connect(socket_path).map_err(Error::SocketOpenError)?;
85            Connection::from_socket(stream)
86        }
87        None => Connection::connect_to_env(),
88    }
89    .map_err(Error::WaylandConnection)?;
90
91    // Retrieve the global interfaces.
92    let (globals, queue) =
93        registry_queue_init::<S>(&conn).map_err(|err| match err {
94                                           GlobalError::Backend(err) => Error::WaylandCommunication(err),
95                                           GlobalError::InvalidId(err) => panic!("How's this possible? \
96                                                                                  Is there no wl_registry? \
97                                                                                  {:?}",
98                                                                                 err),
99                                       })?;
100    let qh = &queue.handle();
101
102    // Verify that we got the clipboard manager.
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 => {
111            return Err(Error::MissingProtocol {
112                name: "ext-data-control, or wlr-data-control",
113                version: wlr_v,
114            })
115        }
116    };
117
118    let registry = globals.registry();
119    let seats = globals.contents().with_list(|globals| {
120        globals
121            .iter()
122            .filter(|global| global.interface == WlSeat::interface().name && global.version >= 2)
123            .map(|global| {
124                let seat = registry.bind(global.name, 2, qh, ());
125                (seat, SeatData::default())
126            })
127            .collect()
128    });
129
130    let state = State {
131        seats,
132        clipboard_manager,
133    };
134
135    Ok((queue, state))
136}