| 1 | use std::cmp; |
| 2 | use std::sync::Arc; |
| 3 | use std::sync::mpsc::{ Receiver, TryRecvError }; |
| 4 | use std::collections::HashMap; |
| 5 | use std::os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd}; |
| 6 | use ::{AtomEnum, EventMask}; |
| 7 | use x11rb::connection::Connection; |
| 8 | use x11rb::protocol::Event; |
| 9 | use x11rb::protocol::xproto::{Atom, ChangeWindowAttributesAux, ConnectionExt, Property, PropMode, SELECTION_NOTIFY_EVENT, SelectionNotifyEvent, Window}; |
| 10 | use ::{ INCR_CHUNK_SIZE, Context, SetMap }; |
| 11 | use error::Error; |
| 12 | |
| 13 | macro_rules! try_continue { |
| 14 | ( $expr:expr ) => { |
| 15 | match $expr { |
| 16 | Some(val) => val, |
| 17 | None => continue |
| 18 | } |
| 19 | }; |
| 20 | } |
| 21 | |
| 22 | struct IncrState { |
| 23 | selection: Atom, |
| 24 | requestor: Window, |
| 25 | property: Atom, |
| 26 | pos: usize |
| 27 | } |
| 28 | |
| 29 | pub(crate) struct PipeDropFds { |
| 30 | pub(crate) read_pipe: OwnedFd, |
| 31 | pub(crate) write_pipe: OwnedFd, |
| 32 | } |
| 33 | |
| 34 | pub(crate) fn create_pipe_drop_fd() -> Result<PipeDropFds, Error>{ |
| 35 | let pipe_drop_fds: PipeDropFds = unsafe { |
| 36 | // Docs Linux: https://man7.org/linux/man-pages/man2/pipe.2.html |
| 37 | // Posix: https://pubs.opengroup.org/onlinepubs/9699919799/ |
| 38 | // Safety: See above docs, api expects a 2-long array of file descriptors, and flags |
| 39 | let mut pipes: [libc::c_int; 2] = [0, 0]; |
| 40 | let pipe_create_res: i32 = libc::pipe2(fds:pipes.as_mut_ptr(), flags:libc::O_CLOEXEC); |
| 41 | if pipe_create_res < 0 { |
| 42 | // Don't want to have to read from errno_location, just skip propagating errno. |
| 43 | return Err(Error::EventFdCreate); |
| 44 | } |
| 45 | // Safety: Trusting the OS to give correct FDs |
| 46 | let read_pipe: OwnedFd = OwnedFd::from_raw_fd(pipes[0]); |
| 47 | let write_pipe: OwnedFd = OwnedFd::from_raw_fd(pipes[1]); |
| 48 | PipeDropFds { |
| 49 | read_pipe, |
| 50 | write_pipe, |
| 51 | } |
| 52 | }; |
| 53 | Ok(pipe_drop_fds) |
| 54 | } |
| 55 | |
| 56 | pub(crate) fn run(context: Arc<Context>, setmap: SetMap, max_length: usize, receiver: Receiver<Atom>, read_pipe: OwnedFd) { |
| 57 | let mut incr_map = HashMap::<Atom, Atom>::new(); |
| 58 | let mut state_map = HashMap::<Atom, IncrState>::new(); |
| 59 | |
| 60 | let stream_fd = context.connection.stream().as_fd(); |
| 61 | let borrowed_fd = read_pipe.as_fd(); |
| 62 | // Poll stream for new Read-ready events, check if the other side of the pipe has been dropped |
| 63 | let mut pollfds: [libc::pollfd; 2] = [libc::pollfd { |
| 64 | fd: stream_fd.as_raw_fd(), |
| 65 | events: libc::POLLIN, |
| 66 | revents: 0, |
| 67 | }, libc::pollfd { |
| 68 | fd: borrowed_fd.as_raw_fd(), |
| 69 | // If the other end is dropped, this pipe will get a HUP on poll |
| 70 | events: libc::POLLHUP, |
| 71 | revents: 0, |
| 72 | }]; |
| 73 | let len = pollfds.len(); |
| 74 | loop { |
| 75 | unsafe { |
| 76 | // Docs Linux: https://man7.org/linux/man-pages/man2/poll.2.html |
| 77 | // Posix: https://pubs.opengroup.org/onlinepubs/9699919799/ |
| 78 | // Safety: Passing in a mutable pointer that lives for the duration of the call, the length is |
| 79 | // set to the length of that pointer. |
| 80 | // Any negative value (-1 for example) means infinite timeout. |
| 81 | let poll_res = libc::poll(&mut pollfds as *mut libc::pollfd, len as libc::nfds_t, -1); |
| 82 | if poll_res < 0 { |
| 83 | // Error polling, can't continue |
| 84 | return; |
| 85 | } |
| 86 | } |
| 87 | if pollfds[1].revents & libc::POLLHUP != 0 { |
| 88 | // kill-signal on pollfd |
| 89 | return; |
| 90 | } |
| 91 | loop { |
| 92 | let evt = if let Ok(evt) = context.connection.poll_for_event() { |
| 93 | evt |
| 94 | } else { |
| 95 | // Connection died, exit |
| 96 | return; |
| 97 | }; |
| 98 | let event = if let Some(evt) = evt { |
| 99 | evt |
| 100 | } else { |
| 101 | // No event on POLLIN happens, fd being readable doesn't mean there's a complete event ready to read. |
| 102 | // Poll again. |
| 103 | break; |
| 104 | }; |
| 105 | loop { |
| 106 | match receiver.try_recv() { |
| 107 | Ok(selection) => if let Some(property) = incr_map.remove(&selection) { |
| 108 | state_map.remove(&property); |
| 109 | }, |
| 110 | Err(TryRecvError::Empty) => break, |
| 111 | Err(TryRecvError::Disconnected) => if state_map.is_empty() { |
| 112 | return |
| 113 | } |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | match event { |
| 118 | Event::SelectionRequest(event) => { |
| 119 | let read_map = try_continue!(setmap.read().ok()); |
| 120 | let &(target, ref value) = try_continue!(read_map.get(&event.selection)); |
| 121 | |
| 122 | if event.target == context.atoms.targets { |
| 123 | let _ = x11rb::wrapper::ConnectionExt::change_property32( |
| 124 | &context.connection, |
| 125 | PropMode::REPLACE, |
| 126 | event.requestor, |
| 127 | event.property, |
| 128 | Atom::from(AtomEnum::ATOM), |
| 129 | &[context.atoms.targets, target] |
| 130 | ); |
| 131 | } else if value.len() < max_length - 24 { |
| 132 | let _ = x11rb::wrapper::ConnectionExt::change_property8( |
| 133 | &context.connection, |
| 134 | PropMode::REPLACE, |
| 135 | event.requestor, |
| 136 | event.property, |
| 137 | target, |
| 138 | value |
| 139 | ); |
| 140 | } else { |
| 141 | let _ = context.connection.change_window_attributes( |
| 142 | event.requestor, |
| 143 | &ChangeWindowAttributesAux::new() |
| 144 | .event_mask(EventMask::PROPERTY_CHANGE) |
| 145 | ); |
| 146 | let _ = x11rb::wrapper::ConnectionExt::change_property32( |
| 147 | &context.connection, |
| 148 | PropMode::REPLACE, |
| 149 | event.requestor, |
| 150 | event.property, |
| 151 | context.atoms.incr, |
| 152 | &[0u32; 0], |
| 153 | ); |
| 154 | incr_map.insert(event.selection, event.property); |
| 155 | state_map.insert( |
| 156 | event.property, |
| 157 | IncrState { |
| 158 | selection: event.selection, |
| 159 | requestor: event.requestor, |
| 160 | property: event.property, |
| 161 | pos: 0 |
| 162 | } |
| 163 | ); |
| 164 | } |
| 165 | let _ = context.connection.send_event( |
| 166 | false, |
| 167 | event.requestor, |
| 168 | EventMask::default(), |
| 169 | SelectionNotifyEvent { |
| 170 | response_type: SELECTION_NOTIFY_EVENT, |
| 171 | sequence: 0, |
| 172 | time: event.time, |
| 173 | requestor: event.requestor, |
| 174 | selection: event.selection, |
| 175 | target: event.target, |
| 176 | property: event.property |
| 177 | } |
| 178 | ); |
| 179 | let _ = context.connection.flush(); |
| 180 | }, |
| 181 | Event::PropertyNotify(event) => { |
| 182 | if event.state != Property::DELETE { continue }; |
| 183 | |
| 184 | let is_end = { |
| 185 | let state = try_continue!(state_map.get_mut(&event.atom)); |
| 186 | let read_setmap = try_continue!(setmap.read().ok()); |
| 187 | let &(target, ref value) = try_continue!(read_setmap.get(&state.selection)); |
| 188 | |
| 189 | let len = cmp::min(INCR_CHUNK_SIZE, value.len() - state.pos); |
| 190 | let _ = x11rb::wrapper::ConnectionExt::change_property8( |
| 191 | &context.connection, |
| 192 | PropMode::REPLACE, |
| 193 | state.requestor, |
| 194 | state.property, |
| 195 | target, |
| 196 | &value[state.pos..][..len] |
| 197 | ); |
| 198 | state.pos += len; |
| 199 | len == 0 |
| 200 | }; |
| 201 | |
| 202 | if is_end { |
| 203 | state_map.remove(&event.atom); |
| 204 | } |
| 205 | let _ = context.connection.flush(); |
| 206 | }, |
| 207 | Event::SelectionClear(event) => { |
| 208 | if let Some(property) = incr_map.remove(&event.selection) { |
| 209 | state_map.remove(&property); |
| 210 | } |
| 211 | if let Ok(mut write_setmap) = setmap.write() { |
| 212 | write_setmap.remove(&event.selection); |
| 213 | } |
| 214 | } |
| 215 | _ => () |
| 216 | } |
| 217 | } |
| 218 | } |
| 219 | } |
| 220 | |