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