Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,78 @@
if(KWIN_BUILD_X11)
set(normalhintsbasesizetest_SRCS normalhintsbasesizetest.cpp)
add_executable(normalhintsbasesizetest ${normalhintsbasesizetest_SRCS})
target_link_libraries(normalhintsbasesizetest XCB::XCB XCB::ICCCM KF6::WindowSystem)
# next target
set(screenedgeshowtest_SRCS screenedgeshowtest.cpp)
add_executable(screenedgeshowtest ${screenedgeshowtest_SRCS})
target_link_libraries(screenedgeshowtest Qt::GuiPrivate Qt::Widgets KF6::ConfigCore KF6::WindowSystem Plasma::KWaylandClient ${XCB_XCB_LIBRARY})
add_executable(x11shadowreader x11shadowreader.cpp)
target_link_libraries(x11shadowreader XCB::XCB Qt::GuiPrivate Qt::Widgets KF6::ConfigCore KF6::WindowSystem)
add_executable(pointerconstraints pointerconstraintstest.cpp)
add_definitions(-DDIR="${CMAKE_CURRENT_SOURCE_DIR}")
target_link_libraries(pointerconstraints XCB::XCB Qt::Gui Qt::Quick Plasma::KWaylandClient)
endif()
add_executable(pointergestures pointergesturestest.cpp)
add_definitions(-DDIR="${CMAKE_CURRENT_SOURCE_DIR}")
target_link_libraries(pointergestures Qt::Gui Qt::Quick Plasma::KWaylandClient)
add_executable(cursorhotspottest cursorhotspottest.cpp)
target_link_libraries(cursorhotspottest Qt::Widgets)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
add_executable(xdgactivationtest-qt6 xdgactivationtest-qt6.cpp)
target_link_libraries(xdgactivationtest-qt6 Qt6::Widgets)
set(testServer_SRCS
waylandservertest.cpp
fakeoutput.cpp
)
add_executable(testServer ${testServer_SRCS})
target_link_libraries(testServer kwin Qt::CorePrivate)
find_package(Qt6Widgets ${QT_MIN_VERSION} CONFIG QUIET)
if (TARGET Qt::Widgets)
set(testRenderingServer_SRCS
renderingservertest.cpp
fakeoutput.cpp
)
add_executable(testRenderingServer ${testRenderingServer_SRCS})
target_link_libraries(testRenderingServer kwin Qt::Core Qt::Widgets)
endif()
add_executable(copyClient copyclient.cpp)
target_link_libraries(copyClient Plasma::KWaylandClient)
add_executable(pasteClient pasteclient.cpp)
target_link_libraries(pasteClient Qt::Core Plasma::KWaylandClient)
add_executable(touchClientTest touchclienttest.cpp)
target_link_libraries(touchClientTest Plasma::KWaylandClient)
add_executable(panelTest paneltest.cpp)
target_link_libraries(panelTest Plasma::KWaylandClient)
add_executable(subsurface-test subsurfacetest.cpp)
target_link_libraries(subsurface-test Qt::Core Qt::Gui Plasma::KWaylandClient)
add_executable(shadowTest shadowtest.cpp)
target_link_libraries(shadowTest Plasma::KWaylandClient)
if (TARGET Qt::Widgets)
add_executable(dpmsTest dpmstest.cpp)
target_link_libraries(dpmsTest Plasma::KWaylandClient Qt::Widgets)
endif()
add_executable(plasmasurface-test plasmasurfacetest.cpp)
target_link_libraries(plasmasurface-test Qt::Gui Plasma::KWaylandClient)
add_executable(xdgforeign-test xdgforeigntest.cpp)
target_link_libraries(xdgforeign-test Qt::Gui Plasma::KWaylandClient)
add_executable(xdg-test xdgtest.cpp)
target_link_libraries(xdg-test Qt::Gui Plasma::KWaylandClient)
@@ -0,0 +1,171 @@
/*
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 "KWayland/Client/compositor.h"
#include "KWayland/Client/connection_thread.h"
#include "KWayland/Client/datadevice.h"
#include "KWayland/Client/datadevicemanager.h"
#include "KWayland/Client/datasource.h"
#include "KWayland/Client/event_queue.h"
#include "KWayland/Client/keyboard.h"
#include "KWayland/Client/pointer.h"
#include "KWayland/Client/registry.h"
#include "KWayland/Client/seat.h"
#include "KWayland/Client/shell.h"
#include "KWayland/Client/shm_pool.h"
#include "KWayland/Client/surface.h"
// Qt
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
#include <QImage>
#include <QThread>
using namespace KWayland::Client;
class CopyClient : public QObject
{
Q_OBJECT
public:
explicit CopyClient(QObject *parent = nullptr);
virtual ~CopyClient();
void init();
private:
void setupRegistry(Registry *registry);
void render();
void copy(const QString &mimeType, qint32 fd);
QThread *m_connectionThread;
ConnectionThread *m_connectionThreadObject;
EventQueue *m_eventQueue = nullptr;
Compositor *m_compositor = nullptr;
DataDeviceManager *m_dataDeviceManager = nullptr;
DataDevice *m_dataDevice = nullptr;
DataSource *m_copySource = nullptr;
Seat *m_seat = nullptr;
Shell *m_shell = nullptr;
ShellSurface *m_shellSurface = nullptr;
ShmPool *m_shm = nullptr;
Surface *m_surface = nullptr;
};
CopyClient::CopyClient(QObject *parent)
: QObject(parent)
, m_connectionThread(new QThread(this))
, m_connectionThreadObject(new ConnectionThread())
{
}
CopyClient::~CopyClient()
{
m_connectionThread->quit();
m_connectionThread->wait();
m_connectionThreadObject->deleteLater();
}
void CopyClient::init()
{
connect(
m_connectionThreadObject,
&ConnectionThread::connected,
this,
[this] {
m_eventQueue = new EventQueue(this);
m_eventQueue->setup(m_connectionThreadObject);
Registry *registry = new Registry(this);
setupRegistry(registry);
},
Qt::QueuedConnection);
m_connectionThreadObject->moveToThread(m_connectionThread);
m_connectionThread->start();
m_connectionThreadObject->initConnection();
}
void CopyClient::setupRegistry(Registry *registry)
{
connect(registry, &Registry::compositorAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_compositor = registry->createCompositor(name, version, this);
});
connect(registry, &Registry::shellAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_shell = registry->createShell(name, version, this);
});
connect(registry, &Registry::shmAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_shm = registry->createShmPool(name, version, this);
});
connect(registry, &Registry::seatAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_seat = registry->createSeat(name, version, this);
connect(m_seat, &Seat::hasPointerChanged, this, [this] {
auto p = m_seat->createPointer(this);
connect(p, &Pointer::entered, this, [this](quint32 serial) {
if (m_copySource) {
m_dataDevice->setSelection(serial, m_copySource);
}
});
});
});
connect(registry, &Registry::dataDeviceManagerAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_dataDeviceManager = registry->createDataDeviceManager(name, version, this);
});
connect(registry, &Registry::interfacesAnnounced, this, [this] {
Q_ASSERT(m_compositor);
Q_ASSERT(m_dataDeviceManager);
Q_ASSERT(m_seat);
Q_ASSERT(m_shell);
Q_ASSERT(m_shm);
m_surface = m_compositor->createSurface(this);
Q_ASSERT(m_surface);
m_shellSurface = m_shell->createSurface(m_surface, this);
Q_ASSERT(m_shellSurface);
m_shellSurface->setFullscreen();
connect(m_shellSurface, &ShellSurface::sizeChanged, this, &CopyClient::render);
m_dataDevice = m_dataDeviceManager->getDataDevice(m_seat, this);
m_copySource = m_dataDeviceManager->createDataSource(this);
m_copySource->offer(QStringLiteral("text/plain"));
connect(m_copySource, &DataSource::sendDataRequested, this, &CopyClient::copy);
});
registry->setEventQueue(m_eventQueue);
registry->create(m_connectionThreadObject);
registry->setup();
}
void CopyClient::render()
{
const QSize &size = m_shellSurface->size();
auto buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef();
buffer->setUsed(true);
QImage image(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::green);
m_surface->attachBuffer(*buffer);
m_surface->damage(QRect(QPoint(0, 0), size));
m_surface->commit(Surface::CommitFlag::None);
buffer->setUsed(false);
}
void CopyClient::copy(const QString &mimeType, qint32 fd)
{
qDebug() << "Requested to copy for mimeType" << mimeType;
QFile c;
if (c.open(fd, QFile::WriteOnly, QFile::AutoCloseHandle)) {
c.write(QByteArrayLiteral("foo"));
c.close();
qDebug() << "Copied foo";
}
}
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
CopyClient client;
client.init();
return app.exec();
}
#include "copyclient.moc"
@@ -0,0 +1,142 @@
/*
SPDX-FileCopyrightText: 2017 Martin Flöser <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include <QApplication>
#include <QCursor>
#include <QMouseEvent>
#include <QPainter>
#include <QWidget>
class MouseCursorWidget : public QWidget
{
Q_OBJECT
public:
explicit MouseCursorWidget();
~MouseCursorWidget() override;
protected:
void paintEvent(QPaintEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
private:
QPoint m_cursorPos;
QCursor m_cursors[5];
int m_cursorIndex = 0;
};
namespace
{
QCursor createCenterHotspotCursor()
{
QPixmap cursor(64, 64);
cursor.fill(Qt::transparent);
QPainter p(&cursor);
p.setPen(Qt::black);
const QPoint center = cursor.rect().center();
p.drawLine(0, center.y(), center.x() - 1, center.y());
p.drawLine(center.x() + 1, center.y(), cursor.width(), center.y());
p.drawLine(center.x(), 0, center.x(), center.y() - 1);
p.drawLine(center.x(), center.y() + 1, center.x(), cursor.height());
return QCursor(cursor, 31, 31);
}
QCursor createTopLeftHotspotCursor()
{
QPixmap cursor(64, 64);
cursor.fill(Qt::transparent);
QPainter p(&cursor);
p.setPen(Qt::black);
p.drawLine(0, 1, 0, cursor.height());
p.drawLine(1, 0, cursor.width(), 0);
return QCursor(cursor, 0, 0);
}
QCursor createTopRightHotspotCursor()
{
QPixmap cursor(64, 64);
cursor.fill(Qt::transparent);
QPainter p(&cursor);
p.setPen(Qt::black);
p.drawLine(cursor.width() - 1, 1, cursor.width() - 1, cursor.height());
p.drawLine(0, 0, cursor.width() - 2, 0);
return QCursor(cursor, 63, 0);
}
QCursor createButtomRightHotspotCursor()
{
QPixmap cursor(64, 64);
cursor.fill(Qt::transparent);
QPainter p(&cursor);
p.setPen(Qt::black);
p.drawLine(cursor.width() - 1, 0, cursor.width() - 1, cursor.height() - 2);
p.drawLine(0, cursor.height() - 1, cursor.width() - 2, cursor.height() - 1);
return QCursor(cursor, 63, 63);
}
QCursor createButtomLeftHotspotCursor()
{
QPixmap cursor(64, 64);
cursor.fill(Qt::transparent);
QPainter p(&cursor);
p.setPen(Qt::black);
p.drawLine(0, 0, 0, cursor.height() - 2);
p.drawLine(1, cursor.height() - 1, cursor.width(), cursor.height() - 1);
return QCursor(cursor, 0, 63);
}
}
MouseCursorWidget::MouseCursorWidget()
: QWidget()
{
setMouseTracking(true);
// create cursors
m_cursors[0] = createCenterHotspotCursor();
m_cursors[1] = createTopLeftHotspotCursor();
m_cursors[2] = createTopRightHotspotCursor();
m_cursors[3] = createButtomRightHotspotCursor();
m_cursors[4] = createButtomLeftHotspotCursor();
setCursor(m_cursors[m_cursorIndex]);
}
MouseCursorWidget::~MouseCursorWidget() = default;
void MouseCursorWidget::paintEvent(QPaintEvent *event)
{
QPainter p(this);
p.fillRect(0, 0, width(), height(), Qt::white);
if (geometry().contains(m_cursorPos)) {
p.setPen(Qt::red);
p.drawPoint(m_cursorPos);
}
}
void MouseCursorWidget::mouseMoveEvent(QMouseEvent *event)
{
m_cursorPos = event->pos();
update();
}
void MouseCursorWidget::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Space) {
m_cursorIndex = (m_cursorIndex + 1) % 5;
setCursor(m_cursors[m_cursorIndex]);
}
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MouseCursorWidget widget;
widget.show();
return app.exec();
}
#include "cursorhotspottest.moc"
@@ -0,0 +1,167 @@
/*
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "KWayland/Client/dpms.h"
#include "KWayland/Client/connection_thread.h"
#include "KWayland/Client/output.h"
#include "KWayland/Client/registry.h"
#include <QApplication>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
using namespace KWayland::Client;
static QString modeToString(Dpms::Mode mode)
{
switch (mode) {
case Dpms::Mode::On:
return QStringLiteral("On");
case Dpms::Mode::Standby:
return QStringLiteral("Standby");
case Dpms::Mode::Suspend:
return QStringLiteral("Suspend");
case Dpms::Mode::Off:
return QStringLiteral("Off");
default:
Q_UNREACHABLE();
}
}
QString supportedToString(bool supported)
{
return supported ? QStringLiteral("Yes") : QStringLiteral("No");
}
static QLayout *setupOutput(Registry::AnnouncedInterface outputInterface, Registry *registry, DpmsManager *manager)
{
Output *output = registry->createOutput(outputInterface.name, outputInterface.version, registry);
QLabel *label = new QLabel(output->model());
QObject::connect(
output,
&Output::changed,
label,
[label, output] {
label->setText(output->model());
},
Qt::QueuedConnection);
Dpms *dpms = nullptr;
if (manager) {
dpms = manager->getDpms(output, output);
}
QFormLayout *dpmsForm = new QFormLayout;
bool supported = dpms ? dpms->isSupported() : false;
QLabel *supportedLabel = new QLabel(supportedToString(supported));
dpmsForm->addRow(QStringLiteral("Supported:"), supportedLabel);
QLabel *modeLabel = new QLabel(modeToString(dpms ? dpms->mode() : Dpms::Mode::On));
dpmsForm->addRow(QStringLiteral("Mode:"), modeLabel);
QPushButton *standbyButton = new QPushButton(QStringLiteral("Standby"));
QPushButton *suspendButton = new QPushButton(QStringLiteral("Suspend"));
QPushButton *offButton = new QPushButton(QStringLiteral("Off"));
standbyButton->setEnabled(supported);
suspendButton->setEnabled(supported);
offButton->setEnabled(supported);
QDialogButtonBox *bg = new QDialogButtonBox;
bg->addButton(standbyButton, QDialogButtonBox::ActionRole);
bg->addButton(suspendButton, QDialogButtonBox::ActionRole);
bg->addButton(offButton, QDialogButtonBox::ActionRole);
if (dpms) {
QObject::connect(
dpms,
&Dpms::supportedChanged,
supportedLabel,
[supportedLabel, dpms, standbyButton, suspendButton, offButton] {
const bool supported = dpms->isSupported();
supportedLabel->setText(supportedToString(supported));
standbyButton->setEnabled(supported);
suspendButton->setEnabled(supported);
offButton->setEnabled(supported);
},
Qt::QueuedConnection);
QObject::connect(
dpms,
&Dpms::modeChanged,
modeLabel,
[modeLabel, dpms] {
modeLabel->setText(modeToString(dpms->mode()));
},
Qt::QueuedConnection);
QObject::connect(standbyButton, &QPushButton::clicked, dpms, [dpms] {
dpms->requestMode(Dpms::Mode::Standby);
});
QObject::connect(suspendButton, &QPushButton::clicked, dpms, [dpms] {
dpms->requestMode(Dpms::Mode::Suspend);
});
QObject::connect(offButton, &QPushButton::clicked, dpms, [dpms] {
dpms->requestMode(Dpms::Mode::Off);
});
}
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(label);
layout->addLayout(dpmsForm);
layout->addWidget(bg);
return layout;
}
int main(int argc, char **argv)
{
qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("wayland"));
QApplication app(argc, argv);
QWidget window;
ConnectionThread *connection = ConnectionThread::fromApplication();
Registry registry;
registry.create(connection);
QObject::connect(
&registry,
&Registry::interfacesAnnounced,
&app,
[&registry, &window] {
const bool hasDpms = registry.hasInterface(Registry::Interface::Dpms);
QLabel *hasDpmsLabel = new QLabel(&window);
if (hasDpms) {
hasDpmsLabel->setText(QStringLiteral("Compositor provides a DpmsManager"));
} else {
hasDpmsLabel->setText(QStringLiteral("Compositor does not provide a DpmsManager"));
}
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(hasDpmsLabel);
QFrame *hline = new QFrame;
hline->setFrameShape(QFrame::HLine);
layout->addWidget(hline);
DpmsManager *dpmsManager = nullptr;
if (hasDpms) {
const auto dpmsData = registry.interface(Registry::Interface::Dpms);
dpmsManager = registry.createDpmsManager(dpmsData.name, dpmsData.version);
}
// get all Outputs
const auto outputs = registry.interfaces(Registry::Interface::Output);
for (auto o : outputs) {
layout->addLayout(setupOutput(o, &registry, dpmsManager));
QFrame *hline = new QFrame;
hline->setFrameShape(QFrame::HLine);
layout->addWidget(hline);
}
window.setLayout(layout);
window.show();
},
Qt::QueuedConnection);
registry.setup();
return app.exec();
}
@@ -0,0 +1,94 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "fakeoutput.h"
FakeOutput::FakeOutput()
{
setMode(QSize(1024, 720), 60000);
}
KWin::RenderLoop *FakeOutput::renderLoop() const
{
return nullptr;
}
void FakeOutput::setMode(QSize size, uint32_t refreshRate)
{
auto mode = std::make_shared<KWin::OutputMode>(size, refreshRate);
State state = m_state;
state.modes = {mode};
state.currentMode = mode;
setState(state);
}
void FakeOutput::setTransform(KWin::OutputTransform transform)
{
State state = m_state;
state.transform = transform;
setState(state);
}
void FakeOutput::moveTo(const QPoint &pos)
{
State state = m_state;
state.position = pos;
setState(state);
}
void FakeOutput::setScale(qreal scale)
{
State state = m_state;
state.scale = scale;
setState(state);
}
void FakeOutput::setSubPixel(SubPixel subPixel)
{
setInformation({
.subPixel = subPixel,
});
}
void FakeOutput::setDpmsSupported(bool supported)
{
setInformation({
.capabilities = supported ? Capability::Dpms : Capabilities(),
});
}
void FakeOutput::setPhysicalSize(QSize size)
{
setInformation({
.physicalSize = size,
});
}
void FakeOutput::setName(const QString &name)
{
Information info = m_information;
info.name = name;
setInformation(info);
}
void FakeOutput::setManufacturer(const QString &manufacturer)
{
Information info = m_information;
info.manufacturer = manufacturer;
setInformation(info);
}
void FakeOutput::setModel(const QString &model)
{
Information info = m_information;
info.model = model;
setInformation(info);
}
#include "moc_fakeoutput.cpp"
@@ -0,0 +1,31 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/output.h"
class FakeOutput : public KWin::Output
{
Q_OBJECT
public:
FakeOutput();
KWin::RenderLoop *renderLoop() const override;
void setName(const QString &name);
void setManufacturer(const QString &manufacturer);
void setModel(const QString &model);
void setMode(QSize size, uint32_t refreshRate);
void setSubPixel(SubPixel subPixel);
void setDpmsSupported(bool supported);
void setPhysicalSize(QSize size);
void setTransform(KWin::OutputTransform transform);
void moveTo(const QPoint &pos);
void setScale(qreal scale);
};
@@ -0,0 +1,73 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick.Controls
import QtQuick.Layouts
GridLayout {
columns: 2
Label {
text: "Normal:"
}
TextField {
}
Label {
text: "Digits:"
}
TextField {
inputMethodHints: Qt.ImhDigitsOnly
}
Label {
text: "Numbers:"
}
TextField {
inputMethodHints: Qt.ImhFormattedNumbersOnly
}
Label {
text: "Uppercase:"
}
TextField {
inputMethodHints: Qt.ImhUppercaseOnly
}
Label {
text: "Lowercase:"
}
TextField {
inputMethodHints: Qt.ImhLowercaseOnly
}
Label {
text: "Phone:"
}
TextField {
inputMethodHints: Qt.ImhDialableCharactersOnly
}
Label {
text: "Email:"
}
TextField {
inputMethodHints: Qt.ImhEmailCharactersOnly
}
Label {
text: "Url:"
}
TextField {
inputMethodHints: Qt.ImhUrlCharactersOnly
}
Label {
text: "Date:"
}
TextField {
inputMethodHints: Qt.ImhDate
}
Label {
text: "Time:"
}
TextField {
inputMethodHints: Qt.ImhTime
}
}
@@ -0,0 +1,89 @@
/*
SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "qwayland-kde-lockscreen-overlay-v1.h"
#include <KWindowSystem>
#include <QWaylandClientExtensionTemplate>
#include <QtWidgets>
#include <qpa/qplatformnativeinterface.h>
class WaylandAboveLockscreen : public QWaylandClientExtensionTemplate<WaylandAboveLockscreen>, public QtWayland::kde_lockscreen_overlay_v1
{
public:
WaylandAboveLockscreen()
: QWaylandClientExtensionTemplate<WaylandAboveLockscreen>(1)
{
QMetaObject::invokeMethod(this, "addRegistryListener");
}
void allowWindow(QWindow *window)
{
QPlatformNativeInterface *native = qGuiApp->platformNativeInterface();
wl_surface *surface = reinterpret_cast<wl_surface *>(native->nativeResourceForWindow(QByteArrayLiteral("surface"), window));
allow(surface);
}
};
class WidgetAllower : public QObject
{
public:
WidgetAllower(QWidget *widget)
: QObject(widget)
, m_widget(widget)
{
widget->installEventFilter(this);
}
bool eventFilter(QObject * /*watched*/, QEvent *event) override
{
if (auto w = m_widget->windowHandle()) {
WaylandAboveLockscreen aboveLockscreen;
Q_ASSERT(aboveLockscreen.isInitialized());
aboveLockscreen.allowWindow(w);
deleteLater();
}
return false;
}
QWidget *const m_widget;
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget window1(nullptr, Qt::Window);
window1.setWindowTitle("Window 1");
window1.setLayout(new QVBoxLayout);
QPushButton p("Lock && Raise the Window 2");
window1.layout()->addWidget(&p);
window1.show();
QWidget window2(nullptr, Qt::Window);
window2.setWindowTitle("Window 2");
window2.setLayout(new QVBoxLayout);
QPushButton p2("Close");
window2.layout()->addWidget(&p2);
new WidgetAllower(&window2);
auto raiseWindow2 = [&] {
KWindowSystem::requestXdgActivationToken(window2.windowHandle(), 0, "lockscreenoverlaytest.desktop");
};
QObject::connect(KWindowSystem::self(), &KWindowSystem::xdgActivationTokenArrived, &window2, [&window2](int, const QString &token) {
KWindowSystem::setCurrentXdgActivationToken(token);
KWindowSystem::activateWindow(window2.windowHandle());
});
QObject::connect(&p, &QPushButton::clicked, &app, [&] {
QProcess::execute("loginctl", {"lock-session"});
window2.showFullScreen();
QTimer::singleShot(3000, &app, raiseWindow2);
});
QObject::connect(&p2, &QPushButton::clicked, &window2, &QWidget::close);
return app.exec();
}
@@ -0,0 +1,9 @@
# copy to $(qtpaths --install-prefix)/share/applications/
# remember to change the Exec line to your builddir path
[Desktop Entry]
Exec=absolute/path/to/the/executable/bin/lockscreenoverlaytest
Type=Application
X-KDE-Wayland-Interfaces=kde_lockscreen_overlay_v1
NoDisplay=true
Name=KWin LockScreen Overay Test
@@ -0,0 +1,107 @@
/*
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include <cstdlib>
#include <iostream>
#include <xcb/xcb.h>
#include <xcb/xcb_icccm.h>
/*
* This is a small test app to ensure that KWin calculates the size of a window correctly
* according to ICCCM section 4.1.2.3
*
* The application creates a window and specifies the normal hints with:
* * min size
* * base size
* * size increment
*
* With these normal flags the size should be calculated as:
* width = base_width + (i * width_inc)
* height = base_height + (j * height_inc)
*
* With i and j being non-negative integers!
*
* This application waits for configure notify events and calculates the i and j and
* tries to calculate the size it expects. If it doesn't match it exits with a non-zero
* exit code and prints the mismatching i and/or j value to stderr.
*
* To simply quit the application just click into the window. This will return with exit code 0.
*/
int main(int, char **)
{
int screenNumber;
xcb_connection_t *c = xcb_connect(nullptr, &screenNumber);
if (xcb_connection_has_error(c)) {
xcb_disconnect(c);
return 1;
}
auto getScreen = [=]() {
const xcb_setup_t *setup = xcb_get_setup(c);
auto it = xcb_setup_roots_iterator(setup);
for (int i = 0; i < screenNumber; ++i) {
xcb_screen_next(&it);
}
return it.data;
};
xcb_screen_t *screen = getScreen();
xcb_window_t w = xcb_generate_id(c);
const uint32_t values[2] = {
screen->white_pixel,
XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_STRUCTURE_NOTIFY};
xcb_create_window(c, 0, w, screen->root, 0, 0, 365, 104, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
screen->root_visual, XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, values);
// set the normal hints
xcb_size_hints_t hints;
hints.flags = XCB_ICCCM_SIZE_HINT_P_MIN_SIZE | XCB_ICCCM_SIZE_HINT_BASE_SIZE | XCB_ICCCM_SIZE_HINT_P_RESIZE_INC;
hints.min_width = 365;
hints.min_height = 104;
hints.base_width = 15;
hints.base_height = 64;
hints.width_inc = 9;
hints.height_inc = 18;
xcb_icccm_set_wm_normal_hints(c, w, &hints);
// and map the window
xcb_map_window(c, w);
xcb_flush(c);
bool error = false;
while (xcb_generic_event_t *event = xcb_wait_for_event(c)) {
bool exit = false;
if ((event->response_type & ~0x80) == XCB_BUTTON_RELEASE) {
exit = true;
} else if ((event->response_type & ~0x80) == XCB_CONFIGURE_NOTIFY) {
auto *ce = reinterpret_cast<xcb_configure_notify_event_t *>(event);
const double i = (ce->width - hints.base_width) / (double)hints.width_inc;
const double j = (ce->height - hints.base_height) / (double)hints.height_inc;
// according to ICCCM the size should be:
// width = base_width + (i * width_inc)
// height = base_height + (j * height_inc)
// thus if the window manager configured correctly we get the same result
if (hints.base_width + (int(i) * hints.width_inc) != ce->width) {
std::cerr << "Incorrect width - i factor is " << i << std::endl;
exit = true;
error = true;
}
if (hints.base_height + (int(j) * hints.height_inc) != ce->height) {
std::cerr << "Incorrect height - j factor is " << i << std::endl;
exit = true;
error = true;
}
}
free(event);
if (exit) {
break;
}
}
xcb_disconnect(c);
return error ? 1 : 0;
}
@@ -0,0 +1,292 @@
/*
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "KWayland/Client/compositor.h"
#include "KWayland/Client/connection_thread.h"
#include "KWayland/Client/datadevice.h"
#include "KWayland/Client/datadevicemanager.h"
#include "KWayland/Client/dataoffer.h"
#include "KWayland/Client/event_queue.h"
#include "KWayland/Client/keyboard.h"
#include "KWayland/Client/plasmashell.h"
#include "KWayland/Client/plasmawindowmanagement.h"
#include "KWayland/Client/pointer.h"
#include "KWayland/Client/registry.h"
#include "KWayland/Client/seat.h"
#include "KWayland/Client/shell.h"
#include "KWayland/Client/shm_pool.h"
#include "KWayland/Client/surface.h"
// Qt
#include <QDebug>
#include <QFile>
#include <QGuiApplication>
#include <QImage>
#include <QMimeType>
#include <QThread>
// system
#include <unistd.h>
#include <linux/input.h>
using namespace KWayland::Client;
class PanelTest : public QObject
{
Q_OBJECT
public:
explicit PanelTest(QObject *parent = nullptr);
virtual ~PanelTest();
void init();
private:
void setupRegistry(Registry *registry);
void render();
void showTooltip(const QPointF &pos);
void hideTooltip();
void moveTooltip(const QPointF &pos);
QThread *m_connectionThread;
ConnectionThread *m_connectionThreadObject;
EventQueue *m_eventQueue = nullptr;
Compositor *m_compositor = nullptr;
Seat *m_seat = nullptr;
Shell *m_shell = nullptr;
ShellSurface *m_shellSurface = nullptr;
ShmPool *m_shm = nullptr;
Surface *m_surface = nullptr;
PlasmaShell *m_plasmaShell = nullptr;
PlasmaShellSurface *m_plasmaShellSurface = nullptr;
PlasmaWindowManagement *m_windowManagement = nullptr;
struct
{
Surface *surface = nullptr;
ShellSurface *shellSurface = nullptr;
PlasmaShellSurface *plasmaSurface = nullptr;
bool visible = false;
} m_tooltip;
};
PanelTest::PanelTest(QObject *parent)
: QObject(parent)
, m_connectionThread(new QThread(this))
, m_connectionThreadObject(new ConnectionThread())
{
}
PanelTest::~PanelTest()
{
m_connectionThread->quit();
m_connectionThread->wait();
m_connectionThreadObject->deleteLater();
}
void PanelTest::init()
{
connect(
m_connectionThreadObject,
&ConnectionThread::connected,
this,
[this] {
m_eventQueue = new EventQueue(this);
m_eventQueue->setup(m_connectionThreadObject);
Registry *registry = new Registry(this);
setupRegistry(registry);
},
Qt::QueuedConnection);
m_connectionThreadObject->moveToThread(m_connectionThread);
m_connectionThread->start();
m_connectionThreadObject->initConnection();
}
void PanelTest::showTooltip(const QPointF &pos)
{
if (!m_tooltip.surface) {
m_tooltip.surface = m_compositor->createSurface(this);
m_tooltip.shellSurface = m_shell->createSurface(m_tooltip.surface, this);
if (m_plasmaShell) {
m_tooltip.plasmaSurface = m_plasmaShell->createSurface(m_tooltip.surface, this);
}
}
m_tooltip.shellSurface->setTransient(m_surface, pos.toPoint());
if (!m_tooltip.visible) {
const QSize size(100, 50);
auto buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef();
buffer->setUsed(true);
QImage image(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::red);
m_tooltip.surface->attachBuffer(*buffer);
m_tooltip.surface->damage(QRect(QPoint(0, 0), size));
m_tooltip.surface->commit(Surface::CommitFlag::None);
m_tooltip.visible = true;
}
}
void PanelTest::hideTooltip()
{
if (!m_tooltip.visible) {
return;
}
m_tooltip.surface->attachBuffer(Buffer::Ptr());
m_tooltip.surface->commit(Surface::CommitFlag::None);
m_tooltip.visible = false;
}
void PanelTest::moveTooltip(const QPointF &pos)
{
if (m_tooltip.plasmaSurface) {
m_tooltip.plasmaSurface->setPosition(QPoint(10, 0) + pos.toPoint());
}
}
void PanelTest::setupRegistry(Registry *registry)
{
connect(registry, &Registry::compositorAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_compositor = registry->createCompositor(name, version, this);
});
connect(registry, &Registry::shellAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_shell = registry->createShell(name, version, this);
});
connect(registry, &Registry::shmAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_shm = registry->createShmPool(name, version, this);
});
connect(registry, &Registry::seatAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_seat = registry->createSeat(name, version, this);
connect(m_seat, &Seat::hasPointerChanged, this, [this](bool has) {
if (!has) {
return;
}
auto p = m_seat->createPointer(this);
connect(p, &Pointer::buttonStateChanged, this, [this](quint32 serial, quint32 time, quint32 button, KWayland::Client::Pointer::ButtonState state) {
if (!m_windowManagement) {
return;
}
if (state == Pointer::ButtonState::Released) {
return;
}
if (button == BTN_LEFT) {
m_windowManagement->showDesktop();
} else if (button == BTN_RIGHT) {
m_windowManagement->hideDesktop();
}
});
connect(p, &Pointer::entered, this, [this, p](quint32 serial, const QPointF &relativeToSurface) {
if (p->enteredSurface() == m_surface) {
showTooltip(relativeToSurface);
}
});
connect(p, &Pointer::motion, this, [this, p](const QPointF &relativeToSurface) {
if (p->enteredSurface() == m_surface) {
moveTooltip(relativeToSurface);
}
});
connect(p, &Pointer::left, this, [this] {
hideTooltip();
});
});
});
connect(registry, &Registry::plasmaShellAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_plasmaShell = registry->createPlasmaShell(name, version, this);
});
connect(registry, &Registry::plasmaWindowManagementAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_windowManagement = registry->createPlasmaWindowManagement(name, version, this);
connect(m_windowManagement, &PlasmaWindowManagement::showingDesktopChanged, this, [](bool set) {
qDebug() << "Showing desktop changed, new state: " << set;
});
connect(m_windowManagement, &PlasmaWindowManagement::windowCreated, this, [this](PlasmaWindow *w) {
connect(w, &PlasmaWindow::titleChanged, this, [w] {
qDebug() << "Window title changed to: " << w->title();
});
connect(w, &PlasmaWindow::activeChanged, this, [w] {
qDebug() << "Window active changed: " << w->isActive();
});
connect(w, &PlasmaWindow::maximizedChanged, this, [w] {
qDebug() << "Window maximized changed: " << w->isMaximized();
});
connect(w, &PlasmaWindow::maximizedChanged, this, [w] {
qDebug() << "Window minimized changed: " << w->isMinimized();
});
connect(w, &PlasmaWindow::keepAboveChanged, this, [w] {
qDebug() << "Window keep above changed: " << w->isKeepAbove();
});
connect(w, &PlasmaWindow::keepBelowChanged, this, [w] {
qDebug() << "Window keep below changed: " << w->isKeepBelow();
});
connect(w, &PlasmaWindow::onAllDesktopsChanged, this, [w] {
qDebug() << "Window on all desktops changed: " << w->isOnAllDesktops();
});
connect(w, &PlasmaWindow::fullscreenChanged, this, [w] {
qDebug() << "Window full screen changed: " << w->isFullscreen();
});
connect(w, &PlasmaWindow::demandsAttentionChanged, this, [w] {
qDebug() << "Window demands attention changed: " << w->isDemandingAttention();
});
connect(w, &PlasmaWindow::closeableChanged, this, [w] {
qDebug() << "Window is closeable changed: " << w->isCloseable();
});
connect(w, &PlasmaWindow::minimizeableChanged, this, [w] {
qDebug() << "Window is minimizeable changed: " << w->isMinimizeable();
});
connect(w, &PlasmaWindow::maximizeableChanged, this, [w] {
qDebug() << "Window is maximizeable changed: " << w->isMaximizeable();
});
connect(w, &PlasmaWindow::fullscreenableChanged, this, [w] {
qDebug() << "Window is fullscreenable changed: " << w->isFullscreenable();
});
connect(w, &PlasmaWindow::iconChanged, this, [w] {
qDebug() << "Window icon changed: " << w->icon().name();
});
});
});
connect(registry, &Registry::interfacesAnnounced, this, [this] {
Q_ASSERT(m_compositor);
Q_ASSERT(m_seat);
Q_ASSERT(m_shell);
Q_ASSERT(m_shm);
m_surface = m_compositor->createSurface(this);
Q_ASSERT(m_surface);
m_shellSurface = m_shell->createSurface(m_surface, this);
Q_ASSERT(m_shellSurface);
m_shellSurface->setToplevel();
connect(m_shellSurface, &ShellSurface::sizeChanged, this, &PanelTest::render);
if (m_plasmaShell) {
m_plasmaShellSurface = m_plasmaShell->createSurface(m_surface, this);
m_plasmaShellSurface->setPosition(QPoint(10, 0));
m_plasmaShellSurface->setRole(PlasmaShellSurface::Role::Panel);
}
render();
});
registry->setEventQueue(m_eventQueue);
registry->create(m_connectionThreadObject);
registry->setup();
}
void PanelTest::render()
{
const QSize &size = m_shellSurface->size().isValid() ? m_shellSurface->size() : QSize(300, 20);
auto buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef();
buffer->setUsed(true);
QImage image(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::blue);
m_surface->attachBuffer(*buffer);
m_surface->damage(QRect(QPoint(0, 0), size));
m_surface->commit(Surface::CommitFlag::None);
buffer->setUsed(false);
}
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
PanelTest client;
client.init();
return app.exec();
}
#include "paneltest.moc"
@@ -0,0 +1,177 @@
/*
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 "KWayland/Client/compositor.h"
#include "KWayland/Client/connection_thread.h"
#include "KWayland/Client/datadevice.h"
#include "KWayland/Client/datadevicemanager.h"
#include "KWayland/Client/dataoffer.h"
#include "KWayland/Client/event_queue.h"
#include "KWayland/Client/keyboard.h"
#include "KWayland/Client/pointer.h"
#include "KWayland/Client/registry.h"
#include "KWayland/Client/seat.h"
#include "KWayland/Client/shell.h"
#include "KWayland/Client/shm_pool.h"
#include "KWayland/Client/surface.h"
// Qt
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
#include <QImage>
#include <QMimeType>
#include <QThreadPool>
// system
#include <unistd.h>
using namespace KWayland::Client;
class PasteClient : public QObject
{
Q_OBJECT
public:
explicit PasteClient(QObject *parent = nullptr);
virtual ~PasteClient();
void init();
private:
void setupRegistry(Registry *registry);
void render();
QThread *m_connectionThread;
ConnectionThread *m_connectionThreadObject;
EventQueue *m_eventQueue = nullptr;
Compositor *m_compositor = nullptr;
DataDeviceManager *m_dataDeviceManager = nullptr;
DataDevice *m_dataDevice = nullptr;
Seat *m_seat = nullptr;
Shell *m_shell = nullptr;
ShellSurface *m_shellSurface = nullptr;
ShmPool *m_shm = nullptr;
Surface *m_surface = nullptr;
};
PasteClient::PasteClient(QObject *parent)
: QObject(parent)
, m_connectionThread(new QThread(this))
, m_connectionThreadObject(new ConnectionThread())
{
}
PasteClient::~PasteClient()
{
m_connectionThread->quit();
m_connectionThread->wait();
m_connectionThreadObject->deleteLater();
}
void PasteClient::init()
{
connect(
m_connectionThreadObject,
&ConnectionThread::connected,
this,
[this] {
m_eventQueue = new EventQueue(this);
m_eventQueue->setup(m_connectionThreadObject);
Registry *registry = new Registry(this);
setupRegistry(registry);
},
Qt::QueuedConnection);
m_connectionThreadObject->moveToThread(m_connectionThread);
m_connectionThread->start();
m_connectionThreadObject->initConnection();
}
void PasteClient::setupRegistry(Registry *registry)
{
connect(registry, &Registry::compositorAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_compositor = registry->createCompositor(name, version, this);
});
connect(registry, &Registry::shellAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_shell = registry->createShell(name, version, this);
});
connect(registry, &Registry::shmAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_shm = registry->createShmPool(name, version, this);
});
connect(registry, &Registry::seatAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_seat = registry->createSeat(name, version, this);
});
connect(registry, &Registry::dataDeviceManagerAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_dataDeviceManager = registry->createDataDeviceManager(name, version, this);
});
connect(registry, &Registry::interfacesAnnounced, this, [this] {
Q_ASSERT(m_compositor);
Q_ASSERT(m_dataDeviceManager);
Q_ASSERT(m_seat);
Q_ASSERT(m_shell);
Q_ASSERT(m_shm);
m_surface = m_compositor->createSurface(this);
Q_ASSERT(m_surface);
m_shellSurface = m_shell->createSurface(m_surface, this);
Q_ASSERT(m_shellSurface);
m_shellSurface->setFullscreen();
connect(m_shellSurface, &ShellSurface::sizeChanged, this, &PasteClient::render);
m_dataDevice = m_dataDeviceManager->getDataDevice(m_seat, this);
connect(m_dataDevice, &DataDevice::selectionOffered, this, [this] {
auto dataOffer = m_dataDevice->offeredSelection();
if (!dataOffer) {
return;
}
const auto &mimeTypes = dataOffer->offeredMimeTypes();
auto it = std::find_if(mimeTypes.constBegin(), mimeTypes.constEnd(), [](const QMimeType &type) {
return type.inherits(QStringLiteral("text/plain"));
});
if (it == mimeTypes.constEnd()) {
return;
}
int pipeFds[2];
if (pipe(pipeFds) != 0) {
return;
}
dataOffer->receive((*it).name(), pipeFds[1]);
close(pipeFds[1]);
QThreadPool::globalInstance()->start([pipeFds] {
QFile readPipe;
if (readPipe.open(pipeFds[0], QIODevice::ReadOnly)) {
qDebug() << "Pasted: " << readPipe.readLine();
}
close(pipeFds[0]);
QCoreApplication::quit();
});
});
});
registry->setEventQueue(m_eventQueue);
registry->create(m_connectionThreadObject);
registry->setup();
}
void PasteClient::render()
{
const QSize &size = m_shellSurface->size();
auto buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef();
buffer->setUsed(true);
QImage image(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::blue);
m_surface->attachBuffer(*buffer);
m_surface->damage(QRect(QPoint(0, 0), size));
m_surface->commit(Surface::CommitFlag::None);
buffer->setUsed(false);
}
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
PasteClient client;
client.init();
return app.exec();
}
#include "pasteclient.moc"
@@ -0,0 +1,200 @@
/*
SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "KWayland/Client/compositor.h"
#include "KWayland/Client/connection_thread.h"
#include "KWayland/Client/event_queue.h"
#include "KWayland/Client/plasmashell.h"
#include "KWayland/Client/registry.h"
#include "KWayland/Client/shell.h"
#include "KWayland/Client/shm_pool.h"
#include "KWayland/Client/surface.h"
// Qt
#include <QCommandLineParser>
#include <QGuiApplication>
#include <QImage>
#include <QThread>
using namespace KWayland::Client;
class PlasmaSurfaceTest : public QObject
{
Q_OBJECT
public:
explicit PlasmaSurfaceTest(QObject *parent = nullptr);
virtual ~PlasmaSurfaceTest();
void init();
void setRole(PlasmaShellSurface::Role role)
{
m_role = role;
}
void setSkipTaskbar(bool set)
{
m_skipTaskbar = set;
}
void setSkipSwitcher(bool set)
{
m_skipSwitcher = set;
}
private:
void setupRegistry(Registry *registry);
void render();
QThread *m_connectionThread;
ConnectionThread *m_connectionThreadObject;
EventQueue *m_eventQueue = nullptr;
Compositor *m_compositor = nullptr;
Shell *m_shell = nullptr;
ShellSurface *m_shellSurface = nullptr;
ShmPool *m_shm = nullptr;
Surface *m_surface = nullptr;
PlasmaShell *m_plasmaShell = nullptr;
PlasmaShellSurface *m_plasmaShellSurface = nullptr;
PlasmaShellSurface::Role m_role = PlasmaShellSurface::Role::Normal;
bool m_skipTaskbar = false;
bool m_skipSwitcher = false;
};
PlasmaSurfaceTest::PlasmaSurfaceTest(QObject *parent)
: QObject(parent)
, m_connectionThread(new QThread(this))
, m_connectionThreadObject(new ConnectionThread())
{
}
PlasmaSurfaceTest::~PlasmaSurfaceTest()
{
m_connectionThread->quit();
m_connectionThread->wait();
m_connectionThreadObject->deleteLater();
}
void PlasmaSurfaceTest::init()
{
connect(
m_connectionThreadObject,
&ConnectionThread::connected,
this,
[this] {
m_eventQueue = new EventQueue(this);
m_eventQueue->setup(m_connectionThreadObject);
Registry *registry = new Registry(this);
setupRegistry(registry);
},
Qt::QueuedConnection);
m_connectionThreadObject->moveToThread(m_connectionThread);
m_connectionThread->start();
m_connectionThreadObject->initConnection();
}
void PlasmaSurfaceTest::setupRegistry(Registry *registry)
{
connect(registry, &Registry::compositorAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_compositor = registry->createCompositor(name, version, this);
});
connect(registry, &Registry::shellAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_shell = registry->createShell(name, version, this);
});
connect(registry, &Registry::shmAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_shm = registry->createShmPool(name, version, this);
});
connect(registry, &Registry::plasmaShellAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_plasmaShell = registry->createPlasmaShell(name, version, this);
m_plasmaShell->setEventQueue(m_eventQueue);
});
connect(registry, &Registry::interfacesAnnounced, this, [this] {
Q_ASSERT(m_compositor);
Q_ASSERT(m_shell);
Q_ASSERT(m_shm);
Q_ASSERT(m_plasmaShell);
m_surface = m_compositor->createSurface(this);
Q_ASSERT(m_surface);
m_shellSurface = m_shell->createSurface(m_surface, this);
Q_ASSERT(m_shellSurface);
m_shellSurface->setToplevel();
connect(m_shellSurface, &ShellSurface::sizeChanged, this, &PlasmaSurfaceTest::render);
m_plasmaShellSurface = m_plasmaShell->createSurface(m_surface, this);
Q_ASSERT(m_plasmaShellSurface);
m_plasmaShellSurface->setSkipTaskbar(m_skipTaskbar);
m_plasmaShellSurface->setSkipSwitcher(m_skipSwitcher);
m_plasmaShellSurface->setRole(m_role);
render();
});
registry->setEventQueue(m_eventQueue);
registry->create(m_connectionThreadObject);
registry->setup();
}
void PlasmaSurfaceTest::render()
{
const QSize &size = m_shellSurface->size().isValid() ? m_shellSurface->size() : QSize(300, 200);
auto buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef();
buffer->setUsed(true);
QImage image(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
image.fill(QColor(255, 255, 255, 128));
m_surface->attachBuffer(*buffer);
m_surface->damage(QRect(QPoint(0, 0), size));
m_surface->commit(Surface::CommitFlag::None);
buffer->setUsed(false);
}
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
QCommandLineParser parser;
parser.addHelpOption();
QCommandLineOption notificationOption(QStringLiteral("notification"));
parser.addOption(notificationOption);
QCommandLineOption criticalNotificationOption(QStringLiteral("criticalNotification"));
parser.addOption(criticalNotificationOption);
QCommandLineOption appletPopupOption(QStringLiteral("appletPopup"));
parser.addOption(appletPopupOption);
QCommandLineOption panelOption(QStringLiteral("panel"));
parser.addOption(panelOption);
QCommandLineOption desktopOption(QStringLiteral("desktop"));
parser.addOption(desktopOption);
QCommandLineOption osdOption(QStringLiteral("osd"));
parser.addOption(osdOption);
QCommandLineOption tooltipOption(QStringLiteral("tooltip"));
parser.addOption(tooltipOption);
QCommandLineOption skipTaskbarOption(QStringLiteral("skipTaskbar"));
parser.addOption(skipTaskbarOption);
parser.process(app);
QCommandLineOption skipSwitcherOption(QStringLiteral("skipSwitcher"));
parser.addOption(skipSwitcherOption);
parser.process(app);
PlasmaSurfaceTest client;
if (parser.isSet(notificationOption)) {
client.setRole(PlasmaShellSurface::Role::Notification);
} else if (parser.isSet(criticalNotificationOption)) {
client.setRole(PlasmaShellSurface::Role::CriticalNotification);
} else if (parser.isSet(appletPopupOption)) {
client.setRole(PlasmaShellSurface::Role::AppletPopup);
} else if (parser.isSet(panelOption)) {
client.setRole(PlasmaShellSurface::Role::Panel);
} else if (parser.isSet(desktopOption)) {
client.setRole(PlasmaShellSurface::Role::Desktop);
} else if (parser.isSet(osdOption)) {
client.setRole(PlasmaShellSurface::Role::OnScreenDisplay);
} else if (parser.isSet(tooltipOption)) {
client.setRole(PlasmaShellSurface::Role::ToolTip);
}
client.setSkipTaskbar(parser.isSet(skipTaskbarOption));
client.setSkipSwitcher(parser.isSet(skipSwitcherOption));
client.init();
return app.exec();
}
#include "plasmasurfacetest.moc"
@@ -0,0 +1,393 @@
/*
SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "pointerconstraintstest.h"
#include <KWayland/Client/compositor.h>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/pointer.h>
#include <KWayland/Client/pointerconstraints.h>
#include <KWayland/Client/region.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/seat.h>
#include <KWayland/Client/surface.h>
#include <QCursor>
#include <QGuiApplication>
#include <QQmlContext>
#include <QQmlEngine>
#include <QDebug>
#include <xcb/xproto.h>
using namespace KWayland::Client;
WaylandBackend::WaylandBackend(QObject *parent)
: Backend(parent)
, m_connectionThreadObject(ConnectionThread::fromApplication(this))
{
setMode(Mode::Wayland);
}
void WaylandBackend::init(QQuickView *view)
{
Backend::init(view);
Registry *registry = new Registry(this);
setupRegistry(registry);
}
void WaylandBackend::setupRegistry(Registry *registry)
{
connect(registry, &Registry::compositorAnnounced, this,
[this, registry](quint32 name, quint32 version) {
m_compositor = registry->createCompositor(name, version, this);
});
connect(registry, &Registry::seatAnnounced, this,
[this, registry](quint32 name, quint32 version) {
m_seat = registry->createSeat(name, version, this);
if (m_seat->hasPointer()) {
m_pointer = m_seat->createPointer(this);
}
connect(m_seat, &Seat::hasPointerChanged, this,
[this]() {
delete m_pointer;
m_pointer = m_seat->createPointer(this);
});
});
connect(registry, &Registry::pointerConstraintsUnstableV1Announced, this,
[this, registry](quint32 name, quint32 version) {
m_pointerConstraints = registry->createPointerConstraints(name, version, this);
});
connect(registry, &Registry::interfacesAnnounced, this,
[this] {
Q_ASSERT(m_compositor);
Q_ASSERT(m_seat);
Q_ASSERT(m_pointerConstraints);
});
registry->create(m_connectionThreadObject);
registry->setup();
}
bool WaylandBackend::isLocked()
{
return m_lockedPointer && m_lockedPointer->isValid();
}
bool WaylandBackend::isConfined()
{
return m_confinedPointer && m_confinedPointer->isValid();
}
static PointerConstraints::LifeTime lifeTime(bool persistent)
{
return persistent ? PointerConstraints::LifeTime::Persistent : PointerConstraints::LifeTime::OneShot;
}
void WaylandBackend::lockRequest(bool persistent, QRect region)
{
if (isLocked()) {
if (!errorsAllowed()) {
qDebug() << "Abort locking because already locked. Allow errors to test relocking (and crashing).";
return;
}
qDebug() << "Trying to lock although already locked. Crash expected.";
}
if (isConfined()) {
if (!errorsAllowed()) {
qDebug() << "Abort locking because already confined. Allow errors to test locking while being confined (and crashing).";
return;
}
qDebug() << "Trying to lock although already confined. Crash expected.";
}
qDebug() << "------ Lock requested ------";
qDebug() << "Persistent:" << persistent << "| Region:" << region;
std::unique_ptr<Surface> winSurface(Surface::fromWindow(view()));
std::unique_ptr<Region> wlRegion(m_compositor->createRegion(this));
wlRegion->add(region);
auto *lockedPointer = m_pointerConstraints->lockPointer(winSurface.get(),
m_pointer,
wlRegion.get(),
lifeTime(persistent),
this);
if (!lockedPointer) {
qDebug() << "ERROR when receiving locked pointer!";
return;
}
m_lockedPointer = lockedPointer;
m_lockedPointerPersistent = persistent;
connect(lockedPointer, &LockedPointer::locked, this, [this]() {
qDebug() << "------ LOCKED! ------";
if (lockHint()) {
m_lockedPointer->setCursorPositionHint(QPointF(10., 10.));
Q_EMIT forceSurfaceCommit();
}
Q_EMIT lockChanged(true);
});
connect(lockedPointer, &LockedPointer::unlocked, this, [this]() {
qDebug() << "------ UNLOCKED! ------";
if (!m_lockedPointerPersistent) {
cleanupLock();
}
Q_EMIT lockChanged(false);
});
}
void WaylandBackend::unlockRequest()
{
if (!m_lockedPointer) {
qDebug() << "Unlock requested, but there is no lock. Abort.";
return;
}
qDebug() << "------ Unlock requested ------";
cleanupLock();
Q_EMIT lockChanged(false);
}
void WaylandBackend::cleanupLock()
{
if (!m_lockedPointer) {
return;
}
m_lockedPointer->release();
m_lockedPointer->deleteLater();
m_lockedPointer = nullptr;
}
void WaylandBackend::confineRequest(bool persistent, QRect region)
{
if (isConfined()) {
if (!errorsAllowed()) {
qDebug() << "Abort confining because already confined. Allow errors to test reconfining (and crashing).";
return;
}
qDebug() << "Trying to lock although already locked. Crash expected.";
}
if (isLocked()) {
if (!errorsAllowed()) {
qDebug() << "Abort confining because already locked. Allow errors to test confining while being locked (and crashing).";
return;
}
qDebug() << "Trying to confine although already locked. Crash expected.";
}
qDebug() << "------ Confine requested ------";
qDebug() << "Persistent:" << persistent << "| Region:" << region;
std::unique_ptr<Surface> winSurface(Surface::fromWindow(view()));
std::unique_ptr<Region> wlRegion(m_compositor->createRegion(this));
wlRegion->add(region);
auto *confinedPointer = m_pointerConstraints->confinePointer(winSurface.get(),
m_pointer,
wlRegion.get(),
lifeTime(persistent),
this);
if (!confinedPointer) {
qDebug() << "ERROR when receiving confined pointer!";
return;
}
m_confinedPointer = confinedPointer;
m_confinedPointerPersistent = persistent;
connect(confinedPointer, &ConfinedPointer::confined, this, [this]() {
qDebug() << "------ CONFINED! ------";
Q_EMIT confineChanged(true);
});
connect(confinedPointer, &ConfinedPointer::unconfined, this, [this]() {
qDebug() << "------ UNCONFINED! ------";
if (!m_confinedPointerPersistent) {
cleanupConfine();
}
Q_EMIT confineChanged(false);
});
}
void WaylandBackend::unconfineRequest()
{
if (!m_confinedPointer) {
qDebug() << "Unconfine requested, but there is no confine. Abort.";
return;
}
qDebug() << "------ Unconfine requested ------";
cleanupConfine();
Q_EMIT confineChanged(false);
}
void WaylandBackend::cleanupConfine()
{
if (!m_confinedPointer) {
return;
}
m_confinedPointer->release();
m_confinedPointer->deleteLater();
m_confinedPointer = nullptr;
}
XBackend::XBackend(QObject *parent)
: Backend(parent)
{
setMode(Mode::X);
if (m_xcbConn) {
xcb_disconnect(m_xcbConn);
free(m_xcbConn);
}
}
void XBackend::init(QQuickView *view)
{
Backend::init(view);
m_xcbConn = xcb_connect(nullptr, nullptr);
if (xcb_connection_has_error(m_xcbConn)) {
xcb_disconnect(m_xcbConn);
qFatal() << "Could not open XCB connection.";
}
}
void XBackend::lockRequest(bool persistent, QRect region)
{
auto winId = view()->winId();
/* Cursor needs to be hidden such that Xwayland emulates warps. */
QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
auto cookie = xcb_warp_pointer_checked(m_xcbConn, /* connection */
XCB_NONE, /* src_w */
winId, /* dest_w */
0, /* src_x */
0, /* src_y */
0, /* src_width */
0, /* src_height */
20, /* dest_x */
20 /* dest_y */
);
xcb_flush(m_xcbConn);
xcb_generic_error_t *error = xcb_request_check(m_xcbConn, cookie);
if (error) {
qDebug() << "Lock (warp) failed with XCB error:" << error->error_code;
free(error);
return;
}
qDebug() << "LOCK (warp)";
Q_EMIT lockChanged(true);
}
void XBackend::unlockRequest()
{
/* Xwayland unlocks the pointer, when the cursor is shown again. */
QGuiApplication::restoreOverrideCursor();
qDebug() << "------ Unlock requested ------";
Q_EMIT lockChanged(false);
}
void XBackend::confineRequest(bool persistent, QRect region)
{
int error;
if (!tryConfine(error)) {
qDebug() << "Confine (grab) failed with XCB error:" << error;
return;
}
qDebug() << "CONFINE (grab)";
Q_EMIT confineChanged(true);
}
void XBackend::unconfineRequest()
{
auto cookie = xcb_ungrab_pointer_checked(m_xcbConn, XCB_CURRENT_TIME);
xcb_flush(m_xcbConn);
xcb_generic_error_t *error = xcb_request_check(m_xcbConn, cookie);
if (error) {
qDebug() << "Unconfine failed with XCB error:" << error->error_code;
free(error);
return;
}
qDebug() << "UNCONFINE (ungrab)";
Q_EMIT confineChanged(false);
}
void XBackend::hideAndConfineRequest(bool confineBeforeHide)
{
if (!confineBeforeHide) {
QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
}
int error;
if (!tryConfine(error)) {
qDebug() << "Confine failed with XCB error:" << error;
if (!confineBeforeHide) {
QGuiApplication::restoreOverrideCursor();
}
return;
}
if (confineBeforeHide) {
QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
}
qDebug() << "HIDE AND CONFINE (lock)";
Q_EMIT confineChanged(true);
}
void XBackend::undoHideRequest()
{
QGuiApplication::restoreOverrideCursor();
qDebug() << "UNDO HIDE AND CONFINE (unlock)";
}
bool XBackend::tryConfine(int &error)
{
auto winId = view()->winId();
auto cookie = xcb_grab_pointer(m_xcbConn, /* display */
1, /* owner_events */
winId, /* grab_window */
0, /* event_mask */
XCB_GRAB_MODE_ASYNC, /* pointer_mode */
XCB_GRAB_MODE_ASYNC, /* keyboard_mode */
winId, /* confine_to */
XCB_NONE, /* cursor */
XCB_CURRENT_TIME /* time */
);
xcb_flush(m_xcbConn);
xcb_generic_error_t *e = nullptr;
auto *reply = xcb_grab_pointer_reply(m_xcbConn, cookie, &e);
if (!reply) {
error = e->error_code;
free(e);
return false;
}
free(reply);
return true;
}
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
Backend *backend;
if (app.platformName() == QStringLiteral("wayland")) {
qDebug() << "Starting up: Wayland native mode";
backend = new WaylandBackend(&app);
} else {
qDebug() << "Starting up: Xserver/Xwayland legacy mode";
backend = new XBackend(&app);
}
QQuickView view;
QQmlContext *context = view.engine()->rootContext();
context->setContextProperty(QStringLiteral("org_kde_kwin_tests_pointerconstraints_backend"), backend);
view.setSource(QUrl::fromLocalFile(QStringLiteral(DIR) + QStringLiteral("/pointerconstraintstest.qml")));
view.show();
backend->init(&view);
return app.exec();
}
#include "moc_pointerconstraintstest.cpp"
@@ -0,0 +1,179 @@
/*
SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef POINTERCONSTRAINTSTEST_H
#define POINTERCONSTRAINTSTEST_H
#include <QObject>
#include <QQuickView>
#include <xcb/xcb.h>
namespace KWayland
{
namespace Client
{
class ConnectionThread;
class Registry;
class Compositor;
class Seat;
class Pointer;
class PointerConstraints;
class LockedPointer;
class ConfinedPointer;
}
}
class MainWindow;
class Backend : public QObject
{
Q_OBJECT
public:
Backend(QObject *parent = nullptr)
: QObject(parent)
{
}
Q_PROPERTY(int mode READ mode CONSTANT)
Q_PROPERTY(bool lockHint MEMBER m_lockHint NOTIFY lockHintChanged)
Q_PROPERTY(bool errorsAllowed READ errorsAllowed WRITE setErrorsAllowed NOTIFY errorsAllowedChanged)
virtual void init(QQuickView *view)
{
m_view = view;
}
int mode() const
{
return (int)m_mode;
}
bool lockHint() const
{
return m_lockHint;
}
bool errorsAllowed() const
{
return m_errorsAllowed;
}
void setErrorsAllowed(bool set)
{
if (m_errorsAllowed == set) {
return;
}
m_errorsAllowed = set;
Q_EMIT errorsAllowedChanged();
}
Q_INVOKABLE virtual void lockRequest(bool persistent = true, QRect region = QRect())
{
}
Q_INVOKABLE virtual void unlockRequest()
{
}
Q_INVOKABLE virtual void confineRequest(bool persistent = true, QRect region = QRect())
{
}
Q_INVOKABLE virtual void unconfineRequest()
{
}
Q_INVOKABLE virtual void hideAndConfineRequest(bool confineBeforeHide = false)
{
}
Q_INVOKABLE virtual void undoHideRequest()
{
}
Q_SIGNALS:
void confineChanged(bool confined);
void lockChanged(bool locked);
void lockHintChanged();
void errorsAllowedChanged();
void forceSurfaceCommit();
protected:
enum class Mode {
Wayland = 0,
X = 1
};
QQuickView *view() const
{
return m_view;
}
void setMode(Mode set)
{
m_mode = set;
}
private:
QQuickView *m_view;
Mode m_mode;
bool m_lockHint = true;
bool m_errorsAllowed = false;
};
class WaylandBackend : public Backend
{
Q_OBJECT
public:
WaylandBackend(QObject *parent = nullptr);
void init(QQuickView *view) override;
void lockRequest(bool persistent, QRect region) override;
void unlockRequest() override;
void confineRequest(bool persistent, QRect region) override;
void unconfineRequest() override;
private:
void setupRegistry(KWayland::Client::Registry *registry);
bool isLocked();
bool isConfined();
void cleanupLock();
void cleanupConfine();
KWayland::Client::ConnectionThread *m_connectionThreadObject;
KWayland::Client::Compositor *m_compositor = nullptr;
KWayland::Client::Seat *m_seat = nullptr;
KWayland::Client::Pointer *m_pointer = nullptr;
KWayland::Client::PointerConstraints *m_pointerConstraints = nullptr;
KWayland::Client::LockedPointer *m_lockedPointer = nullptr;
bool m_lockedPointerPersistent = false;
KWayland::Client::ConfinedPointer *m_confinedPointer = nullptr;
bool m_confinedPointerPersistent = false;
};
class XBackend : public Backend
{
Q_OBJECT
public:
XBackend(QObject *parent = nullptr);
void init(QQuickView *view) override;
void lockRequest(bool persistent, QRect region) override;
void unlockRequest() override;
void confineRequest(bool persistent, QRect region) override;
void unconfineRequest() override;
void hideAndConfineRequest(bool confineBeforeHide) override;
void undoHideRequest() override;
private:
bool tryConfine(int &error);
xcb_connection_t *m_xcbConn = nullptr;
};
#endif
@@ -0,0 +1,205 @@
/*
SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
ColumnLayout {
/* for margins */
ColumnLayout {
id: root
focus: true
Layout.margins: 20
function lock() {
org_kde_kwin_tests_pointerconstraints_backend.lockRequest(lockPersChck.checked, root.activRect());
}
function confine() {
org_kde_kwin_tests_pointerconstraints_backend.confineRequest(confPersChck.checked, root.activRect());
}
function unlock() {
org_kde_kwin_tests_pointerconstraints_backend.unlockRequest();
}
function unconfine() {
org_kde_kwin_tests_pointerconstraints_backend.unconfineRequest();
}
function hideAndConfine() {
org_kde_kwin_tests_pointerconstraints_backend.hideAndConfineRequest();
}
function undoHideAndConfine() {
org_kde_kwin_tests_pointerconstraints_backend.undoHideRequest();
}
property bool waylandNative: org_kde_kwin_tests_pointerconstraints_backend.mode === 0
Keys.onPressed: {
if (event.key === Qt.Key_L) {
root.lock();
event.accepted = true;
} else if (event.key === Qt.Key_C) {
root.confine();
event.accepted = true;
} else if (event.key === Qt.Key_K) {
root.unlock();
event.accepted = true;
} else if (event.key === Qt.Key_X) {
root.unconfine();
event.accepted = true;
} else if (event.key === Qt.Key_H) {
root.hideAndConfine();
event.accepted = true;
} else if (event.key === Qt.Key_G) {
root.undoHideAndConfine();
event.accepted = true;
}
}
function activRect() {
if (fullWindowChck.checked) {
return Qt.rect(0, 0, -1, -1);
}
return activArea.rect();
}
Connections {
target: org_kde_kwin_tests_pointerconstraints_backend
function onForceSurfaceCommit() {
forceCommitRect.visible = true
}
}
Rectangle {
id: forceCommitRect
width: 10
height: 10
color: "red"
visible: false
Timer {
interval: 500
running: forceCommitRect.visible
repeat: false
onTriggered: forceCommitRect.visible = false;
}
}
GridLayout {
columns: 2
rowSpacing: 10
columnSpacing: 10
Button {
id: lockButton
text: "Lock pointer"
onClicked: root.lock()
}
CheckBox {
id: lockPersChck
text: "Persistent lock"
checked: root.waylandNative
enabled: root.waylandNative
}
Button {
id: confButton
text: "Confine pointer"
onClicked: root.confine()
}
CheckBox {
id: confPersChck
text: "Persistent confine"
checked: root.waylandNative
enabled: root.waylandNative
}
Button {
id: hideConfButton
text: "Hide and confine pointer"
onClicked: root.hideAndConfine()
visible: !root.waylandNative
}
CheckBox {
id: confBeforeHideChck
text: "Confine first, then hide"
checked: false
visible: !root.waylandNative
}
}
CheckBox {
id: lockHintChck
text: "Send position hint on lock"
checked: root.waylandNative
enabled: root.waylandNative
onCheckedChanged: org_kde_kwin_tests_pointerconstraints_backend.lockHint = checked;
}
CheckBox {
id: restrAreaChck
text: "Restrict input area (not yet implemented)"
enabled: false
}
CheckBox {
id: fullWindowChck
text: "Full window area activates"
checked: !root.waylandNative
enabled: root.waylandNative
}
CheckBox {
id: errorsChck
text: "Allow critical errors"
checked: false
enabled: root.waylandNative
onCheckedChanged: org_kde_kwin_tests_pointerconstraints_backend.errorsAllowed = checked;
}
Item {
width: childrenRect.width
height: childrenRect.height
Rectangle {
id: activArea
width: 400
height: 200
enabled: root.waylandNative && !fullWindowChck.checked
function rect() {
const scenePosition = mapToItem(null, x, y);
return Qt.rect(scenePosition.x, scenePosition.y, width, height);
}
border.color: enabled ? "black" : "lightgrey"
border.width: 2
Label {
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
text: "Activation area"
}
}
Button {
id: unconfButton
anchors.horizontalCenter: activArea.horizontalCenter
anchors.verticalCenter: activArea.verticalCenter
text: "Unconfine pointer"
onClicked: root.unconfine()
}
}
Label {
text: "Lock: L / Unlock: K"
}
Label {
text: "Confine: C / Unconfine: X"
}
Label {
text: "Hide cursor and confine pointer: H / undo hide: G"
visible: !root.waylandNative
}
}
}
@@ -0,0 +1,148 @@
/*
SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include <QGuiApplication>
#include <QQuickItem>
#include <QQuickView>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/pointer.h>
#include <KWayland/Client/pointergestures.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/seat.h>
using namespace KWayland::Client;
class PinchGesture : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(qreal scale READ scale NOTIFY scaleChanged)
Q_PROPERTY(qreal progressScale READ progressScale NOTIFY progressScaleChanged)
public:
explicit PinchGesture(QQuickItem *parent = nullptr);
~PinchGesture() override;
qreal scale() const
{
return m_scale;
}
qreal progressScale() const
{
return m_progressScale;
}
protected:
void componentComplete() override;
Q_SIGNALS:
void progressScaleChanged();
private:
void initWayland();
void setupGesture();
Pointer *m_pointer = nullptr;
PointerGestures *m_pointerGestures = nullptr;
PointerPinchGesture *m_gesture = nullptr;
qreal m_scale = 1.0;
qreal m_progressScale = 1.0;
};
PinchGesture::PinchGesture(QQuickItem *parent)
: QQuickItem(parent)
{
}
PinchGesture::~PinchGesture() = default;
void PinchGesture::componentComplete()
{
QQuickItem::componentComplete();
initWayland();
}
void PinchGesture::initWayland()
{
auto c = ConnectionThread::fromApplication(this);
Registry *r = new Registry(c);
r->create(c);
connect(r, &Registry::interfacesAnnounced, this,
[this, r] {
const auto gi = r->interface(Registry::Interface::PointerGesturesUnstableV1);
if (gi.name == 0) {
return;
}
m_pointerGestures = r->createPointerGestures(gi.name, gi.version, this);
// now create seat
const auto si = r->interface(Registry::Interface::Seat);
if (si.name == 0) {
return;
}
auto seat = r->createSeat(si.name, si.version, this);
connect(seat, &Seat::hasKeyboardChanged, this,
[this, seat](bool hasPointer) {
if (hasPointer) {
m_pointer = seat->createPointer(this);
setupGesture();
} else {
delete m_pointer;
delete m_gesture;
m_pointer = nullptr;
m_gesture = nullptr;
}
});
});
r->setup();
c->roundtrip();
}
void PinchGesture::setupGesture()
{
if (m_gesture || !m_pointerGestures || !m_pointer) {
return;
}
m_gesture = m_pointerGestures->createPinchGesture(m_pointer, this);
connect(m_gesture, &PointerPinchGesture::updated, this,
[this](const QSizeF &delta, qreal scale) {
m_progressScale = scale;
Q_EMIT progressScaleChanged();
});
connect(m_gesture, &PointerPinchGesture::ended, this,
[this] {
m_scale = m_scale * m_progressScale;
m_progressScale = 1.0;
Q_EMIT scaleChanged();
Q_EMIT progressScaleChanged();
});
connect(m_gesture, &PointerPinchGesture::cancelled, this,
[this] {
m_progressScale = 1.0;
Q_EMIT progressScaleChanged();
});
}
int main(int argc, char *argv[])
{
qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("wayland"));
QGuiApplication app(argc, argv);
qmlRegisterType<PinchGesture>("org.kde.kwin.tests", 1, 0, "PinchGesture");
QQuickView view;
view.setSource(QUrl::fromLocalFile(QStringLiteral(DIR) + QStringLiteral("/pointergesturestest.qml")));
view.show();
return app.exec();
}
#include "pointergesturestest.moc"
@@ -0,0 +1,16 @@
/*
SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick
import org.kde.kwin.tests
Image {
source: "../48-apps-kwin.png"
scale: gesture.scale * gesture.progressScale
PinchGesture {
id: gesture
}
}
@@ -0,0 +1,287 @@
/*
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 "wayland/compositor.h"
#include "wayland/datadevicemanager.h"
#include "wayland/display.h"
#include "wayland/keyboard.h"
#include "wayland/output.h"
#include "wayland/pointer.h"
#include "wayland/seat.h"
#include "wayland/xdgshell.h"
#include "fakeoutput.h"
#include <QApplication>
#include <QCommandLineParser>
#include <QDateTime>
#include <QFile>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QPainter>
#include <QPointer>
#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(KWin::XdgToplevelInterface *surface);
void setSeat(const QPointer<KWin::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<KWin::XdgToplevelInterface *> m_stackingOrder;
QPointer<KWin::SeatInterface> m_seat;
};
CompositorWindow::CompositorWindow(QWidget *parent)
: QWidget(parent)
{
setMouseTracking(true);
}
CompositorWindow::~CompositorWindow() = default;
void CompositorWindow::surfaceCreated(KWin::XdgToplevelInterface *surface)
{
using namespace KWin;
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 KWin;
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<KWin::SeatInterface> &seat)
{
m_seat = seat;
}
void CompositorWindow::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
QPainter p(this);
for (auto window : m_stackingOrder) {
KWin::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, KWin::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, KWin::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->position().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->globalPosition());
}
}
m_seat->setTimestamp(std::chrono::milliseconds(event->timestamp()));
m_seat->notifyPointerButton(event->button(), KWin::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(), KWin::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, KWin::PointerAxisSource::Wheel);
}
if (angle.y() != 0) {
m_seat->notifyPointerAxis(Qt::Vertical, angle.y(), 1, KWin::PointerAxisSource::Wheel);
}
m_seat->notifyPointerFrame();
}
int main(int argc, char **argv)
{
using namespace KWin;
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);
KWin::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, QStringLiteral("testSeat0"));
seat->setHasKeyboard(true);
seat->setHasPointer(true);
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"
@@ -0,0 +1,349 @@
/*
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "utils/xcbutils.h"
#include <QApplication>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QMenu>
#include <QPlatformSurfaceEvent>
#include <QPushButton>
#include <QScreen>
#include <QTimer>
#include <QToolButton>
#include <QWidget>
#include <QWindow>
#include <private/qtx11extras_p.h>
#include <KWindowSystem>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/plasmashell.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/surface.h>
class ScreenEdgeHelper : public QObject
{
Q_OBJECT
protected:
ScreenEdgeHelper(QWidget *widget, QObject *parent = nullptr);
QWindow *window() const
{
return m_widget->windowHandle();
}
virtual void restore() = 0;
public:
~ScreenEdgeHelper() override;
virtual void hide() = 0;
virtual void raiseOrShow(bool raise) = 0;
virtual void init(){};
virtual void moveToTop();
virtual void moveToRight();
virtual void moveToBottom();
virtual void moveToLeft();
virtual void moveToFloating();
void hideAndRestore()
{
hide();
m_timer->start(10000);
}
private:
QWidget *m_widget;
QTimer *m_timer;
};
class ScreenEdgeHelperX11 : public ScreenEdgeHelper
{
Q_OBJECT
public:
ScreenEdgeHelperX11(QWidget *widget, QObject *parent = nullptr);
~ScreenEdgeHelperX11() override = default;
void hide() override;
void raiseOrShow(bool raise) override;
void moveToTop() override;
void moveToRight() override;
void moveToBottom() override;
void moveToLeft() override;
void moveToFloating() override;
protected:
void restore() override;
private:
uint32_t m_locationValue = 2;
uint32_t m_actionValue = 0;
KWin::Xcb::Atom m_atom;
};
class ScreenEdgeHelperWayland : public ScreenEdgeHelper
{
Q_OBJECT
public:
ScreenEdgeHelperWayland(QWidget *widget, QObject *parent = nullptr);
~ScreenEdgeHelperWayland() override = default;
void hide() override;
void raiseOrShow(bool raise) override;
void init() override;
bool eventFilter(QObject *watched, QEvent *event) override;
protected:
void restore() override;
private:
void setupSurface();
KWayland::Client::PlasmaShell *m_shell = nullptr;
KWayland::Client::PlasmaShellSurface *m_shellSurface = nullptr;
bool m_autoHide = true;
};
ScreenEdgeHelper::ScreenEdgeHelper(QWidget *widget, QObject *parent)
: QObject(parent)
, m_widget(widget)
, m_timer(new QTimer(this))
{
m_timer->setSingleShot(true);
connect(m_timer, &QTimer::timeout, this, &ScreenEdgeHelper::restore);
}
ScreenEdgeHelper::~ScreenEdgeHelper() = default;
void ScreenEdgeHelper::moveToTop()
{
const QRect geo = QGuiApplication::primaryScreen()->geometry();
m_widget->setGeometry(geo.x(), geo.y(), geo.width(), 100);
}
void ScreenEdgeHelper::moveToRight()
{
const QRect geo = QGuiApplication::primaryScreen()->geometry();
m_widget->setGeometry(geo.x(), geo.y(), geo.width(), 100);
}
void ScreenEdgeHelper::moveToBottom()
{
const QRect geo = QGuiApplication::primaryScreen()->geometry();
m_widget->setGeometry(geo.x(), geo.y() + geo.height() - 100, geo.width(), 100);
}
void ScreenEdgeHelper::moveToLeft()
{
const QRect geo = QGuiApplication::primaryScreen()->geometry();
m_widget->setGeometry(geo.x(), geo.y(), 100, geo.height());
}
void ScreenEdgeHelper::moveToFloating()
{
const QRect geo = QGuiApplication::primaryScreen()->geometry();
m_widget->setGeometry(QRect(geo.center(), QSize(100, 100)));
}
ScreenEdgeHelperX11::ScreenEdgeHelperX11(QWidget *widget, QObject *parent)
: ScreenEdgeHelper(widget, parent)
, m_atom(QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW"))
{
}
void ScreenEdgeHelperX11::hide()
{
uint32_t value = m_locationValue | (m_actionValue << 8);
xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, window()->winId(), m_atom, XCB_ATOM_CARDINAL, 32, 1, &value);
}
void ScreenEdgeHelperX11::restore()
{
xcb_delete_property(QX11Info::connection(), window()->winId(), m_atom);
}
void ScreenEdgeHelperX11::raiseOrShow(bool raise)
{
m_actionValue = raise ? 1 : 0;
}
void ScreenEdgeHelperX11::moveToBottom()
{
ScreenEdgeHelper::moveToBottom();
m_locationValue = 2;
}
void ScreenEdgeHelperX11::moveToFloating()
{
ScreenEdgeHelper::moveToFloating();
m_locationValue = 4;
}
void ScreenEdgeHelperX11::moveToLeft()
{
ScreenEdgeHelper::moveToLeft();
m_locationValue = 3;
}
void ScreenEdgeHelperX11::moveToRight()
{
ScreenEdgeHelper::moveToRight();
m_locationValue = 1;
}
void ScreenEdgeHelperX11::moveToTop()
{
ScreenEdgeHelper::moveToTop();
m_locationValue = 0;
}
using namespace KWayland::Client;
ScreenEdgeHelperWayland::ScreenEdgeHelperWayland(QWidget *widget, QObject *parent)
: ScreenEdgeHelper(widget, parent)
{
ConnectionThread *connection = ConnectionThread::fromApplication(this);
Registry *registry = new Registry(connection);
registry->create(connection);
connect(registry, &Registry::interfacesAnnounced, this,
[registry, this] {
const auto interface = registry->interface(Registry::Interface::PlasmaShell);
if (interface.name == 0) {
return;
}
m_shell = registry->createPlasmaShell(interface.name, interface.version);
});
registry->setup();
connection->roundtrip();
}
void ScreenEdgeHelperWayland::init()
{
window()->installEventFilter(this);
setupSurface();
}
void ScreenEdgeHelperWayland::setupSurface()
{
if (!m_shell) {
return;
}
if (auto s = Surface::fromWindow(window())) {
m_shellSurface = m_shell->createSurface(s, window());
m_shellSurface->setRole(PlasmaShellSurface::Role::Panel);
m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AutoHide);
m_shellSurface->setPosition(window()->position());
}
}
void ScreenEdgeHelperWayland::hide()
{
if (m_shellSurface && m_autoHide) {
m_shellSurface->requestHideAutoHidingPanel();
}
}
void ScreenEdgeHelperWayland::restore()
{
if (m_shellSurface && m_autoHide) {
m_shellSurface->requestShowAutoHidingPanel();
}
}
void ScreenEdgeHelperWayland::raiseOrShow(bool raise)
{
m_autoHide = !raise;
if (m_shellSurface) {
if (raise) {
m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::WindowsCanCover);
} else {
m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AutoHide);
}
}
}
bool ScreenEdgeHelperWayland::eventFilter(QObject *watched, QEvent *event)
{
if (watched != window() || !m_shell) {
return false;
}
if (event->type() == QEvent::PlatformSurface) {
QPlatformSurfaceEvent *pe = static_cast<QPlatformSurfaceEvent *>(event);
if (pe->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) {
setupSurface();
} else {
delete m_shellSurface;
m_shellSurface = nullptr;
}
}
if (event->type() == QEvent::Move) {
if (m_shellSurface) {
m_shellSurface->setPosition(window()->position());
}
}
return false;
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QApplication::setApplicationDisplayName(QStringLiteral("Screen Edge Show Test App"));
ScreenEdgeHelper *helper = nullptr;
std::unique_ptr<QWidget> widget(new QWidget(nullptr, Qt::FramelessWindowHint));
if (KWindowSystem::isPlatformX11()) {
app.setProperty("x11Connection", QVariant::fromValue<void *>(QX11Info::connection()));
helper = new ScreenEdgeHelperX11(widget.get(), &app);
} else if (KWindowSystem::isPlatformWayland()) {
helper = new ScreenEdgeHelperWayland(widget.get(), &app);
}
if (!helper) {
return 2;
}
QPushButton *hideWindowButton = new QPushButton(QStringLiteral("Hide"), widget.get());
QObject::connect(hideWindowButton, &QPushButton::clicked, helper, &ScreenEdgeHelper::hide);
QPushButton *hideAndRestoreButton = new QPushButton(QStringLiteral("Hide and Restore after 10 sec"), widget.get());
QObject::connect(hideAndRestoreButton, &QPushButton::clicked, helper, &ScreenEdgeHelper::hideAndRestore);
QToolButton *edgeButton = new QToolButton(widget.get());
QCheckBox *raiseCheckBox = new QCheckBox("Raise:", widget.get());
QObject::connect(raiseCheckBox, &QCheckBox::toggled, helper, &ScreenEdgeHelper::raiseOrShow);
edgeButton->setText(QStringLiteral("Edge"));
edgeButton->setPopupMode(QToolButton::MenuButtonPopup);
QMenu *edgeButtonMenu = new QMenu(edgeButton);
QObject::connect(edgeButtonMenu->addAction("Top"), &QAction::triggered, helper, &ScreenEdgeHelper::moveToTop);
QObject::connect(edgeButtonMenu->addAction("Right"), &QAction::triggered, helper, &ScreenEdgeHelper::moveToRight);
QObject::connect(edgeButtonMenu->addAction("Bottom"), &QAction::triggered, helper, &ScreenEdgeHelper::moveToBottom);
QObject::connect(edgeButtonMenu->addAction("Left"), &QAction::triggered, helper, &ScreenEdgeHelper::moveToLeft);
edgeButtonMenu->addSeparator();
QObject::connect(edgeButtonMenu->addAction("Floating"), &QAction::triggered, helper, &ScreenEdgeHelper::moveToFloating);
edgeButton->setMenu(edgeButtonMenu);
QHBoxLayout *layout = new QHBoxLayout(widget.get());
layout->addWidget(hideWindowButton);
layout->addWidget(hideAndRestoreButton);
layout->addWidget(edgeButton);
widget->setLayout(layout);
const QRect geo = QGuiApplication::primaryScreen()->geometry();
widget->setGeometry(geo.x(), geo.y() + geo.height() - 100, geo.width(), 100);
widget->show();
helper->init();
return app.exec();
}
#include "screenedgeshowtest.moc"
@@ -0,0 +1,161 @@
/*
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "KWayland/Client/shadow.h"
#include "KWayland/Client/compositor.h"
#include "KWayland/Client/connection_thread.h"
#include "KWayland/Client/event_queue.h"
#include "KWayland/Client/registry.h"
#include "KWayland/Client/shell.h"
#include "KWayland/Client/shm_pool.h"
#include "KWayland/Client/surface.h"
// Qt
#include <QGuiApplication>
#include <QImage>
#include <QThread>
using namespace KWayland::Client;
class ShadowTest : public QObject
{
Q_OBJECT
public:
explicit ShadowTest(QObject *parent = nullptr);
virtual ~ShadowTest();
void init();
private:
void setupRegistry(Registry *registry);
void setupShadow();
void render();
QThread *m_connectionThread;
ConnectionThread *m_connectionThreadObject;
EventQueue *m_eventQueue = nullptr;
Compositor *m_compositor = nullptr;
Shell *m_shell = nullptr;
ShellSurface *m_shellSurface = nullptr;
ShmPool *m_shm = nullptr;
Surface *m_surface = nullptr;
ShadowManager *m_shadowManager = nullptr;
};
ShadowTest::ShadowTest(QObject *parent)
: QObject(parent)
, m_connectionThread(new QThread(this))
, m_connectionThreadObject(new ConnectionThread())
{
}
ShadowTest::~ShadowTest()
{
m_connectionThread->quit();
m_connectionThread->wait();
m_connectionThreadObject->deleteLater();
}
void ShadowTest::init()
{
connect(
m_connectionThreadObject,
&ConnectionThread::connected,
this,
[this] {
m_eventQueue = new EventQueue(this);
m_eventQueue->setup(m_connectionThreadObject);
Registry *registry = new Registry(this);
setupRegistry(registry);
},
Qt::QueuedConnection);
m_connectionThreadObject->moveToThread(m_connectionThread);
m_connectionThread->start();
m_connectionThreadObject->initConnection();
}
void ShadowTest::setupRegistry(Registry *registry)
{
connect(registry, &Registry::compositorAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_compositor = registry->createCompositor(name, version, this);
});
connect(registry, &Registry::shellAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_shell = registry->createShell(name, version, this);
});
connect(registry, &Registry::shmAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_shm = registry->createShmPool(name, version, this);
});
connect(registry, &Registry::shadowAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_shadowManager = registry->createShadowManager(name, version, this);
m_shadowManager->setEventQueue(m_eventQueue);
});
connect(registry, &Registry::interfacesAnnounced, this, [this] {
Q_ASSERT(m_compositor);
Q_ASSERT(m_shell);
Q_ASSERT(m_shm);
m_surface = m_compositor->createSurface(this);
Q_ASSERT(m_surface);
setupShadow();
m_shellSurface = m_shell->createSurface(m_surface, this);
Q_ASSERT(m_shellSurface);
m_shellSurface->setToplevel();
connect(m_shellSurface, &ShellSurface::sizeChanged, this, &ShadowTest::render);
render();
});
registry->setEventQueue(m_eventQueue);
registry->create(m_connectionThreadObject);
registry->setup();
}
void ShadowTest::setupShadow()
{
Q_ASSERT(m_shadowManager);
Shadow *shadow = m_shadowManager->createShadow(m_surface, this);
Q_ASSERT(shadow);
auto addElement = [this](const QColor color) {
const QSize size = QSize(10, 10);
auto buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef();
buffer->setUsed(true);
QImage image(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
image.fill(color);
return buffer;
};
shadow->attachTopLeft(addElement(Qt::red));
shadow->attachTop(addElement(Qt::darkRed));
shadow->attachTopRight(addElement(Qt::green));
shadow->attachRight(addElement(Qt::darkGreen));
shadow->attachBottomRight(addElement(Qt::darkBlue));
shadow->attachBottom(addElement(Qt::cyan));
shadow->attachBottomLeft(addElement(Qt::darkCyan));
shadow->attachLeft(addElement(Qt::magenta));
shadow->setOffsets(QMarginsF(5, 5, 5, 5));
shadow->commit();
}
void ShadowTest::render()
{
const QSize &size = m_shellSurface->size().isValid() ? m_shellSurface->size() : QSize(300, 200);
auto buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef();
buffer->setUsed(true);
QImage image(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
image.fill(QColor(255, 255, 255, 128));
m_surface->attachBuffer(*buffer);
m_surface->damage(QRect(QPoint(0, 0), size));
m_surface->commit(Surface::CommitFlag::None);
buffer->setUsed(false);
}
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
ShadowTest client;
client.init();
return app.exec();
}
#include "shadowtest.moc"
@@ -0,0 +1,184 @@
/*
SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "KWayland/Client/compositor.h"
#include "KWayland/Client/connection_thread.h"
#include "KWayland/Client/datadevice.h"
#include "KWayland/Client/datadevicemanager.h"
#include "KWayland/Client/dataoffer.h"
#include "KWayland/Client/event_queue.h"
#include "KWayland/Client/keyboard.h"
#include "KWayland/Client/registry.h"
#include "KWayland/Client/seat.h"
#include "KWayland/Client/shell.h"
#include "KWayland/Client/shm_pool.h"
#include "KWayland/Client/subcompositor.h"
#include "KWayland/Client/surface.h"
// Qt
#include <QDebug>
#include <QFile>
#include <QGuiApplication>
#include <QImage>
#include <QMimeType>
#include <QThread>
#include <QTimer>
// system
#include <unistd.h>
#include <linux/input.h>
using namespace KWayland::Client;
class SubSurfaceTest : public QObject
{
Q_OBJECT
public:
explicit SubSurfaceTest(QObject *parent = nullptr);
virtual ~SubSurfaceTest();
void init();
private:
void setupRegistry(Registry *registry);
void render();
QThread *m_connectionThread;
ConnectionThread *m_connectionThreadObject;
EventQueue *m_eventQueue = nullptr;
Compositor *m_compositor = nullptr;
Seat *m_seat = nullptr;
Shell *m_shell = nullptr;
ShellSurface *m_shellSurface = nullptr;
ShmPool *m_shm = nullptr;
Surface *m_surface = nullptr;
SubCompositor *m_subCompositor = nullptr;
};
SubSurfaceTest::SubSurfaceTest(QObject *parent)
: QObject(parent)
, m_connectionThread(new QThread(this))
, m_connectionThreadObject(new ConnectionThread())
{
}
SubSurfaceTest::~SubSurfaceTest()
{
m_connectionThread->quit();
m_connectionThread->wait();
m_connectionThreadObject->deleteLater();
}
void SubSurfaceTest::init()
{
connect(
m_connectionThreadObject,
&ConnectionThread::connected,
this,
[this] {
m_eventQueue = new EventQueue(this);
m_eventQueue->setup(m_connectionThreadObject);
Registry *registry = new Registry(this);
setupRegistry(registry);
},
Qt::QueuedConnection);
m_connectionThreadObject->moveToThread(m_connectionThread);
m_connectionThread->start();
m_connectionThreadObject->initConnection();
}
void SubSurfaceTest::setupRegistry(Registry *registry)
{
connect(registry, &Registry::compositorAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_compositor = registry->createCompositor(name, version, this);
});
connect(registry, &Registry::shellAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_shell = registry->createShell(name, version, this);
});
connect(registry, &Registry::shmAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_shm = registry->createShmPool(name, version, this);
});
connect(registry, &Registry::seatAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_seat = registry->createSeat(name, version, this);
});
connect(registry, &Registry::interfacesAnnounced, this, [this, registry] {
Q_ASSERT(m_compositor);
Q_ASSERT(m_seat);
Q_ASSERT(m_shell);
Q_ASSERT(m_shm);
m_surface = m_compositor->createSurface(this);
Q_ASSERT(m_surface);
m_shellSurface = m_shell->createSurface(m_surface, this);
Q_ASSERT(m_shellSurface);
m_shellSurface->setToplevel();
connect(m_shellSurface, &ShellSurface::sizeChanged, this, &SubSurfaceTest::render);
auto subInterface = registry->interface(Registry::Interface::SubCompositor);
if (subInterface.name != 0) {
m_subCompositor = registry->createSubCompositor(subInterface.name, subInterface.version, this);
Q_ASSERT(m_subCompositor);
// create the sub surface
auto surface = m_compositor->createSurface(this);
Q_ASSERT(surface);
auto subsurface = m_subCompositor->createSubSurface(surface, m_surface, this);
Q_ASSERT(subsurface);
QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::red);
surface->attachBuffer(m_shm->createBuffer(image));
surface->damage(QRect(0, 0, 100, 100));
surface->commit(Surface::CommitFlag::None);
// and another sub-surface to the sub-surface
auto surface2 = m_compositor->createSurface(this);
Q_ASSERT(surface2);
auto subsurface2 = m_subCompositor->createSubSurface(surface2, surface, this);
Q_ASSERT(subsurface2);
QImage green(QSize(50, 50), QImage::Format_ARGB32_Premultiplied);
green.fill(Qt::green);
surface2->attachBuffer(m_shm->createBuffer(green));
surface2->damage(QRect(0, 0, 50, 50));
surface2->commit(Surface::CommitFlag::None);
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, surface2, [surface2, this] {
QImage yellow(QSize(50, 50), QImage::Format_ARGB32_Premultiplied);
yellow.fill(Qt::yellow);
surface2->attachBuffer(m_shm->createBuffer(yellow));
surface2->damage(QRect(0, 0, 50, 50));
surface2->commit(Surface::CommitFlag::None);
m_surface->commit(Surface::CommitFlag::None);
});
timer->setSingleShot(true);
timer->start(5000);
}
render();
});
registry->setEventQueue(m_eventQueue);
registry->create(m_connectionThreadObject);
registry->setup();
}
void SubSurfaceTest::render()
{
const QSize &size = m_shellSurface->size().isValid() ? m_shellSurface->size() : QSize(200, 200);
auto buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef();
buffer->setUsed(true);
QImage image(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::blue);
m_surface->attachBuffer(*buffer);
m_surface->damage(QRect(QPoint(0, 0), size));
m_surface->commit(Surface::CommitFlag::None);
buffer->setUsed(false);
}
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
SubSurfaceTest client;
client.init();
return app.exec();
}
#include "subsurfacetest.moc"
@@ -0,0 +1,205 @@
/*
SPDX-FileCopyrightText: 2014, 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "touchclienttest.h"
// KWin::Wayland
#include <KWayland/Client/buffer.h>
#include <KWayland/Client/compositor.h>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/event_queue.h>
#include <KWayland/Client/keyboard.h>
#include <KWayland/Client/output.h>
#include <KWayland/Client/pointer.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/seat.h>
#include <KWayland/Client/shell.h>
#include <KWayland/Client/shm_pool.h>
#include <KWayland/Client/surface.h>
#include <KWayland/Client/touch.h>
// Qt
#include <QAbstractEventDispatcher>
#include <QCoreApplication>
#include <QDebug>
#include <QImage>
#include <QPainter>
#include <QThread>
#include <QTimer>
#include <linux/input.h>
using namespace KWayland::Client;
static Qt::GlobalColor s_colors[] = {Qt::white, Qt::red, Qt::green, Qt::blue, Qt::black};
static int s_colorIndex = 0;
WaylandClientTest::WaylandClientTest(QObject *parent)
: QObject(parent)
, m_connectionThread(new QThread(this))
, m_connectionThreadObject(new ConnectionThread(nullptr))
, m_eventQueue(nullptr)
, m_compositor(nullptr)
, m_output(nullptr)
, m_surface(nullptr)
, m_shm(nullptr)
, m_timer(new QTimer(this))
{
init();
}
WaylandClientTest::~WaylandClientTest()
{
m_connectionThread->quit();
m_connectionThread->wait();
m_connectionThreadObject->deleteLater();
}
void WaylandClientTest::init()
{
connect(
m_connectionThreadObject,
&ConnectionThread::connected,
this,
[this]() {
// create the event queue for the main gui thread
m_eventQueue = new EventQueue(this);
m_eventQueue->setup(m_connectionThreadObject);
// setup registry
Registry *registry = new Registry(this);
setupRegistry(registry);
},
Qt::QueuedConnection);
m_connectionThreadObject->moveToThread(m_connectionThread);
m_connectionThread->start();
m_connectionThreadObject->initConnection();
connect(m_timer, &QTimer::timeout, this, [this]() {
s_colorIndex = (s_colorIndex + 1) % 5;
render();
});
m_timer->setInterval(1000);
m_timer->start();
}
void WaylandClientTest::setupRegistry(Registry *registry)
{
connect(registry, &Registry::compositorAnnounced, this, [this, registry](quint32 name) {
m_compositor = registry->createCompositor(name, 1, this);
m_surface = m_compositor->createSurface(this);
});
connect(registry, &Registry::shellAnnounced, this, [this, registry](quint32 name) {
Shell *shell = registry->createShell(name, 1, this);
ShellSurface *shellSurface = shell->createSurface(m_surface, m_surface);
shellSurface->setToplevel();
render(QSize(400, 200));
});
connect(registry, &Registry::outputAnnounced, this, [this, registry](quint32 name) {
if (m_output) {
return;
}
m_output = registry->createOutput(name, 2, this);
});
connect(registry, &Registry::shmAnnounced, this, [this, registry](quint32 name) {
m_shm = registry->createShmPool(name, 1, this);
});
connect(registry, &Registry::seatAnnounced, this, [this, registry](quint32 name) {
Seat *s = registry->createSeat(name, 2, this);
connect(s, &Seat::hasKeyboardChanged, this, [this, s](bool has) {
if (!has) {
return;
}
Keyboard *k = s->createKeyboard(this);
connect(k, &Keyboard::keyChanged, this, [](quint32 key, Keyboard::KeyState state) {
if (key == KEY_Q && state == Keyboard::KeyState::Released) {
QCoreApplication::instance()->quit();
}
});
});
connect(s, &Seat::hasPointerChanged, this, [this, s](bool has) {
if (!has) {
return;
}
Pointer *p = s->createPointer(this);
connect(p, &Pointer::buttonStateChanged, this, [this](quint32 serial, quint32 time, quint32 button, Pointer::ButtonState state) {
if (state == Pointer::ButtonState::Released) {
if (button == BTN_LEFT) {
if (m_timer->isActive()) {
m_timer->stop();
} else {
m_timer->start();
}
}
if (button == BTN_RIGHT) {
QCoreApplication::instance()->quit();
}
}
});
});
connect(s, &Seat::hasTouchChanged, this, [this, s](bool has) {
if (!has) {
return;
}
Touch *t = s->createTouch(this);
connect(t, &Touch::sequenceStarted, this, [](KWayland::Client::TouchPoint *startPoint) {
qDebug() << "Touch sequence started at" << startPoint->position() << "with id" << startPoint->id();
});
connect(t, &Touch::sequenceCanceled, this, []() {
qDebug() << "Touch sequence canceled";
});
connect(t, &Touch::sequenceEnded, this, []() {
qDebug() << "Touch sequence finished";
});
connect(t, &Touch::frameEnded, this, []() {
qDebug() << "End of touch contact point list";
});
connect(t, &Touch::pointAdded, this, [](KWayland::Client::TouchPoint *point) {
qDebug() << "Touch point added at" << point->position() << "with id" << point->id();
});
connect(t, &Touch::pointRemoved, this, [](KWayland::Client::TouchPoint *point) {
qDebug() << "Touch point " << point->id() << " removed at" << point->position();
});
connect(t, &Touch::pointMoved, this, [](KWayland::Client::TouchPoint *point) {
qDebug() << "Touch point " << point->id() << " moved to" << point->position();
});
});
});
registry->create(m_connectionThreadObject->display());
registry->setEventQueue(m_eventQueue);
registry->setup();
}
void WaylandClientTest::render(const QSize &size)
{
m_currentSize = size;
render();
}
void WaylandClientTest::render()
{
if (!m_shm || !m_surface || !m_surface->isValid() || !m_currentSize.isValid()) {
return;
}
auto buffer = m_shm->getBuffer(m_currentSize, m_currentSize.width() * 4).toStrongRef();
buffer->setUsed(true);
QImage image(buffer->address(), m_currentSize.width(), m_currentSize.height(), QImage::Format_ARGB32_Premultiplied);
image.fill(s_colors[s_colorIndex]);
m_surface->attachBuffer(*buffer);
m_surface->damage(QRect(QPoint(0, 0), m_currentSize));
m_surface->commit(Surface::CommitFlag::None);
buffer->setUsed(false);
}
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
new WaylandClientTest(&app);
return app.exec();
}
#include "moc_touchclienttest.cpp"
@@ -0,0 +1,49 @@
/*
SPDX-FileCopyrightText: 2014, 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#pragma once
#include <QObject>
#include <QSize>
namespace KWayland
{
namespace Client
{
class Compositor;
class ConnectionThread;
class EventQueue;
class Output;
class Registry;
class ShmPool;
class Surface;
}
}
class QThread;
class QTimer;
class WaylandClientTest : public QObject
{
Q_OBJECT
public:
explicit WaylandClientTest(QObject *parent = nullptr);
virtual ~WaylandClientTest();
private:
void init();
void render(const QSize &size);
void render();
void setupRegistry(KWayland::Client::Registry *registry);
QThread *m_connectionThread;
KWayland::Client::ConnectionThread *m_connectionThreadObject;
KWayland::Client::EventQueue *m_eventQueue;
KWayland::Client::Compositor *m_compositor;
KWayland::Client::Output *m_output;
KWayland::Client::Surface *m_surface;
KWayland::Client::ShmPool *m_shm;
QSize m_currentSize;
QTimer *m_timer;
};
@@ -0,0 +1,72 @@
/*
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import QtQuick.Controls
/*
* Small test application to test the difference between unmap and destroy.
* The window has two buttons: one called "Unmap" which will hide() the QWindow,
* one called "Destroy" which will close() the QWindow.
*
* The test application can be run with qmlscene:
*
* @code
* qmlscene unmapdestroytest.qml
* @endcode
*
* In order to test different modes, the test application understands some arguments:
* --unmanaged Creates an override redirect window
* --frameless Creates a frameless window (comparable Plasma Dialog)
*/
Window {
id: window
visible: false
x: 0
y: 0
width: layout.implicitWidth
height: layout.implicitHeight
color: "black"
RowLayout {
id: layout
Button {
Timer {
id: timer
interval: 2000
running: false
repeat: false
onTriggered: window.show()
}
text: "unmap"
onClicked: {
timer.start();
window.hide();
}
}
Button {
text: "destroy"
onClicked: window.close()
}
}
Component.onCompleted: {
var flags = Qt.Window;
for (var i = 0; i < Qt.application.arguments.length; ++i) {
var argument = Qt.application.arguments[i];
if (argument == "--unmanaged") {
flags = flags | Qt.BypassWindowManagerHint;
}
if (argument == "--frameless") {
flags = flags | Qt.FramelessWindowHint
}
}
window.flags = flags;
window.visible = true;
}
}
@@ -0,0 +1,112 @@
/*
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 "wayland/compositor.h"
#include "wayland/display.h"
#include "wayland/output.h"
#include "wayland/seat.h"
#include "wayland/xdgshell.h"
#include "fakeoutput.h"
#include <QFile>
#include <QGuiApplication>
#include <private/qeventdispatcher_glib_p.h>
#include <iostream>
#include <sys/select.h>
#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, (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);
}
int main(int argc, char **argv)
{
using namespace KWin;
// set our own event dispatcher to be able to dispatch events before the event loop is started
QAbstractEventDispatcher *eventDispatcher = new QEventDispatcherGlib();
QCoreApplication::setEventDispatcher(eventDispatcher);
// first create the Server and setup with minimum to get an XWayland connected
KWin::Display display;
display.start();
display.createShm();
new CompositorInterface(&display, &display);
new XdgShellInterface(&display, &display);
auto outputHandle = std::make_unique<FakeOutput>();
outputHandle->setMode(QSize(1024, 768), 60000);
outputHandle->setPhysicalSize(QSize(10, 10));
auto outputInterface = std::make_unique<OutputInterface>(&display, outputHandle.get());
// starts XWayland by forking and opening a pipe
const int pipe = startXServer();
if (pipe == -1) {
exit(1);
}
fd_set rfds;
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
do {
eventDispatcher->processEvents(QEventLoop::WaitForMoreEvents);
FD_ZERO(&rfds);
FD_SET(pipe, &rfds);
} while (select(pipe + 1, &rfds, nullptr, nullptr, &tv) == 0);
// now Xwayland is ready and we can read the pipe to get the display
readDisplayFromPipe(pipe);
QGuiApplication app(argc, argv);
new SeatInterface(&display, QStringLiteral("testSeat0"));
return app.exec();
}
@@ -0,0 +1,117 @@
/*
SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "utils/xcbutils.h"
#include <QApplication>
#include <QCommandLineParser>
#include <QDebug>
#include <QFormLayout>
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>
#include <private/qtx11extras_p.h>
static QList<uint32_t> readShadow(quint32 windowId)
{
KWin::Xcb::Atom atom(QByteArrayLiteral("_KDE_NET_WM_SHADOW"), false, QX11Info::connection());
QList<uint32_t> ret;
if (windowId != XCB_WINDOW) {
KWin::Xcb::Property property(false, windowId, atom, XCB_ATOM_CARDINAL, 0, 12);
uint32_t *shadow = property.value<uint32_t *>();
if (shadow) {
ret.reserve(12);
for (int i = 0; i < 12; ++i) {
ret << shadow[i];
}
} else {
qDebug() << "!!!! no shadow";
}
} else {
qDebug() << "!!!! no window";
}
return ret;
}
static QList<QPixmap> getPixmaps(const QList<uint32_t> &data)
{
QList<QPixmap> ret;
static const int ShadowElementsCount = 8;
QList<KWin::Xcb::WindowGeometry> pixmapGeometries(ShadowElementsCount);
QList<xcb_get_image_cookie_t> getImageCookies(ShadowElementsCount);
auto *c = KWin::connection();
for (int i = 0; i < ShadowElementsCount; ++i) {
pixmapGeometries[i] = KWin::Xcb::WindowGeometry(data[i]);
}
auto discardReplies = [&getImageCookies](int start) {
for (int i = start; i < getImageCookies.size(); ++i) {
xcb_discard_reply(KWin::connection(), getImageCookies.at(i).sequence);
}
};
for (int i = 0; i < ShadowElementsCount; ++i) {
auto &geo = pixmapGeometries[i];
if (geo.isNull()) {
discardReplies(0);
return QList<QPixmap>();
}
getImageCookies[i] = xcb_get_image_unchecked(c, XCB_IMAGE_FORMAT_Z_PIXMAP, data[i],
0, 0, geo->width, geo->height, ~0);
}
for (int i = 0; i < ShadowElementsCount; ++i) {
auto *reply = xcb_get_image_reply(c, getImageCookies.at(i), nullptr);
if (!reply) {
discardReplies(i + 1);
return QList<QPixmap>();
}
auto &geo = pixmapGeometries[i];
QImage image(xcb_get_image_data(reply), geo->width, geo->height, QImage::Format_ARGB32);
ret << QPixmap::fromImage(image);
free(reply);
}
return ret;
}
int main(int argc, char **argv)
{
qputenv("QT_QPA_PLATFORM", "xcb");
QApplication app(argc, argv);
app.setProperty("x11Connection", QVariant::fromValue<void *>(QX11Info::connection()));
QCommandLineParser parser;
parser.addPositionalArgument(QStringLiteral("windowId"), QStringLiteral("The X11 windowId from which to read the shadow"));
parser.addHelpOption();
parser.process(app);
if (parser.positionalArguments().count() != 1) {
parser.showHelp(1);
}
bool ok = false;
const auto shadow = readShadow(parser.positionalArguments().constFirst().toULongLong(&ok, 16));
if (!ok) {
qDebug() << "!!! Failed to read window id";
return 1;
}
if (shadow.isEmpty()) {
qDebug() << "!!!! Read shadow failed";
return 1;
}
const auto pixmaps = getPixmaps(shadow);
if (pixmaps.isEmpty()) {
qDebug() << "!!!! Read pixmap failed";
return 1;
}
std::unique_ptr<QWidget> widget(new QWidget());
QFormLayout *layout = new QFormLayout(widget.get());
for (const auto &pix : pixmaps) {
QLabel *l = new QLabel(widget.get());
l->setPixmap(pix);
layout->addRow(QStringLiteral("%1x%2:").arg(pix.width()).arg(pix.height()), l);
}
widget->setLayout(layout);
widget->show();
return app.exec();
}
@@ -0,0 +1,27 @@
/*
SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-FileCopyrightText: 2022 Ilya Fedin <fedin-ilja2010@ya.ru>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget window1(nullptr, Qt::Window);
window1.setWindowTitle("Window 1");
window1.setLayout(new QVBoxLayout);
QPushButton p("Raise the Window 2");
window1.layout()->addWidget(&p);
window1.show();
QWidget window2(nullptr, Qt::Window);
window2.setWindowTitle("Window 2");
window2.show();
QObject::connect(&p, &QPushButton::clicked, window2.windowHandle(), &QWindow::requestActivate);
return app.exec();
}
@@ -0,0 +1,172 @@
/*
SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "KWayland/Client/xdgforeign.h"
#include "KWayland/Client/compositor.h"
#include "KWayland/Client/connection_thread.h"
#include "KWayland/Client/event_queue.h"
#include "KWayland/Client/registry.h"
#include "KWayland/Client/shell.h"
#include "KWayland/Client/shm_pool.h"
#include "KWayland/Client/xdgshell.h"
// Qt
#include <QCommandLineParser>
#include <QDebug>
#include <QGuiApplication>
#include <QImage>
#include <QThread>
using namespace KWayland::Client;
class XdgForeignTest : public QObject
{
Q_OBJECT
public:
explicit XdgForeignTest(QObject *parent = nullptr);
virtual ~XdgForeignTest();
void init();
private:
void setupRegistry(Registry *registry);
void render();
QThread *m_connectionThread;
ConnectionThread *m_connectionThreadObject;
EventQueue *m_eventQueue = nullptr;
Compositor *m_compositor = nullptr;
XdgShell *m_shell = nullptr;
XdgShellSurface *m_shellSurface = nullptr;
ShmPool *m_shm = nullptr;
Surface *m_surface = nullptr;
XdgShellSurface *m_childShellSurface = nullptr;
Surface *m_childSurface = nullptr;
KWayland::Client::XdgExporter *m_exporter = nullptr;
KWayland::Client::XdgImporter *m_importer = nullptr;
KWayland::Client::XdgExported *m_exported = nullptr;
KWayland::Client::XdgImported *m_imported = nullptr;
};
XdgForeignTest::XdgForeignTest(QObject *parent)
: QObject(parent)
, m_connectionThread(new QThread(this))
, m_connectionThreadObject(new ConnectionThread())
{
}
XdgForeignTest::~XdgForeignTest()
{
m_connectionThread->quit();
m_connectionThread->wait();
m_connectionThreadObject->deleteLater();
}
void XdgForeignTest::init()
{
connect(
m_connectionThreadObject,
&ConnectionThread::connected,
this,
[this] {
m_eventQueue = new EventQueue(this);
m_eventQueue->setup(m_connectionThreadObject);
Registry *registry = new Registry(this);
setupRegistry(registry);
},
Qt::QueuedConnection);
m_connectionThreadObject->moveToThread(m_connectionThread);
m_connectionThread->start();
m_connectionThreadObject->initConnection();
}
void XdgForeignTest::setupRegistry(Registry *registry)
{
connect(registry, &Registry::compositorAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_compositor = registry->createCompositor(name, version, this);
});
connect(registry, &Registry::xdgShellUnstableV5Announced, this, [this, registry](quint32 name, quint32 version) {
m_shell = registry->createXdgShell(name, version, this);
});
connect(registry, &Registry::shmAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_shm = registry->createShmPool(name, version, this);
});
connect(registry, &Registry::exporterUnstableV2Announced, this, [this, registry](quint32 name, quint32 version) {
m_exporter = registry->createXdgExporter(name, version, this);
m_exporter->setEventQueue(m_eventQueue);
});
connect(registry, &Registry::importerUnstableV2Announced, this, [this, registry](quint32 name, quint32 version) {
m_importer = registry->createXdgImporter(name, version, this);
m_importer->setEventQueue(m_eventQueue);
});
connect(registry, &Registry::interfacesAnnounced, this, [this] {
Q_ASSERT(m_compositor);
Q_ASSERT(m_shell);
Q_ASSERT(m_shm);
Q_ASSERT(m_exporter);
Q_ASSERT(m_importer);
m_surface = m_compositor->createSurface(this);
Q_ASSERT(m_surface);
m_shellSurface = m_shell->createSurface(m_surface, this);
Q_ASSERT(m_shellSurface);
connect(m_shellSurface, &XdgShellSurface::sizeChanged, this, &XdgForeignTest::render);
m_childSurface = m_compositor->createSurface(this);
Q_ASSERT(m_childSurface);
m_childShellSurface = m_shell->createSurface(m_childSurface, this);
Q_ASSERT(m_childShellSurface);
connect(m_childShellSurface, &XdgShellSurface::sizeChanged, this, &XdgForeignTest::render);
m_exported = m_exporter->exportTopLevel(m_surface, this);
connect(m_exported, &XdgExported::done, this, [this]() {
m_imported = m_importer->importTopLevel(m_exported->handle(), this);
m_imported->setParentOf(m_childSurface);
});
render();
});
registry->setEventQueue(m_eventQueue);
registry->create(m_connectionThreadObject);
registry->setup();
}
void XdgForeignTest::render()
{
QSize size = m_shellSurface->size().isValid() ? m_shellSurface->size() : QSize(500, 500);
auto buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef();
buffer->setUsed(true);
QImage image(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
image.fill(QColor(255, 255, 255, 255));
m_surface->attachBuffer(*buffer);
m_surface->damage(QRect(QPoint(0, 0), size));
m_surface->commit(Surface::CommitFlag::None);
buffer->setUsed(false);
size = m_childShellSurface->size().isValid() ? m_childShellSurface->size() : QSize(200, 200);
buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef();
buffer->setUsed(true);
image = QImage(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
image.fill(QColor(255, 0, 0, 255));
m_childSurface->attachBuffer(*buffer);
m_childSurface->damage(QRect(QPoint(0, 0), size));
m_childSurface->commit(Surface::CommitFlag::None);
buffer->setUsed(false);
}
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
XdgForeignTest client;
client.init();
return app.exec();
}
#include "xdgforeigntest.moc"
@@ -0,0 +1,200 @@
/*
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "KWayland/Client/compositor.h"
#include "KWayland/Client/connection_thread.h"
#include "KWayland/Client/event_queue.h"
#include "KWayland/Client/pointer.h"
#include "KWayland/Client/registry.h"
#include "KWayland/Client/seat.h"
#include "KWayland/Client/shadow.h"
#include "KWayland/Client/shell.h"
#include "KWayland/Client/shm_pool.h"
#include "KWayland/Client/surface.h"
#include "KWayland/Client/xdgshell.h"
// Qt
#include <QGuiApplication>
#include <QImage>
#include <QPainter>
#include <QThread>
using namespace KWayland::Client;
class XdgTest : public QObject
{
Q_OBJECT
public:
explicit XdgTest(QObject *parent = nullptr);
virtual ~XdgTest();
void init();
private:
void setupRegistry(Registry *registry);
void createPopup();
void render();
void renderPopup();
QThread *m_connectionThread;
ConnectionThread *m_connectionThreadObject;
EventQueue *m_eventQueue = nullptr;
Compositor *m_compositor = nullptr;
ShmPool *m_shm = nullptr;
Surface *m_surface = nullptr;
XdgShell *m_xdgShell = nullptr;
XdgShellSurface *m_xdgShellSurface = nullptr;
Surface *m_popupSurface = nullptr;
XdgShellPopup *m_xdgShellPopup = nullptr;
};
XdgTest::XdgTest(QObject *parent)
: QObject(parent)
, m_connectionThread(new QThread(this))
, m_connectionThreadObject(new ConnectionThread())
{
}
XdgTest::~XdgTest()
{
m_connectionThread->quit();
m_connectionThread->wait();
m_connectionThreadObject->deleteLater();
}
void XdgTest::init()
{
connect(
m_connectionThreadObject,
&ConnectionThread::connected,
this,
[this] {
m_eventQueue = new EventQueue(this);
m_eventQueue->setup(m_connectionThreadObject);
Registry *registry = new Registry(this);
setupRegistry(registry);
},
Qt::QueuedConnection);
m_connectionThreadObject->moveToThread(m_connectionThread);
m_connectionThread->start();
m_connectionThreadObject->initConnection();
}
void XdgTest::setupRegistry(Registry *registry)
{
connect(registry, &Registry::compositorAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_compositor = registry->createCompositor(name, version, this);
});
connect(registry, &Registry::shmAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_shm = registry->createShmPool(name, version, this);
});
connect(registry, &Registry::xdgShellUnstableV6Announced, this, [this, registry](quint32 name, quint32 version) {
m_xdgShell = registry->createXdgShell(name, version, this);
m_xdgShell->setEventQueue(m_eventQueue);
});
connect(registry, &Registry::interfacesAnnounced, this, [this] {
Q_ASSERT(m_compositor);
Q_ASSERT(m_xdgShell);
Q_ASSERT(m_shm);
m_surface = m_compositor->createSurface(this);
Q_ASSERT(m_surface);
m_xdgShellSurface = m_xdgShell->createSurface(m_surface, this);
Q_ASSERT(m_xdgShellSurface);
connect(m_xdgShellSurface,
&XdgShellSurface::configureRequested,
this,
[this](const QSize &size, KWayland::Client::XdgShellSurface::States states, int serial) {
m_xdgShellSurface->ackConfigure(serial);
render();
});
m_xdgShellSurface->setTitle(QStringLiteral("Test Window"));
m_surface->commit();
});
connect(registry, &Registry::seatAnnounced, this, [this, registry](quint32 name) {
Seat *s = registry->createSeat(name, 2, this);
connect(s, &Seat::hasPointerChanged, this, [this, s](bool has) {
if (!has) {
return;
}
Pointer *p = s->createPointer(this);
connect(p, &Pointer::buttonStateChanged, this, [this](quint32 serial, quint32 time, quint32 button, Pointer::ButtonState state) {
if (state == Pointer::ButtonState::Released) {
if (m_popupSurface) {
m_popupSurface->deleteLater();
m_popupSurface = nullptr;
} else {
createPopup();
}
}
});
});
});
registry->setEventQueue(m_eventQueue);
registry->create(m_connectionThreadObject);
registry->setup();
}
void XdgTest::createPopup()
{
if (m_popupSurface) {
m_popupSurface->deleteLater();
}
m_popupSurface = m_compositor->createSurface(this);
XdgPositioner positioner(QSize(200, 200), QRect(50, 50, 400, 400));
positioner.setAnchorEdge(Qt::BottomEdge | Qt::RightEdge);
positioner.setGravity(Qt::BottomEdge);
positioner.setConstraints(XdgPositioner::Constraint::FlipX | XdgPositioner::Constraint::SlideY);
m_xdgShellPopup = m_xdgShell->createPopup(m_popupSurface, m_xdgShellSurface, positioner, m_popupSurface);
renderPopup();
}
void XdgTest::render()
{
const QSize &size = m_xdgShellSurface->size().isValid() ? m_xdgShellSurface->size() : QSize(500, 500);
auto buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef();
buffer->setUsed(true);
QImage image(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
image.fill(QColor(255, 255, 255, 255));
// draw a red rectangle indicating the anchor of the top level
QPainter painter(&image);
painter.setBrush(Qt::red);
painter.setPen(Qt::black);
painter.drawRect(50, 50, 400, 400);
m_surface->attachBuffer(*buffer);
m_surface->damage(QRect(QPoint(0, 0), size));
m_surface->commit(Surface::CommitFlag::None);
buffer->setUsed(false);
}
void XdgTest::renderPopup()
{
QSize size(200, 200);
auto buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef();
buffer->setUsed(true);
QImage image(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
image.fill(QColor(0, 0, 255, 255));
m_popupSurface->attachBuffer(*buffer);
m_popupSurface->damage(QRect(QPoint(0, 0), size));
m_popupSurface->commit(Surface::CommitFlag::None);
buffer->setUsed(false);
}
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
XdgTest client;
client.init();
return app.exec();
}
#include "xdgtest.moc"