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::pipe(fds:pipes.as_mut_ptr()); |
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 | |