KWayland

connection_thread.cpp
1 /*
2  SPDX-FileCopyrightText: 2014 Martin Gräßlin <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6 #include "connection_thread.h"
7 #include "logging.h"
8 // Qt
9 #include <QAbstractEventDispatcher>
10 #include <QDebug>
11 #include <QDir>
12 #include <QFileSystemWatcher>
13 #include <QGuiApplication>
14 #include <QMutex>
15 #include <QMutexLocker>
16 #include <QSocketNotifier>
17 #include <qpa/qplatformnativeinterface.h>
18 // Wayland
19 #include <wayland-client-protocol.h>
20 
21 #include <poll.h>
22 
23 namespace KWayland
24 {
25 namespace Client
26 {
27 class Q_DECL_HIDDEN ConnectionThread::Private
28 {
29 public:
30  Private(ConnectionThread *q);
31  ~Private();
32  void doInitConnection();
33  void setupSocketNotifier();
34  void setupSocketFileWatcher();
35  void dispatchEvents();
36 
37  wl_display *display = nullptr;
38  int fd = -1;
39  QString socketName;
40  QDir runtimeDir;
41  QScopedPointer<QSocketNotifier> socketNotifier;
43  bool serverDied = false;
44  bool foreign = false;
45  QMetaObject::Connection eventDispatcherConnection;
46  int error = 0;
47  static QVector<ConnectionThread *> connections;
48  static QRecursiveMutex mutex;
49 
50 private:
51  ConnectionThread *q;
52 };
53 
54 QVector<ConnectionThread *> ConnectionThread::Private::connections = QVector<ConnectionThread *>{};
55 QRecursiveMutex ConnectionThread::Private::mutex;
56 
57 ConnectionThread::Private::Private(ConnectionThread *q)
58  : socketName(QString::fromUtf8(qgetenv("WAYLAND_DISPLAY")))
59  , runtimeDir(QString::fromUtf8(qgetenv("XDG_RUNTIME_DIR")))
60  , q(q)
61 {
62  if (socketName.isEmpty()) {
63  socketName = QStringLiteral("wayland-0");
64  }
65  {
66  QMutexLocker lock(&mutex);
67  connections << q;
68  }
69 }
70 
71 ConnectionThread::Private::~Private()
72 {
73  {
74  QMutexLocker lock(&mutex);
75  connections.removeOne(q);
76  }
77  if (display && !foreign) {
78  wl_display_flush(display);
79  wl_display_disconnect(display);
80  }
81 }
82 
83 void ConnectionThread::Private::doInitConnection()
84 {
85  if (fd != -1) {
86  display = wl_display_connect_to_fd(fd);
87  } else {
88  display = wl_display_connect(socketName.toUtf8().constData());
89  }
90  if (!display) {
91  qCWarning(KWAYLAND_CLIENT) << "Failed connecting to Wayland display";
92  Q_EMIT q->failed();
93  return;
94  }
95  if (fd != -1) {
96  qCDebug(KWAYLAND_CLIENT) << "Connected to Wayland server over file descriptor:" << fd;
97  } else {
98  qCDebug(KWAYLAND_CLIENT) << "Connected to Wayland server at:" << socketName;
99  }
100 
101  // setup socket notifier
102  setupSocketNotifier();
103  setupSocketFileWatcher();
104  Q_EMIT q->connected();
105 }
106 
107 void ConnectionThread::Private::setupSocketNotifier()
108 {
109  const int fd = wl_display_get_fd(display);
110  socketNotifier.reset(new QSocketNotifier(fd, QSocketNotifier::Read));
111  QObject::connect(socketNotifier.data(), &QSocketNotifier::activated, q, [this]() {
112  dispatchEvents();
113  });
114 }
115 
116 void ConnectionThread::Private::dispatchEvents()
117 {
118  if (!display) {
119  return;
120  }
121  // first dispatch any pending events on the default queue
122  while (wl_display_prepare_read(display) != 0) {
123  wl_display_dispatch_pending(display);
124  }
125  wl_display_flush(display);
126  // then check if there are any new events waiting to be read
127  struct pollfd pfd;
128  pfd.fd = wl_display_get_fd(display);
129  pfd.events = POLLIN;
130  int ret = poll(&pfd, 1, 0);
131  if (ret > 0) {
132  // if yes, read them now
133  wl_display_read_events(display);
134  } else {
135  wl_display_cancel_read(display);
136  }
137 
138  // finally, dispatch the default queue and all frame queues
139  if (wl_display_dispatch_pending(display) == -1) {
140  error = wl_display_get_error(display);
141  if (error != 0) {
142  if (display) {
143  free(display);
144  display = nullptr;
145  }
146  Q_EMIT q->errorOccurred();
147  return;
148  }
149  }
150  Q_EMIT q->eventsRead();
151 }
152 
153 void ConnectionThread::Private::setupSocketFileWatcher()
154 {
155  if (!runtimeDir.exists() || fd != -1) {
156  return;
157  }
158  socketWatcher.reset(new QFileSystemWatcher);
159  socketWatcher->addPath(runtimeDir.absoluteFilePath(socketName));
160  QObject::connect(socketWatcher.data(), &QFileSystemWatcher::fileChanged, q, [this](const QString &file) {
161  if (QFile::exists(file) || serverDied) {
162  return;
163  }
164  qCWarning(KWAYLAND_CLIENT) << "Connection to server went away";
165  serverDied = true;
166  if (display) {
167  free(display);
168  display = nullptr;
169  }
170  socketNotifier.reset();
171 
172  // need a new filesystem watcher
173  socketWatcher.reset(new QFileSystemWatcher);
174  socketWatcher->addPath(runtimeDir.absolutePath());
175  QObject::connect(socketWatcher.data(), &QFileSystemWatcher::directoryChanged, q, [this]() {
176  if (!serverDied) {
177  return;
178  }
179  if (runtimeDir.exists(socketName)) {
180  qCDebug(KWAYLAND_CLIENT) << "Socket reappeared";
181  socketWatcher.reset();
182  serverDied = false;
183  error = 0;
184  q->initConnection();
185  }
186  });
187  Q_EMIT q->connectionDied();
188  });
189 }
190 
191 ConnectionThread::ConnectionThread(QObject *parent)
192  : QObject(parent)
193  , d(new Private(this))
194 {
195  d->eventDispatcherConnection = connect(
198  this,
199  [this] {
200  if (d->display) {
201  wl_display_flush(d->display);
202  }
203  },
205 }
206 
207 ConnectionThread::ConnectionThread(wl_display *display, QObject *parent)
208  : QObject(parent)
209  , d(new Private(this))
210 {
211  d->display = display;
212  d->foreign = true;
213 }
214 
215 ConnectionThread::~ConnectionThread()
216 {
217  disconnect(d->eventDispatcherConnection);
218 }
219 
220 ConnectionThread *ConnectionThread::fromApplication(QObject *parent)
221 {
222  QPlatformNativeInterface *native = qApp->platformNativeInterface();
223  if (!native) {
224  return nullptr;
225  }
226  wl_display *display = reinterpret_cast<wl_display *>(native->nativeResourceForIntegration(QByteArrayLiteral("wl_display")));
227  if (!display) {
228  return nullptr;
229  }
230  ConnectionThread *ct = new ConnectionThread(display, parent);
231  connect(native, &QObject::destroyed, ct, &ConnectionThread::connectionDied);
232  return ct;
233 }
234 
235 void ConnectionThread::initConnection()
236 {
237  QMetaObject::invokeMethod(this, &ConnectionThread::doInitConnection, Qt::QueuedConnection);
238 }
239 
240 void ConnectionThread::doInitConnection()
241 {
242  d->doInitConnection();
243 }
244 
245 void ConnectionThread::setSocketName(const QString &socketName)
246 {
247  if (d->display) {
248  // already initialized
249  return;
250  }
251  d->socketName = socketName;
252 }
253 
254 void ConnectionThread::setSocketFd(int fd)
255 {
256  if (d->display) {
257  // already initialized
258  return;
259  }
260  d->fd = fd;
261 }
262 
263 wl_display *ConnectionThread::display()
264 {
265  return d->display;
266 }
267 
268 QString ConnectionThread::socketName() const
269 {
270  return d->socketName;
271 }
272 
273 void ConnectionThread::flush()
274 {
275  if (!d->display) {
276  return;
277  }
278  wl_display_flush(d->display);
279 }
280 
281 void ConnectionThread::roundtrip()
282 {
283  if (!d->display) {
284  return;
285  }
286  if (d->foreign) {
287  // try to perform roundtrip through the QPA plugin if it's supported
288  if (QPlatformNativeInterface *native = qApp->platformNativeInterface()) {
289  // in case the platform provides a dedicated roundtrip function use that install of wl_display_roundtrip
290  QFunctionPointer roundtripFunction = native->platformFunction(QByteArrayLiteral("roundtrip"));
291  if (roundtripFunction) {
292  roundtripFunction();
293  return;
294  }
295  }
296  }
297  wl_display_roundtrip(d->display);
298 }
299 
300 bool ConnectionThread::hasError() const
301 {
302  return d->error != 0;
303 }
304 
305 int ConnectionThread::errorCode() const
306 {
307  return d->error;
308 }
309 
310 QVector<ConnectionThread *> ConnectionThread::connections()
311 {
312  return Private::connections;
313 }
314 
315 }
316 }
void fileChanged(const QString &path)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void destroyed(QObject *obj)
Creates and manages the connection to a Wayland server.
DirectConnection
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
Display * display()
Definition: global.cpp:78
QAbstractEventDispatcher * eventDispatcher()
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
void activated(QSocketDescriptor socket, QSocketNotifier::Type type)
void directoryChanged(const QString &path)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Jan 30 2023 03:56:23 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.