| 1 | use std::io::{Error, ErrorKind, Result}; |
| 2 | use std::sync::mpsc::Sender; |
| 3 | |
| 4 | use sctk::reexports::calloop::channel::Channel; |
| 5 | use sctk::reexports::calloop::{channel, EventLoop}; |
| 6 | use sctk::reexports::calloop_wayland_source::WaylandSource; |
| 7 | use sctk::reexports::client::globals::registry_queue_init; |
| 8 | use sctk::reexports::client::Connection; |
| 9 | |
| 10 | use crate::state::{SelectionTarget, State}; |
| 11 | |
| 12 | /// Spawn a clipboard worker, which dispatches its own `EventQueue` and handles |
| 13 | /// clipboard requests. |
| 14 | pub 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 | stdResult, Error>::thread::Builder::new() |
| 21 | .name(name) |
| 22 | .spawn(move || { |
| 23 | worker_impl(connection:display, rx_chan, reply_tx:worker_replier); |
| 24 | }) |
| 25 | .ok() |
| 26 | } |
| 27 | |
| 28 | /// Clipboard worker thread command. |
| 29 | #[derive (Eq, PartialEq)] |
| 30 | pub 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. |
| 44 | fn 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 | } |
| 103 | |