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:
@@ -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(
|
||||
®istry,
|
||||
&Registry::interfacesAnnounced,
|
||||
&app,
|
||||
[®istry, &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, ®istry, 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"
|
||||
Reference in New Issue
Block a user