1use std::cmp;
2use std::sync::Arc;
3use std::sync::mpsc::{ Receiver, TryRecvError };
4use std::collections::HashMap;
5use std::os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd};
6use ::{AtomEnum, EventMask};
7use x11rb::connection::Connection;
8use x11rb::protocol::Event;
9use x11rb::protocol::xproto::{Atom, ChangeWindowAttributesAux, ConnectionExt, Property, PropMode, SELECTION_NOTIFY_EVENT, SelectionNotifyEvent, Window};
10use ::{ INCR_CHUNK_SIZE, Context, SetMap };
11use error::Error;
12
13macro_rules! try_continue {
14 ( $expr:expr ) => {
15 match $expr {
16 Some(val) => val,
17 None => continue
18 }
19 };
20}
21
22struct IncrState {
23 selection: Atom,
24 requestor: Window,
25 property: Atom,
26 pos: usize
27}
28
29pub(crate) struct PipeDropFds {
30 pub(crate) read_pipe: OwnedFd,
31 pub(crate) write_pipe: OwnedFd,
32}
33
34pub(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
56pub(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