2015-10-02 13:04:57 +00:00
|
|
|
/********************************************************************
|
|
|
|
KWin - the KDE window manager
|
|
|
|
This file is part of the KDE project.
|
|
|
|
|
|
|
|
Copyright (C) 2015 Martin Gräßlin <mgraesslin@kde.org>
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program 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 "kwin_wayland_test.h"
|
2016-04-07 07:24:17 +00:00
|
|
|
#include "../../platform.h"
|
2016-04-14 08:29:27 +00:00
|
|
|
#include "../../composite.h"
|
2015-11-12 10:29:56 +00:00
|
|
|
#include "../../effects.h"
|
2015-10-02 13:04:57 +00:00
|
|
|
#include "../../wayland_server.h"
|
|
|
|
#include "../../workspace.h"
|
|
|
|
#include "../../xcbutils.h"
|
|
|
|
|
2016-04-06 15:30:00 +00:00
|
|
|
#include <KPluginMetaData>
|
|
|
|
|
2015-10-02 13:04:57 +00:00
|
|
|
#include <QAbstractEventDispatcher>
|
|
|
|
#include <QPluginLoader>
|
|
|
|
#include <QSocketNotifier>
|
2016-11-03 10:00:08 +00:00
|
|
|
#include <QStyle>
|
2015-10-02 13:04:57 +00:00
|
|
|
#include <QThread>
|
|
|
|
#include <QtConcurrentRun>
|
|
|
|
|
|
|
|
// system
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
namespace KWin
|
|
|
|
{
|
|
|
|
|
|
|
|
static void readDisplay(int pipe);
|
|
|
|
|
2017-09-30 14:35:52 +00:00
|
|
|
WaylandTestApplication::WaylandTestApplication(OperationMode mode, int &argc, char **argv)
|
|
|
|
: Application(mode, argc, argv)
|
2015-10-02 13:04:57 +00:00
|
|
|
{
|
2016-10-05 11:21:33 +00:00
|
|
|
QStandardPaths::setTestModeEnabled(true);
|
2018-11-16 19:07:09 +00:00
|
|
|
// TODO: add a test move to kglobalaccel instead?
|
|
|
|
QFile{QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("kglobalshortcutsrc"))}.remove();
|
2018-02-27 17:17:53 +00:00
|
|
|
QIcon::setThemeName(QStringLiteral("breeze"));
|
2015-12-18 15:40:52 +00:00
|
|
|
#ifdef KWIN_BUILD_ACTIVITIES
|
|
|
|
setUseKActivities(false);
|
|
|
|
#endif
|
2016-02-12 14:22:55 +00:00
|
|
|
qputenv("KWIN_COMPOSE", QByteArrayLiteral("Q"));
|
2018-11-07 15:09:34 +00:00
|
|
|
qunsetenv("XKB_DEFAULT_RULES");
|
|
|
|
qunsetenv("XKB_DEFAULT_MODEL");
|
|
|
|
qunsetenv("XKB_DEFAULT_LAYOUT");
|
|
|
|
qunsetenv("XKB_DEFAULT_VARIANT");
|
|
|
|
qunsetenv("XKB_DEFAULT_OPTIONS");
|
2018-12-06 17:38:36 +00:00
|
|
|
|
|
|
|
const auto ownPath = libraryPaths().last();
|
|
|
|
removeLibraryPath(ownPath);
|
|
|
|
addLibraryPath(ownPath);
|
|
|
|
|
|
|
|
const auto plugins = KPluginLoader::findPluginsById(QStringLiteral("org.kde.kwin.waylandbackends"), "KWinWaylandVirtualBackend");
|
|
|
|
if (plugins.empty()) {
|
|
|
|
quit();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
initPlatform(plugins.first());
|
2016-04-06 15:30:00 +00:00
|
|
|
WaylandServer::create(this);
|
2015-10-02 13:04:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
WaylandTestApplication::~WaylandTestApplication()
|
|
|
|
{
|
2016-04-07 06:28:35 +00:00
|
|
|
kwinApp()->platform()->setOutputsEnabled(false);
|
2015-11-12 10:29:56 +00:00
|
|
|
// need to unload all effects prior to destroying X connection as they might do X calls
|
2016-07-04 12:37:35 +00:00
|
|
|
// also before destroy Workspace, as effects might call into Workspace
|
2015-11-12 10:29:56 +00:00
|
|
|
if (effects) {
|
|
|
|
static_cast<EffectsHandlerImpl*>(effects)->unloadAllEffects();
|
|
|
|
}
|
2016-07-04 12:37:35 +00:00
|
|
|
destroyWorkspace();
|
|
|
|
waylandServer()->dispatch();
|
2015-11-12 13:05:09 +00:00
|
|
|
disconnect(m_xwaylandFailConnection);
|
2015-10-02 13:04:57 +00:00
|
|
|
if (x11Connection()) {
|
|
|
|
Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT);
|
|
|
|
destroyAtoms();
|
2015-11-12 14:13:42 +00:00
|
|
|
emit x11ConnectionAboutToBeDestroyed();
|
2015-10-02 13:04:57 +00:00
|
|
|
xcb_disconnect(x11Connection());
|
2015-11-10 12:54:26 +00:00
|
|
|
setX11Connection(nullptr);
|
2015-10-02 13:04:57 +00:00
|
|
|
}
|
|
|
|
if (m_xwaylandProcess) {
|
|
|
|
m_xwaylandProcess->terminate();
|
2015-11-12 14:15:44 +00:00
|
|
|
while (m_xwaylandProcess->state() != QProcess::NotRunning) {
|
|
|
|
processEvents(QEventLoop::WaitForMoreEvents);
|
|
|
|
}
|
2015-11-10 13:21:48 +00:00
|
|
|
waylandServer()->destroyXWaylandConnection();
|
2015-10-02 13:04:57 +00:00
|
|
|
}
|
2016-11-03 10:00:08 +00:00
|
|
|
if (QStyle *s = style()) {
|
|
|
|
s->unpolish(this);
|
|
|
|
}
|
2015-11-18 09:29:10 +00:00
|
|
|
waylandServer()->terminateClientConnections();
|
2015-11-10 07:52:40 +00:00
|
|
|
destroyCompositor();
|
2015-10-02 13:04:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void WaylandTestApplication::performStartup()
|
|
|
|
{
|
|
|
|
// first load options - done internally by a different thread
|
|
|
|
createOptions();
|
|
|
|
waylandServer()->createInternalConnection();
|
|
|
|
|
|
|
|
// try creating the Wayland Backend
|
|
|
|
createInput();
|
|
|
|
createBackend();
|
|
|
|
}
|
|
|
|
|
|
|
|
void WaylandTestApplication::createBackend()
|
|
|
|
{
|
2016-04-07 07:18:10 +00:00
|
|
|
Platform *platform = kwinApp()->platform();
|
|
|
|
connect(platform, &Platform::screensQueried, this, &WaylandTestApplication::continueStartupWithScreens);
|
|
|
|
connect(platform, &Platform::initFailed, this,
|
2015-10-02 13:04:57 +00:00
|
|
|
[] () {
|
|
|
|
std::cerr << "FATAL ERROR: backend failed to initialize, exiting now" << std::endl;
|
|
|
|
::exit(1);
|
|
|
|
}
|
|
|
|
);
|
2016-04-07 07:18:10 +00:00
|
|
|
platform->init();
|
2015-10-02 13:04:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void WaylandTestApplication::continueStartupWithScreens()
|
|
|
|
{
|
2016-04-07 07:18:10 +00:00
|
|
|
disconnect(kwinApp()->platform(), &Platform::screensQueried, this, &WaylandTestApplication::continueStartupWithScreens);
|
2015-10-02 13:04:57 +00:00
|
|
|
createScreens();
|
|
|
|
|
2017-09-30 14:35:52 +00:00
|
|
|
if (operationMode() == OperationModeWaylandOnly) {
|
|
|
|
createCompositor();
|
2017-10-01 07:06:51 +00:00
|
|
|
connect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithSceen);
|
2017-09-30 14:35:52 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-10-02 13:04:57 +00:00
|
|
|
createCompositor();
|
2016-04-14 08:29:27 +00:00
|
|
|
connect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::startXwaylandServer);
|
2015-10-02 13:04:57 +00:00
|
|
|
}
|
|
|
|
|
2017-10-01 07:06:51 +00:00
|
|
|
void WaylandTestApplication::continueStartupWithSceen()
|
|
|
|
{
|
|
|
|
disconnect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithSceen);
|
|
|
|
createWorkspace();
|
|
|
|
}
|
|
|
|
|
2015-10-02 13:04:57 +00:00
|
|
|
void WaylandTestApplication::continueStartupWithX()
|
|
|
|
{
|
|
|
|
createX11Connection();
|
|
|
|
xcb_connection_t *c = x11Connection();
|
|
|
|
if (!c) {
|
|
|
|
// about to quit
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
QSocketNotifier *notifier = new QSocketNotifier(xcb_get_file_descriptor(c), QSocketNotifier::Read, this);
|
|
|
|
auto processXcbEvents = [this, c] {
|
|
|
|
while (auto event = xcb_poll_for_event(c)) {
|
|
|
|
updateX11Time(event);
|
|
|
|
long result = 0;
|
|
|
|
if (QThread::currentThread()->eventDispatcher()->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result)) {
|
|
|
|
free(event);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (Workspace::self()) {
|
|
|
|
Workspace::self()->workspaceEvent(event);
|
|
|
|
}
|
|
|
|
free(event);
|
|
|
|
}
|
|
|
|
xcb_flush(c);
|
|
|
|
};
|
|
|
|
connect(notifier, &QSocketNotifier::activated, this, processXcbEvents);
|
|
|
|
connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, processXcbEvents);
|
|
|
|
connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::awake, this, processXcbEvents);
|
|
|
|
|
|
|
|
// create selection owner for WM_S0 - magic X display number expected by XWayland
|
|
|
|
KSelectionOwner owner("WM_S0", c, x11RootWindow());
|
|
|
|
owner.claim(true);
|
|
|
|
|
|
|
|
createAtoms();
|
|
|
|
|
|
|
|
setupEventFilters();
|
|
|
|
|
|
|
|
// Check whether another windowmanager is running
|
|
|
|
const uint32_t maskValues[] = {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT};
|
|
|
|
ScopedCPointer<xcb_generic_error_t> redirectCheck(xcb_request_check(connection(),
|
|
|
|
xcb_change_window_attributes_checked(connection(),
|
|
|
|
rootWindow(),
|
|
|
|
XCB_CW_EVENT_MASK,
|
|
|
|
maskValues)));
|
|
|
|
if (!redirectCheck.isNull()) {
|
|
|
|
::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
createWorkspace();
|
|
|
|
|
|
|
|
Xcb::sync(); // Trigger possible errors, there's still a chance to abort
|
|
|
|
}
|
|
|
|
|
|
|
|
void WaylandTestApplication::createX11Connection()
|
|
|
|
{
|
|
|
|
int screenNumber = 0;
|
|
|
|
xcb_connection_t *c = nullptr;
|
|
|
|
if (m_xcbConnectionFd == -1) {
|
|
|
|
c = xcb_connect(nullptr, &screenNumber);
|
|
|
|
} else {
|
|
|
|
c = xcb_connect_to_fd(m_xcbConnectionFd, nullptr);
|
|
|
|
}
|
|
|
|
if (int error = xcb_connection_has_error(c)) {
|
|
|
|
std::cerr << "FATAL ERROR: Creating connection to XServer failed: " << error << std::endl;
|
|
|
|
exit(1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
setX11Connection(c);
|
|
|
|
// we don't support X11 multi-head in Wayland
|
|
|
|
setX11ScreenNumber(screenNumber);
|
|
|
|
setX11RootWindow(defaultScreen()->root);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WaylandTestApplication::startXwaylandServer()
|
|
|
|
{
|
2016-04-14 08:29:27 +00:00
|
|
|
disconnect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::startXwaylandServer);
|
2015-10-02 13:04:57 +00:00
|
|
|
int pipeFds[2];
|
|
|
|
if (pipe(pipeFds) != 0) {
|
|
|
|
std::cerr << "FATAL ERROR failed to create pipe to start Xwayland " << std::endl;
|
|
|
|
exit(1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
int sx[2];
|
|
|
|
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) {
|
|
|
|
std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl;
|
|
|
|
exit(1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
int fd = dup(sx[1]);
|
|
|
|
if (fd < 0) {
|
|
|
|
std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl;
|
|
|
|
exit(20);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const int waylandSocket = waylandServer()->createXWaylandConnection();
|
|
|
|
if (waylandSocket == -1) {
|
|
|
|
std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl;
|
|
|
|
exit(1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const int wlfd = dup(waylandSocket);
|
|
|
|
if (wlfd < 0) {
|
|
|
|
std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl;
|
|
|
|
exit(20);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_xcbConnectionFd = sx[0];
|
|
|
|
|
|
|
|
m_xwaylandProcess = new QProcess(kwinApp());
|
|
|
|
m_xwaylandProcess->setProgram(QStringLiteral("Xwayland"));
|
|
|
|
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
|
|
|
env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd));
|
|
|
|
m_xwaylandProcess->setProcessEnvironment(env);
|
|
|
|
m_xwaylandProcess->setArguments({QStringLiteral("-displayfd"),
|
|
|
|
QString::number(pipeFds[1]),
|
|
|
|
QStringLiteral("-rootless"),
|
|
|
|
QStringLiteral("-wm"),
|
|
|
|
QString::number(fd)});
|
2015-11-12 13:05:09 +00:00
|
|
|
m_xwaylandFailConnection = connect(m_xwaylandProcess, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error), this,
|
2015-10-02 13:04:57 +00:00
|
|
|
[] (QProcess::ProcessError error) {
|
|
|
|
if (error == QProcess::FailedToStart) {
|
|
|
|
std::cerr << "FATAL ERROR: failed to start Xwayland" << std::endl;
|
|
|
|
} else {
|
|
|
|
std::cerr << "FATAL ERROR: Xwayland failed, going to exit now" << std::endl;
|
|
|
|
}
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
const int xDisplayPipe = pipeFds[0];
|
|
|
|
connect(m_xwaylandProcess, &QProcess::started, this,
|
|
|
|
[this, xDisplayPipe] {
|
|
|
|
QFutureWatcher<void> *watcher = new QFutureWatcher<void>(this);
|
|
|
|
QObject::connect(watcher, &QFutureWatcher<void>::finished, this, &WaylandTestApplication::continueStartupWithX, Qt::QueuedConnection);
|
|
|
|
QObject::connect(watcher, &QFutureWatcher<void>::finished, watcher, &QFutureWatcher<void>::deleteLater, Qt::QueuedConnection);
|
|
|
|
watcher->setFuture(QtConcurrent::run(readDisplay, xDisplayPipe));
|
|
|
|
}
|
|
|
|
);
|
|
|
|
m_xwaylandProcess->start();
|
|
|
|
close(pipeFds[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void readDisplay(int pipe)
|
|
|
|
{
|
|
|
|
QFile readPipe;
|
|
|
|
if (!readPipe.open(pipe, QIODevice::ReadOnly)) {
|
|
|
|
std::cerr << "FATAL ERROR failed to open pipe to start X Server" << std::endl;
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
QByteArray displayNumber = readPipe.readLine();
|
|
|
|
|
|
|
|
displayNumber.prepend(QByteArray(":"));
|
|
|
|
displayNumber.remove(displayNumber.size() -1, 1);
|
|
|
|
std::cout << "X-Server started on display " << displayNumber.constData() << std::endl;
|
|
|
|
|
|
|
|
setenv("DISPLAY", displayNumber.constData(), true);
|
|
|
|
|
|
|
|
// close our pipe
|
|
|
|
close(pipe);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|