kwin/src/wayland/tests/renderingservertest.cpp
Vlad Zahorodnii 6f3cb932df wayland: Rewrite wl-shm implementation
The main motivation for the rewrite is to properly port ShmClientBuffer
to our GraphicsBuffer abstractions. As is, libwayland implementation is
not suitable for our needs. We may need ShmClientBuffer to be alive when
its corresponding wl_shm_buffer resource is destroyed, for example to
play a window closing animation. With the existing api, we need to fight
libwayland. Besides that, libwayland doesn't provide a way to get
underlying pool file descriptor, which is needed to fill ShmAttributes.
2023-06-08 06:22:46 +00:00

287 lines
8.9 KiB
C++

/*
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "core/graphicsbufferview.h"
#include "../compositor_interface.h"
#include "../datadevicemanager_interface.h"
#include "../display.h"
#include "../keyboard_interface.h"
#include "../output_interface.h"
#include "../pointer_interface.h"
#include "../seat_interface.h"
#include "../xdgshell_interface.h"
#include "fakeoutput.h"
#include <QApplication>
#include <QCommandLineParser>
#include <QDateTime>
#include <QFile>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QPainter>
#include <QThreadPool>
#include <QWidget>
#include <iostream>
#include <unistd.h>
static int startXServer()
{
const QByteArray process = QByteArrayLiteral("Xwayland");
int pipeFds[2];
if (pipe(pipeFds) != 0) {
std::cerr << "FATAL ERROR failed to create pipe to start X Server " << process.constData() << std::endl;
exit(1);
}
pid_t pid = fork();
if (pid == 0) {
// child process - should be turned into Xwayland
// writes to pipe, closes read side
close(pipeFds[0]);
char fdbuf[16];
sprintf(fdbuf, "%d", pipeFds[1]);
execlp(process.constData(), process.constData(), "-displayfd", fdbuf, "-rootless", (char *)nullptr);
close(pipeFds[1]);
exit(20);
}
// parent process - this is the wayland server
// reads from pipe, closes write side
close(pipeFds[1]);
return pipeFds[0];
}
static void readDisplayFromPipe(int pipe)
{
QFile readPipe;
if (!readPipe.open(pipe, QIODevice::ReadOnly)) {
std::cerr << "FATAL ERROR failed to open pipe to start X Server XWayland" << 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);
}
class CompositorWindow : public QWidget
{
Q_OBJECT
public:
explicit CompositorWindow(QWidget *parent = nullptr);
virtual ~CompositorWindow();
void surfaceCreated(KWaylandServer::XdgToplevelInterface *surface);
void setSeat(const QPointer<KWaylandServer::SeatInterface> &seat);
protected:
void paintEvent(QPaintEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
void keyReleaseEvent(QKeyEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
private:
void updateFocus();
QList<KWaylandServer::XdgToplevelInterface *> m_stackingOrder;
QPointer<KWaylandServer::SeatInterface> m_seat;
};
CompositorWindow::CompositorWindow(QWidget *parent)
: QWidget(parent)
{
setMouseTracking(true);
}
CompositorWindow::~CompositorWindow() = default;
void CompositorWindow::surfaceCreated(KWaylandServer::XdgToplevelInterface *surface)
{
using namespace KWaylandServer;
surface->sendConfigure(QSize(), XdgToplevelInterface::States());
m_stackingOrder << surface;
connect(surface->surface(), &SurfaceInterface::damaged, this, static_cast<void (CompositorWindow::*)()>(&CompositorWindow::update));
connect(surface, &XdgToplevelInterface::destroyed, this, [surface, this] {
m_stackingOrder.removeAll(surface);
updateFocus();
update();
});
updateFocus();
}
void CompositorWindow::updateFocus()
{
using namespace KWaylandServer;
if (!m_seat || m_stackingOrder.isEmpty()) {
return;
}
auto it = std::find_if(m_stackingOrder.constBegin(), m_stackingOrder.constEnd(), [](XdgToplevelInterface *toplevel) {
return toplevel->surface()->isMapped();
});
if (it == m_stackingOrder.constEnd()) {
return;
}
m_seat->notifyPointerEnter((*it)->surface(), m_seat->pointerPos());
m_seat->setFocusedKeyboardSurface((*it)->surface());
}
void CompositorWindow::setSeat(const QPointer<KWaylandServer::SeatInterface> &seat)
{
m_seat = seat;
}
void CompositorWindow::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
QPainter p(this);
for (auto window : m_stackingOrder) {
KWaylandServer::SurfaceInterface *surface = window->surface();
if (!surface || !surface->isMapped()) {
continue;
}
KWin::GraphicsBufferView view(surface->buffer());
if (view.image()) {
p.drawImage(QPoint(0, 0), *view.image());
}
surface->frameRendered(QDateTime::currentMSecsSinceEpoch());
}
}
void CompositorWindow::keyPressEvent(QKeyEvent *event)
{
QWidget::keyPressEvent(event);
if (!m_seat) {
return;
}
if (!m_seat->focusedKeyboardSurface()) {
updateFocus();
}
m_seat->setTimestamp(std::chrono::milliseconds(event->timestamp()));
m_seat->notifyKeyboardKey(event->nativeScanCode() - 8, KWaylandServer::KeyboardKeyState::Pressed);
}
void CompositorWindow::keyReleaseEvent(QKeyEvent *event)
{
QWidget::keyReleaseEvent(event);
if (!m_seat) {
return;
}
m_seat->setTimestamp(std::chrono::milliseconds(event->timestamp()));
m_seat->notifyKeyboardKey(event->nativeScanCode() - 8, KWaylandServer::KeyboardKeyState::Released);
}
void CompositorWindow::mouseMoveEvent(QMouseEvent *event)
{
QWidget::mouseMoveEvent(event);
if (!m_seat->focusedPointerSurface()) {
updateFocus();
}
m_seat->setTimestamp(std::chrono::milliseconds(event->timestamp()));
m_seat->notifyPointerMotion(event->localPos().toPoint());
m_seat->notifyPointerFrame();
}
void CompositorWindow::mousePressEvent(QMouseEvent *event)
{
QWidget::mousePressEvent(event);
if (!m_seat->focusedPointerSurface()) {
if (!m_stackingOrder.isEmpty()) {
m_seat->notifyPointerEnter(m_stackingOrder.last()->surface(), event->globalPos());
}
}
m_seat->setTimestamp(std::chrono::milliseconds(event->timestamp()));
m_seat->notifyPointerButton(event->button(), KWaylandServer::PointerButtonState::Pressed);
m_seat->notifyPointerFrame();
}
void CompositorWindow::mouseReleaseEvent(QMouseEvent *event)
{
QWidget::mouseReleaseEvent(event);
m_seat->setTimestamp(std::chrono::milliseconds(event->timestamp()));
m_seat->notifyPointerButton(event->button(), KWaylandServer::PointerButtonState::Released);
m_seat->notifyPointerFrame();
}
void CompositorWindow::wheelEvent(QWheelEvent *event)
{
QWidget::wheelEvent(event);
m_seat->setTimestamp(std::chrono::milliseconds(event->timestamp()));
const QPoint &angle = event->angleDelta() / (8 * 15);
if (angle.x() != 0) {
m_seat->notifyPointerAxis(Qt::Horizontal, angle.x(), 1, KWaylandServer::PointerAxisSource::Wheel);
}
if (angle.y() != 0) {
m_seat->notifyPointerAxis(Qt::Vertical, angle.y(), 1, KWaylandServer::PointerAxisSource::Wheel);
}
m_seat->notifyPointerFrame();
}
int main(int argc, char **argv)
{
using namespace KWaylandServer;
QApplication app(argc, argv);
QCommandLineParser parser;
parser.addHelpOption();
QCommandLineOption xwaylandOption(QStringList{QStringLiteral("x"), QStringLiteral("xwayland")}, QStringLiteral("Start a rootless Xwayland server"));
parser.addOption(xwaylandOption);
parser.process(app);
KWaylandServer::Display display;
display.start();
new DataDeviceManagerInterface(&display);
new CompositorInterface(&display, &display);
XdgShellInterface *shell = new XdgShellInterface(&display);
display.createShm();
const QSize windowSize(1024, 768);
auto outputHandle = std::make_unique<FakeOutput>();
outputHandle->setPhysicalSize(QSize(269, 202));
outputHandle->setMode(windowSize, 60000);
auto outputInterface = std::make_unique<OutputInterface>(&display, outputHandle.get());
SeatInterface *seat = new SeatInterface(&display);
seat->setHasKeyboard(true);
seat->setHasPointer(true);
seat->setName(QStringLiteral("testSeat0"));
CompositorWindow compositorWindow;
compositorWindow.setSeat(seat);
compositorWindow.setMinimumSize(windowSize);
compositorWindow.setMaximumSize(windowSize);
compositorWindow.setGeometry(QRect(QPoint(0, 0), windowSize));
compositorWindow.show();
QObject::connect(shell, &XdgShellInterface::toplevelCreated, &compositorWindow, &CompositorWindow::surfaceCreated);
// start XWayland
if (parser.isSet(xwaylandOption)) {
// starts XWayland by forking and opening a pipe
const int pipe = startXServer();
if (pipe == -1) {
exit(1);
}
QThreadPool::globalInstance()->start([pipe] {
readDisplayFromPipe(pipe);
});
}
return app.exec();
}
#include "renderingservertest.moc"