smithay_clipboard/
worker.rs

1use std::io::{Error, ErrorKind, Result};
2use std::sync::mpsc::Sender;
3
4use sctk::reexports::calloop::channel::Channel;
5use sctk::reexports::calloop::{channel, EventLoop};
6use sctk::reexports::calloop_wayland_source::WaylandSource;
7use sctk::reexports::client::globals::registry_queue_init;
8use sctk::reexports::client::Connection;
9
10use crate::state::{SelectionTarget, State};
11
12/// Spawn a clipboard worker, which dispatches its own `EventQueue` and handles
13/// clipboard requests.
14pub fn spawn(
15    name: String,
16    display: Connection,
17    rx_chan: Channel<Command>,
18    worker_replier: Sender<Result<String>>,
19) -> Option<std::thread::JoinHandle<()>> {
20    std::thread::Builder::new()
21        .name(name)
22        .spawn(move || {
23            worker_impl(display, rx_chan, worker_replier);
24        })
25        .ok()
26}
27
28/// Clipboard worker thread command.
29#[derive(Eq, PartialEq)]
30pub enum Command {
31    /// Store data to a clipboard.
32    Store(String),
33    /// Store data to a primary selection.
34    StorePrimary(String),
35    /// Load data from a clipboard.
36    Load,
37    /// Load primary selection.
38    LoadPrimary,
39    /// Shutdown the worker.
40    Exit,
41}
42
43/// Handle clipboard requests.
44fn worker_impl(
45    connection: Connection,
46    rx_chan: Channel<Command>,
47    reply_tx: Sender<Result<String>>,
48) {
49    let (globals, event_queue) = match registry_queue_init(&connection) {
50        Ok(data) => data,
51        Err(_) => return,
52    };
53
54    let mut event_loop = EventLoop::<State>::try_new().unwrap();
55    let loop_handle = event_loop.handle();
56
57    let mut state = match State::new(&globals, &event_queue.handle(), loop_handle.clone(), reply_tx)
58    {
59        Some(state) => state,
60        None => return,
61    };
62
63    loop_handle
64        .insert_source(rx_chan, |event, _, state| {
65            if let channel::Event::Msg(event) = event {
66                match event {
67                    Command::StorePrimary(contents) => {
68                        state.store_selection(SelectionTarget::Primary, contents);
69                    },
70                    Command::Store(contents) => {
71                        state.store_selection(SelectionTarget::Clipboard, contents);
72                    },
73                    Command::Load if state.data_device_manager_state.is_some() => {
74                        if let Err(err) = state.load_selection(SelectionTarget::Clipboard) {
75                            let _ = state.reply_tx.send(Err(err));
76                        }
77                    },
78                    Command::LoadPrimary if state.data_device_manager_state.is_some() => {
79                        if let Err(err) = state.load_selection(SelectionTarget::Primary) {
80                            let _ = state.reply_tx.send(Err(err));
81                        }
82                    },
83                    Command::Load | Command::LoadPrimary => {
84                        let _ = state.reply_tx.send(Err(Error::new(
85                            ErrorKind::Other,
86                            "requested selection is not supported",
87                        )));
88                    },
89                    Command::Exit => state.exit = true,
90                }
91            }
92        })
93        .unwrap();
94
95    WaylandSource::new(connection, event_queue).insert(loop_handle).unwrap();
96
97    loop {
98        if event_loop.dispatch(None, &mut state).is_err() || state.exit {
99            break;
100        }
101    }
102}