aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/platform/XWindowsEventQueueBuffer.cpp
blob: 397973f3d7178e75e9c83325b6cda39801a3215e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
/*
 * barrier -- mouse and keyboard sharing utility
 * Copyright (C) 2012-2016 Symless Ltd.
 * Copyright (C) 2004 Chris Schoeneman
 *
 * This package is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * found in the file LICENSE that should have accompanied this file.
 *
 * This package is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "platform/XWindowsEventQueueBuffer.h"

#include "mt/Lock.h"
#include "mt/Thread.h"
#include "base/Event.h"
#include "base/IEventQueue.h"

#include <fcntl.h>
#if HAVE_UNISTD_H
#    include <unistd.h>
#endif
#if HAVE_POLL
#    include <poll.h>
#else
#    if HAVE_SYS_SELECT_H
#        include <sys/select.h>
#    endif
#    if HAVE_SYS_TIME_H
#        include <sys/time.h>
#    endif
#    if HAVE_SYS_TYPES_H
#        include <sys/types.h>
#    endif
#endif

//
// EventQueueTimer
//

class EventQueueTimer { };


//
// XWindowsEventQueueBuffer
//

XWindowsEventQueueBuffer::XWindowsEventQueueBuffer(IXWindowsImpl* impl,
        Display* display, Window window, IEventQueue* events) :
    m_events(events),
    m_display(display),
    m_window(window),
    m_waiting(false)
{
    m_impl = impl;
    assert(m_display != NULL);
    assert(m_window  != None);

    m_userEvent = m_impl->XInternAtom(m_display, "BARRIER_USER_EVENT", False);
    // set up for pipe hack
    int result = pipe(m_pipefd);
    assert(result == 0);

    int pipeflags;
    pipeflags = fcntl(m_pipefd[0], F_GETFL);
    fcntl(m_pipefd[0], F_SETFL, pipeflags | O_NONBLOCK);
    pipeflags = fcntl(m_pipefd[1], F_GETFL);
    fcntl(m_pipefd[1], F_SETFL, pipeflags | O_NONBLOCK);
}

XWindowsEventQueueBuffer::~XWindowsEventQueueBuffer()
{
    // release pipe hack resources
    close(m_pipefd[0]);
    close(m_pipefd[1]);
}

int XWindowsEventQueueBuffer::getPendingCountLocked()
{
    Lock lock(&m_mutex);
    // work around a bug in old libx11 which causes the first XPending not to read events under
    // certain conditions. The issue happens when libx11 has not yet received replies for all
    // flushed events. In that case, internally XPending will not try to process received events
    // as the reply for the last event was not found. As a result, XPending will return the number
    // of pending events without regard to the events it has just read.
    // https://gitlab.freedesktop.org/xorg/lib/libx11/-/merge_requests/1 fixes this on libx11 side.
    m_impl->XPending(m_display);
    return m_impl->XPending(m_display);
}

void
XWindowsEventQueueBuffer::waitForEvent(double dtimeout)
{
    Thread::testCancel();

    // clear out the pipe in preparation for waiting.

    char buf[16];
    ssize_t read_response = read(m_pipefd[0], buf, 15);

    // with linux automake, warnings are treated as errors by default
    if (read_response < 0)
    {
        // todo: handle read response
    }

    {
        Lock lock(&m_mutex);
        // we're now waiting for events
        m_waiting = true;

        // push out pending events
        flush();
    }
    // calling flush may have queued up a new event.
    if (!XWindowsEventQueueBuffer::isEmpty()) {
        Thread::testCancel();
        return;
    }

    // use poll() to wait for a message from the X server or for timeout.
    // this is a good deal more efficient than polling and sleeping.
#if HAVE_POLL
    struct pollfd pfds[2];
    pfds[0].fd     = ConnectionNumber(m_display);
    pfds[0].events = POLLIN;
    pfds[1].fd     = m_pipefd[0];
    pfds[1].events = POLLIN;
    int timeout    = (dtimeout < 0.0) ? -1 :
                        static_cast<int>(1000.0 * dtimeout);
    int remaining  =  timeout;
    int retval     =  0;
#else
    struct timeval timeout;
    struct timeval* timeoutPtr;
    if (dtimeout < 0.0) {
        timeoutPtr = NULL;
    }
    else {
        timeout.tv_sec  = static_cast<int>(dtimeout);
        timeout.tv_usec = static_cast<int>(1.0e+6 *
                                (dtimeout - timeout.tv_sec));
        timeoutPtr      = &timeout;
    }

    // initialize file descriptor sets
    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET(ConnectionNumber(m_display), &rfds);
    FD_SET(m_pipefd[0], &rfds);
     int nfds;
     if (ConnectionNumber(m_display) > m_pipefd[0]) {
         nfds = ConnectionNumber(m_display) + 1;
     }
     else {
         nfds = m_pipefd[0] + 1;
     }
#endif
    // It's possible that the X server has queued events locally
    // in xlib's event buffer and not pushed on to the fd. Hence we
    // can't simply monitor the fd as we may never be woken up.
    // ie addEvent calls flush, XFlush may not send via the fd hence
    // there is an event waiting to be sent but we must exit the poll
    // before it can.
    // Instead we poll for a brief period of time (so if events
    // queued locally in the xlib buffer can be processed)
    // and continue doing this until timeout is reached.
    // The human eye can notice 60hz (ansi) which is 16ms, however
    // we want to give the cpu a chance s owe up this to 25ms
#define TIMEOUT_DELAY 25

    while (((dtimeout < 0.0) || (remaining > 0)) && getPendingCountLocked() == 0 && retval == 0) {
#if HAVE_POLL
    retval = poll(pfds, 2, TIMEOUT_DELAY); //16ms = 60hz, but we make it > to play nicely with the cpu
     if (pfds[1].revents & POLLIN) {
         ssize_t read_response = read(m_pipefd[0], buf, 15);

        // with linux automake, warnings are treated as errors by default
        if (read_response < 0)
        {
            // todo: handle read response
        }

     }
#else
    retval = select(nfds,
                        SELECT_TYPE_ARG234 &rfds,
                        SELECT_TYPE_ARG234 NULL,
                        SELECT_TYPE_ARG234 NULL,
                        SELECT_TYPE_ARG5   TIMEOUT_DELAY);
    if (FD_SET(m_pipefd[0], &rfds)) {
        read(m_pipefd[0], buf, 15);
    }
#endif
        remaining-=TIMEOUT_DELAY;
    }

    {
        // we're no longer waiting for events
        Lock lock(&m_mutex);
        m_waiting = false;
    }

    Thread::testCancel();
}

IEventQueueBuffer::Type
XWindowsEventQueueBuffer::getEvent(Event& event, UInt32& dataID)
{
    Lock lock(&m_mutex);

    // push out pending events
    flush();

    // get next event
    m_impl->XNextEvent(m_display, &m_event);

    // process event
    if (m_event.xany.type == ClientMessage &&
        m_event.xclient.message_type == m_userEvent) {
        dataID = static_cast<UInt32>(m_event.xclient.data.l[0]);
        return kUser;
    }
    else {
        event = Event(Event::kSystem,
                            m_events->getSystemTarget(), &m_event);
        return kSystem;
    }
}

bool
XWindowsEventQueueBuffer::addEvent(UInt32 dataID)
{
    // prepare a message
    XEvent xevent;
    xevent.xclient.type         = ClientMessage;
    xevent.xclient.window       = m_window;
    xevent.xclient.message_type = m_userEvent;
    xevent.xclient.format       = 32;
    xevent.xclient.data.l[0]    = static_cast<long>(dataID);

    // save the message
    Lock lock(&m_mutex);
    m_postedEvents.push_back(xevent);

    // if we're currently waiting for an event then send saved events to
    // the X server now.  if we're not waiting then some other thread
    // might be using the display connection so we can't safely use it
    // too.
    if (m_waiting) {
        flush();
        // Send a character through the round-trip pipe to wake a thread
        // that is waiting for a ConnectionNumber() socket to be readable.
        // The flush call can read incoming data from the socket and put
        // it in Xlib's input buffer.  That sneaks it past the other thread.
        ssize_t write_response = write(m_pipefd[1], "!", 1);

        // with linux automake, warnings are treated as errors by default
        if (write_response < 0)
        {
            // todo: handle read response
        }
    }

    return true;
}

bool
XWindowsEventQueueBuffer::isEmpty() const
{
    Lock lock(&m_mutex);
    return (m_impl->XPending(m_display) == 0 );
}

EventQueueTimer*
XWindowsEventQueueBuffer::newTimer(double, bool) const
{
    return new EventQueueTimer;
}

void
XWindowsEventQueueBuffer::deleteTimer(EventQueueTimer* timer) const
{
    delete timer;
}

void
XWindowsEventQueueBuffer::flush()
{
    // note -- m_mutex must be locked on entry

    // flush the posted event list to the X server
    for (size_t i = 0; i < m_postedEvents.size(); ++i) {
        m_impl->XSendEvent(m_display, m_window, False, 0, &m_postedEvents[i]);
    }
    m_impl->XFlush(m_display);
    m_postedEvents.clear();
}