summaryrefslogtreecommitdiffstats
path: root/xbmc/windowing/wayland/WinEventsWayland.cpp
blob: 4345cf00d9fd0056956e7ed77e00e7c78b53fbcc (plain)
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
/*
 *  Copyright (C) 2017-2018 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#include "WinEventsWayland.h"

#include "ServiceBroker.h"
#include "application/AppInboundProtocol.h"
#include "threads/CriticalSection.h"
#include "threads/Thread.h"
#include "utils/log.h"

#include "platform/posix/utils/FileHandle.h"

#include <exception>
#include <memory>
#include <mutex>
#include <system_error>

#include <sys/poll.h>
#include <unistd.h>
#include <wayland-client.hpp>

using namespace KODI::UTILS::POSIX;
using namespace KODI::WINDOWING::WAYLAND;

namespace
{
/**
 * Thread for processing Wayland events
 *
 * While not strictly needed, reading from the Wayland display file descriptor
 * and dispatching the resulting events is done in an extra thread here.
 * Sometime in the future, MessagePump() might be gone and then the
 * transition will be easier since this extra thread is already here.
 */
class CWinEventsWaylandThread : CThread
{
  wayland::display_t& m_display;
  // Pipe used for cancelling poll() on shutdown
  CFileHandle m_pipeRead;
  CFileHandle m_pipeWrite;

  CCriticalSection m_roundtripQueueMutex;
  std::atomic<wayland::event_queue_t*> m_roundtripQueue{nullptr};
  CEvent m_roundtripQueueEvent;

public:
  CWinEventsWaylandThread(wayland::display_t& display)
  : CThread("Wayland message pump"), m_display{display}
  {
    std::array<int, 2> fds;
    if (pipe(fds.data()) < 0)
    {
      throw std::system_error(errno, std::generic_category(), "Error creating pipe for Wayland message pump cancellation");
    }
    m_pipeRead.attach(fds[0]);
    m_pipeWrite.attach(fds[1]);
    Create();
  }

  ~CWinEventsWaylandThread() override
  {
    Stop();
    // Wait for roundtrip invocation to finish
    std::unique_lock<CCriticalSection> lock(m_roundtripQueueMutex);
  }

  void Stop()
  {
    CLog::Log(LOGDEBUG, "Stopping Wayland message pump");
    // Set m_bStop
    StopThread(false);
    InterruptPoll();
    // Now wait for actual exit
    StopThread(true);
  }

  void RoundtripQueue(wayland::event_queue_t const& queue)
  {
    wayland::event_queue_t queueCopy{queue};

    // Serialize invocations of this function - it's used very rarely and usually
    // not in parallel anyway, and doing it avoids lots of complications
    std::unique_lock<CCriticalSection> lock(m_roundtripQueueMutex);

    m_roundtripQueueEvent.Reset();
    // We can just set the value here since there is no other writer in parallel
    m_roundtripQueue.store(&queueCopy);
    // Dispatching can happen now

    // Make sure we don't wait for an event to happen on the socket
    InterruptPoll();

    if (m_bStop)
      return;

    m_roundtripQueueEvent.Wait();
  }

  wayland::display_t& GetDisplay()
  {
    return m_display;
  }

private:
  void InterruptPoll()
  {
    char c = 0;
    if (write(m_pipeWrite, &c, 1) != 1)
      throw std::runtime_error("Failed to write to wayland message pipe");
  }

