kwin/autotests/integration/test_helpers.cpp
2023-05-03 11:21:53 +00:00

1558 lines
47 KiB
C++

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kwin.h>
#include "kwin_wayland_test.h"
#if KWIN_BUILD_SCREENLOCKER
#include "screenlockerwatcher.h"
#endif
#include "inputmethod.h"
#include "wayland/display.h"
#include "wayland_server.h"
#include "workspace.h"
#include <wayland-zkde-screencast-unstable-v1-client-protocol.h>
#include <KWayland/Client/appmenu.h>
#include <KWayland/Client/compositor.h>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/event_queue.h>
#include <KWayland/Client/output.h>
#include <KWayland/Client/plasmashell.h>
#include <KWayland/Client/plasmawindowmanagement.h>
#include <KWayland/Client/pointerconstraints.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/seat.h>
#include <KWayland/Client/server_decoration.h>
#include <KWayland/Client/shadow.h>
#include <KWayland/Client/shm_pool.h>
#include <KWayland/Client/subcompositor.h>
#include <KWayland/Client/subsurface.h>
#include <KWayland/Client/surface.h>
#include <KWayland/Client/textinput.h>
// screenlocker
#if KWIN_BUILD_SCREENLOCKER
#include <KScreenLocker/KsldApp>
#endif
#include <QFutureWatcher>
#include <QThread>
#include <QtConcurrent>
// system
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <xf86drm.h>
namespace KWin
{
namespace Test
{
LayerShellV1::~LayerShellV1()
{
destroy();
}
LayerSurfaceV1::~LayerSurfaceV1()
{
destroy();
}
void LayerSurfaceV1::zwlr_layer_surface_v1_configure(uint32_t serial, uint32_t width, uint32_t height)
{
Q_EMIT configureRequested(serial, QSize(width, height));
}
void LayerSurfaceV1::zwlr_layer_surface_v1_closed()
{
Q_EMIT closeRequested();
}
XdgShell::~XdgShell()
{
destroy();
}
XdgSurface::XdgSurface(XdgShell *shell, KWayland::Client::Surface *surface, QObject *parent)
: QObject(parent)
, QtWayland::xdg_surface(shell->get_xdg_surface(*surface))
, m_surface(surface)
{
}
XdgSurface::~XdgSurface()
{
destroy();
}
KWayland::Client::Surface *XdgSurface::surface() const
{
return m_surface;
}
void XdgSurface::xdg_surface_configure(uint32_t serial)
{
Q_EMIT configureRequested(serial);
}
XdgToplevel::XdgToplevel(XdgSurface *surface, QObject *parent)
: QObject(parent)
, QtWayland::xdg_toplevel(surface->get_toplevel())
, m_xdgSurface(surface)
{
}
XdgToplevel::~XdgToplevel()
{
destroy();
}
XdgSurface *XdgToplevel::xdgSurface() const
{
return m_xdgSurface.get();
}
void XdgToplevel::xdg_toplevel_configure(int32_t width, int32_t height, wl_array *states)
{
States requestedStates;
const uint32_t *stateData = static_cast<const uint32_t *>(states->data);
const size_t stateCount = states->size / sizeof(uint32_t);
for (size_t i = 0; i < stateCount; ++i) {
switch (stateData[i]) {
case QtWayland::xdg_toplevel::state_maximized:
requestedStates |= State::Maximized;
break;
case QtWayland::xdg_toplevel::state_fullscreen:
requestedStates |= State::Fullscreen;
break;
case QtWayland::xdg_toplevel::state_resizing:
requestedStates |= State::Resizing;
break;
case QtWayland::xdg_toplevel::state_activated:
requestedStates |= State::Activated;
break;
}
}
Q_EMIT configureRequested(QSize(width, height), requestedStates);
}
void XdgToplevel::xdg_toplevel_close()
{
Q_EMIT closeRequested();
}
XdgPositioner::XdgPositioner(XdgShell *shell)
: QtWayland::xdg_positioner(shell->create_positioner())
{
}
XdgPositioner::~XdgPositioner()
{
destroy();
}
XdgPopup::XdgPopup(XdgSurface *surface, XdgSurface *parentSurface, XdgPositioner *positioner, QObject *parent)
: QObject(parent)
, QtWayland::xdg_popup(surface->get_popup(parentSurface->object(), positioner->object()))
, m_xdgSurface(surface)
{
}
XdgPopup::~XdgPopup()
{
destroy();
}
XdgSurface *XdgPopup::xdgSurface() const
{
return m_xdgSurface.get();
}
void XdgPopup::xdg_popup_configure(int32_t x, int32_t y, int32_t width, int32_t height)
{
Q_EMIT configureRequested(QRect(x, y, width, height));
}
void XdgPopup::xdg_popup_popup_done()
{
Q_EMIT doneReceived();
}
XdgDecorationManagerV1::~XdgDecorationManagerV1()
{
destroy();
}
XdgToplevelDecorationV1::XdgToplevelDecorationV1(XdgDecorationManagerV1 *manager,
XdgToplevel *toplevel, QObject *parent)
: QObject(parent)
, QtWayland::zxdg_toplevel_decoration_v1(manager->get_toplevel_decoration(toplevel->object()))
{
}
XdgToplevelDecorationV1::~XdgToplevelDecorationV1()
{
destroy();
}
void XdgToplevelDecorationV1::zxdg_toplevel_decoration_v1_configure(uint32_t m)
{
Q_EMIT configureRequested(mode(m));
}
IdleInhibitManagerV1::~IdleInhibitManagerV1()
{
destroy();
}
IdleInhibitorV1::IdleInhibitorV1(IdleInhibitManagerV1 *manager, KWayland::Client::Surface *surface)
: QtWayland::zwp_idle_inhibitor_v1(manager->create_inhibitor(*surface))
{
}
IdleInhibitorV1::~IdleInhibitorV1()
{
destroy();
}
static struct
{
KWayland::Client::ConnectionThread *connection = nullptr;
KWayland::Client::EventQueue *queue = nullptr;
KWayland::Client::Compositor *compositor = nullptr;
KWayland::Client::SubCompositor *subCompositor = nullptr;
KWayland::Client::ServerSideDecorationManager *decoration = nullptr;
KWayland::Client::ShadowManager *shadowManager = nullptr;
XdgShell *xdgShell = nullptr;
KWayland::Client::ShmPool *shm = nullptr;
KWayland::Client::Seat *seat = nullptr;
KWayland::Client::PlasmaShell *plasmaShell = nullptr;
KWayland::Client::PlasmaWindowManagement *windowManagement = nullptr;
KWayland::Client::PointerConstraints *pointerConstraints = nullptr;
KWayland::Client::Registry *registry = nullptr;
WaylandOutputManagementV2 *outputManagementV2 = nullptr;
QThread *thread = nullptr;
QVector<KWayland::Client::Output *> outputs;
QVector<WaylandOutputDeviceV2 *> outputDevicesV2;
IdleInhibitManagerV1 *idleInhibitManagerV1 = nullptr;
KWayland::Client::AppMenuManager *appMenu = nullptr;
XdgDecorationManagerV1 *xdgDecorationManagerV1 = nullptr;
KWayland::Client::TextInputManager *textInputManager = nullptr;
QtWayland::zwp_input_panel_v1 *inputPanelV1 = nullptr;
MockInputMethod *inputMethodV1 = nullptr;
QtWayland::zwp_input_method_context_v1 *inputMethodContextV1 = nullptr;
LayerShellV1 *layerShellV1 = nullptr;
TextInputManagerV3 *textInputManagerV3 = nullptr;
FractionalScaleManagerV1 *fractionalScaleManagerV1 = nullptr;
ScreencastingV1 *screencastingV1 = nullptr;
} s_waylandConnection;
MockInputMethod *inputMethod()
{
return s_waylandConnection.inputMethodV1;
}
KWayland::Client::Surface *inputPanelSurface()
{
return s_waylandConnection.inputMethodV1->inputPanelSurface();
}
MockInputMethod::MockInputMethod(struct wl_registry *registry, int id, int version)
: QtWayland::zwp_input_method_v1(registry, id, version)
{
}
MockInputMethod::~MockInputMethod()
{
}
void MockInputMethod::zwp_input_method_v1_activate(struct ::zwp_input_method_context_v1 *context)
{
if (!m_inputSurface) {
m_inputSurface = Test::createSurface();
m_inputMethodSurface = Test::createInputPanelSurfaceV1(m_inputSurface.get(), s_waylandConnection.outputs.first());
}
m_context = context;
Test::render(m_inputSurface.get(), QSize(1280, 400), Qt::blue);
Q_EMIT activate();
}
void MockInputMethod::zwp_input_method_v1_deactivate(struct ::zwp_input_method_context_v1 *context)
{
QCOMPARE(context, m_context);
zwp_input_method_context_v1_destroy(context);
m_context = nullptr;
if (m_inputSurface) {
m_inputSurface->release();
m_inputSurface->destroy();
m_inputSurface.reset();
delete m_inputMethodSurface;
m_inputMethodSurface = nullptr;
}
}
bool setupWaylandConnection(AdditionalWaylandInterfaces flags)
{
if (s_waylandConnection.connection) {
return false;
}
int sx[2];
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) {
return false;
}
KWin::waylandServer()->display()->createClient(sx[0]);
// setup connection
s_waylandConnection.connection = new KWayland::Client::ConnectionThread;
QSignalSpy connectedSpy(s_waylandConnection.connection, &KWayland::Client::ConnectionThread::connected);
if (!connectedSpy.isValid()) {
return false;
}
s_waylandConnection.connection->setSocketFd(sx[1]);
s_waylandConnection.thread = new QThread(kwinApp());
s_waylandConnection.connection->moveToThread(s_waylandConnection.thread);
s_waylandConnection.thread->start();
s_waylandConnection.connection->initConnection();
if (!connectedSpy.wait()) {
return false;
}
s_waylandConnection.queue = new KWayland::Client::EventQueue;
s_waylandConnection.queue->setup(s_waylandConnection.connection);
if (!s_waylandConnection.queue->isValid()) {
return false;
}
KWayland::Client::Registry *registry = new KWayland::Client::Registry;
s_waylandConnection.registry = registry;
registry->setEventQueue(s_waylandConnection.queue);
QObject::connect(registry, &KWayland::Client::Registry::outputAnnounced, [=](quint32 name, quint32 version) {
KWayland::Client::Output *output = registry->createOutput(name, version, s_waylandConnection.registry);
s_waylandConnection.outputs << output;
QObject::connect(output, &KWayland::Client::Output::removed, [=]() {
output->deleteLater();
s_waylandConnection.outputs.removeOne(output);
});
QObject::connect(output, &KWayland::Client::Output::destroyed, [=]() {
s_waylandConnection.outputs.removeOne(output);
});
});
QObject::connect(registry, &KWayland::Client::Registry::interfaceAnnounced, [=](const QByteArray &interface, quint32 name, quint32 version) {
if (flags & AdditionalWaylandInterface::InputMethodV1) {
if (interface == QByteArrayLiteral("zwp_input_method_v1")) {
s_waylandConnection.inputMethodV1 = new MockInputMethod(*registry, name, version);
} else if (interface == QByteArrayLiteral("zwp_input_panel_v1")) {
s_waylandConnection.inputPanelV1 = new QtWayland::zwp_input_panel_v1(*registry, name, version);
}
}
if (flags & AdditionalWaylandInterface::LayerShellV1) {
if (interface == QByteArrayLiteral("zwlr_layer_shell_v1")) {
s_waylandConnection.layerShellV1 = new LayerShellV1();
s_waylandConnection.layerShellV1->init(*registry, name, version);
}
}
if (flags & AdditionalWaylandInterface::TextInputManagerV3) {
// do something
if (interface == QByteArrayLiteral("zwp_text_input_manager_v3")) {
s_waylandConnection.textInputManagerV3 = new TextInputManagerV3();
s_waylandConnection.textInputManagerV3->init(*registry, name, version);
}
}
if (interface == QByteArrayLiteral("xdg_wm_base")) {
s_waylandConnection.xdgShell = new XdgShell();
s_waylandConnection.xdgShell->init(*registry, name, version);
}
if (flags & AdditionalWaylandInterface::XdgDecorationV1) {
if (interface == zxdg_decoration_manager_v1_interface.name) {
s_waylandConnection.xdgDecorationManagerV1 = new XdgDecorationManagerV1();
s_waylandConnection.xdgDecorationManagerV1->init(*registry, name, version);
return;
}
}
if (flags & AdditionalWaylandInterface::IdleInhibitV1) {
if (interface == zwp_idle_inhibit_manager_v1_interface.name) {
s_waylandConnection.idleInhibitManagerV1 = new IdleInhibitManagerV1();
s_waylandConnection.idleInhibitManagerV1->init(*registry, name, version);
return;
}
}
if (flags & AdditionalWaylandInterface::OutputDeviceV2) {
if (interface == kde_output_device_v2_interface.name) {
WaylandOutputDeviceV2 *device = new WaylandOutputDeviceV2(name);
device->init(*registry, name, version);
s_waylandConnection.outputDevicesV2 << device;
QObject::connect(device, &WaylandOutputDeviceV2::destroyed, [=]() {
s_waylandConnection.outputDevicesV2.removeOne(device);
device->deleteLater();
});
QObject::connect(registry, &KWayland::Client::Registry::interfaceRemoved, device, [name, device](const quint32 &interfaceName) {
if (name == interfaceName) {
s_waylandConnection.outputDevicesV2.removeOne(device);
device->deleteLater();
}
});
return;
}
}
if (flags & AdditionalWaylandInterface::OutputManagementV2) {
if (interface == kde_output_management_v2_interface.name) {
s_waylandConnection.outputManagementV2 = new WaylandOutputManagementV2(*registry, name, version);
return;
}
}
if (flags & AdditionalWaylandInterface::FractionalScaleManagerV1) {
if (interface == wp_fractional_scale_manager_v1_interface.name) {
s_waylandConnection.fractionalScaleManagerV1 = new FractionalScaleManagerV1();
s_waylandConnection.fractionalScaleManagerV1->init(*registry, name, version);
return;
}
}
if (flags & AdditionalWaylandInterface::ScreencastingV1) {
if (interface == zkde_screencast_unstable_v1_interface.name) {
s_waylandConnection.screencastingV1 = new ScreencastingV1();
s_waylandConnection.screencastingV1->init(*registry, name, version);
return;
}
}
});
QSignalSpy allAnnounced(registry, &KWayland::Client::Registry::interfacesAnnounced);
if (!allAnnounced.isValid()) {
return false;
}
registry->create(s_waylandConnection.connection);
if (!registry->isValid()) {
return false;
}
registry->setup();
if (!allAnnounced.wait()) {
return false;
}
s_waylandConnection.compositor = registry->createCompositor(registry->interface(KWayland::Client::Registry::Interface::Compositor).name, registry->interface(KWayland::Client::Registry::Interface::Compositor).version);
if (!s_waylandConnection.compositor->isValid()) {
return false;
}
s_waylandConnection.subCompositor = registry->createSubCompositor(registry->interface(KWayland::Client::Registry::Interface::SubCompositor).name, registry->interface(KWayland::Client::Registry::Interface::SubCompositor).version);
if (!s_waylandConnection.subCompositor->isValid()) {
return false;
}
s_waylandConnection.shm = registry->createShmPool(registry->interface(KWayland::Client::Registry::Interface::Shm).name, registry->interface(KWayland::Client::Registry::Interface::Shm).version);
if (!s_waylandConnection.shm->isValid()) {
return false;
}
if (flags.testFlag(AdditionalWaylandInterface::Seat)) {
s_waylandConnection.seat = registry->createSeat(registry->interface(KWayland::Client::Registry::Interface::Seat).name, registry->interface(KWayland::Client::Registry::Interface::Seat).version);
if (!s_waylandConnection.seat->isValid()) {
return false;
}
}
if (flags.testFlag(AdditionalWaylandInterface::ShadowManager)) {
s_waylandConnection.shadowManager = registry->createShadowManager(registry->interface(KWayland::Client::Registry::Interface::Shadow).name,
registry->interface(KWayland::Client::Registry::Interface::Shadow).version);
if (!s_waylandConnection.shadowManager->isValid()) {
return false;
}
}
if (flags.testFlag(AdditionalWaylandInterface::Decoration)) {
s_waylandConnection.decoration = registry->createServerSideDecorationManager(registry->interface(KWayland::Client::Registry::Interface::ServerSideDecorationManager).name,
registry->interface(KWayland::Client::Registry::Interface::ServerSideDecorationManager).version);
if (!s_waylandConnection.decoration->isValid()) {
return false;
}
}
if (flags.testFlag(AdditionalWaylandInterface::PlasmaShell)) {
s_waylandConnection.plasmaShell = registry->createPlasmaShell(registry->interface(KWayland::Client::Registry::Interface::PlasmaShell).name,
registry->interface(KWayland::Client::Registry::Interface::PlasmaShell).version);
if (!s_waylandConnection.plasmaShell->isValid()) {
return false;
}
}
if (flags.testFlag(AdditionalWaylandInterface::WindowManagement)) {
s_waylandConnection.windowManagement = registry->createPlasmaWindowManagement(registry->interface(KWayland::Client::Registry::Interface::PlasmaWindowManagement).name,
registry->interface(KWayland::Client::Registry::Interface::PlasmaWindowManagement).version);
if (!s_waylandConnection.windowManagement->isValid()) {
return false;
}
}
if (flags.testFlag(AdditionalWaylandInterface::PointerConstraints)) {
s_waylandConnection.pointerConstraints = registry->createPointerConstraints(registry->interface(KWayland::Client::Registry::Interface::PointerConstraintsUnstableV1).name,
registry->interface(KWayland::Client::Registry::Interface::PointerConstraintsUnstableV1).version);
if (!s_waylandConnection.pointerConstraints->isValid()) {
return false;
}
}
if (flags.testFlag(AdditionalWaylandInterface::AppMenu)) {
s_waylandConnection.appMenu = registry->createAppMenuManager(registry->interface(KWayland::Client::Registry::Interface::AppMenu).name, registry->interface(KWayland::Client::Registry::Interface::AppMenu).version);
if (!s_waylandConnection.appMenu->isValid()) {
return false;
}
}
if (flags.testFlag(AdditionalWaylandInterface::TextInputManagerV2)) {
s_waylandConnection.textInputManager = registry->createTextInputManager(registry->interface(KWayland::Client::Registry::Interface::TextInputManagerUnstableV2).name, registry->interface(KWayland::Client::Registry::Interface::TextInputManagerUnstableV2).version);
if (!s_waylandConnection.textInputManager->isValid()) {
return false;
}
}
return true;
}
void destroyWaylandConnection()
{
delete s_waylandConnection.compositor;
s_waylandConnection.compositor = nullptr;
delete s_waylandConnection.subCompositor;
s_waylandConnection.subCompositor = nullptr;
delete s_waylandConnection.windowManagement;
s_waylandConnection.windowManagement = nullptr;
delete s_waylandConnection.plasmaShell;
s_waylandConnection.plasmaShell = nullptr;
delete s_waylandConnection.decoration;
s_waylandConnection.decoration = nullptr;
delete s_waylandConnection.decoration;
s_waylandConnection.decoration = nullptr;
delete s_waylandConnection.seat;
s_waylandConnection.seat = nullptr;
delete s_waylandConnection.pointerConstraints;
s_waylandConnection.pointerConstraints = nullptr;
delete s_waylandConnection.xdgShell;
s_waylandConnection.xdgShell = nullptr;
delete s_waylandConnection.shadowManager;
s_waylandConnection.shadowManager = nullptr;
delete s_waylandConnection.idleInhibitManagerV1;
s_waylandConnection.idleInhibitManagerV1 = nullptr;
delete s_waylandConnection.shm;
s_waylandConnection.shm = nullptr;
delete s_waylandConnection.registry;
s_waylandConnection.registry = nullptr;
delete s_waylandConnection.appMenu;
s_waylandConnection.appMenu = nullptr;
delete s_waylandConnection.xdgDecorationManagerV1;
s_waylandConnection.xdgDecorationManagerV1 = nullptr;
delete s_waylandConnection.textInputManager;
s_waylandConnection.textInputManager = nullptr;
delete s_waylandConnection.inputPanelV1;
s_waylandConnection.inputPanelV1 = nullptr;
delete s_waylandConnection.layerShellV1;
s_waylandConnection.layerShellV1 = nullptr;
delete s_waylandConnection.outputManagementV2;
s_waylandConnection.outputManagementV2 = nullptr;
delete s_waylandConnection.fractionalScaleManagerV1;
s_waylandConnection.fractionalScaleManagerV1 = nullptr;
delete s_waylandConnection.queue; // Must be destroyed last
s_waylandConnection.queue = nullptr;
if (s_waylandConnection.thread) {
s_waylandConnection.connection->deleteLater();
s_waylandConnection.thread->quit();
s_waylandConnection.thread->wait();
delete s_waylandConnection.thread;
s_waylandConnection.thread = nullptr;
s_waylandConnection.connection = nullptr;
}
s_waylandConnection.outputs.clear();
s_waylandConnection.outputDevicesV2.clear();
}
KWayland::Client::ConnectionThread *waylandConnection()
{
return s_waylandConnection.connection;
}
KWayland::Client::Compositor *waylandCompositor()
{
return s_waylandConnection.compositor;
}
KWayland::Client::SubCompositor *waylandSubCompositor()
{
return s_waylandConnection.subCompositor;
}
KWayland::Client::ShadowManager *waylandShadowManager()
{
return s_waylandConnection.shadowManager;
}
KWayland::Client::ShmPool *waylandShmPool()
{
return s_waylandConnection.shm;
}
KWayland::Client::Seat *waylandSeat()
{
return s_waylandConnection.seat;
}
KWayland::Client::ServerSideDecorationManager *waylandServerSideDecoration()
{
return s_waylandConnection.decoration;
}
KWayland::Client::PlasmaShell *waylandPlasmaShell()
{
return s_waylandConnection.plasmaShell;
}
KWayland::Client::PlasmaWindowManagement *waylandWindowManagement()
{
return s_waylandConnection.windowManagement;
}
KWayland::Client::PointerConstraints *waylandPointerConstraints()
{
return s_waylandConnection.pointerConstraints;
}
KWayland::Client::AppMenuManager *waylandAppMenuManager()
{
return s_waylandConnection.appMenu;
}
KWin::Test::WaylandOutputManagementV2 *waylandOutputManagementV2()
{
return s_waylandConnection.outputManagementV2;
}
KWayland::Client::TextInputManager *waylandTextInputManager()
{
return s_waylandConnection.textInputManager;
}
TextInputManagerV3 *waylandTextInputManagerV3()
{
return s_waylandConnection.textInputManagerV3;
}
QVector<KWayland::Client::Output *> waylandOutputs()
{
return s_waylandConnection.outputs;
}
KWayland::Client::Output *waylandOutput(const QString &name)
{
for (KWayland::Client::Output *output : std::as_const(s_waylandConnection.outputs)) {
if (output->name() == name) {
return output;
}
}
return nullptr;
}
ScreencastingV1 *screencasting()
{
return s_waylandConnection.screencastingV1;
}
QVector<KWin::Test::WaylandOutputDeviceV2 *> waylandOutputDevicesV2()
{
return s_waylandConnection.outputDevicesV2;
}
bool waitForWaylandSurface(Window *window)
{
if (window->surface()) {
return true;
}
QSignalSpy surfaceChangedSpy(window, &Window::surfaceChanged);
return surfaceChangedSpy.wait();
}
bool waitForWaylandPointer()
{
if (!s_waylandConnection.seat) {
return false;
}
QSignalSpy hasPointerSpy(s_waylandConnection.seat, &KWayland::Client::Seat::hasPointerChanged);
if (!hasPointerSpy.isValid()) {
return false;
}
return hasPointerSpy.wait();
}
bool waitForWaylandTouch()
{
if (!s_waylandConnection.seat) {
return false;
}
QSignalSpy hasTouchSpy(s_waylandConnection.seat, &KWayland::Client::Seat::hasTouchChanged);
if (!hasTouchSpy.isValid()) {
return false;
}
return hasTouchSpy.wait();
}
bool waitForWaylandKeyboard()
{
if (!s_waylandConnection.seat) {
return false;
}
QSignalSpy hasKeyboardSpy(s_waylandConnection.seat, &KWayland::Client::Seat::hasKeyboardChanged);
if (!hasKeyboardSpy.isValid()) {
return false;
}
return hasKeyboardSpy.wait();
}
void render(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format)
{
QImage img(size, format);
img.fill(color);
render(surface, img);
}
void render(KWayland::Client::Surface *surface, const QImage &img)
{
surface->attachBuffer(s_waylandConnection.shm->createBuffer(img));
surface->damage(QRect(QPoint(0, 0), img.size()));
surface->commit(KWayland::Client::Surface::CommitFlag::None);
}
Window *waitForWaylandWindowShown(int timeout)
{
QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
if (!windowAddedSpy.isValid()) {
return nullptr;
}
if (!windowAddedSpy.wait(timeout)) {
return nullptr;
}
return windowAddedSpy.first().first().value<Window *>();
}
Window *renderAndWaitForShown(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format, int timeout)
{
QImage img(size, format);
img.fill(color);
return renderAndWaitForShown(surface, img, timeout);
}
Window *renderAndWaitForShown(KWayland::Client::Surface *surface, const QImage &img, int timeout)
{
QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
if (!windowAddedSpy.isValid()) {
return nullptr;
}
render(surface, img);
flushWaylandConnection();
if (!windowAddedSpy.wait(timeout)) {
return nullptr;
}
return windowAddedSpy.first().first().value<Window *>();
}
void flushWaylandConnection()
{
if (s_waylandConnection.connection) {
s_waylandConnection.connection->flush();
}
}
class WaylandSyncPoint : public QObject
{
Q_OBJECT
public:
explicit WaylandSyncPoint(KWayland::Client::ConnectionThread *connection, KWayland::Client::EventQueue *eventQueue)
{
static const wl_callback_listener listener = {
.done = [](void *data, wl_callback *callback, uint32_t callback_data) {
auto syncPoint = static_cast<WaylandSyncPoint *>(data);
Q_EMIT syncPoint->done();
},
};
m_callback = wl_display_sync(connection->display());
eventQueue->addProxy(m_callback);
wl_callback_add_listener(m_callback, &listener, this);
}
~WaylandSyncPoint() override
{
wl_callback_destroy(m_callback);
}
Q_SIGNALS:
void done();
private:
wl_callback *m_callback;
};
bool waylandSync()
{
WaylandSyncPoint syncPoint(s_waylandConnection.connection, s_waylandConnection.queue);
QSignalSpy doneSpy(&syncPoint, &WaylandSyncPoint::done);
return doneSpy.wait();
}
std::unique_ptr<KWayland::Client::Surface> createSurface()
{
if (!s_waylandConnection.compositor) {
return nullptr;
}
std::unique_ptr<KWayland::Client::Surface> s{s_waylandConnection.compositor->createSurface()};
return s->isValid() ? std::move(s) : nullptr;
}
KWayland::Client::SubSurface *createSubSurface(KWayland::Client::Surface *surface, KWayland::Client::Surface *parentSurface, QObject *parent)
{
if (!s_waylandConnection.subCompositor) {
return nullptr;
}
auto s = s_waylandConnection.subCompositor->createSubSurface(surface, parentSurface, parent);
if (!s->isValid()) {
delete s;
return nullptr;
}
return s;
}
LayerSurfaceV1 *createLayerSurfaceV1(KWayland::Client::Surface *surface, const QString &scope, KWayland::Client::Output *output, LayerShellV1::layer layer)
{
LayerShellV1 *shell = s_waylandConnection.layerShellV1;
if (!shell) {
qWarning() << "Could not create a layer surface because the layer shell global is not bound";
return nullptr;
}
struct ::wl_output *nativeOutput = nullptr;
if (output) {
nativeOutput = *output;
}
LayerSurfaceV1 *shellSurface = new LayerSurfaceV1();
shellSurface->init(shell->get_layer_surface(*surface, nativeOutput, layer, scope));
return shellSurface;
}
QtWayland::zwp_input_panel_surface_v1 *createInputPanelSurfaceV1(KWayland::Client::Surface *surface, KWayland::Client::Output *output)
{
if (!s_waylandConnection.inputPanelV1) {
qWarning() << "Unable to create the input panel surface. The interface input_panel global is not bound";
return nullptr;
}
QtWayland::zwp_input_panel_surface_v1 *s = new QtWayland::zwp_input_panel_surface_v1(s_waylandConnection.inputPanelV1->get_input_panel_surface(*surface));
if (!s->isInitialized()) {
delete s;
return nullptr;
}
s->set_toplevel(output->output(), QtWayland::zwp_input_panel_surface_v1::position_center_bottom);
return s;
}
FractionalScaleV1 *createFractionalScaleV1(KWayland::Client::Surface *surface)
{
if (!s_waylandConnection.fractionalScaleManagerV1) {
qWarning() << "Unable to create fractional scale surface. The global is not bound";
return nullptr;
}
auto scale = new FractionalScaleV1();
scale->init(s_waylandConnection.fractionalScaleManagerV1->get_fractional_scale(*surface));
return scale;
}
static void waitForConfigured(XdgSurface *shellSurface)
{
QSignalSpy surfaceConfigureRequestedSpy(shellSurface, &XdgSurface::configureRequested);
shellSurface->surface()->commit(KWayland::Client::Surface::CommitFlag::None);
QVERIFY(surfaceConfigureRequestedSpy.wait());
shellSurface->ack_configure(surfaceConfigureRequestedSpy.last().first().toUInt());
}
XdgToplevel *createXdgToplevelSurface(KWayland::Client::Surface *surface, QObject *parent)
{
return createXdgToplevelSurface(surface, CreationSetup::CreateAndConfigure, parent);
}
XdgToplevel *createXdgToplevelSurface(KWayland::Client::Surface *surface, CreationSetup configureMode, QObject *parent)
{
XdgShell *shell = s_waylandConnection.xdgShell;
if (!shell) {
qWarning() << "Could not create an xdg_toplevel surface because xdg_wm_base global is not bound";
return nullptr;
}
XdgSurface *xdgSurface = new XdgSurface(shell, surface);
XdgToplevel *xdgToplevel = new XdgToplevel(xdgSurface, parent);
if (configureMode == CreationSetup::CreateAndConfigure) {
waitForConfigured(xdgSurface);
}
return xdgToplevel;
}
XdgPositioner *createXdgPositioner()
{
XdgShell *shell = s_waylandConnection.xdgShell;
if (!shell) {
qWarning() << "Could not create an xdg_positioner object because xdg_wm_base global is not bound";
return nullptr;
}
return new XdgPositioner(shell);
}
XdgPopup *createXdgPopupSurface(KWayland::Client::Surface *surface, XdgSurface *parentSurface, XdgPositioner *positioner,
CreationSetup configureMode, QObject *parent)
{
XdgShell *shell = s_waylandConnection.xdgShell;
if (!shell) {
qWarning() << "Could not create an xdg_popup surface because xdg_wm_base global is not bound";
return nullptr;
}
XdgSurface *xdgSurface = new XdgSurface(shell, surface);
XdgPopup *xdgPopup = new XdgPopup(xdgSurface, parentSurface, positioner, parent);
if (configureMode == CreationSetup::CreateAndConfigure) {
waitForConfigured(xdgSurface);
}
return xdgPopup;
}
XdgToplevelDecorationV1 *createXdgToplevelDecorationV1(XdgToplevel *toplevel, QObject *parent)
{
XdgDecorationManagerV1 *manager = s_waylandConnection.xdgDecorationManagerV1;
if (!manager) {
qWarning() << "Could not create an xdg_toplevel_decoration_v1 because xdg_decoration_manager_v1 global is not bound";
return nullptr;
}
return new XdgToplevelDecorationV1(manager, toplevel, parent);
}
IdleInhibitorV1 *createIdleInhibitorV1(KWayland::Client::Surface *surface)
{
IdleInhibitManagerV1 *manager = s_waylandConnection.idleInhibitManagerV1;
if (!manager) {
qWarning() << "Could not create an idle_inhibitor_v1 because idle_inhibit_manager_v1 global is not bound";
return nullptr;
}
return new IdleInhibitorV1(manager, surface);
}
bool waitForWindowClosed(Window *window)
{
QSignalSpy closedSpy(window, &Window::closed);
if (!closedSpy.isValid()) {
return false;
}
return closedSpy.wait();
}
#if KWIN_BUILD_SCREENLOCKER
bool lockScreen()
{
if (waylandServer()->isScreenLocked()) {
return false;
}
QSignalSpy lockStateChangedSpy(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged);
if (!lockStateChangedSpy.isValid()) {
return false;
}
ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate);
if (lockStateChangedSpy.count() != 1) {
return false;
}
if (!waylandServer()->isScreenLocked()) {
return false;
}
if (!kwinApp()->screenLockerWatcher()->isLocked()) {
QSignalSpy lockedSpy(kwinApp()->screenLockerWatcher(), &ScreenLockerWatcher::locked);
if (!lockedSpy.isValid()) {
return false;
}
if (!lockedSpy.wait()) {
return false;
}
if (!kwinApp()->screenLockerWatcher()->isLocked()) {
return false;
}
}
return true;
}
bool unlockScreen()
{
QSignalSpy lockStateChangedSpy(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged);
if (!lockStateChangedSpy.isValid()) {
return false;
}
using namespace ScreenLocker;
const auto children = KSldApp::self()->children();
for (auto it = children.begin(); it != children.end(); ++it) {
if (qstrcmp((*it)->metaObject()->className(), "LogindIntegration") != 0) {
continue;
}
QMetaObject::invokeMethod(*it, "requestUnlock");
break;
}
if (waylandServer()->isScreenLocked()) {
lockStateChangedSpy.wait();
}
if (waylandServer()->isScreenLocked()) {
return true;
}
if (kwinApp()->screenLockerWatcher()->isLocked()) {
QSignalSpy lockedSpy(kwinApp()->screenLockerWatcher(), &ScreenLockerWatcher::locked);
if (!lockedSpy.isValid()) {
return false;
}
if (!lockedSpy.wait()) {
return false;
}
if (kwinApp()->screenLockerWatcher()->isLocked()) {
return false;
}
}
return true;
}
#endif // KWIN_BUILD_LOCKSCREEN
bool renderNodeAvailable()
{
const int deviceCount = drmGetDevices2(0, nullptr, 0);
if (deviceCount <= 0) {
return false;
}
QVector<drmDevice *> devices(deviceCount);
if (drmGetDevices2(0, devices.data(), devices.size()) < 0) {
return false;
}
auto deviceCleanup = qScopeGuard([&devices]() {
drmFreeDevices(devices.data(), devices.size());
});
return std::any_of(devices.constBegin(), devices.constEnd(), [](drmDevice *device) {
return device->available_nodes & (1 << DRM_NODE_RENDER);
});
}
void XcbConnectionDeleter::operator()(xcb_connection_t *pointer)
{
xcb_disconnect(pointer);
};
Test::XcbConnectionPtr createX11Connection()
{
QFutureWatcher<xcb_connection_t *> watcher;
QEventLoop e;
e.connect(&watcher, &QFutureWatcher<xcb_connection_t *>::finished, &e, &QEventLoop::quit);
QFuture<xcb_connection_t *> future = QtConcurrent::run([]() {
return xcb_connect(nullptr, nullptr);
});
watcher.setFuture(future);
e.exec();
return Test::XcbConnectionPtr(future.result());
}
WaylandOutputManagementV2::WaylandOutputManagementV2(struct ::wl_registry *registry, int id, int version)
: QObject()
, QtWayland::kde_output_management_v2()
{
init(registry, id, version);
}
WaylandOutputConfigurationV2 *WaylandOutputManagementV2::createConfiguration()
{
return new WaylandOutputConfigurationV2(create_configuration());
}
WaylandOutputConfigurationV2::WaylandOutputConfigurationV2(struct ::kde_output_configuration_v2 *object)
: QObject()
, QtWayland::kde_output_configuration_v2()
{
init(object);
}
void WaylandOutputConfigurationV2::kde_output_configuration_v2_applied()
{
Q_EMIT applied();
}
void WaylandOutputConfigurationV2::kde_output_configuration_v2_failed()
{
Q_EMIT failed();
}
WaylandOutputDeviceV2Mode::WaylandOutputDeviceV2Mode(struct ::kde_output_device_mode_v2 *object)
: QtWayland::kde_output_device_mode_v2(object)
{
}
WaylandOutputDeviceV2Mode::~WaylandOutputDeviceV2Mode()
{
kde_output_device_mode_v2_destroy(object());
}
void WaylandOutputDeviceV2Mode::kde_output_device_mode_v2_size(int32_t width, int32_t height)
{
m_size = QSize(width, height);
}
void WaylandOutputDeviceV2Mode::kde_output_device_mode_v2_refresh(int32_t refresh)
{
m_refreshRate = refresh;
}
void WaylandOutputDeviceV2Mode::kde_output_device_mode_v2_preferred()
{
m_preferred = true;
}
void WaylandOutputDeviceV2Mode::kde_output_device_mode_v2_removed()
{
Q_EMIT removed();
}
int WaylandOutputDeviceV2Mode::refreshRate() const
{
return m_refreshRate;
}
QSize WaylandOutputDeviceV2Mode::size() const
{
return m_size;
}
bool WaylandOutputDeviceV2Mode::preferred() const
{
return m_preferred;
}
bool WaylandOutputDeviceV2Mode::operator==(const WaylandOutputDeviceV2Mode &other) const
{
return m_size == other.m_size && m_refreshRate == other.m_refreshRate && m_preferred == other.m_preferred;
}
WaylandOutputDeviceV2Mode *WaylandOutputDeviceV2Mode::get(struct ::kde_output_device_mode_v2 *object)
{
auto mode = QtWayland::kde_output_device_mode_v2::fromObject(object);
return static_cast<WaylandOutputDeviceV2Mode *>(mode);
}
WaylandOutputDeviceV2::WaylandOutputDeviceV2(int id)
: QObject()
, kde_output_device_v2()
, m_id(id)
{
}
WaylandOutputDeviceV2::~WaylandOutputDeviceV2()
{
qDeleteAll(m_modes);
kde_output_device_v2_destroy(object());
}
void WaylandOutputDeviceV2::kde_output_device_v2_geometry(int32_t x,
int32_t y,
int32_t physical_width,
int32_t physical_height,
int32_t subpixel,
const QString &make,
const QString &model,
int32_t transform)
{
m_pos = QPoint(x, y);
m_physicalSize = QSize(physical_width, physical_height);
m_subpixel = subpixel;
m_manufacturer = make;
m_model = model;
m_transform = transform;
}
void WaylandOutputDeviceV2::kde_output_device_v2_current_mode(struct ::kde_output_device_mode_v2 *mode)
{
auto m = WaylandOutputDeviceV2Mode::get(mode);
if (*m == *m_mode) {
// unchanged
return;
}
m_mode = m;
}
void WaylandOutputDeviceV2::kde_output_device_v2_mode(struct ::kde_output_device_mode_v2 *mode)
{
WaylandOutputDeviceV2Mode *m = new WaylandOutputDeviceV2Mode(mode);
// last mode sent is the current one
m_mode = m;
m_modes.append(m);
connect(m, &WaylandOutputDeviceV2Mode::removed, this, [this, m]() {
m_modes.removeOne(m);
if (m_mode == m) {
if (!m_modes.isEmpty()) {
m_mode = m_modes.first();
} else {
// was last mode
qFatal("KWaylandBackend: no output modes available anymore, this seems like a compositor bug");
}
}
delete m;
});
}
QString WaylandOutputDeviceV2::modeId() const
{
return QString::number(m_modes.indexOf(m_mode));
}
WaylandOutputDeviceV2Mode *WaylandOutputDeviceV2::deviceModeFromId(const int modeId) const
{
return m_modes.at(modeId);
}
QString WaylandOutputDeviceV2::modeName(const WaylandOutputDeviceV2Mode *m) const
{
return QString::number(m->size().width()) + QLatin1Char('x') + QString::number(m->size().height()) + QLatin1Char('@')
+ QString::number(qRound(m->refreshRate() / 1000.0));
}
QString WaylandOutputDeviceV2::name() const
{
return QStringLiteral("%1 %2").arg(m_manufacturer, m_model);
}
QDebug operator<<(QDebug dbg, const WaylandOutputDeviceV2 *output)
{
dbg << "WaylandOutput(Id:" << output->id() << ", Name:" << QString(output->manufacturer() + QLatin1Char(' ') + output->model()) << ")";
return dbg;
}
void WaylandOutputDeviceV2::kde_output_device_v2_done()
{
Q_EMIT done();
}
void WaylandOutputDeviceV2::kde_output_device_v2_scale(wl_fixed_t factor)
{
m_factor = wl_fixed_to_double(factor);
}
void WaylandOutputDeviceV2::kde_output_device_v2_edid(const QString &edid)
{
m_edid = QByteArray::fromBase64(edid.toUtf8());
}
void WaylandOutputDeviceV2::kde_output_device_v2_enabled(int32_t enabled)
{
if (m_enabled != enabled) {
m_enabled = enabled;
Q_EMIT enabledChanged();
}
}
void WaylandOutputDeviceV2::kde_output_device_v2_uuid(const QString &uuid)
{
m_uuid = uuid;
}
void WaylandOutputDeviceV2::kde_output_device_v2_serial_number(const QString &serialNumber)
{
m_serialNumber = serialNumber;
}
void WaylandOutputDeviceV2::kde_output_device_v2_eisa_id(const QString &eisaId)
{
m_eisaId = eisaId;
}
void WaylandOutputDeviceV2::kde_output_device_v2_capabilities(uint32_t flags)
{
m_flags = flags;
}
void WaylandOutputDeviceV2::kde_output_device_v2_overscan(uint32_t overscan)
{
m_overscan = overscan;
}
void WaylandOutputDeviceV2::kde_output_device_v2_vrr_policy(uint32_t vrr_policy)
{
m_vrr_policy = vrr_policy;
}
void WaylandOutputDeviceV2::kde_output_device_v2_rgb_range(uint32_t rgb_range)
{
m_rgbRange = rgb_range;
}
QByteArray WaylandOutputDeviceV2::edid() const
{
return m_edid;
}
bool WaylandOutputDeviceV2::enabled() const
{
return m_enabled;
}
int WaylandOutputDeviceV2::id() const
{
return m_id;
}
qreal WaylandOutputDeviceV2::scale() const
{
return m_factor;
}
QString WaylandOutputDeviceV2::manufacturer() const
{
return m_manufacturer;
}
QString WaylandOutputDeviceV2::model() const
{
return m_model;
}
QPoint WaylandOutputDeviceV2::globalPosition() const
{
return m_pos;
}
QSize WaylandOutputDeviceV2::pixelSize() const
{
return m_mode->size();
}
int WaylandOutputDeviceV2::refreshRate() const
{
return m_mode->refreshRate();
}
uint32_t WaylandOutputDeviceV2::vrrPolicy() const
{
return m_vrr_policy;
}
uint32_t WaylandOutputDeviceV2::overscan() const
{
return m_overscan;
}
uint32_t WaylandOutputDeviceV2::capabilities() const
{
return m_flags;
}
uint32_t WaylandOutputDeviceV2::rgbRange() const
{
return m_rgbRange;
}
VirtualInputDevice::VirtualInputDevice(QObject *parent)
: InputDevice(parent)
{
}
void VirtualInputDevice::setPointer(bool set)
{
m_pointer = set;
}
void VirtualInputDevice::setKeyboard(bool set)
{
m_keyboard = set;
}
void VirtualInputDevice::setTouch(bool set)
{
m_touch = set;
}
void VirtualInputDevice::setName(const QString &name)
{
m_name = name;
}
QString VirtualInputDevice::sysName() const
{
return QString();
}
QString VirtualInputDevice::name() const
{
return m_name;
}
bool VirtualInputDevice::isEnabled() const
{
return true;
}
void VirtualInputDevice::setEnabled(bool enabled)
{
}
LEDs VirtualInputDevice::leds() const
{
return LEDs();
}
void VirtualInputDevice::setLeds(LEDs leds)
{
}
bool VirtualInputDevice::isKeyboard() const
{
return m_keyboard;
}
bool VirtualInputDevice::isAlphaNumericKeyboard() const
{
return m_keyboard;
}
bool VirtualInputDevice::isPointer() const
{
return m_pointer;
}
bool VirtualInputDevice::isTouchpad() const
{
return false;
}
bool VirtualInputDevice::isTouch() const
{
return m_touch;
}
bool VirtualInputDevice::isTabletTool() const
{
return false;
}
bool VirtualInputDevice::isTabletPad() const
{
return false;
}
bool VirtualInputDevice::isTabletModeSwitch() const
{
return false;
}
bool VirtualInputDevice::isLidSwitch() const
{
return false;
}
void keyboardKeyPressed(quint32 key, quint32 time)
{
auto virtualKeyboard = static_cast<WaylandTestApplication *>(kwinApp())->virtualKeyboard();
Q_EMIT virtualKeyboard->keyChanged(key, InputRedirection::KeyboardKeyState::KeyboardKeyPressed, std::chrono::milliseconds(time), virtualKeyboard);
}
void keyboardKeyReleased(quint32 key, quint32 time)
{
auto virtualKeyboard = static_cast<WaylandTestApplication *>(kwinApp())->virtualKeyboard();
Q_EMIT virtualKeyboard->keyChanged(key, InputRedirection::KeyboardKeyState::KeyboardKeyReleased, std::chrono::milliseconds(time), virtualKeyboard);
}
void pointerAxisHorizontal(qreal delta, quint32 time, qint32 discreteDelta, InputRedirection::PointerAxisSource source)
{
auto virtualPointer = static_cast<WaylandTestApplication *>(kwinApp())->virtualPointer();
Q_EMIT virtualPointer->pointerAxisChanged(InputRedirection::PointerAxis::PointerAxisHorizontal, delta, discreteDelta, source, std::chrono::milliseconds(time), virtualPointer);
}
void pointerAxisVertical(qreal delta, quint32 time, qint32 discreteDelta, InputRedirection::PointerAxisSource source)
{
auto virtualPointer = static_cast<WaylandTestApplication *>(kwinApp())->virtualPointer();
Q_EMIT virtualPointer->pointerAxisChanged(InputRedirection::PointerAxis::PointerAxisVertical, delta, discreteDelta, source, std::chrono::milliseconds(time), virtualPointer);
}
void pointerButtonPressed(quint32 button, quint32 time)
{
auto virtualPointer = static_cast<WaylandTestApplication *>(kwinApp())->virtualPointer();
Q_EMIT virtualPointer->pointerButtonChanged(button, InputRedirection::PointerButtonState::PointerButtonPressed, std::chrono::milliseconds(time), virtualPointer);
}
void pointerButtonReleased(quint32 button, quint32 time)
{
auto virtualPointer = static_cast<WaylandTestApplication *>(kwinApp())->virtualPointer();
Q_EMIT virtualPointer->pointerButtonChanged(button, InputRedirection::PointerButtonState::PointerButtonReleased, std::chrono::milliseconds(time), virtualPointer);
}
void pointerMotion(const QPointF &position, quint32 time)
{
auto virtualPointer = static_cast<WaylandTestApplication *>(kwinApp())->virtualPointer();
Q_EMIT virtualPointer->pointerMotionAbsolute(position, std::chrono::milliseconds(time), virtualPointer);
}
void pointerMotionRelative(const QPointF &delta, quint32 time)
{
auto virtualPointer = static_cast<WaylandTestApplication *>(kwinApp())->virtualPointer();
Q_EMIT virtualPointer->pointerMotion(delta, delta, std::chrono::milliseconds(time), virtualPointer);
}
void touchCancel()
{
auto virtualTouch = static_cast<WaylandTestApplication *>(kwinApp())->virtualTouch();
Q_EMIT virtualTouch->touchCanceled(virtualTouch);
}
void touchDown(qint32 id, const QPointF &pos, quint32 time)
{
auto virtualTouch = static_cast<WaylandTestApplication *>(kwinApp())->virtualTouch();
Q_EMIT virtualTouch->touchDown(id, pos, std::chrono::milliseconds(time), virtualTouch);
}
void touchMotion(qint32 id, const QPointF &pos, quint32 time)
{
auto virtualTouch = static_cast<WaylandTestApplication *>(kwinApp())->virtualTouch();
Q_EMIT virtualTouch->touchMotion(id, pos, std::chrono::milliseconds(time), virtualTouch);
}
void touchUp(qint32 id, quint32 time)
{
auto virtualTouch = static_cast<WaylandTestApplication *>(kwinApp())->virtualTouch();
Q_EMIT virtualTouch->touchUp(id, std::chrono::milliseconds(time), virtualTouch);
}
}
}
#include "test_helpers.moc"