kwin/main_wayland.cpp
Martin Gräßlin 7369a2c3a5 [kwin_wayland] Support using platform wayland for the QPA
This reorders the startup sequence quite a bit:
1. Create QAbstractEventDispatcher and install it on QCoreApplication
2. Create Application
3. Start Xwayland, use thread to get when its ready
4. Create xcb connection
5. perform startup

For using the wayland QPA it needs a patch in QtWayland which will be
part of Qt 5.4.2, otherwise it blocks.
2015-03-17 10:20:19 +01:00

310 lines
11 KiB
C++

/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2014 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 "main_wayland.h"
#include "workspace.h"
#include <config-kwin.h>
// kwin
#include "wayland_backend.h"
#include "wayland_server.h"
#include "xcbutils.h"
// KWayland
#include <KWayland/Server/display.h>
// KDE
#include <KLocalizedString>
// Qt
#include <qplatformdefs.h>
#include <QCommandLineParser>
#include <QtConcurrentRun>
#include <QFile>
#include <QFutureWatcher>
#include <QtCore/private/qeventdispatcher_unix_p.h>
#include <QSocketNotifier>
#include <QThread>
#include <QDebug>
// system
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif // HAVE_UNISTD_H
#include <iostream>
namespace KWin
{
static void sighandler(int)
{
QApplication::exit();
}
//************************************
// ApplicationWayland
//************************************
ApplicationWayland::ApplicationWayland(int &argc, char **argv)
: Application(OperationModeWaylandAndX11, argc, argv)
{
}
ApplicationWayland::~ApplicationWayland()
{
destroyWorkspace();
delete Wayland::WaylandBackend::self();
if (x11Connection()) {
Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT);
xcb_disconnect(x11Connection());
}
}
void ApplicationWayland::performStartup()
{
xcb_connection_t *c = x11Connection();
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);
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);
// we need to do an XSync here, otherwise the QPA might crash us later on
// TODO: remove
Xcb::sync();
createAtoms();
setupEventFilters();
// first load options - done internally by a different thread
createOptions();
// 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()) {
fputs(i18n("kwin_wayland: an X11 window manager is running on the X11 Display.\n").toLocal8Bit().constData(), stderr);
::exit(1);
}
// try creating the Wayland Backend
Wayland::WaylandBackend *backend = Wayland::WaylandBackend::create();
connect(backend, &Wayland::WaylandBackend::connectionFailed, this,
[] () {
fputs(i18n("kwin_wayland: could not connect to Wayland Server, ensure WAYLAND_DISPLAY is set.\n").toLocal8Bit().constData(), stderr);
::exit(1);
}
);
createWorkspace();
Xcb::sync(); // Trigger possible errors, there's still a chance to abort
notifyKSplash();
}
void ApplicationWayland::createX11Connection(int fd)
{
int screenNumber = 0;
xcb_connection_t *c = nullptr;
if (fd == -1) {
c = xcb_connect(nullptr, &screenNumber);
} else {
c = xcb_connect_to_fd(fd, nullptr);
}
if (xcb_connection_has_error(c)) {
std::cerr << "FATAL ERROR: Creating connection to XServer failed" << std::endl;
exit(1);
return;
}
setX11Connection(c);
// we don't support X11 multi-head in Wayland
setX11ScreenNumber(screenNumber);
setX11RootWindow(defaultScreen()->root);
}
/**
* Starts the Xwayland-Server.
* The new process is started by forking into it.
**/
static int startXServer(const QByteArray &waylandSocket, int wmFd)
{
int pipeFds[2];
if (pipe(pipeFds) != 0) {
std::cerr << "FATAL ERROR failed to create pipe to start Xwayland " << std::endl;
exit(1);
}
pid_t pid = fork();
if (pid == 0) {
// child process - should be turned into X-Server
// writes to pipe, closes read side
close(pipeFds[0]);
char fdbuf[16];
sprintf(fdbuf, "%d", pipeFds[1]);
char wmfdbuf[16];
int fd = dup(wmFd);
if (fd < 0) {
std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl;
exit(20);
return -1;
}
sprintf(wmfdbuf, "%d", fd);
qputenv("WAYLAND_DISPLAY", waylandSocket.isEmpty() ? QByteArrayLiteral("wayland-0") : waylandSocket);
execlp("Xwayland", "Xwayland", "-displayfd", fdbuf, "-rootless", "-wm", wmfdbuf, (char *)0);
close(pipeFds[1]);
exit(20);
}
// parent process - this is KWin
// reads from pipe, closes write side
close(pipeFds[1]);
return pipeFds[0];
}
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);
}
} // namespace
extern "C"
KWIN_EXPORT int kdemain(int argc, char * argv[])
{
// process command line arguments to figure out whether we have to start Xwayland and the Wayland socket
QByteArray waylandSocket;
for (int i = 1; i < argc; ++i) {
QByteArray arg = QByteArray::fromRawData(argv[i], qstrlen(argv[i]));
if (arg == "--socket" || arg == "-s") {
if (++i < argc) {
waylandSocket = QByteArray::fromRawData(argv[i], qstrlen(argv[i]));
}
continue;
}
if (arg.startsWith("--socket=")) {
waylandSocket = arg.mid(9);
}
}
// set our own event dispatcher to be able to dispatch events before the event loop is started
QAbstractEventDispatcher *eventDispatcher = new QEventDispatcherUNIX();
QCoreApplication::setEventDispatcher(eventDispatcher);
KWin::WaylandServer *server = KWin::WaylandServer::create(nullptr);
server->init(waylandSocket);
KWin::Application::setupMalloc();
KWin::Application::setupLocalizedString();
KWin::Application::setupLoggingCategoryFilters();
if (signal(SIGTERM, KWin::sighandler) == SIG_IGN)
signal(SIGTERM, SIG_IGN);
if (signal(SIGINT, KWin::sighandler) == SIG_IGN)
signal(SIGINT, SIG_IGN);
if (signal(SIGHUP, KWin::sighandler) == SIG_IGN)
signal(SIGHUP, SIG_IGN);
// we want QtWayland to connect to our Wayland display, but the WaylandBackend to the existing Wayland backend
// so fiddling around with the env variables.
const QByteArray systemDisplay = qgetenv("WAYLAND_DISPLAY");
qputenv("WAYLAND_DISPLAY", waylandSocket.isEmpty() ? QByteArrayLiteral("wayland-0") : waylandSocket);
KWin::ApplicationWayland a(argc, argv);
qputenv("WAYLAND_DISPLAY", systemDisplay);
a.setupTranslator();
server->setParent(&a);
KWin::Application::createAboutData();
QCommandLineOption xwaylandOption(QStringLiteral("xwayland"),
i18n("Start a rootless Xwayland server."));
QCommandLineOption waylandSocketOption(QStringList{QStringLiteral("s"), QStringLiteral("socket")},
i18n("Name of the Wayland socket to listen on. If not set \"wayland-0\" is used."),
QStringLiteral("socket"));
QCommandLineParser parser;
a.setupCommandLine(&parser);
parser.addOption(xwaylandOption);
parser.addOption(waylandSocketOption);
#if HAVE_INPUT
QCommandLineOption libinputOption(QStringLiteral("libinput"),
i18n("Enable libinput support for input events processing. Note: never use in a nested session."));
parser.addOption(libinputOption);
#endif
parser.process(a);
a.processCommandLine(&parser);
#if HAVE_INPUT
KWin::Application::setUseLibinput(parser.isSet(libinputOption));
#endif
if (parser.isSet(xwaylandOption)) {
a.setOperationMode(KWin::Application::OperationModeXwayland);
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;
return 1;
}
const int xDisplayPipe = KWin::startXServer(waylandSocket, sx[1]);
QFutureWatcher<void> *watcher = new QFutureWatcher<void>(&a);
QObject::connect(watcher, &QFutureWatcher<void>::finished,
[&a, watcher, sx] {
// create xcb connection
a.createX11Connection(sx[0]);
// create selection owner for WM_S0 - magic X display number expected by XWayland
KSelectionOwner owner("WM_S0", KWin::connection(), KWin::rootWindow());
owner.claim(true);
watcher->deleteLater();
a.start();
}
);
watcher->setFuture(QtConcurrent::run(KWin::readDisplay, xDisplayPipe));
} else {
a.createX11Connection();
a.start();
}
return a.exec();
}