1 /*
2 * synergy -- mouse and keyboard sharing utility
3 * Copyright (C) 2012-2016 Symless Ltd.
4 * Copyright (C) 2004 Chris Schoeneman
5 *
6 * This package is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * found in the file LICENSE that should have accompanied this file.
9 *
10 * This package is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "platform/XWindowsEventQueueBuffer.h"
20
21 #include "mt/Lock.h"
22 #include "mt/Thread.h"
23 #include "base/Event.h"
24 #include "base/IEventQueue.h"
25
26 #include <fcntl.h>
27 #if HAVE_UNISTD_H
28 # include <unistd.h>
29 #endif
30 #if HAVE_POLL
31 # include <poll.h>
32 #else
33 # if HAVE_SYS_SELECT_H
34 # include <sys/select.h>
35 # endif
36 # if HAVE_SYS_TIME_H
37 # include <sys/time.h>
38 # endif
39 # if HAVE_SYS_TYPES_H
40 # include <sys/types.h>
41 # endif
42 #endif
43
44 //
45 // EventQueueTimer
46 //
47
48 class EventQueueTimer { };
49
50
51 //
52 // XWindowsEventQueueBuffer
53 //
54
XWindowsEventQueueBuffer(Display * display,Window window,IEventQueue * events)55 XWindowsEventQueueBuffer::XWindowsEventQueueBuffer(
56 Display* display, Window window, IEventQueue* events) :
57 m_events(events),
58 m_display(display),
59 m_window(window),
60 m_waiting(false)
61 {
62 assert(m_display != NULL);
63 assert(m_window != None);
64
65 m_userEvent = XInternAtom(m_display, "SYNERGY_USER_EVENT", False);
66 // set up for pipe hack
67 int result = pipe(m_pipefd);
68 assert(result == 0);
69
70 int pipeflags;
71 pipeflags = fcntl(m_pipefd[0], F_GETFL);
72 fcntl(m_pipefd[0], F_SETFL, pipeflags | O_NONBLOCK);
73 pipeflags = fcntl(m_pipefd[1], F_GETFL);
74 fcntl(m_pipefd[1], F_SETFL, pipeflags | O_NONBLOCK);
75 }
76
~XWindowsEventQueueBuffer()77 XWindowsEventQueueBuffer::~XWindowsEventQueueBuffer()
78 {
79 // release pipe hack resources
80 close(m_pipefd[0]);
81 close(m_pipefd[1]);
82 }
83
getPendingCountLocked()84 int XWindowsEventQueueBuffer::getPendingCountLocked()
85 {
86 Lock lock(&m_mutex);
87 return XPending(m_display);
88 }
89
90 void
waitForEvent(double dtimeout)91 XWindowsEventQueueBuffer::waitForEvent(double dtimeout)
92 {
93 Thread::testCancel();
94
95 // clear out the pipe in preparation for waiting.
96
97 char buf[16];
98 ssize_t read_response = read(m_pipefd[0], buf, 15);
99
100 // with linux automake, warnings are treated as errors by default
101 if (read_response < 0)
102 {
103 // todo: handle read response
104 }
105
106 {
107 Lock lock(&m_mutex);
108 // we're now waiting for events
109 m_waiting = true;
110
111 // push out pending events
112 flush();
113 }
114 // calling flush may have queued up a new event.
115 if (!XWindowsEventQueueBuffer::isEmpty()) {
116 Thread::testCancel();
117 return;
118 }
119
120 // use poll() to wait for a message from the X server or for timeout.
121 // this is a good deal more efficient than polling and sleeping.
122 #if HAVE_POLL
123 struct pollfd pfds[2];
124 pfds[0].fd = ConnectionNumber(m_display);
125 pfds[0].events = POLLIN;
126 pfds[1].fd = m_pipefd[0];
127 pfds[1].events = POLLIN;
128 int timeout = (dtimeout < 0.0) ? -1 :
129 static_cast<int>(1000.0 * dtimeout);
130 int remaining = timeout;
131 int retval = 0;
132 #else
133 struct timeval timeout;
134 struct timeval* timeoutPtr;
135 if (dtimeout < 0.0) {
136 timeoutPtr = NULL;
137 }
138 else {
139 timeout.tv_sec = static_cast<int>(dtimeout);
140 timeout.tv_usec = static_cast<int>(1.0e+6 *
141 (dtimeout - timeout.tv_sec));
142 timeoutPtr = &timeout;
143 }
144
145 // initialize file descriptor sets
146 fd_set rfds;
147 FD_ZERO(&rfds);
148 FD_SET(ConnectionNumber(m_display), &rfds);
149 FD_SET(m_pipefd[0], &rfds);
150 int nfds;
151 if (ConnectionNumber(m_display) > m_pipefd[0]) {
152 nfds = ConnectionNumber(m_display) + 1;
153 }
154 else {
155 nfds = m_pipefd[0] + 1;
156 }
157 #endif
158 // It's possible that the X server has queued events locally
159 // in xlib's event buffer and not pushed on to the fd. Hence we
160 // can't simply monitor the fd as we may never be woken up.
161 // ie addEvent calls flush, XFlush may not send via the fd hence
162 // there is an event waiting to be sent but we must exit the poll
163 // before it can.
164 // Instead we poll for a brief period of time (so if events
165 // queued locally in the xlib buffer can be processed)
166 // and continue doing this until timeout is reached.
167 // The human eye can notice 60hz (ansi) which is 16ms, however
168 // we want to give the cpu a chance s owe up this to 25ms
169 #define TIMEOUT_DELAY 25
170
171 while (((dtimeout < 0.0) || (remaining > 0)) && getPendingCountLocked() == 0 && retval == 0) {
172 #if HAVE_POLL
173 retval = poll(pfds, 2, TIMEOUT_DELAY); //16ms = 60hz, but we make it > to play nicely with the cpu
174 if (pfds[1].revents & POLLIN) {
175 ssize_t read_response = read(m_pipefd[0], buf, 15);
176
177 // with linux automake, warnings are treated as errors by default
178 if (read_response < 0)
179 {
180 // todo: handle read response
181 }
182
183 }
184 #else
185 retval = select(nfds,
186 SELECT_TYPE_ARG234 &rfds,
187 SELECT_TYPE_ARG234 NULL,
188 SELECT_TYPE_ARG234 NULL,
189 SELECT_TYPE_ARG5 TIMEOUT_DELAY);
190 if (FD_SET(m_pipefd[0], &rfds)) {
191 read(m_pipefd[0], buf, 15);
192 }
193 #endif
194 remaining-=TIMEOUT_DELAY;
195 }
196
197 {
198 // we're no longer waiting for events
199 Lock lock(&m_mutex);
200 m_waiting = false;
201 }
202
203 Thread::testCancel();
204 }
205
206 IEventQueueBuffer::Type
getEvent(Event & event,UInt32 & dataID)207 XWindowsEventQueueBuffer::getEvent(Event& event, UInt32& dataID)
208 {
209 Lock lock(&m_mutex);
210
211 // push out pending events
212 flush();
213
214 // get next event
215 XNextEvent(m_display, &m_event);
216
217 // process event
218 if (m_event.xany.type == ClientMessage &&
219 m_event.xclient.message_type == m_userEvent) {
220 dataID = static_cast<UInt32>(m_event.xclient.data.l[0]);
221 return kUser;
222 }
223 else {
224 event = Event(Event::kSystem,
225 m_events->getSystemTarget(), &m_event);
226 return kSystem;
227 }
228 }
229
230 bool
addEvent(UInt32 dataID)231 XWindowsEventQueueBuffer::addEvent(UInt32 dataID)
232 {
233 // prepare a message
234 XEvent xevent;
235 xevent.xclient.type = ClientMessage;
236 xevent.xclient.window = m_window;
237 xevent.xclient.message_type = m_userEvent;
238 xevent.xclient.format = 32;
239 xevent.xclient.data.l[0] = static_cast<long>(dataID);
240
241 // save the message
242 Lock lock(&m_mutex);
243 m_postedEvents.push_back(xevent);
244
245 // if we're currently waiting for an event then send saved events to
246 // the X server now. if we're not waiting then some other thread
247 // might be using the display connection so we can't safely use it
248 // too.
249 if (m_waiting) {
250 flush();
251 // Send a character through the round-trip pipe to wake a thread
252 // that is waiting for a ConnectionNumber() socket to be readable.
253 // The flush call can read incoming data from the socket and put
254 // it in Xlib's input buffer. That sneaks it past the other thread.
255 ssize_t write_response = write(m_pipefd[1], "!", 1);
256
257 // with linux automake, warnings are treated as errors by default
258 if (write_response < 0)
259 {
260 // todo: handle read response
261 }
262 }
263
264 return true;
265 }
266
267 bool
isEmpty() const268 XWindowsEventQueueBuffer::isEmpty() const
269 {
270 Lock lock(&m_mutex);
271 return (XPending(m_display) == 0 );
272 }
273
274 EventQueueTimer*
newTimer(double,bool) const275 XWindowsEventQueueBuffer::newTimer(double, bool) const
276 {
277 return new EventQueueTimer;
278 }
279
280 void
deleteTimer(EventQueueTimer * timer) const281 XWindowsEventQueueBuffer::deleteTimer(EventQueueTimer* timer) const
282 {
283 delete timer;
284 }
285
286 void
flush()287 XWindowsEventQueueBuffer::flush()
288 {
289 // note -- m_mutex must be locked on entry
290
291 // flush the posted event list to the X server
292 for (size_t i = 0; i < m_postedEvents.size(); ++i) {
293 XSendEvent(m_display, m_window, False, 0, &m_postedEvents[i]);
294 }
295 XFlush(m_display);
296 m_postedEvents.clear();
297 }
298