File indexing completed on 2024-05-19 16:33:11

0001 /*
0002     SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 #include "../src/server/buffer_interface.h"
0007 #include "../src/server/compositor_interface.h"
0008 #include "../src/server/datadevicemanager_interface.h"
0009 #include "../src/server/display.h"
0010 #include "../src/server/keyboard_interface.h"
0011 #include "../src/server/output_interface.h"
0012 #include "../src/server/pointer_interface.h"
0013 #include "../src/server/seat_interface.h"
0014 #include "../src/server/shell_interface.h"
0015 
0016 #include <QApplication>
0017 #include <QCommandLineParser>
0018 #include <QDateTime>
0019 #include <QKeyEvent>
0020 #include <QMouseEvent>
0021 #include <QPainter>
0022 #include <QWidget>
0023 #include <QtConcurrentRun>
0024 
0025 #include <iostream>
0026 #include <unistd.h>
0027 
0028 static int startXServer()
0029 {
0030     const QByteArray process = QByteArrayLiteral("Xwayland");
0031     int pipeFds[2];
0032     if (pipe(pipeFds) != 0) {
0033         std::cerr << "FATAL ERROR failed to create pipe to start X Server " << process.constData() << std::endl;
0034         exit(1);
0035     }
0036 
0037     pid_t pid = fork();
0038     if (pid == 0) {
0039         // child process - should be turned into Xwayland
0040         // writes to pipe, closes read side
0041         close(pipeFds[0]);
0042         char fdbuf[16];
0043         sprintf(fdbuf, "%d", pipeFds[1]);
0044         execlp(process.constData(), process.constData(), "-displayfd", fdbuf, "-rootless", (char *)nullptr);
0045         close(pipeFds[1]);
0046         exit(20);
0047     }
0048     // parent process - this is the wayland server
0049     // reads from pipe, closes write side
0050     close(pipeFds[1]);
0051     return pipeFds[0];
0052 }
0053 
0054 static void readDisplayFromPipe(int pipe)
0055 {
0056     QFile readPipe;
0057     if (!readPipe.open(pipe, QIODevice::ReadOnly)) {
0058         std::cerr << "FATAL ERROR failed to open pipe to start X Server XWayland" << std::endl;
0059         exit(1);
0060     }
0061     QByteArray displayNumber = readPipe.readLine();
0062 
0063     displayNumber.prepend(QByteArray(":"));
0064     displayNumber.remove(displayNumber.size() - 1, 1);
0065     std::cout << "X-Server started on display " << displayNumber.constData() << std::endl;
0066 
0067     setenv("DISPLAY", displayNumber.constData(), true);
0068 
0069     // close our pipe
0070     close(pipe);
0071 }
0072 
0073 class CompositorWindow : public QWidget
0074 {
0075     Q_OBJECT
0076 public:
0077     explicit CompositorWindow(QWidget *parent = nullptr);
0078     ~CompositorWindow() override;
0079 
0080     void surfaceCreated(KWayland::Server::ShellSurfaceInterface *surface);
0081 
0082     void setSeat(const QPointer<KWayland::Server::SeatInterface> &seat);
0083 
0084 protected:
0085     void paintEvent(QPaintEvent *event) override;
0086     void keyPressEvent(QKeyEvent *event) override;
0087     void keyReleaseEvent(QKeyEvent *event) override;
0088     void mouseMoveEvent(QMouseEvent *event) override;
0089     void mousePressEvent(QMouseEvent *event) override;
0090     void mouseReleaseEvent(QMouseEvent *event) override;
0091     void wheelEvent(QWheelEvent *event) override;
0092 
0093 private:
0094     void updateFocus();
0095     QList<KWayland::Server::ShellSurfaceInterface *> m_stackingOrder;
0096     QPointer<KWayland::Server::SeatInterface> m_seat;
0097 };
0098 
0099 CompositorWindow::CompositorWindow(QWidget *parent)
0100     : QWidget(parent)
0101 {
0102     setMouseTracking(true);
0103 }
0104 
0105 CompositorWindow::~CompositorWindow() = default;
0106 
0107 void CompositorWindow::surfaceCreated(KWayland::Server::ShellSurfaceInterface *surface)
0108 {
0109     using namespace KWayland::Server;
0110     m_stackingOrder << surface;
0111     connect(surface, &ShellSurfaceInterface::fullscreenChanged, this, [surface, this](bool fullscreen) {
0112         if (fullscreen) {
0113             surface->requestSize(size());
0114         }
0115     });
0116     connect(surface, &ShellSurfaceInterface::maximizedChanged, this, [surface, this](bool maximized) {
0117         if (maximized) {
0118             surface->requestSize(size());
0119         }
0120     });
0121     connect(surface->surface(), &SurfaceInterface::damaged, this, static_cast<void (CompositorWindow::*)()>(&CompositorWindow::update));
0122     connect(surface, &ShellSurfaceInterface::destroyed, this, [surface, this] {
0123         m_stackingOrder.removeAll(surface);
0124         updateFocus();
0125         update();
0126     });
0127     updateFocus();
0128 }
0129 
0130 void CompositorWindow::updateFocus()
0131 {
0132     using namespace KWayland::Server;
0133     if (!m_seat || m_stackingOrder.isEmpty()) {
0134         return;
0135     }
0136     auto it = std::find_if(m_stackingOrder.constBegin(), m_stackingOrder.constEnd(), [](ShellSurfaceInterface *s) {
0137         return s->surface()->buffer() != nullptr;
0138     });
0139     if (it == m_stackingOrder.constEnd()) {
0140         return;
0141     }
0142     m_seat->setFocusedPointerSurface((*it)->surface());
0143     m_seat->setFocusedKeyboardSurface((*it)->surface());
0144 }
0145 
0146 void CompositorWindow::setSeat(const QPointer<KWayland::Server::SeatInterface> &seat)
0147 {
0148     m_seat = seat;
0149 }
0150 
0151 void CompositorWindow::paintEvent(QPaintEvent *event)
0152 {
0153     QWidget::paintEvent(event);
0154     QPainter p(this);
0155     for (auto s : m_stackingOrder) {
0156         if (auto *b = s->surface()->buffer()) {
0157             p.drawImage(QPoint(0, 0), b->data());
0158             s->surface()->frameRendered(QDateTime::currentMSecsSinceEpoch());
0159         }
0160     }
0161 }
0162 
0163 void CompositorWindow::keyPressEvent(QKeyEvent *event)
0164 {
0165     QWidget::keyPressEvent(event);
0166     if (!m_seat) {
0167         return;
0168     }
0169     if (!m_seat->focusedKeyboardSurface()) {
0170         updateFocus();
0171     }
0172     m_seat->setTimestamp(event->timestamp());
0173     m_seat->keyPressed(event->nativeScanCode() - 8);
0174 }
0175 
0176 void CompositorWindow::keyReleaseEvent(QKeyEvent *event)
0177 {
0178     QWidget::keyReleaseEvent(event);
0179     if (!m_seat) {
0180         return;
0181     }
0182     m_seat->setTimestamp(event->timestamp());
0183     m_seat->keyReleased(event->nativeScanCode() - 8);
0184 }
0185 
0186 void CompositorWindow::mouseMoveEvent(QMouseEvent *event)
0187 {
0188     QWidget::mouseMoveEvent(event);
0189     if (!m_seat->focusedPointerSurface()) {
0190         updateFocus();
0191     }
0192     m_seat->setTimestamp(event->timestamp());
0193     m_seat->setPointerPos(event->localPos().toPoint());
0194 }
0195 
0196 void CompositorWindow::mousePressEvent(QMouseEvent *event)
0197 {
0198     QWidget::mousePressEvent(event);
0199     if (!m_seat->focusedPointerSurface()) {
0200         if (!m_stackingOrder.isEmpty()) {
0201             m_seat->setFocusedPointerSurface(m_stackingOrder.last()->surface());
0202         }
0203     }
0204     m_seat->setTimestamp(event->timestamp());
0205     m_seat->pointerButtonPressed(event->button());
0206 }
0207 
0208 void CompositorWindow::mouseReleaseEvent(QMouseEvent *event)
0209 {
0210     QWidget::mouseReleaseEvent(event);
0211     m_seat->setTimestamp(event->timestamp());
0212     m_seat->pointerButtonReleased(event->button());
0213 }
0214 
0215 void CompositorWindow::wheelEvent(QWheelEvent *event)
0216 {
0217     QWidget::wheelEvent(event);
0218     m_seat->setTimestamp(event->timestamp());
0219     const QPoint &angle = event->angleDelta() / (8 * 15);
0220     if (angle.x() != 0) {
0221         m_seat->pointerAxis(Qt::Horizontal, angle.x());
0222     }
0223     if (angle.y() != 0) {
0224         m_seat->pointerAxis(Qt::Vertical, angle.y());
0225     }
0226 }
0227 
0228 int main(int argc, char **argv)
0229 {
0230     using namespace KWayland::Server;
0231     QApplication app(argc, argv);
0232 
0233     QCommandLineParser parser;
0234     parser.addHelpOption();
0235     QCommandLineOption xwaylandOption(QStringList{QStringLiteral("x"), QStringLiteral("xwayland")}, QStringLiteral("Start a rootless Xwayland server"));
0236     parser.addOption(xwaylandOption);
0237     parser.process(app);
0238 
0239     Display display;
0240     display.start();
0241     DataDeviceManagerInterface *ddm = display.createDataDeviceManager();
0242     ddm->create();
0243     CompositorInterface *compositor = display.createCompositor(&display);
0244     compositor->create();
0245     ShellInterface *shell = display.createShell();
0246     shell->create();
0247     display.createShm();
0248     OutputInterface *output = display.createOutput(&display);
0249     output->setPhysicalSize(QSize(269, 202));
0250     const QSize windowSize(1024, 768);
0251     output->addMode(windowSize);
0252     output->create();
0253     SeatInterface *seat = display.createSeat();
0254     seat->setHasKeyboard(true);
0255     seat->setHasPointer(true);
0256     seat->setName(QStringLiteral("testSeat0"));
0257     seat->create();
0258 
0259     CompositorWindow compositorWindow;
0260     compositorWindow.setSeat(seat);
0261     compositorWindow.setMinimumSize(windowSize);
0262     compositorWindow.setMaximumSize(windowSize);
0263     compositorWindow.setGeometry(QRect(QPoint(0, 0), windowSize));
0264     compositorWindow.show();
0265     QObject::connect(shell, &ShellInterface::surfaceCreated, &compositorWindow, &CompositorWindow::surfaceCreated);
0266 
0267     // start XWayland
0268     if (parser.isSet(xwaylandOption)) {
0269         // starts XWayland by forking and opening a pipe
0270         const int pipe = startXServer();
0271         if (pipe == -1) {
0272             exit(1);
0273         }
0274 
0275         QtConcurrent::run([pipe] {
0276             readDisplayFromPipe(pipe);
0277         });
0278     }
0279 
0280     return app.exec();
0281 }
0282 
0283 #include "renderingservertest.moc"