  void Process() override
  {
    try
    {
      std::array<pollfd, 2> pollFds;
      pollfd& waylandPoll = pollFds[0];
      pollfd& cancelPoll = pollFds[1];
      // Wayland filedescriptor
      waylandPoll.fd = m_display.get_fd();
      waylandPoll.events = POLLIN;
      waylandPoll.revents = 0;
      // Read end of the cancellation pipe
      cancelPoll.fd = m_pipeRead;
      cancelPoll.events = POLLIN;
      cancelPoll.revents = 0;

      CLog::Log(LOGDEBUG, "Starting Wayland message pump");

      // Run until cancelled or error
      while (!m_bStop)
      {
        // dispatch() provides no way to cancel a blocked read from the socket
        // wl_display_disconnect would just close the socket, leading to problems
        // with the poll() that dispatch() uses internally - so we have to implement
        // cancellation ourselves here

        // Acquire global read intent
        wayland::read_intent readIntent = m_display.obtain_read_intent();
        m_display.flush();

        if (poll(pollFds.data(), pollFds.size(), -1) < 0)
        {
          if (errno == EINTR)
          {
            continue;
          }
          else
          {
            throw std::system_error(errno, std::generic_category(), "Error polling on Wayland socket");
          }
        }

        if (cancelPoll.revents & POLLERR || cancelPoll.revents & POLLHUP || cancelPoll.revents & POLLNVAL)
        {
          throw std::runtime_error("poll() signalled error condition on poll interruption socket");
        }

        if (waylandPoll.revents & POLLERR || waylandPoll.revents & POLLHUP || waylandPoll.revents & POLLNVAL)
        {
          throw std::runtime_error("poll() signalled error condition on Wayland socket");
        }

        // Read events and release intent; this does not block
        readIntent.read();
        // Dispatch default event queue
        m_display.dispatch_pending();

        if (auto* roundtripQueue = m_roundtripQueue.exchange(nullptr))
        {
          m_display.roundtrip_queue(*roundtripQueue);
          m_roundtripQueueEvent.Set();
        }
        if (cancelPoll.revents & POLLIN)
        {
          // Read away the char so we don't get another notification
          // Indepentent from m_roundtripQueue so there are no races
          char c;
          if (read(m_pipeRead, &c, 1) != 1)
            throw std::runtime_error("Error reading from wayland message pipe");
        }
      }

      CLog::Log(LOGDEBUG, "Wayland message pump stopped");
    }
    catch (std::exception const& e)
    {
      // FIXME CThread::OnException is very badly named and should probably go away
      // FIXME Thread exception handling is seriously broken:
      // Exceptions will be swallowed and do not terminate the program.
      // Even XbmcCommons::UncheckedException which claims to be there for just this
      // purpose does not cause termination, the log message will just be slightly different.

      // But here, going on would be meaningless, so do a hard exit
      CLog::Log(LOGFATAL, "Exception in Wayland message pump, exiting: {}", e.what());
      std::terminate();
    }

    // Wake up if someone is still waiting for roundtrip, won't happen anytime soon...
    m_roundtripQueueEvent.Set();
  }
};

std::unique_ptr<CWinEventsWaylandThread> g_WlMessagePump{nullptr};

}

void CWinEventsWayland::SetDisplay(wayland::display_t* display)
{
  if (display && !g_WlMessagePump)
  {
    // Start message processing as soon as we have a display
    g_WlMessagePump.reset(new CWinEventsWaylandThread(*display));
  }
  else if (g_WlMessagePump)
  {
    // Stop if display is set to nullptr
    g_WlMessagePump.reset();
  }
}

void CWinEventsWayland::Flush()
{
  if (g_WlMessagePump)
  {
    g_WlMessagePump->GetDisplay().flush();
  }
}

void CWinEventsWayland::RoundtripQueue(const wayland::event_queue_t& queue)
{
  if (g_WlMessagePump)
  {
    g_WlMessagePump->RoundtripQueue(queue);
  }
}

bool CWinEventsWayland::MessagePump()
{
  std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
  // Forward any events that may have been pushed to our queue
  while (true)
  {
    XBMC_Event event;
    {
      // Scoped lock for reentrancy
      std::unique_lock<CCriticalSection> lock(m_queueMutex);

      if (m_queue.empty())
      {
        break;
      }

      // First get event and remove it from the queue, then pass it on - be aware that this
      // function must be reentrant
      event = m_queue.front();
      m_queue.pop();
    }

    if (appPort)
      appPort->OnEvent(event);
  }

  return true;
}

void CWinEventsWayland::MessagePush(XBMC_Event* ev)
{
  std::unique_lock<CCriticalSection> lock(m_queueMutex);
  m_queue.emplace(*ev);
}