virtualkeyboard: resize the focused window to make room for the keyboard

Summary:
alternative approach: try to resize the winidow to make room for the keyboard.
the new input wayland protocol doesn't have anymore the overlap rectangle (and it would not be going to work with qwidget apps anyways)

in the future will probably be needed anextension to the input protocol v3 which partially gets back this, tough window resizing is needed regardless

what's missing: the resize should be "temporary" and the window should be restored to its previous geometry when the keyboard closes

Test Plan: tested with test QML code

Reviewers: #plasma, #kwin, bshah, graesslin, romangg, davidedmundson

Reviewed By: #plasma, #kwin, romangg, davidedmundson

Subscribers: nicolasfella, mart, kwin, davidedmundson, graesslin

Tags: #kwin

Maniphest Tasks: T9815

Differential Revision: https://phabricator.kde.org/D18818
icc-effect-5.17.5
Marco Martin 2019-03-20 11:04:51 +01:00
parent d51070223b
commit 6bc2ddd56a
5 changed files with 264 additions and 16 deletions

View File

@ -72,6 +72,17 @@ AbstractClient::AbstractClient()
connect(Decoration::DecorationBridge::self(), &QObject::destroyed, this, &AbstractClient::destroyDecoration);
// If the user manually moved the window, don't restore it after the keyboard closes
connect(this, &AbstractClient::clientFinishUserMovedResized, this, [this] () {
m_keyboardGeometryRestore = QRect();
});
connect(this, qOverload<AbstractClient *, bool, bool>(&AbstractClient::clientMaximizedStateChanged), this, [this] () {
m_keyboardGeometryRestore = QRect();
});
connect(this, &AbstractClient::fullScreenChanged, this, [this] () {
m_keyboardGeometryRestore = QRect();
});
// replace on-screen-display on size changes
connect(this, &AbstractClient::geometryShapeChanged, this,
[this] (Toplevel *c, const QRect &old) {
@ -1871,6 +1882,38 @@ QRect AbstractClient::inputGeometry() const
return Toplevel::inputGeometry();
}
QRect AbstractClient::virtualKeyboardGeometry() const
{
return m_virtualKeyboardGeometry;
}
void AbstractClient::setVirtualKeyboardGeometry(const QRect &geo)
{
// No keyboard anymore
if (geo.isEmpty() && !m_keyboardGeometryRestore.isEmpty()) {
setGeometry(m_keyboardGeometryRestore);
m_keyboardGeometryRestore = QRect();
} else if (geo.isEmpty()) {
return;
// The keyboard has just been opened (rather than resized) save client geometry for a restore
} else if (m_keyboardGeometryRestore.isEmpty()) {
m_keyboardGeometryRestore = geometry();
}
m_virtualKeyboardGeometry = geo;
if (!geo.intersects(m_keyboardGeometryRestore)) {
return;
}
const QRect availableArea = workspace()->clientArea(MaximizeArea, this);
QRect newWindowGeometry = m_keyboardGeometryRestore;
newWindowGeometry.moveBottom(geo.top());
newWindowGeometry.setTop(qMax(newWindowGeometry.top(), availableArea.top()));
setGeometry(newWindowGeometry);
}
bool AbstractClient::dockWantsInput() const
{
return false;

View File

@ -727,6 +727,18 @@ public:
QRect inputGeometry() const override;
/**
* @returns the geometry of the virtual keyboard
* This geometry is in global coordinates
*/
QRect virtualKeyboardGeometry() const;
/**
* 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);
/**
* Restores the AbstractClient after it had been hidden due to show on screen edge functionality.
* The AbstractClient also gets raised (e.g. Panel mode windows can cover) and the AbstractClient
@ -957,6 +969,7 @@ protected:
int borderBottom() const;
virtual void changeMaximize(bool horizontal, bool vertical, bool adjust) = 0;
virtual void setGeometryRestore(const QRect &geo) = 0;
/**
* Called from move after updating the geometry. Can be reimplemented to perform specific tasks.
* The base implementation does nothing.
@ -1201,6 +1214,8 @@ private:
friend class GeometryUpdatesBlocker;
QRect m_visibleRectBeforeGeometryUpdate;
QRect m_geometryBeforeUpdateBlocking;
QRect m_virtualKeyboardGeometry;
QRect m_keyboardGeometryRestore;
struct {
bool enabled = false;

View File

@ -36,6 +36,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <KWayland/Client/plasmashell.h>
#include <KWayland/Client/seat.h>
#include <KWayland/Client/shell.h>
#include <KWayland/Client/xdgshell.h>
#include <KWayland/Client/surface.h>
#include <KWayland/Client/xdgshell.h>
@ -76,6 +77,9 @@ private Q_SLOTS:
void testAdjustClientGeometryOfAutohidingX11Panel();
void testAdjustClientGeometryOfAutohidingWaylandPanel_data();
void testAdjustClientGeometryOfAutohidingWaylandPanel();
void testResizeForVirtualKeyboard();
void testResizeForVirtualKeyboardWithMaximize();
void testResizeForVirtualKeyboardWithFullScreen();
private:
KWayland::Client::ConnectionThread *m_connection = nullptr;
@ -852,6 +856,136 @@ void MoveResizeWindowTest::testAdjustClientGeometryOfAutohidingWaylandPanel()
QVERIFY(windowClosedSpy.wait());
}
void MoveResizeWindowTest::testResizeForVirtualKeyboard()
{
using namespace KWayland::Client;
QScopedPointer<Surface> surface(Test::createSurface());
QVERIFY(!surface.isNull());
QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellSurface(
Test::ShellSurfaceType::XdgShellStable, surface.data()));
QVERIFY(!shellSurface.isNull());
QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested);
// let's render
auto client = Test::renderAndWaitForShown(surface.data(), QSize(500, 800), Qt::blue);
client->move(100, 300);
QSignalSpy geometryChangedSpy(client, &ShellClient::geometryChanged);
QCOMPARE(client->geometry(), QRect(100, 300, 500, 800));
client->setVirtualKeyboardGeometry(QRect(0, 100, 1280, 500));
configureRequestedSpy.wait();
shellSurface->ackConfigure(configureRequestedSpy.last()[2].toInt());
// render at the new size
Test::render(surface.data(), configureRequestedSpy.last().first().toSize(), Qt::blue);
geometryChangedSpy.wait();
QCOMPARE(client->geometry(), QRect(100, 0, 500, 101));
client->setVirtualKeyboardGeometry(QRect());
configureRequestedSpy.wait();
shellSurface->ackConfigure(configureRequestedSpy.last()[2].toInt());
// render at the new size
Test::render(surface.data(), configureRequestedSpy.last().first().toSize(), Qt::blue);
geometryChangedSpy.wait();
QCOMPARE(client->geometry(), QRect(100, 300, 500, 800));
}
void MoveResizeWindowTest::testResizeForVirtualKeyboardWithMaximize()
{
using namespace KWayland::Client;
QScopedPointer<Surface> surface(Test::createSurface());
QVERIFY(!surface.isNull());
QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellSurface(
Test::ShellSurfaceType::XdgShellStable, surface.data()));
QVERIFY(!shellSurface.isNull());
QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested);
// let's render
auto client = Test::renderAndWaitForShown(surface.data(), QSize(500, 800), Qt::blue);
client->move(100, 300);
QSignalSpy geometryChangedSpy(client, &ShellClient::geometryChanged);
QCOMPARE(client->geometry(), QRect(100, 300, 500, 800));
client->setVirtualKeyboardGeometry(QRect(0, 100, 1280, 500));
configureRequestedSpy.wait();
shellSurface->ackConfigure(configureRequestedSpy.last()[2].toInt());
// render at the new size
Test::render(surface.data(), configureRequestedSpy.last().first().toSize(), Qt::blue);
geometryChangedSpy.wait();
QCOMPARE(client->geometry(), QRect(100, 0, 500, 101));
client->setMaximize(true, true);
configureRequestedSpy.wait();
shellSurface->ackConfigure(configureRequestedSpy.last()[2].toInt());
Test::render(surface.data(), configureRequestedSpy.last().first().toSize(), Qt::blue);
geometryChangedSpy.wait();
QCOMPARE(client->geometry(), QRect(0, 0, 1280, 1024));
client->setVirtualKeyboardGeometry(QRect());
QVERIFY(!configureRequestedSpy.wait(10));
// render at the size of the configureRequested.. it won't have changed
Test::render(surface.data(), configureRequestedSpy.last().first().toSize(), Qt::blue);
QVERIFY(!geometryChangedSpy.wait(10));
// Size will NOT be restored
QCOMPARE(client->geometry(), QRect(0, 0, 1280, 1024));
}
void MoveResizeWindowTest::testResizeForVirtualKeyboardWithFullScreen()
{
using namespace KWayland::Client;
QScopedPointer<Surface> surface(Test::createSurface());
QVERIFY(!surface.isNull());
QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellSurface(
Test::ShellSurfaceType::XdgShellStable, surface.data()));
QVERIFY(!shellSurface.isNull());
QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested);
// let's render
auto client = Test::renderAndWaitForShown(surface.data(), QSize(500, 800), Qt::blue);
client->move(100, 300);
QSignalSpy geometryChangedSpy(client, &ShellClient::geometryChanged);
QCOMPARE(client->geometry(), QRect(100, 300, 500, 800));
client->setVirtualKeyboardGeometry(QRect(0, 100, 1280, 500));
configureRequestedSpy.wait();
shellSurface->ackConfigure(configureRequestedSpy.last()[2].toInt());
// render at the new size
Test::render(surface.data(), configureRequestedSpy.last().first().toSize(), Qt::blue);
geometryChangedSpy.wait();
QCOMPARE(client->geometry(), QRect(100, 0, 500, 101));
client->setFullScreen(true, true);
configureRequestedSpy.wait();
shellSurface->ackConfigure(configureRequestedSpy.last()[2].toInt());
Test::render(surface.data(), configureRequestedSpy.last().first().toSize(), Qt::blue);
geometryChangedSpy.wait();
QCOMPARE(client->geometry(), QRect(0, 0, 1280, 1024));
client->setVirtualKeyboardGeometry(QRect());
QVERIFY(!configureRequestedSpy.wait(10));
// render at the size of the configureRequested.. it won't have changed
Test::render(surface.data(), configureRequestedSpy.last().first().toSize(), Qt::blue);
QVERIFY(!geometryChangedSpy.wait(10));
// Size will NOT be restored
QCOMPARE(client->geometry(), QRect(0, 0, 1280, 1024));
}
}
WAYLANDTEST_MAIN(KWin::MoveResizeWindowTest)

View File

@ -26,10 +26,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "wayland_server.h"
#include "workspace.h"
#include "xkb.h"
#include "shell_client.h"
#include <KWayland/Server/display.h>
#include <KWayland/Server/seat_interface.h>
#include <KWayland/Server/textinput_interface.h>
#include <KWayland/Server/surface_interface.h>
#include <KStatusNotifierItem>
#include <KLocalizedString>
@ -44,6 +46,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <QQuickItem>
#include <QQuickView>
#include <QQuickWindow>
#include <QTimer>
// xkbcommon
#include <xkbcommon/xkbcommon.h>
@ -57,6 +60,9 @@ KWIN_SINGLETON_FACTORY(VirtualKeyboard)
VirtualKeyboard::VirtualKeyboard(QObject *parent)
: QObject(parent)
{
m_floodTimer = new QTimer(this);
m_floodTimer->setSingleShot(true);
m_floodTimer->setInterval(250);
// this is actually too late. Other processes are started before init,
// so might miss the availability of text input
// but without Workspace we don't have the window listed at all
@ -146,8 +152,20 @@ void VirtualKeyboard::init()
qApp->inputMethod()->update(Qt::ImQueryAll);
}
);
// TODO: calculate overlap
t->setInputPanelState(m_inputWindow->isVisible(), QRect(0, 0, 0, 0));
auto newClient = waylandServer()->findAbstractClient(waylandServer()->seat()->focusedTextInputSurface());
// Reset the old client virtual keybaord geom if necessary
// Old and new clients could be the same if focus moves between subsurfaces
if (newClient != m_trackedClient) {
if (m_trackedClient) {
m_trackedClient->setVirtualKeyboardGeometry(QRect());
}
m_trackedClient = newClient;
}
m_trackedClient = waylandServer()->findAbstractClient(waylandServer()->seat()->focusedTextInputSurface());
updateInputPanelState();
} else {
m_waylandShowConnection = QMetaObject::Connection();
m_waylandHideConnection = QMetaObject::Connection();
@ -176,20 +194,10 @@ void VirtualKeyboard::init()
m_inputWindow->setMask(m_inputWindow->rootObject()->childrenRect().toRect());
}
);
connect(qApp->inputMethod(), &QInputMethod::visibleChanged, m_inputWindow.data(),
[this] {
m_inputWindow->setVisible(qApp->inputMethod()->isVisible());
if (qApp->inputMethod()->isVisible()) {
m_inputWindow->setMask(m_inputWindow->rootObject()->childrenRect().toRect());
}
if (waylandServer()) {
if (auto t = waylandServer()->seat()->focusedTextInput()) {
// TODO: calculate overlap
t->setInputPanelState(m_inputWindow->isVisible(), QRect(0, 0, 0, 0));
}
}
}
);
connect(qApp->inputMethod(), &QInputMethod::visibleChanged, this, &VirtualKeyboard::updateInputPanelState);
connect(m_inputWindow->rootObject(), &QQuickItem::childrenRectChanged, this, &VirtualKeyboard::updateInputPanelState);
}
void VirtualKeyboard::setEnabled(bool enabled)
@ -227,6 +235,46 @@ void VirtualKeyboard::updateSni()
m_sni->setToolTipTitle(i18n("Whether to show the virtual keyboard on demand."));
}
void VirtualKeyboard::updateInputPanelState()
{
if (!waylandServer()) {
return;
}
auto t = waylandServer()->seat()->focusedTextInput();
if (!t) {
return;
}
const bool inputPanelHasBeenClosed = m_inputWindow->isVisible() && !qApp->inputMethod()->isVisible();
if (inputPanelHasBeenClosed && m_floodTimer->isActive()) {
return;
}
m_floodTimer->start();
m_inputWindow->setVisible(qApp->inputMethod()->isVisible());
if (qApp->inputMethod()->isVisible()) {
m_inputWindow->setMask(m_inputWindow->rootObject()->childrenRect().toRect());
}
if (m_inputWindow->isVisible() && m_trackedClient && m_inputWindow->rootObject()) {
const QRect inputPanelGeom = m_inputWindow->rootObject()->childrenRect().toRect().translated(m_inputWindow->geometry().topLeft());
m_trackedClient->setVirtualKeyboardGeometry(inputPanelGeom);
t->setInputPanelState(true, QRect(0, 0, 0, 0));
} else {
if (inputPanelHasBeenClosed && m_trackedClient) {
m_trackedClient->setVirtualKeyboardGeometry(QRect());
}
t->setInputPanelState(false, QRect(0, 0, 0, 0));
}
}
void VirtualKeyboard::show()
{
if (m_inputWindow.isNull() || !m_enabled) {

View File

@ -25,7 +25,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <kwinglobals.h>
#include <kwin_export.h>
#include <abstract_client.h>
class QQuickView;
class QTimer;
class QWindow;
class KStatusNotifierItem;
@ -53,10 +56,15 @@ private:
void hide();
void setEnabled(bool enable);
void updateSni();
void updateInputPanelState();
bool m_enabled = false;
KStatusNotifierItem *m_sni = nullptr;
QScopedPointer<QQuickView> m_inputWindow;
QPointer<AbstractClient> m_trackedClient;
// If a surface loses focus immediately after being resized by the keyboard, don't react to it to avoid resize loops
QTimer *m_floodTimer;
QMetaObject::Connection m_waylandShowConnection;
QMetaObject::Connection m_waylandHideConnection;
QMetaObject::Connection m_waylandHintsConnection;