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 namespace KWayland
22 {
23 namespace Client
24 {
25 class Q_DECL_HIDDEN ConnectionThread::Private
26 {
27 public:
28  Private(ConnectionThread *q);
29  ~Private();
30  void doInitConnection();
31  void setupSocketNotifier();
32  void setupSocketFileWatcher();
33 
34  wl_display *display = nullptr;
35  int fd = -1;
36  QString socketName;
37  QDir runtimeDir;
38  QScopedPointer<QSocketNotifier> socketNotifier;
40  bool serverDied = false;
41  bool foreign = false;
42  QMetaObject::Connection eventDispatcherConnection;
43  int error = 0;
44  static QVector<ConnectionThread *> connections;
45  static QRecursiveMutex mutex;
46 
47 private:
48  ConnectionThread *q;
49 };
50 
51 QVector<ConnectionThread *> ConnectionThread::Private::connections = QVector<ConnectionThread *>{};
52 QRecursiveMutex ConnectionThread::Private::mutex;
53 
54 ConnectionThread::Private::Private(ConnectionThread *q)
55  : socketName(QString::fromUtf8(qgetenv("WAYLAND_DISPLAY")))
56  , runtimeDir(QString::fromUtf8(qgetenv("XDG_RUNTIME_DIR")))
57  , q(q)
58 {
59  if (socketName.isEmpty()) {
60  socketName = QStringLiteral("wayland-0");
61  }
62  {
63  QMutexLocker lock(&mutex);
64  connections << q;
65  }
66 }
67 
68 ConnectionThread::Private::~Private()
69 {
70  {
71  QMutexLocker lock(&mutex);
72  connections.removeOne(q);
73  }
74  if (display && !foreign) {
75  wl_display_flush(display);
76  wl_display_disconnect(display);
77  }
78 }
79 
80 void ConnectionThread::Private::doInitConnection()
81 {
82  if (fd != -1) {
83  display = wl_display_connect_to_fd(fd);
84  } else {
85  display = wl_display_connect(socketName.toUtf8().constData());
86  }
87  if (!display) {
88  qCWarning(KWAYLAND_CLIENT) << "Failed connecting to Wayland display";
89  Q_EMIT q->failed();
90  return;
91  }
92  if (fd != -1) {
93  qCDebug(KWAYLAND_CLIENT) << "Connected to Wayland server over file descriptor:" << fd;
94  } else {
95  qCDebug(KWAYLAND_CLIENT) << "Connected to Wayland server at:" << socketName;
96  }
97 
98  // setup socket notifier
99  setupSocketNotifier();
100  setupSocketFileWatcher();
101  Q_EMIT q->connected();
102 }
103 
104 void ConnectionThread::Private::setupSocketNotifier()
105 {
106  const int fd = wl_display_get_fd(display);
107  socketNotifier.reset(new QSocketNotifier(fd, QSocketNotifier::Read));
108  QObject::connect(socketNotifier.data(), &QSocketNotifier::activated, q, [this]() {
109  if (!display) {
110  return;
111  }
112  if (wl_display_dispatch(display) == -1) {
113  error = wl_display_get_error(display);
114  if (error != 0) {
115  if (display) {
116  free(display);
117  display = nullptr;
118  }
119  Q_EMIT q->errorOccurred();
120  return;
121  }
122  }
123  Q_EMIT q->eventsRead();
124  });
125 }
126 
127 void ConnectionThread::Private::setupSocketFileWatcher()
128 {
129  if (!runtimeDir.exists() || fd != -1) {
130  return;
131  }
132  socketWatcher.reset(new QFileSystemWatcher);
133  socketWatcher->addPath(runtimeDir.absoluteFilePath(socketName));
134  QObject::connect(socketWatcher.data(), &QFileSystemWatcher::fileChanged, q, [this](const QString &file) {
135  if (QFile::exists(file) || serverDied) {
136  return;
137  }
138  qCWarning(KWAYLAND_CLIENT) << "Connection to server went away";
139  serverDied = true;
140  if (display) {
141  free(display);
142  display = nullptr;
143  }
144  socketNotifier.reset();
145 
146  // need a new filesystem watcher
147  socketWatcher.reset(new QFileSystemWatcher);
148  socketWatcher->addPath(runtimeDir.absolutePath());
149  QObject::connect(socketWatcher.data(), &QFileSystemWatcher::directoryChanged, q, [this]() {
150  if (!serverDied) {
151  return;
152  }
153  if (runtimeDir.exists(socketName)) {
154  qCDebug(KWAYLAND_CLIENT) << "Socket reappeared";
155  socketWatcher.reset();
156  serverDied = false;
157  error = 0;
158  q->initConnection();
159  }
160  });
161  Q_EMIT q->connectionDied();
162  });
163 }
164 
165 ConnectionThread::ConnectionThread(QObject *parent)
166  : QObject(parent)
167  , d(new Private(this))
168 {
169  d->eventDispatcherConnection = connect(
172  this,
173  [this] {
174  if (d->display) {
175  wl_display_flush(d->display);
176  }
177  },
179 }
180 
181 ConnectionThread::ConnectionThread(wl_display *display, QObject *parent)
182  : QObject(parent)
183  , d(new Private(this))
184 {
185  d->display = display;
186  d->foreign = true;
187 }
188 
189 ConnectionThread::~ConnectionThread()
190 {
191  disconnect(d->eventDispatcherConnection);
192 }
193 
194 ConnectionThread *ConnectionThread::fromApplication(QObject *parent)
195 {
196  QPlatformNativeInterface *native = qApp->platformNativeInterface();
197  if (!native) {
198  return nullptr;
199  }
200  wl_display *display = reinterpret_cast<wl_display *>(native->nativeResourceForIntegration(QByteArrayLiteral("wl_display")));
201  if (!display) {
202  return nullptr;
203  }
204  ConnectionThread *ct = new ConnectionThread(display, parent);
205  connect(native, &QObject::destroyed, ct, &ConnectionThread::connectionDied);
206  return ct;
207 }
208 
209 void ConnectionThread::initConnection()
210 {
211  QMetaObject::invokeMethod(this, "doInitConnection", Qt::QueuedConnection);
212 }
213 
214 void ConnectionThread::doInitConnection()
215 {
216  d->doInitConnection();
217 }
218 
219 void ConnectionThread::setSocketName(const QString &socketName)
220 {
221  if (d->display) {
222  // already initialized
223  return;
224  }
225  d->socketName = socketName;
226 }
227 
228 void ConnectionThread::setSocketFd(int fd)
229 {
230  if (d->display) {
231  // already initialized
232  return;
233  }
234  d->fd = fd;
235 }
236 
237 wl_display *ConnectionThread::display()
238 {
239  return d->display;
240 }
241 
242 QString ConnectionThread::socketName() const
243 {
244  return d->socketName;
245 }
246 
247 void ConnectionThread::flush()
248 {
249  if (!d->display) {
250  return;
251  }
252  wl_display_flush(d->display);
253 }
254 
255 void ConnectionThread::roundtrip()
256 {
257  if (!d->display) {
258  return;
259  }
260  if (d->foreign) {
261  // try to perform roundtrip through the QPA plugin if it's supported
262  if (QPlatformNativeInterface *native = qApp->platformNativeInterface()) {
263  // in case the platform provides a dedicated roundtrip function use that install of wl_display_roundtrip
264  QFunctionPointer roundtripFunction = native->platformFunction(QByteArrayLiteral("roundtrip"));
265  if (roundtripFunction) {
266  roundtripFunction();
267  return;
268  }
269  }
270  }
271  wl_display_roundtrip(d->display);
272 }
273 
274 bool ConnectionThread::hasError() const
275 {
276  return d->error != 0;
277 }
278 
279 int ConnectionThread::errorCode() const
280 {
281  return d->error;
282 }
283 
284 QVector<ConnectionThread *> ConnectionThread::connections()
285 {
286  return Private::connections;
287 }
288 
289 }
290 }
void activated(QSocketDescriptor socket, QSocketNotifier::Type type)
bool exists() const const
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
Creates and manages the connection to a Wayland server.
void error(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
void fileChanged(const QString &path)
void directoryChanged(const QString &path)
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)
QAbstractEventDispatcher * eventDispatcher()
Display * display()
Definition: global.cpp:78
DirectConnection
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
void destroyed(QObject *obj)
Q_EMITQ_EMIT
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Oct 16 2021 22:52:00 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.