Fix size restore upon keyboard close in XdgSurfaceClient

in XdgSurfaceClient setFrameGeometry is async,
so we can't rely on it having the final value immediately.
make setVirtualKeyboardGeometry a virtual.
in the implementation on setVirtualKeyboardGeometry
use requestedFrameGeometry() instead of frameGeometry()
master
Marco Martin 2020-09-01 08:58:46 +00:00
parent f894976e2e
commit 97f4712f29
12 changed files with 382 additions and 9 deletions

View File

@ -827,6 +827,10 @@ install(FILES scripting/kwinscript.desktop DESTINATION ${SERVICETYPES_INSTALL_DI
add_subdirectory(qml)
if (BUILD_TESTING)
find_package(WaylandProtocols REQUIRED)
find_package(QtWaylandScanner ${QT_MIN_VERSION} REQUIRED)
find_package(Wayland REQUIRED COMPONENTS Client)
add_subdirectory(autotests)
add_subdirectory(tests)
endif()

View File

@ -2571,6 +2571,16 @@ void AbstractClient::setVirtualKeyboardGeometry(const QRect &geo)
setFrameGeometry(newWindowGeometry);
}
QRect AbstractClient::keyboardGeometryRestore() const
{
return m_keyboardGeometryRestore;
}
void AbstractClient::setKeyboardGeometryRestore(const QRect &geom)
{
m_keyboardGeometryRestore = geom;
}
bool AbstractClient::dockWantsInput() const
{
return false;

View File

@ -757,7 +757,7 @@ public:
* Sets the geometry of the virtual keyboard, The window may resize itself in order to make space for the keybaord
* This geometry is in global coordinates
*/
void setVirtualKeyboardGeometry(const QRect &geo);
virtual void setVirtualKeyboardGeometry(const QRect &geo);
/**
* Restores the AbstractClient after it had been hidden due to show on screen edge functionality.
@ -1210,6 +1210,11 @@ protected:
void startShadeHoverTimer();
void startShadeUnhoverTimer();
// The geometry that the client should be restored when the virtual keyboard closes
QRect keyboardGeometryRestore() const;
void setKeyboardGeometryRestore(const QRect &geom);
QRect m_virtualKeyboardGeometry;
private Q_SLOTS:
void shadeHover();
void shadeUnhover();
@ -1264,7 +1269,6 @@ private:
QRect m_bufferGeometryBeforeUpdateBlocking;
QRect m_frameGeometryBeforeUpdateBlocking;
QRect m_clientGeometryBeforeUpdateBlocking;
QRect m_virtualKeyboardGeometry;
QRect m_keyboardGeometryRestore;
QRect m_maximizeGeometryRestore;

View File

@ -1,7 +1,20 @@
add_subdirectory(helper)
add_library(KWinIntegrationTestFramework STATIC kwin_wayland_test.cpp test_helpers.cpp generic_scene_opengl_test.cpp ../../cursor.cpp ${kwin_XWAYLAND_SRCS})
target_link_libraries(KWinIntegrationTestFramework kwin Qt5::Test)
set(KWinIntegrationTestFramework_SOURCES
../../cursor.cpp
generic_scene_opengl_test.cpp
kwin_wayland_test.cpp
test_helpers.cpp
${kwin_XWAYLAND_SRCS}
)
ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework_SOURCES
PROTOCOL ${WaylandProtocols_DATADIR}/unstable/input-method/input-method-unstable-v1.xml
BASENAME input-method-unstable-v1
)
add_library(KWinIntegrationTestFramework STATIC ${KWinIntegrationTestFramework_SOURCES})
target_link_libraries(KWinIntegrationTestFramework kwin Qt5::Test Wayland::Client)
function(integrationTest)
set(optionArgs WAYLAND_ONLY)
@ -12,7 +25,7 @@ function(integrationTest)
target_link_libraries(${ARGS_NAME} KWinIntegrationTestFramework kwin Qt5::Test ${ARGS_LIBS})
add_test(NAME kwin-${ARGS_NAME} COMMAND dbus-run-session ${CMAKE_BINARY_DIR}/bin/${ARGS_NAME})
if (${ARGS_WAYLAND_ONLY})
add_executable(${ARGS_NAME}_waylandonly ${ARGS_SRCS})
add_executable(${ARGS_NAME}_waylandonly ${ARGS_SRCS} )
set_target_properties(${ARGS_NAME}_waylandonly PROPERTIES COMPILE_DEFINITIONS "NO_XWAYLAND")
target_link_libraries(${ARGS_NAME}_waylandonly KWinIntegrationTestFramework kwin Qt5::Test ${ARGS_LIBS})
add_test(NAME kwin-${ARGS_NAME}-waylandonly COMMAND dbus-run-session ${CMAKE_BINARY_DIR}/bin/${ARGS_NAME}_waylandonly)
@ -60,6 +73,7 @@ integrationTest(WAYLAND_ONLY NAME testNoGlobalShortcuts SRCS no_global_shortcuts
integrationTest(WAYLAND_ONLY NAME testBufferSizeChange SRCS buffer_size_change_test.cpp )
integrationTest(WAYLAND_ONLY NAME testPlacement SRCS placement_test.cpp)
integrationTest(WAYLAND_ONLY NAME testActivation SRCS activation_test.cpp)
integrationTest(WAYLAND_ONLY NAME testVirtualKeyboard SRCS virtualkeyboard_test.cpp)
if (XCB_ICCCM_FOUND)
integrationTest(NAME testMoveResize SRCS move_resize_window_test.cpp LIBS XCB::ICCCM)

View File

@ -14,6 +14,7 @@
#include "../../workspace.h"
#include "../../xcbutils.h"
#include "../../xwl/xwayland.h"
#include "../../virtualkeyboard.h"
#include <KPluginMetaData>
@ -85,6 +86,38 @@ WaylandTestApplication::~WaylandTestApplication()
void WaylandTestApplication::performStartup()
{
if (!m_inputMethodServerToStart.isEmpty()) {
VirtualKeyboard::create();
if (m_inputMethodServerToStart != QStringLiteral("internal")) {
int socket = dup(waylandServer()->createInputMethodConnection());
if (socket >= 0) {
QProcessEnvironment environment = processStartupEnvironment();
environment.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket));
environment.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("wayland"));
environment.remove("DISPLAY");
environment.remove("WAYLAND_DISPLAY");
QProcess *p = new Process(this);
p->setProcessChannelMode(QProcess::ForwardedErrorChannel);
connect(p, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this,
[p] {
if (waylandServer()) {
waylandServer()->destroyInputMethodConnection();
}
p->deleteLater();
}
);
p->setProcessEnvironment(environment);
p->setProgram(m_inputMethodServerToStart);
// p->setArguments(arguments);
p->start();
connect(waylandServer(), &WaylandServer::terminatingInternalClientConnection, p, [p] {
p->kill();
p->waitForFinished();
});
}
}
}
// first load options - done internally by a different thread
createOptions();
waylandServer()->createInternalConnection();

View File

@ -37,9 +37,15 @@ class SubSurface;
class Surface;
class XdgDecorationManager;
class OutputManagement;
class TextInputManager;
}
}
namespace QtWayland
{
class zwp_input_panel_surface_v1;
}
namespace KWin
{
namespace Xwl
@ -56,6 +62,9 @@ public:
WaylandTestApplication(OperationMode mode, int &argc, char **argv);
~WaylandTestApplication() override;
void setInputMethodServerToStart(const QString &inputMethodServer) {
m_inputMethodServerToStart = inputMethodServer;
}
protected:
void performStartup() override;
@ -66,11 +75,14 @@ private:
void finalizeStartup();
Xwl::Xwayland *m_xwayland = nullptr;
QString m_inputMethodServerToStart;
};
namespace Test
{
class MockInputMethod;
enum class AdditionalWaylandInterface {
Seat = 1 << 0,
Decoration = 1 << 1,
@ -82,6 +94,8 @@ enum class AdditionalWaylandInterface {
ShadowManager = 1 << 7,
XdgDecoration = 1 << 8,
OutputManagement = 1 << 9,
TextInputManagerV2 = 1 << 10,
InputMethodV1 = 1 << 11
};
Q_DECLARE_FLAGS(AdditionalWaylandInterfaces, AdditionalWaylandInterface)
/**
@ -114,6 +128,7 @@ KWayland::Client::IdleInhibitManager *waylandIdleInhibitManager();
KWayland::Client::AppMenuManager *waylandAppMenuManager();
KWayland::Client::XdgDecorationManager *xdgDecorationManager();
KWayland::Client::OutputManagement *waylandOutputManagement();
KWayland::Client::TextInputManager *waylandTextInputManager();
bool waitForWaylandPointer();
bool waitForWaylandTouch();
@ -133,6 +148,9 @@ enum class CreationSetup {
CreateAndConfigure, /// commit and wait for the configure event, making this surface ready to commit buffers
};
QtWayland::zwp_input_panel_surface_v1 *createInputPanelSurfaceV1(KWayland::Client::Surface *surface,
KWayland::Client::Output *output);
KWayland::Client::XdgShellSurface *createXdgShellSurface(XdgShellSurfaceType type,
KWayland::Client::Surface *surface,
QObject *parent = nullptr,

View File

@ -11,6 +11,8 @@
#include "screenlockerwatcher.h"
#include "wayland_server.h"
#include "workspace.h"
#include "qwayland-input-method-unstable-v1.h"
#include "virtualkeyboard.h"
#include <KWayland/Client/compositor.h>
#include <KWayland/Client/connection_thread.h>
@ -28,6 +30,7 @@
#include <KWayland/Client/subcompositor.h>
#include <KWayland/Client/subsurface.h>
#include <KWayland/Client/surface.h>
#include <KWayland/Client/textinput.h>
#include <KWayland/Client/appmenu.h>
#include <KWayland/Client/xdgshell.h>
#include <KWayland/Client/xdgdecoration.h>
@ -71,8 +74,60 @@ static struct {
IdleInhibitManager *idleInhibit = nullptr;
AppMenuManager *appMenu = nullptr;
XdgDecorationManager *xdgDecoration = nullptr;
TextInputManager *textInputManager = nullptr;
QtWayland::zwp_input_panel_v1 *inputPanelV1 = nullptr;
MockInputMethod *inputMethodV1 = nullptr;
QtWayland::zwp_input_method_context_v1 *inputMethodContextV1 = nullptr;
} s_waylandConnection;
class MockInputMethod : public QtWayland::zwp_input_method_v1
{
public:
MockInputMethod(struct wl_registry *registry, int id, int version);
~MockInputMethod();
protected:
void zwp_input_method_v1_activate(struct ::zwp_input_method_context_v1 *context) override;
void zwp_input_method_v1_deactivate(struct ::zwp_input_method_context_v1 *context) override;
private:
Surface *m_inputSurface = nullptr;
QtWayland::zwp_input_panel_surface_v1 *m_inputMethodSurface = nullptr;
AbstractClient *m_client = nullptr;
};
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, s_waylandConnection.outputs.first());
}
m_client = Test::renderAndWaitForShown(m_inputSurface, QSize(1280, 400), Qt::blue);
}
void MockInputMethod::zwp_input_method_v1_deactivate(struct ::zwp_input_method_context_v1 *context)
{
zwp_input_method_context_v1_destroy(context);
if (m_inputSurface) {
m_inputSurface->release();
m_inputSurface->destroy();
delete m_inputSurface;
m_inputSurface = nullptr;
delete m_inputMethodSurface;
m_inputMethodSurface = nullptr;
}
}
bool setupWaylandConnection(AdditionalWaylandInterfaces flags)
{
if (s_waylandConnection.connection) {
@ -123,6 +178,16 @@ bool setupWaylandConnection(AdditionalWaylandInterfaces flags)
});
});
QObject::connect(registry, &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);
}
}
});
QSignalSpy allAnnounced(registry, &Registry::interfacesAnnounced);
if (!allAnnounced.isValid()) {
return false;
@ -219,6 +284,12 @@ bool setupWaylandConnection(AdditionalWaylandInterfaces flags)
return false;
}
}
if (flags.testFlag(AdditionalWaylandInterface::TextInputManagerV2)) {
s_waylandConnection.textInputManager = registry->createTextInputManager(registry->interface(Registry::Interface::TextInputManagerUnstableV2).name, registry->interface(Registry::Interface::TextInputManagerUnstableV2).version);
if (!s_waylandConnection.textInputManager->isValid()) {
return false;
}
}
return true;
}
@ -257,6 +328,10 @@ void destroyWaylandConnection()
s_waylandConnection.appMenu = nullptr;
delete s_waylandConnection.xdgDecoration;
s_waylandConnection.xdgDecoration = nullptr;
delete s_waylandConnection.textInputManager;
s_waylandConnection.textInputManager = nullptr;
delete s_waylandConnection.inputPanelV1;
s_waylandConnection.inputPanelV1 = nullptr;
if (s_waylandConnection.thread) {
QSignalSpy spy(s_waylandConnection.connection, &QObject::destroyed);
s_waylandConnection.connection->deleteLater();
@ -341,6 +416,11 @@ OutputManagement *waylandOutputManagement()
return s_waylandConnection.outputManagement;
}
TextInputManager *waylandTextInputManager()
{
return s_waylandConnection.textInputManager;
}
bool waitForWaylandPointer()
{
@ -467,6 +547,24 @@ XdgShellSurface *createXdgShellStableSurface(Surface *surface, QObject *parent,
return s;
}
QtWayland::zwp_input_panel_surface_v1 *createInputPanelSurfaceV1(Surface *surface, 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;
}
XdgShellPopup *createXdgShellStablePopup(Surface *surface, XdgShellSurface *parentSurface, const XdgPositioner &positioner, QObject *parent, CreationSetup creationSetup)
{
if (!s_waylandConnection.xdgShellStable) {

View File

@ -0,0 +1,154 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2020 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "kwin_wayland_test.h"
#include "abstract_client.h"
#include "cursor.h"
#include "effects.h"
#include "deleted.h"
#include "platform.h"
#include "screens.h"
#include "wayland_server.h"
#include "workspace.h"
#include "virtualkeyboard.h"
#include "virtualkeyboard_dbus.h"
#include "qwayland-input-method-unstable-v1.h"
#include <QTest>
#include <QSignalSpy>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingReply>
#include <KWaylandServer/clientconnection.h>
#include <KWaylandServer/display.h>
#include <KWaylandServer/surface_interface.h>
#include <KWayland/Client/compositor.h>
#include <KWayland/Client/output.h>
#include <KWayland/Client/surface.h>
#include <KWayland/Client/xdgshell.h>
#include <KWayland/Client/textinput.h>
using namespace KWin;
using namespace KWayland::Client;
using KWin::VirtualKeyboardDBus;
static const QString s_socketName = QStringLiteral("wayland_test_kwin_virtualkeyboard-0");
class VirtualKeyboardTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void init();
void cleanup();
void testOpenClose();
};
void VirtualKeyboardTest::initTestCase()
{
qRegisterMetaType<KWin::Deleted *>();
qRegisterMetaType<KWin::AbstractClient *>();
qRegisterMetaType<KWayland::Client::Output *>();
QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
QVERIFY(applicationStartedSpy.isValid());
kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024));
QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit()));
QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2));
static_cast<WaylandTestApplication *>(kwinApp())->setInputMethodServerToStart("internal");
kwinApp()->start();
QVERIFY(applicationStartedSpy.wait());
QCOMPARE(screens()->count(), 2);
QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024));
QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024));
waylandServer()->initWorkspace();
}
void VirtualKeyboardTest::init()
{
QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat |
Test::AdditionalWaylandInterface::TextInputManagerV2 | Test::AdditionalWaylandInterface::InputMethodV1));
screens()->setCurrent(0);
KWin::Cursors::self()->mouse()->setPos(QPoint(1280, 512));
const QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kwin.testvirtualkeyboard"),
QStringLiteral("/VirtualKeyboard"),
QStringLiteral("org.kde.kwin.VirtualKeyboard"),
"enable");
QDBusConnection::sessionBus().call(message);
}
void VirtualKeyboardTest::cleanup()
{
Test::destroyWaylandConnection();
}
void VirtualKeyboardTest::testOpenClose()
{
QSignalSpy clientAddedSpy(workspace(), &Workspace::clientAdded);
QSignalSpy clientRemovedSpy(workspace(), &Workspace::clientRemoved);
QVERIFY(clientAddedSpy.isValid());
// Create an xdg_toplevel surface and wait for the compositor to catch up.
QScopedPointer<Surface> surface(Test::createSurface());
QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellStableSurface(surface.data()));
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(1280, 1024), Qt::red);
QVERIFY(client);
QVERIFY(client->isActive());
QCOMPARE(client->frameGeometry().size(), QSize(1280, 1024));
QSignalSpy frameGeometryChangedSpy(client, &AbstractClient::frameGeometryChanged);
QVERIFY(frameGeometryChangedSpy.isValid());
QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested);
QVERIFY(configureRequestedSpy.isValid());
QScopedPointer<TextInput> textInput(Test::waylandTextInputManager()->createTextInput(Test::waylandSeat()));
QVERIFY(!textInput.isNull());
textInput->enable(surface.data());
QVERIFY(configureRequestedSpy.wait());
// Show the keyboard
textInput->showInputPanel();
QVERIFY(clientAddedSpy.wait());
AbstractClient *keyboardClient = clientAddedSpy.last().first().value<AbstractClient *>();
QVERIFY(keyboardClient);
QVERIFY(keyboardClient->isInputMethod());
// Do the actual resize
QVERIFY(configureRequestedSpy.wait());
Test::render(surface.data(), configureRequestedSpy.last().first().value<QSize>(), Qt::red);
QVERIFY(frameGeometryChangedSpy.wait());
QCOMPARE(client->frameGeometry().height(), 1024 - keyboardClient->inputGeometry().height() + 1);
// Hide the keyboard
textInput->hideInputPanel();
QVERIFY(configureRequestedSpy.wait());
Test::render(surface.data(), configureRequestedSpy.last().first().value<QSize>(), Qt::red);
QVERIFY(frameGeometryChangedSpy.wait());
QCOMPARE(client->frameGeometry().height(), 1024);
// Destroy the test client.
shellSurface.reset();
QVERIFY(Test::waitForWindowDestroyed(client));
}
WAYLANDTEST_MAIN(VirtualKeyboardTest)
#include "virtualkeyboard_test.moc"

View File

@ -123,6 +123,9 @@ NET::WindowType InputPanelV1Client::windowType(bool, int) const
QRect InputPanelV1Client::inputGeometry() const
{
if (surface()->inputIsInfinite()) {
return frameGeometry();
}
return surface()->input().boundingRect().translated(pos());
}

View File

@ -112,7 +112,9 @@ void VirtualKeyboard::init()
return;
}
m_trackedClient->setVirtualKeyboardGeometry(m_inputClient ? m_inputClient->inputGeometry() : QRect());
if (m_inputClient && !m_inputClient->inputGeometry().isEmpty()) {
m_trackedClient->setVirtualKeyboardGeometry(m_inputClient->inputGeometry());
}
};
connect(surface->surface(), &SurfaceInterface::inputChanged, this, refreshFrame);
connect(surface->surface(), &QObject::destroyed, this, [this] {
@ -120,8 +122,7 @@ void VirtualKeyboard::init()
m_trackedClient->setVirtualKeyboardGeometry({});
}
});
updateInputPanelState();
refreshFrame();
connect(m_inputClient, &AbstractClient::frameGeometryChanged, this, refreshFrame);
});
connect(waylandServer()->seat(), &SeatInterface::focusedTextInputChanged, this,
@ -158,6 +159,7 @@ void VirtualKeyboard::init()
});
m_waylandEnabledConnection = connect(t, &TextInputInterface::enabledChanged, this, [t, this] {
if (t->isEnabled()) {
//FIXME This sendDeactivate shouldn't be necessary?
waylandServer()->inputMethod()->sendDeactivate();
waylandServer()->inputMethod()->sendActivate();
adoptInputMethodContext();
@ -177,7 +179,6 @@ void VirtualKeyboard::init()
}
m_trackedClient = newClient;
}
updateInputPanelState();
} else {
m_waylandShowConnection = QMetaObject::Connection();
@ -198,6 +199,7 @@ void VirtualKeyboard::show()
{
auto t = waylandServer()->seat()->focusedTextInput();
if (t) {
//FIXME: this shouldn't be necessary and causes double emits?
Q_EMIT t->enabledChanged();
}
}

View File

@ -518,6 +518,38 @@ void XdgSurfaceClient::destroyClient()
delete this;
}
void XdgSurfaceClient::setVirtualKeyboardGeometry(const QRect &geo)
{
// No keyboard anymore
if (geo.isEmpty() && !keyboardGeometryRestore().isEmpty()) {
setFrameGeometry(keyboardGeometryRestore());
setKeyboardGeometryRestore(QRect());
} else if (geo.isEmpty()) {
return;
// The keyboard has just been opened (rather than resized) save client geometry for a restore
} else if (keyboardGeometryRestore().isEmpty()) {
setKeyboardGeometryRestore(requestedFrameGeometry().isEmpty() ? frameGeometry() : requestedFrameGeometry());
}
m_virtualKeyboardGeometry = geo;
// Don't resize Desktop and fullscreen windows
if (isFullScreen() || isDesktop()) {
return;
}
if (!geo.intersects(keyboardGeometryRestore())) {
return;
}
const QRect availableArea = workspace()->clientArea(MaximizeArea, this);
QRect newWindowGeometry = keyboardGeometryRestore();
newWindowGeometry.moveBottom(geo.top());
newWindowGeometry.setTop(qMax(newWindowGeometry.top(), availableArea.top()));
setFrameGeometry(newWindowGeometry);
}
void XdgSurfaceClient::cleanGrouping()
{
if (transientFor()) {

View File

@ -61,6 +61,7 @@ public:
void move(int x, int y, ForceGeometry_t force = NormalGeometrySet) override;
bool isInitialPositionSet() const override;
void destroyClient() override;
void setVirtualKeyboardGeometry(const QRect &geo) override;
QRect frameRectToBufferRect(const QRect &rect) const;
QRect requestedFrameGeometry() const;