Changeset View
Changeset View
Standalone View
Standalone View
autotests/integration/kwin_wayland_test.cpp
Show All 18 Lines | |||||
19 | *********************************************************************/ | 19 | *********************************************************************/ | ||
20 | #include "kwin_wayland_test.h" | 20 | #include "kwin_wayland_test.h" | ||
21 | #include "../../platform.h" | 21 | #include "../../platform.h" | ||
22 | #include "../../composite.h" | 22 | #include "../../composite.h" | ||
23 | #include "../../effects.h" | 23 | #include "../../effects.h" | ||
24 | #include "../../wayland_server.h" | 24 | #include "../../wayland_server.h" | ||
25 | #include "../../workspace.h" | 25 | #include "../../workspace.h" | ||
26 | #include "../../xcbutils.h" | 26 | #include "../../xcbutils.h" | ||
27 | #include "../../xwl/xwayland.h" | ||||
27 | 28 | | |||
28 | #include <KPluginMetaData> | 29 | #include <KPluginMetaData> | ||
29 | 30 | | |||
30 | #include <QAbstractEventDispatcher> | 31 | #include <QAbstractEventDispatcher> | ||
31 | #include <QPluginLoader> | 32 | #include <QPluginLoader> | ||
32 | #include <QSocketNotifier> | 33 | #include <QSocketNotifier> | ||
33 | #include <QStyle> | 34 | #include <QStyle> | ||
34 | #include <QThread> | 35 | #include <QThread> | ||
35 | #include <QtConcurrentRun> | 36 | #include <QtConcurrentRun> | ||
36 | 37 | | |||
37 | // system | 38 | // system | ||
38 | #include <unistd.h> | 39 | #include <unistd.h> | ||
39 | #include <sys/socket.h> | 40 | #include <sys/socket.h> | ||
40 | #include <iostream> | 41 | #include <iostream> | ||
41 | 42 | | |||
42 | namespace KWin | 43 | namespace KWin | ||
43 | { | 44 | { | ||
44 | 45 | | |||
45 | static void readDisplay(int pipe); | | |||
46 | | ||||
47 | WaylandTestApplication::WaylandTestApplication(OperationMode mode, int &argc, char **argv) | 46 | WaylandTestApplication::WaylandTestApplication(OperationMode mode, int &argc, char **argv) | ||
48 | : Application(mode, argc, argv) | 47 | : ApplicationWaylandAbstract(mode, argc, argv) | ||
49 | { | 48 | { | ||
50 | QStandardPaths::setTestModeEnabled(true); | 49 | QStandardPaths::setTestModeEnabled(true); | ||
51 | QIcon::setThemeName(QStringLiteral("breeze")); | 50 | QIcon::setThemeName(QStringLiteral("breeze")); | ||
52 | #ifdef KWIN_BUILD_ACTIVITIES | 51 | #ifdef KWIN_BUILD_ACTIVITIES | ||
53 | setUseKActivities(false); | 52 | setUseKActivities(false); | ||
54 | #endif | 53 | #endif | ||
55 | qputenv("KWIN_COMPOSE", QByteArrayLiteral("Q")); | 54 | qputenv("KWIN_COMPOSE", QByteArrayLiteral("Q")); | ||
56 | initPlatform(KPluginMetaData(QStringLiteral("KWinWaylandVirtualBackend.so"))); | 55 | initPlatform(KPluginMetaData(QStringLiteral("KWinWaylandVirtualBackend.so"))); | ||
57 | WaylandServer::create(this); | 56 | WaylandServer::create(this); | ||
57 | setProcessStartupEnvironment(QProcessEnvironment::systemEnvironment()); | ||||
58 | } | 58 | } | ||
59 | 59 | | |||
60 | WaylandTestApplication::~WaylandTestApplication() | 60 | WaylandTestApplication::~WaylandTestApplication() | ||
61 | { | 61 | { | ||
62 | kwinApp()->platform()->setOutputsEnabled(false); | 62 | kwinApp()->platform()->setOutputsEnabled(false); | ||
63 | // need to unload all effects prior to destroying X connection as they might do X calls | 63 | // need to unload all effects prior to destroying X connection as they might do X calls | ||
64 | // also before destroy Workspace, as effects might call into Workspace | 64 | // also before destroy Workspace, as effects might call into Workspace | ||
65 | if (effects) { | 65 | if (effects) { | ||
66 | static_cast<EffectsHandlerImpl*>(effects)->unloadAllEffects(); | 66 | static_cast<EffectsHandlerImpl*>(effects)->unloadAllEffects(); | ||
67 | } | 67 | } | ||
68 | destroyWorkspace(); | 68 | destroyWorkspace(); | ||
69 | waylandServer()->dispatch(); | 69 | waylandServer()->dispatch(); | ||
70 | disconnect(m_xwaylandFailConnection); | | |||
71 | if (x11Connection()) { | | |||
72 | Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT); | | |||
73 | destroyAtoms(); | | |||
74 | emit x11ConnectionAboutToBeDestroyed(); | | |||
75 | xcb_disconnect(x11Connection()); | | |||
76 | setX11Connection(nullptr); | | |||
77 | } | | |||
78 | if (m_xwaylandProcess) { | | |||
79 | m_xwaylandProcess->terminate(); | | |||
80 | while (m_xwaylandProcess->state() != QProcess::NotRunning) { | | |||
81 | processEvents(QEventLoop::WaitForMoreEvents); | | |||
82 | } | | |||
83 | waylandServer()->destroyXWaylandConnection(); | | |||
84 | } | | |||
85 | if (QStyle *s = style()) { | 70 | if (QStyle *s = style()) { | ||
86 | s->unpolish(this); | 71 | s->unpolish(this); | ||
87 | } | 72 | } | ||
73 | // kill Xwayland before terminating its connection | ||||
74 | delete m_xwayland; | ||||
88 | waylandServer()->terminateClientConnections(); | 75 | waylandServer()->terminateClientConnections(); | ||
89 | destroyCompositor(); | 76 | destroyCompositor(); | ||
90 | } | 77 | } | ||
91 | 78 | | |||
92 | void WaylandTestApplication::performStartup() | 79 | void WaylandTestApplication::performStartup() | ||
93 | { | 80 | { | ||
94 | // first load options - done internally by a different thread | 81 | // first load options - done internally by a different thread | ||
95 | createOptions(); | 82 | createOptions(); | ||
Show All 23 Lines | 104 | { | |||
119 | createScreens(); | 106 | createScreens(); | ||
120 | 107 | | |||
121 | if (operationMode() == OperationModeWaylandOnly) { | 108 | if (operationMode() == OperationModeWaylandOnly) { | ||
122 | createCompositor(); | 109 | createCompositor(); | ||
123 | connect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithSceen); | 110 | connect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithSceen); | ||
124 | return; | 111 | return; | ||
125 | } | 112 | } | ||
126 | createCompositor(); | 113 | createCompositor(); | ||
127 | connect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::startXwaylandServer); | 114 | connect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithXwayland); | ||
128 | } | 115 | } | ||
129 | 116 | | |||
130 | void WaylandTestApplication::continueStartupWithSceen() | 117 | void WaylandTestApplication::continueStartupWithSceen() | ||
131 | { | 118 | { | ||
132 | disconnect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithSceen); | 119 | disconnect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithSceen); | ||
133 | createWorkspace(); | 120 | createWorkspace(); | ||
134 | } | 121 | } | ||
135 | 122 | | |||
136 | void WaylandTestApplication::continueStartupWithX() | 123 | void WaylandTestApplication::continueStartupWithXwayland() | ||
137 | { | | |||
138 | createX11Connection(); | | |||
139 | xcb_connection_t *c = x11Connection(); | | |||
140 | if (!c) { | | |||
141 | // about to quit | | |||
142 | return; | | |||
143 | } | | |||
144 | QSocketNotifier *notifier = new QSocketNotifier(xcb_get_file_descriptor(c), QSocketNotifier::Read, this); | | |||
145 | auto processXcbEvents = [this, c] { | | |||
146 | while (auto event = xcb_poll_for_event(c)) { | | |||
147 | updateX11Time(event); | | |||
148 | long result = 0; | | |||
149 | if (QThread::currentThread()->eventDispatcher()->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result)) { | | |||
150 | free(event); | | |||
151 | continue; | | |||
152 | } | | |||
153 | if (Workspace::self()) { | | |||
154 | Workspace::self()->workspaceEvent(event); | | |||
155 | } | | |||
156 | free(event); | | |||
157 | } | | |||
158 | xcb_flush(c); | | |||
159 | }; | | |||
160 | connect(notifier, &QSocketNotifier::activated, this, processXcbEvents); | | |||
161 | connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, processXcbEvents); | | |||
162 | connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::awake, this, processXcbEvents); | | |||
163 | | ||||
164 | // create selection owner for WM_S0 - magic X display number expected by XWayland | | |||
165 | KSelectionOwner owner("WM_S0", c, x11RootWindow()); | | |||
166 | owner.claim(true); | | |||
167 | | ||||
168 | createAtoms(); | | |||
169 | | ||||
170 | setupEventFilters(); | | |||
171 | | ||||
172 | // Check whether another windowmanager is running | | |||
173 | const uint32_t maskValues[] = {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT}; | | |||
174 | ScopedCPointer<xcb_generic_error_t> redirectCheck(xcb_request_check(connection(), | | |||
175 | xcb_change_window_attributes_checked(connection(), | | |||
176 | rootWindow(), | | |||
177 | XCB_CW_EVENT_MASK, | | |||
178 | maskValues))); | | |||
179 | if (!redirectCheck.isNull()) { | | |||
180 | ::exit(1); | | |||
181 | } | | |||
182 | | ||||
183 | createWorkspace(); | | |||
184 | | ||||
185 | Xcb::sync(); // Trigger possible errors, there's still a chance to abort | | |||
186 | } | | |||
187 | | ||||
188 | void WaylandTestApplication::createX11Connection() | | |||
189 | { | | |||
190 | int screenNumber = 0; | | |||
191 | xcb_connection_t *c = nullptr; | | |||
192 | if (m_xcbConnectionFd == -1) { | | |||
193 | c = xcb_connect(nullptr, &screenNumber); | | |||
194 | } else { | | |||
195 | c = xcb_connect_to_fd(m_xcbConnectionFd, nullptr); | | |||
196 | } | | |||
197 | if (int error = xcb_connection_has_error(c)) { | | |||
198 | std::cerr << "FATAL ERROR: Creating connection to XServer failed: " << error << std::endl; | | |||
199 | exit(1); | | |||
200 | return; | | |||
201 | } | | |||
202 | setX11Connection(c); | | |||
203 | // we don't support X11 multi-head in Wayland | | |||
204 | setX11ScreenNumber(screenNumber); | | |||
205 | setX11RootWindow(defaultScreen()->root); | | |||
206 | } | | |||
207 | | ||||
208 | void WaylandTestApplication::startXwaylandServer() | | |||
209 | { | | |||
210 | disconnect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::startXwaylandServer); | | |||
211 | int pipeFds[2]; | | |||
212 | if (pipe(pipeFds) != 0) { | | |||
213 | std::cerr << "FATAL ERROR failed to create pipe to start Xwayland " << std::endl; | | |||
214 | exit(1); | | |||
215 | return; | | |||
216 | } | | |||
217 | int sx[2]; | | |||
218 | if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { | | |||
219 | std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl; | | |||
220 | exit(1); | | |||
221 | return; | | |||
222 | } | | |||
223 | int fd = dup(sx[1]); | | |||
224 | if (fd < 0) { | | |||
225 | std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl; | | |||
226 | exit(20); | | |||
227 | return; | | |||
228 | } | | |||
229 | | ||||
230 | const int waylandSocket = waylandServer()->createXWaylandConnection(); | | |||
231 | if (waylandSocket == -1) { | | |||
232 | std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl; | | |||
233 | exit(1); | | |||
234 | return; | | |||
235 | } | | |||
236 | const int wlfd = dup(waylandSocket); | | |||
237 | if (wlfd < 0) { | | |||
238 | std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl; | | |||
239 | exit(20); | | |||
240 | return; | | |||
241 | } | | |||
242 | | ||||
243 | m_xcbConnectionFd = sx[0]; | | |||
244 | | ||||
245 | m_xwaylandProcess = new QProcess(kwinApp()); | | |||
246 | m_xwaylandProcess->setProgram(QStringLiteral("Xwayland")); | | |||
247 | QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); | | |||
248 | env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd)); | | |||
249 | m_xwaylandProcess->setProcessEnvironment(env); | | |||
250 | m_xwaylandProcess->setArguments({QStringLiteral("-displayfd"), | | |||
251 | QString::number(pipeFds[1]), | | |||
252 | QStringLiteral("-rootless"), | | |||
253 | QStringLiteral("-wm"), | | |||
254 | QString::number(fd)}); | | |||
255 | m_xwaylandFailConnection = connect(m_xwaylandProcess, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error), this, | | |||
256 | [] (QProcess::ProcessError error) { | | |||
257 | if (error == QProcess::FailedToStart) { | | |||
258 | std::cerr << "FATAL ERROR: failed to start Xwayland" << std::endl; | | |||
259 | } else { | | |||
260 | std::cerr << "FATAL ERROR: Xwayland failed, going to exit now" << std::endl; | | |||
261 | } | | |||
262 | exit(1); | | |||
263 | } | | |||
264 | ); | | |||
265 | const int xDisplayPipe = pipeFds[0]; | | |||
266 | connect(m_xwaylandProcess, &QProcess::started, this, | | |||
267 | [this, xDisplayPipe] { | | |||
268 | QFutureWatcher<void> *watcher = new QFutureWatcher<void>(this); | | |||
269 | QObject::connect(watcher, &QFutureWatcher<void>::finished, this, &WaylandTestApplication::continueStartupWithX, Qt::QueuedConnection); | | |||
270 | QObject::connect(watcher, &QFutureWatcher<void>::finished, watcher, &QFutureWatcher<void>::deleteLater, Qt::QueuedConnection); | | |||
271 | watcher->setFuture(QtConcurrent::run(readDisplay, xDisplayPipe)); | | |||
272 | } | | |||
273 | ); | | |||
274 | m_xwaylandProcess->start(); | | |||
275 | close(pipeFds[1]); | | |||
276 | } | | |||
277 | | ||||
278 | static void readDisplay(int pipe) | | |||
279 | { | 124 | { | ||
280 | QFile readPipe; | 125 | disconnect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithXwayland); | ||
281 | if (!readPipe.open(pipe, QIODevice::ReadOnly)) { | | |||
282 | std::cerr << "FATAL ERROR failed to open pipe to start X Server" << std::endl; | | |||
283 | exit(1); | | |||
284 | } | | |||
285 | QByteArray displayNumber = readPipe.readLine(); | | |||
286 | | ||||
287 | displayNumber.prepend(QByteArray(":")); | | |||
288 | displayNumber.remove(displayNumber.size() -1, 1); | | |||
289 | std::cout << "X-Server started on display " << displayNumber.constData() << std::endl; | | |||
290 | | ||||
291 | setenv("DISPLAY", displayNumber.constData(), true); | | |||
292 | 126 | | |||
293 | // close our pipe | 127 | m_xwayland = new Xwl::Xwayland(this); | ||
294 | close(pipe); | 128 | connect(m_xwayland, &Xwl::Xwayland::criticalError, this, [](int code) { | ||
129 | // we currently exit on Xwayland errors always directly | ||||
130 | // TODO: restart Xwayland | ||||
131 | std::cerr << "Xwayland had a critical error. Going to exit now." << std::endl; | ||||
132 | exit(code); | ||||
133 | }); | ||||
134 | m_xwayland->init(); | ||||
295 | } | 135 | } | ||
296 | 136 | | |||
297 | } | 137 | } |